From 22419fbd3b1f8717f6ebc5ff6aa4be3068852965 Mon Sep 17 00:00:00 2001 From: shanshi <3316401569@qq.com> Date: Wed, 21 Oct 2026 16:45:54 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B4=A2=E5=8A=A1=E5=8F=B0=E8=B4=A6=EF=BC=9A?= =?UTF-8?q?=E6=8C=89=E8=B4=A6=E6=88=B7=E7=8A=B6=E6=80=81=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E5=BD=93=E5=89=8D=E4=BD=99=E9=A2=9D=E9=87=91=E9=A2=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Business/FinanceLedger.php | 30 ++++++---- deploy/run-finance-batch.sh | 2 +- .../Services/FinanceLedgerCalculator.php | 59 ++++++++++++++++++- .../View/FinanceLedgerPageData.php | 3 +- .../pages/financial-ledger/ledger.blade.php | 56 ++++++++++++++---- .../unavailable-balance-tasks.blade.php | 21 +++++-- .../FinanceLedgerAccountStatusTest.php | 57 +++++++++++++++--- .../Deployment/FinanceBatchIsolationTest.php | 20 +++++++ .../FinanceLedgerCalculatorTest.php | 38 +++++++++++- 9 files changed, 242 insertions(+), 44 deletions(-) diff --git a/app/Http/Controllers/Business/FinanceLedger.php b/app/Http/Controllers/Business/FinanceLedger.php index d8a1f773..458a98c5 100644 --- a/app/Http/Controllers/Business/FinanceLedger.php +++ b/app/Http/Controllers/Business/FinanceLedger.php @@ -99,7 +99,8 @@ class FinanceLedger extends Controller $summaryQuery = clone $query; $summary = [ 'count' => (clone $query)->count(), - 'balance' => round((float) (clone $summaryQuery)->sum('accounts.current_balance'), 2), + 'current_balance' => round((float) (clone $summaryQuery)->sum('accounts.current_balance'), 2), + 'balance' => round((float) (clone $summaryQuery)->sum('accounts.daily_available'), 2), 'blocked' => round((float) (clone $summaryQuery)->sum('accounts.blocked_amount'), 2), 'daily_available' => round((float) (clone $summaryQuery)->sum('accounts.daily_available'), 2), ]; @@ -187,7 +188,7 @@ class FinanceLedger extends Controller ]); } - public function updateAccountTodo(Request $request) + public function updateAccountTodo(Request $request, FinanceLedgerCalculator $calculator) { abort_unless(FinanceLedgerAccess::canEditAccountStatus(user()), 403, '只有财务台账管理员可以修改待办事项。'); @@ -240,7 +241,7 @@ class FinanceLedger extends Controller 'updated_by' => (string) user()->name, 'updated_at' => $now, ]; - $connection->transaction(function () use ($connection, $accountShort, $values, $now, $completionStatus): void { + $connection->transaction(function () use ($connection, $accountShort, $values, $now, $completionStatus, $calculator): void { $todos = $connection->table('fin_account_todos'); $existing = $todos->where('account_short', $accountShort)->exists(); if ($existing) { @@ -260,6 +261,7 @@ class FinanceLedger extends Controller 'updated_by' => (string) user()->name, 'updated_at' => $now, ]); + $calculator->refreshAccountAmounts($accountShort); } }); @@ -320,7 +322,7 @@ class FinanceLedger extends Controller ], 200, ['Cache-Control' => 'private, no-store']); } - public function updateAccountStatus(Request $request): JsonResponse + public function updateAccountStatus(Request $request, FinanceLedgerCalculator $calculator): JsonResponse { abort_unless(FinanceLedgerAccess::canEditAccountStatus(user()), 403, '只有财务台账管理员可以修改账户状态。'); @@ -349,14 +351,18 @@ class FinanceLedger extends Controller 'updated_at' => $now, ]; - if ($existing === null) { - $statuses->insert($values + [ - 'account_short' => $accountShort, - 'created_at' => $now, - ]); - } else { - $statuses->where('account_short', $accountShort)->update($values); - } + $connection->transaction(function () use ($statuses, $existing, $values, $accountShort, $now, $calculator): void { + if ($existing === null) { + $statuses->insert($values + [ + 'account_short' => $accountShort, + 'created_at' => $now, + ]); + } else { + $statuses->where('account_short', $accountShort)->update($values); + } + + $calculator->refreshAccountAmounts($accountShort); + }); return response()->json([ 'ok' => true, diff --git a/deploy/run-finance-batch.sh b/deploy/run-finance-batch.sh index 325e5bcf..a314c7f3 100644 --- a/deploy/run-finance-batch.sh +++ b/deploy/run-finance-batch.sh @@ -8,7 +8,7 @@ CAIWU_BATCH_CALLER_DIR="$PWD" export CAIWU_BATCH_CALLER_DIR case "${1:-}" in *:import|*:import-*|*:import:*|product:monthly-weighted-average:base) is_import=1 ;; - jushuitan:sync-shipping|store-receipt:order-match:sync|store-receipt:product-cost:sync|store-receipt:auto-resync|order-data:warm-shop-candidates|business-mirror:index-work-order-waybills|business-mirror:refresh-paid-sales|account-bills:exports:run) ;; + jushuitan:sync-shipping|store-receipt:order-match:sync|store-receipt:product-cost:sync|store-receipt:auto-resync|order-data:warm-shop-candidates|business-mirror:index-work-order-waybills|business-mirror:refresh-paid-sales|account-bills:exports:run|finance-ledger:recalculate) ;; *) echo 'Unsupported finance batch command' >&2; exit 64 ;; esac cd "$REMOTE_DIR" diff --git a/modules/EcommerceFinance/Services/FinanceLedgerCalculator.php b/modules/EcommerceFinance/Services/FinanceLedgerCalculator.php index 8e72acf5..47ac5b54 100644 --- a/modules/EcommerceFinance/Services/FinanceLedgerCalculator.php +++ b/modules/EcommerceFinance/Services/FinanceLedgerCalculator.php @@ -2,6 +2,7 @@ namespace Modules\EcommerceFinance\Services; +use App\Utilities\FinanceLedgerAccountStatus; use Illuminate\Support\Facades\DB; /** @@ -10,7 +11,8 @@ use Illuminate\Support\Facades\DB; * 计算口径固定在服务端,页面只读结果: * 期末 = 期初 + 当日入账 - 当日出账 * 账户当前余额 = 最新一条流水期末 - * 每日可出金额 = 当前余额 - 卡住金额 + * 正常账户:可用金额 = 当前余额,不可用金额 = 0 + * 异常账户:可用金额 = 0,不可用金额 = 当前余额 * 店铺余额 = 店铺所属公司名下账户当前余额合计(待财务确认) */ class FinanceLedgerCalculator @@ -150,11 +152,12 @@ class FinanceLedgerCalculator ->where('account_short', $account) ->update([ 'current_balance' => round((float) $balance, 2), - 'daily_available' => $connection->raw('ROUND(current_balance - blocked_amount, 2)'), 'updated_at' => $now, ]); } + $this->refreshAccountAmounts(); + $companies = $connection->table('fin_accounts') ->selectRaw('company_name, SUM(current_balance) AS balance') ->groupBy('company_name') @@ -175,6 +178,58 @@ class FinanceLedgerCalculator ]; } + /** + * 按账户状态回填可用金额和不可用金额。 + * + * 正常账户:可用金额 = 当前余额,不可用金额 = 0; + * 异常账户:可用金额 = 0,不可用金额 = 当前余额。 + */ + public function refreshAccountAmounts(?string $accountShort = null): int + { + $connection = $this->connection(); + if ($accountShort !== null) { + $status = $connection->table('fin_account_statuses') + ->where('account_short', $accountShort) + ->value('status'); + $normal = $status === null + || trim((string) $status) === '' + || $status === FinanceLedgerAccountStatus::NORMAL; + + return $connection->table('fin_accounts') + ->where('account_short', $accountShort) + ->update([ + 'daily_available' => $normal ? $connection->raw('current_balance') : 0, + 'blocked_amount' => $normal ? 0 : $connection->raw('current_balance'), + 'updated_at' => now(), + ]); + } + + $abnormalAccounts = $connection->table('fin_account_statuses') + ->where('status', '<>', FinanceLedgerAccountStatus::NORMAL) + ->pluck('account_short') + ->filter() + ->unique() + ->values() + ->all(); + $normalCount = $connection->table('fin_accounts')->update([ + 'daily_available' => $connection->raw('current_balance'), + 'blocked_amount' => 0, + 'updated_at' => now(), + ]); + + if ($abnormalAccounts === []) { + return $normalCount; + } + + return $normalCount + $connection->table('fin_accounts') + ->whereIn('account_short', $abnormalAccounts) + ->update([ + 'daily_available' => 0, + 'blocked_amount' => $connection->raw('current_balance'), + 'updated_at' => now(), + ]); + } + private function connection() { return DB::connection((string) config('upstream_mirrors.connection', 'business_mirror')); diff --git a/modules/EcommerceFinance/View/FinanceLedgerPageData.php b/modules/EcommerceFinance/View/FinanceLedgerPageData.php index f76310ef..56c540f8 100644 --- a/modules/EcommerceFinance/View/FinanceLedgerPageData.php +++ b/modules/EcommerceFinance/View/FinanceLedgerPageData.php @@ -150,7 +150,8 @@ class FinanceLedgerPageData $summaryQuery = clone $query; $summary = [ 'count' => (clone $query)->count(), - 'balance' => round((float) (clone $summaryQuery)->sum('current_balance'), 2), + 'current_balance' => round((float) (clone $summaryQuery)->sum('current_balance'), 2), + 'balance' => round((float) (clone $summaryQuery)->sum('daily_available'), 2), 'blocked' => round((float) (clone $summaryQuery)->sum('blocked_amount'), 2), ]; diff --git a/resources/views/business/pages/financial-ledger/ledger.blade.php b/resources/views/business/pages/financial-ledger/ledger.blade.php index 01b9c906..92e221e5 100644 --- a/resources/views/business/pages/financial-ledger/ledger.blade.php +++ b/resources/views/business/pages/financial-ledger/ledger.blade.php @@ -84,8 +84,8 @@ .ledger-mapping-bulk-actions { display:flex; flex-wrap:wrap; gap:.45rem; } .ledger-mapping-bulk .ledger-btn { height:1.95rem; } .ledger-download-status { flex-basis:100%; color:var(--vip-success, #53793E); font-size:.68rem; font-weight:600; line-height:1.4; text-align:right; } - .ledger-summary { display:grid; grid-template-columns:repeat(4, minmax(0, 1fr)); gap:.65rem; margin-bottom:.85rem; } - .ledger-summary__item { display:flex; flex-direction:column; justify-content:center; gap:.2rem; min-width:0; min-height:3.4rem; padding:.6rem .8rem; border:1px solid var(--vip-border, #D8D2C7); border-radius:.55rem; background:var(--vip-content-accent-soft, #F7F0DE); box-shadow:0 1px 2px rgba(16,24,40,.04); } + .ledger-summary { display:flex; flex-wrap:wrap; gap:.65rem; margin-bottom:.85rem; } + .ledger-summary__item { display:flex; flex:0 0 14rem; flex-direction:column; justify-content:center; gap:.2rem; width:14rem; min-width:14rem; height:3.4rem; padding:.6rem .8rem; border:1px solid var(--vip-border, #D8D2C7); border-radius:.55rem; background:var(--vip-content-accent-soft, #F7F0DE); box-shadow:0 1px 2px rgba(16,24,40,.04); } .ledger-summary__item--available { background:#f3f7ee; } .ledger-summary__item--blocked { background:#fff5f5; } .ledger-summary__item--link { color:inherit; text-decoration:none; transition:border-color .15s ease, box-shadow .15s ease; } @@ -170,8 +170,7 @@ .ledger-toast.is-error { border-color:var(--vip-danger, #B80000); color:var(--vip-danger, #B80000); } .ledger-source-tag { display:inline-flex; align-items:center; padding:.1rem .4rem; border-radius:.25rem; background:#f4f1eb; color:var(--vip-content-muted, #595959); font-size:.68rem; } .ledger-mono { font-variant-numeric:tabular-nums; } - @media (max-width: 900px) { .ledger-hero { align-items:flex-start; flex-direction:column; } .ledger-field { min-width:calc(50% - .5rem); } .ledger-summary { grid-template-columns:repeat(2, minmax(0, 1fr)); } } - @media (max-width: 560px) { .ledger-summary { grid-template-columns:1fr; } } + @media (max-width: 900px) { .ledger-hero { align-items:flex-start; flex-direction:column; } .ledger-field { min-width:calc(50% - .5rem); } }
拖动字段可调整列顺序;拖动表头右侧边界可调整列宽,双击边界恢复默认。
@@ -230,13 +229,13 @@| 公司名称 | 账户简称 | 账户类型 | 账户状态 | 地区 | 账号 | 开户行 | 开户城市/网点 | 公司规划 | 账户是否保留 | 后4位 | 单笔限额 | 日限额 | 月限额 | 年限额 | 可用金额 | 不可用金额 | 每日可出 | 预计出清天数 | 提额目标 | 提额状态 | 提额动作 | 已做动作 | 下一步 | 备注 | 责任人 | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 公司名称 | 账户简称 | 账户类型 | 账户状态 | 地区 | 账号 | 开户行 | 开户城市/网点 | 公司规划 | 账户是否保留 | 后4位 | 单笔限额 | 日限额 | 月限额 | 年限额 | 当前余额 | 可用金额 | 不可用金额 | 每日可出 | 预计出清天数 | 提额目标 | 提额状态 | 提额动作 | 已做动作 | 下一步 | 备注 | 责任人 | ||||||
| {{ $row->account_last4 }} | {{ $row->single_limit }} | {{ $row->daily_limit }} | {{ $row->monthly_limit }} | {{ $row->yearly_limit }} | {{ $money($row->current_balance) }} | {{ $money($row->blocked_amount) }} | {{ $money($row->daily_available) }} | {{ $row->clearing_days !== null ? number_format((float) $row->clearing_days, 2) : '' }} | {{ $row->raise_target }} | {{ $row->raise_status }} | {{ $row->raise_actions }} | {{ $row->done_actions }} | {{ $row->next_action }} | {{ $row->remark }} | {{ $row->owner }} | +{{ $row->account_last4 }} | {{ $row->single_limit }} | {{ $row->daily_limit }} | {{ $row->monthly_limit }} | {{ $row->yearly_limit }} | {{ $money($row->current_balance) }} | {{ $money($row->daily_available) }} | {{ $money($row->blocked_amount) }} | {{ $money($row->daily_available) }} | {{ $row->clearing_days !== null ? number_format((float) $row->clearing_days, 2) : '' }} | {{ $row->raise_target }} | {{ $row->raise_status }} | {{ $row->raise_actions }} | {{ $row->done_actions }} | {{ $row->next_action }} | {{ $row->remark }} | {{ $row->owner }} |
没有匹配的账户数据。 | ||||||||||||||||||||||||||||||||
没有匹配的账户数据。 | ||||||||||||||||||||||||||||||||