connection(); $openingDate = (string) config('finance_ledger.opening_date', '2026-10-17'); $now = now(); $opening = $connection->table('fin_account_opening') ->where('opening_date', '<=', $openingDate) ->orderBy('opening_date') ->get() ->groupBy('account_short') ->map(static fn ($rows) => $rows->last()); $accountCompanies = $connection->table('fin_accounts') ->select(['account_short', 'company_name']) ->get() ->groupBy('account_short') ->map(static fn ($rows) => (string) ($rows->first()->company_name ?? '')); $mapping = $connection->table('fin_account_map') ->whereNotNull('account_short') ->where('account_short', '<>', '') ->get(['source', 'source_value', 'account_short']) ->keyBy(static fn ($row): string => $row->source . "\0" . $row->source_value); $transactions = $connection->table('fin_transactions') ->where('txn_date', '>=', $openingDate) ->orderBy('txn_date') ->orderBy('id') ->get(['txn_date', 'account_short', 'source', 'source_value', 'income', 'expense']); $daily = []; $balances = []; foreach ($transactions as $transaction) { $mapKey = $transaction->source . "\0" . $transaction->source_value; $account = (string) ($mapping->get($mapKey)->account_short ?? ''); if ($account === '') { $account = trim((string) $transaction->account_short); } if ($account === '' || ! $opening->has($account)) { continue; } $date = (string) $transaction->txn_date; $key = $account . "\0" . $date; $daily[$key] ??= [ 'account_short' => $account, 'flow_date' => $date, 'inflow' => 0.0, 'outflow' => 0.0, ]; $daily[$key]['inflow'] += (float) $transaction->income; $daily[$key]['outflow'] += (float) $transaction->expense; } $flows = []; $latest = []; foreach ($opening as $account => $openingRow) { $account = (string) $account; $balance = (float) $openingRow->opening_balance; $company = $accountCompanies->get($account, ''); $start = max((string) $openingRow->opening_date, $openingDate); $rows = array_values(array_filter( $daily, static fn (array $row): bool => $row['account_short'] === $account, )); usort($rows, static fn (array $a, array $b): int => strcmp($a['flow_date'], $b['flow_date'])); $openingFlow = [ 'flow_date' => $start, 'company_name' => $company, 'account_short' => $account, 'opening_balance' => round($balance, 2), 'inflow' => 0.0, 'outflow' => 0.0, 'closing_balance' => round($balance, 2), 'remark' => '期初', 'created_at' => $now, 'updated_at' => $now, ]; $flows[] = $openingFlow; $openingIndex = array_key_last($flows); foreach ($rows as $row) { if ($row['flow_date'] < $start) { continue; } $inflow = round((float) $row['inflow'], 2); $outflow = round((float) $row['outflow'], 2); if (abs($inflow) < 0.005 && abs($outflow) < 0.005) { continue; } if ($row['flow_date'] === $start) { // 期初日发生动账时合并到同一行,避免唯一键冲突。 $balance = round($balance + $inflow - $outflow, 2); $flows[$openingIndex]['inflow'] = $inflow; $flows[$openingIndex]['outflow'] = $outflow; $flows[$openingIndex]['closing_balance'] = $balance; $flows[$openingIndex]['remark'] = null; continue; } $balance = round($balance + $inflow - $outflow, 2); $flows[] = [ 'flow_date' => $row['flow_date'], 'company_name' => $company, 'account_short' => $account, 'opening_balance' => round($balance - $inflow + $outflow, 2), 'inflow' => $inflow, 'outflow' => $outflow, 'closing_balance' => $balance, 'remark' => null, 'created_at' => $now, 'updated_at' => $now, ]; } $latest[$account] = $balance; } $connection->transaction(function () use ($connection, $flows, $latest, $now): void { $connection->table('fin_daily_flows')->delete(); foreach (array_chunk($flows, 500) as $chunk) { $connection->table('fin_daily_flows')->insert($chunk); } foreach ($latest as $account => $balance) { $connection->table('fin_accounts') ->where('account_short', $account) ->update([ 'current_balance' => round((float) $balance, 2), 'daily_available' => $connection->raw('ROUND(current_balance - blocked_amount, 2)'), 'updated_at' => $now, ]); } $companies = $connection->table('fin_accounts') ->selectRaw('company_name, SUM(current_balance) AS balance') ->groupBy('company_name') ->pluck('balance', 'company_name'); foreach ($companies as $company => $balance) { $connection->table('fin_shops') ->where('company_name', (string) $company) ->update(['shop_balance' => round((float) $balance, 2), 'updated_at' => $now]); } }); return [ 'accounts' => count($latest), 'flows' => count($flows), 'opening_date' => $openingDate, 'balance_sum' => round(array_sum($latest), 2), ]; } private function connection() { return DB::connection((string) config('upstream_mirrors.connection', 'business_mirror')); } }