connection = $databases->connection( (string) config('upstream_mirrors.connection', 'business_mirror'), ); } /** * @param array $data * @return array */ public function update(int $id, array $data): array { return $this->connection->transaction(function () use ($id, $data): array { $account = $this->connection->table('fin_accounts')->where('id', $id)->lockForUpdate()->first(); if ($account === null) { abort(404); } $oldShort = trim((string) $account->account_short); $accountShort = $this->text($data['account_short'] ?? ''); $companyName = $this->text($data['company_name'] ?? ''); if ($accountShort === '' || $companyName === '') { throw ValidationException::withMessages([ 'account_short' => '账户简称和公司名称不能为空。', ]); } $duplicate = $this->connection->table('fin_accounts') ->where('account_short', $accountShort) ->where('id', '<>', $id) ->exists(); if ($duplicate) { throw ValidationException::withMessages([ 'account_short' => '账户简称已存在,请改用其他简称。', ]); } $accountType = $this->nullableText($data['account_type'] ?? null); $accountNo = $this->nullableText($data['account_no'] ?? null); $status = FinanceLedgerAccountStatus::normalize((string) ($data['status'] ?? '')); $accountLast4 = FinanceLedgerAccountType::last4( (string) $accountType, (string) $accountNo, (string) ($account->account_last4 ?? ''), ); $values = [ 'company_name' => $companyName, 'account_short' => $accountShort, 'region' => $this->nullableText($data['region'] ?? null), 'account_no' => $accountNo, 'bank_name' => $this->nullableText($data['bank_name'] ?? null), 'bank_city_branch' => $this->nullableText($data['bank_city_branch'] ?? null), 'account_type' => $accountType, 'company_plan' => $this->nullableText($data['company_plan'] ?? null), 'account_should_keep' => $this->nullableText($data['account_should_keep'] ?? null), 'account_last4' => $accountLast4, 'single_limit' => $this->nullableText($data['single_limit'] ?? null), 'daily_limit' => $this->nullableText($data['daily_limit'] ?? null), 'monthly_limit' => $this->nullableText($data['monthly_limit'] ?? null), 'yearly_limit' => $this->nullableText($data['yearly_limit'] ?? null), 'clearing_days' => $this->nullableNumber($data['clearing_days'] ?? null), 'raise_target' => $this->nullableText($data['raise_target'] ?? null), 'raise_status' => $this->nullableText($data['raise_status'] ?? null), 'raise_actions' => $this->nullableText($data['raise_actions'] ?? null), 'done_actions' => $this->nullableText($data['done_actions'] ?? null), 'next_action' => $this->nullableText($data['next_action'] ?? null), 'remark' => $this->nullableText($data['remark'] ?? null), 'owner' => $this->nullableText($data['owner'] ?? null), 'updated_at' => now(), ]; $before = (array) $account; if ($oldShort !== $accountShort) { $this->renameReferences($oldShort, $accountShort); } $this->connection->table('fin_accounts')->where('id', $id)->update($values); $this->upsertStatus($accountShort, $status); $this->connection->table('fin_account_type_overrides') ->where('account_short', $accountShort) ->delete(); $this->connection->table('fin_daily_flows') ->where('account_short', $accountShort) ->update([ 'company_name' => $companyName, 'updated_at' => now(), ]); $this->calculator->refreshAccountAmounts($accountShort); $after = (array) $this->connection->table('fin_accounts')->where('id', $id)->first(); $this->connection->table('fin_ledger_edit_events')->insert([ 'entity_type' => 'account', 'entity_key' => (string) $id, 'changes' => json_encode([ 'before' => $before, 'after' => $after, ], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR), 'user_id' => user()?->id, 'user_name' => (string) (user()?->name ?? ''), 'created_at' => now(), ]); return $after; }); } private function upsertStatus(string $accountShort, string $status): void { $values = [ 'status' => $status, 'updated_by' => (string) (user()?->name ?? ''), 'updated_at' => now(), ]; if ($this->connection->table('fin_account_statuses')->where('account_short', $accountShort)->exists()) { $this->connection->table('fin_account_statuses')->where('account_short', $accountShort)->update($values); return; } $this->connection->table('fin_account_statuses')->insert($values + [ 'account_short' => $accountShort, 'created_at' => now(), ]); } private function renameReferences(string $oldShort, string $newShort): void { $now = now(); foreach ([ 'fin_account_opening' => ['account_short' => [], 'updated_at' => $now], 'fin_daily_flows' => ['account_short' => [], 'updated_at' => $now], 'fin_transactions' => ['account_short' => [], 'updated_by' => (string) (user()?->name ?? ''), 'updated_at' => $now], 'fin_account_map' => ['account_short' => [], 'updated_by' => (string) (user()?->name ?? ''), 'updated_at' => $now], 'fin_account_statuses' => ['account_short' => [], 'updated_by' => (string) (user()?->name ?? ''), 'updated_at' => $now], 'fin_account_type_overrides' => ['account_short' => [], 'updated_by' => (string) (user()?->name ?? ''), 'updated_at' => $now], 'fin_account_todos' => ['account_short' => [], 'updated_by' => (string) (user()?->name ?? ''), 'updated_at' => $now], 'fin_account_keep_todos' => ['account_short' => [], 'updated_by' => (string) (user()?->name ?? ''), 'updated_at' => $now], 'fin_shops' => ['account_short' => [], 'updated_at' => $now], ] as $table => $extra) { if (! Schema::connection($this->connection->getName())->hasTable($table) || ! Schema::connection($this->connection->getName())->hasColumn($table, 'account_short')) { continue; } $this->connection->table($table)->where('account_short', $oldShort)->update([ 'account_short' => $newShort, ...$extra, ]); } if (Schema::connection($this->connection->getName())->hasTable('fin_daily_flow_overrides')) { $this->connection->table('fin_daily_flow_overrides') ->where('account_short', $oldShort) ->update([ 'account_short' => $newShort, 'updated_at' => $now, ]); } $this->connection->table('fin_account_map_events') ->where('old_account_short', $oldShort) ->update(['old_account_short' => $newShort]); $this->connection->table('fin_account_map_events') ->where('new_account_short', $oldShort) ->update(['new_account_short' => $newShort]); } private function text(mixed $value): string { return preg_replace('/\s+/u', ' ', trim((string) $value)) ?? trim((string) $value); } private function nullableText(mixed $value): ?string { $text = $this->text($value); return $text !== '' ? $text : null; } private function nullableNumber(mixed $value): ?float { if ($value === null || trim((string) $value) === '') { return null; } return round((float) $value, 2); } }