diff --git a/app/Http/Controllers/Business/FinanceLedger.php b/app/Http/Controllers/Business/FinanceLedger.php index 95802fdb..bdd38e32 100644 --- a/app/Http/Controllers/Business/FinanceLedger.php +++ b/app/Http/Controllers/Business/FinanceLedger.php @@ -673,6 +673,145 @@ class FinanceLedger extends Controller ], $inserted ? 201 : 200, ['Cache-Control' => 'private, no-store']); } + public function updateTransactionAccount(Request $request, int $id, FinanceLedgerCalculator $calculator): JsonResponse + { + abort_unless(FinanceLedgerAccess::canEditMapping(user()), 403, '只有财务台账管理员可以修改动账账户关联。'); + + $data = $request->validate([ + 'account_short' => ['required', 'string', 'max:100'], + ]); + + $accountShort = trim((string) $data['account_short']); + $connection = DB::connection((string) config('upstream_mirrors.connection', 'business_mirror')); + $account = $connection->table('fin_accounts') + ->where('account_short', $accountShort) + ->orderBy('id') + ->first(['account_short', 'company_name']); + abort_if($account === null, 422, '账户简称不存在,请从账户总表中选择。'); + + $result = $connection->transaction(function () use ( + $connection, + $calculator, + $id, + $accountShort, + ): array { + $transaction = $connection->table('fin_transactions') + ->where('id', $id) + ->lockForUpdate() + ->first(); + abort_if($transaction === null, 404); + + $source = trim((string) $transaction->source); + $sourceValue = trim((string) ($transaction->source_value ?? '')); + $directTransaction = $source === FinanceLedgerTransactionWriter::MANUAL_SOURCE || $sourceValue === ''; + $groupKey = $directTransaction + ? 'transaction:' . $id + : hash('sha256', $source . '|' . $sourceValue); + $mapping = null; + $old = trim((string) ($transaction->account_short ?? '')); + + if (! $directTransaction) { + $mapping = $connection->table('fin_account_map') + ->where('source', $source) + ->where('source_value', $sourceValue) + ->lockForUpdate() + ->first(); + $mappedAccount = trim((string) ($mapping->account_short ?? '')); + if ($mappedAccount !== '') { + $old = $mappedAccount; + } + } + + if ($old === $accountShort) { + return [ + 'changed' => false, + 'scope' => $directTransaction ? 'transaction' : 'mapping', + 'group_key' => $groupKey, + 'txn_date' => (string) $transaction->txn_date, + ]; + } + + $now = now(); + if ($directTransaction) { + $connection->table('fin_transactions')->where('id', $id)->update([ + 'account_short' => $accountShort, + 'updated_by' => (string) user()->name, + 'updated_at' => $now, + ]); + } else { + if ($mapping === null) { + $stats = $connection->table('fin_transactions') + ->where('source', $source) + ->where('source_value', $sourceValue) + ->selectRaw('COUNT(*) AS txn_count, SUM(income) AS income_sum, SUM(expense) AS expense_sum') + ->first(); + $connection->table('fin_account_map')->insert([ + 'source' => $source, + 'source_value' => $sourceValue, + 'account_short' => $accountShort, + 'txn_count' => (int) ($stats->txn_count ?? 0), + 'income_sum' => round((float) ($stats->income_sum ?? 0), 2), + 'expense_sum' => round((float) ($stats->expense_sum ?? 0), 2), + 'updated_by' => (string) user()->name, + 'created_at' => $now, + 'updated_at' => $now, + ]); + } else { + $connection->table('fin_account_map')->where('id', $mapping->id)->update([ + 'account_short' => $accountShort, + 'updated_by' => (string) user()->name, + 'updated_at' => $now, + ]); + } + + $connection->table('fin_account_map_events')->insert([ + 'source' => $source, + 'source_value' => $sourceValue, + 'old_account_short' => $old !== '' ? $old : null, + 'new_account_short' => $accountShort, + 'user_id' => (int) user()->id, + 'user_name' => (string) user()->name, + 'created_at' => $now, + ]); + } + + $calculator->recalculate(); + + return [ + 'changed' => true, + 'scope' => $directTransaction ? 'transaction' : 'mapping', + 'group_key' => $groupKey, + 'txn_date' => (string) $transaction->txn_date, + ]; + }); + + $flow = $connection->table('fin_daily_flows') + ->where('account_short', $accountShort) + ->whereDate('flow_date', (string) $result['txn_date']) + ->first(['flow_date', 'opening_balance', 'inflow', 'outflow', 'closing_balance']); + $currentBalance = $connection->table('fin_accounts') + ->where('account_short', $accountShort) + ->orderBy('id') + ->value('current_balance'); + + return response()->json([ + 'ok' => true, + 'changed' => (bool) $result['changed'], + 'scope' => (string) $result['scope'], + 'group_key' => (string) $result['group_key'], + 'account_short' => $accountShort, + 'company_name' => (string) ($account->company_name ?? ''), + 'current_balance' => round((float) ($currentBalance ?? 0), 2), + 'daily_flow' => $flow === null ? null : [ + 'flow_date' => (string) $flow->flow_date, + 'opening_balance' => round((float) $flow->opening_balance, 2), + 'inflow' => round((float) $flow->inflow, 2), + 'outflow' => round((float) $flow->outflow, 2), + 'closing_balance' => round((float) $flow->closing_balance, 2), + ], + ], 200, ['Cache-Control' => 'private, no-store']); + } + public function updateAccountStatus(Request $request, FinanceLedgerCalculator $calculator): JsonResponse { abort_unless(FinanceLedgerAccess::canEditAccountStatus(user()), 403, '只有财务台账管理员可以修改账户状态。'); diff --git a/resources/views/business/pages/financial-ledger/ledger.blade.php b/resources/views/business/pages/financial-ledger/ledger.blade.php index 544ebcd2..f02e2057 100644 --- a/resources/views/business/pages/financial-ledger/ledger.blade.php +++ b/resources/views/business/pages/financial-ledger/ledger.blade.php @@ -278,6 +278,12 @@ .ledger-manual-date__button:hover { background:#efe4c8; } .ledger-manual-date__button:focus-visible { position:relative; z-index:2; outline:none; box-shadow:0 0 0 3px rgba(133,102,50,.18); } .ledger-manual-date__button .material-icons-outlined { font-size:1rem; } + .ledger-transaction-account-input { width:100%; min-width:0; height:1.65rem; padding:0 .38rem; border:1px solid transparent; border-radius:.3rem; background:transparent; color:var(--vip-content-ink, #171717); font:inherit; font-size:.7rem; } + .ledger-transaction-account-input:hover { border-color:var(--vip-border, #D8D2C7); background:#fff; } + .ledger-transaction-account-input:focus { border-color:var(--vip-content-accent-dark, #856632); outline:none; background:#fff; box-shadow:0 0 0 3px rgba(133,102,50,.14); } + .ledger-transaction-account-input.is-saving { border-color:var(--vip-content-accent, #B9954F); background:#fffdf6; } + .ledger-transaction-account-input.is-error { border-color:var(--vip-danger, #B80000); background:#fff5f5; } + .ledger-transaction-account-input:disabled { cursor:wait; opacity:.65; } .ledger-manual-actions { display:flex; align-items:center; gap:.25rem; } .ledger-manual-action { display:inline-grid; place-items:center; width:1.75rem; height:1.75rem; padding:0; border:1px solid var(--vip-border, #D8D2C7); border-radius:.35rem; background:#fff; color:var(--vip-content-muted, #595959); cursor:pointer; } .ledger-manual-action.is-save { border-color:var(--vip-content-accent-dark, #856632); background:var(--vip-content-accent-dark, #856632); color:#fff; } @@ -547,7 +553,7 @@
- +