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); } }
@@ -217,7 +216,7 @@ -
+