download(); } public function updateMapping(Request $request, int $id, FinanceLedgerCalculator $calculator): JsonResponse { abort_unless(FinanceLedgerAccess::canEditMapping(user()), 403, '只有财务台账管理员可以修改账户映射。'); $data = $request->validate([ 'account_short' => ['nullable', 'string', 'max:100'], ]); $account = trim((string) ($data['account_short'] ?? '')); $connection = DB::connection((string) config('upstream_mirrors.connection', 'business_mirror')); $row = $connection->table('fin_account_map')->where('id', $id)->first(); abort_if($row === null, 404); if ($account !== '') { $exists = $connection->table('fin_accounts')->where('account_short', $account)->exists(); abort_unless($exists, 422, '账户简称不存在,请从账户总表中选择。'); } $old = (string) ($row->account_short ?? ''); $connection->table('fin_account_map')->where('id', $id)->update([ 'account_short' => $account !== '' ? $account : null, 'updated_by' => (string) user()->name, 'updated_at' => now(), ]); $connection->table('fin_account_map_events')->insert([ 'source' => (string) $row->source, 'source_value' => (string) $row->source_value, 'old_account_short' => $old !== '' ? $old : null, 'new_account_short' => $account !== '' ? $account : null, 'user_id' => (int) user()->id, 'user_name' => (string) user()->name, 'created_at' => now(), ]); if ($request->boolean('recalculate', true)) { $calculator->recalculate(); } $mapped = $connection->table('fin_account_map') ->whereNotNull('account_short')->where('account_short', '<>', '')->count(); return response()->json([ 'ok' => true, 'account_short' => $account, 'mapped' => $mapped, 'total' => $connection->table('fin_account_map')->count(), ], 200, ['Cache-Control' => 'private, no-store']); } public function updateAccountStatus(Request $request): JsonResponse { abort_unless(FinanceLedgerAccess::canEditAccountStatus(user()), 403, '只有财务台账管理员可以修改账户状态。'); $data = $request->validate([ 'account_short' => ['required', 'string', 'max:100'], 'status' => ['required', 'string', Rule::in(array_keys(FinanceLedgerAccountStatus::OPTIONS))], ]); $accountShort = trim((string) $data['account_short']); abort_if($accountShort === '', 422, '账户简称不能为空。'); $connection = DB::connection((string) config('upstream_mirrors.connection', 'business_mirror')); abort_unless( $connection->table('fin_accounts')->where('account_short', $accountShort)->exists(), 422, '账户简称不存在,请刷新账户总表后重试。', ); $status = FinanceLedgerAccountStatus::normalize((string) $data['status']); $statuses = $connection->table('fin_account_statuses'); $existing = $statuses->where('account_short', $accountShort)->first(); $now = now(); $values = [ 'status' => $status, 'updated_by' => (string) user()->name, 'updated_at' => $now, ]; if ($existing === null) { $statuses->insert($values + [ 'account_short' => $accountShort, 'created_at' => $now, ]); } else { $statuses->where('account_short', $accountShort)->update($values); } return response()->json([ 'ok' => true, 'account_short' => $accountShort, 'status' => $status, 'label' => FinanceLedgerAccountStatus::label($status), ], 200, ['Cache-Control' => 'private, no-store']); } public function updateAccountType(Request $request): JsonResponse { abort_unless(FinanceLedgerAccess::canEditAccountType(user()), 403, '只有财务台账管理员可以修改账户类型。'); $data = $request->validate([ 'account_short' => ['required', 'string', 'max:100'], 'account_type' => ['nullable', 'string', 'max:50'], ]); $accountShort = trim((string) $data['account_short']); $accountType = trim((string) ($data['account_type'] ?? '')); abort_if($accountShort === '', 422, '账户简称不能为空。'); $connection = DB::connection((string) config('upstream_mirrors.connection', 'business_mirror')); abort_unless( $connection->table('fin_accounts')->where('account_short', $accountShort)->exists(), 422, '账户简称不存在,请刷新账户总表后重试。', ); if ($accountType !== '') { $allowedTypes = FinanceLedgerAccountType::options( $connection->table('fin_accounts') ->whereNotNull('account_type')->where('account_type', '<>', '') ->distinct()->pluck('account_type')->all(), $connection->table('fin_account_type_overrides') ->whereNotNull('account_type')->where('account_type', '<>', '') ->distinct()->pluck('account_type')->all(), ); abort_unless(in_array($accountType, $allowedTypes, true), 422, '请选择现有账户类型。'); } $overrides = $connection->table('fin_account_type_overrides'); $existing = $overrides->where('account_short', $accountShort)->first(); $now = now(); $values = [ 'account_type' => $accountType !== '' ? $accountType : null, 'updated_by' => (string) user()->name, 'updated_at' => $now, ]; if ($existing === null) { $overrides->insert($values + [ 'account_short' => $accountShort, 'created_at' => $now, ]); } else { $overrides->where('account_short', $accountShort)->update($values); } return response()->json([ 'ok' => true, 'account_short' => $accountShort, 'account_type' => $accountType, 'label' => $accountType !== '' ? $accountType : '未填写', ], 200, ['Cache-Control' => 'private, no-store']); } public function updateAccountPlan(Request $request): JsonResponse { abort_unless(FinanceLedgerAccess::canEditAccountStatus(user()), 403, '只有财务台账管理员可以修改公司规划。'); $data = $request->validate([ 'account_short' => ['required', 'string', 'max:100'], 'company_plan' => ['nullable', 'string', 'max:50'], ]); $accountShort = trim((string) $data['account_short']); $plan = trim((string) ($data['company_plan'] ?? '')); if ($accountShort === '') { abort(422, '账户简称不能为空。'); } $connection = DB::connection((string) config('upstream_mirrors.connection', 'business_mirror')); abort_unless( $connection->table('fin_accounts')->where('account_short', $accountShort)->exists(), 422, '账户简称不存在,请刷新账户总表后重试。', ); $allowedPlans = $connection->table('fin_accounts') ->whereNotNull('company_plan') ->where('company_plan', '<>', '') ->distinct() ->pluck('company_plan') ->map(static fn (mixed $value): string => trim((string) $value)) ->filter() ->values() ->all(); abort_unless($plan === '' || in_array($plan, $allowedPlans, true), 422, '请选择现有公司规划。'); $connection->table('fin_accounts')->where('account_short', $accountShort)->update([ 'company_plan' => $plan !== '' ? $plan : null, 'updated_at' => now(), ]); return response()->json([ 'ok' => true, 'account_short' => $accountShort, 'company_plan' => $plan, 'label' => $plan !== '' ? $plan : '未填写', ], 200, ['Cache-Control' => 'private, no-store']); } public function updateAccountKeep(Request $request): JsonResponse { abort_unless(FinanceLedgerAccess::canEditAccountStatus(user()), 403, '只有财务台账管理员可以修改账户是否保留。'); $data = $request->validate([ 'account_short' => ['required', 'string', 'max:100'], 'account_should_keep' => ['nullable', 'string', Rule::in(FinanceLedgerAccountShouldKeep::OPTIONS)], ]); $accountShort = trim((string) $data['account_short']); $keep = FinanceLedgerAccountShouldKeep::normalize((string) ($data['account_should_keep'] ?? '')); if ($accountShort === '') { abort(422, '账户简称不能为空。'); } $connection = DB::connection((string) config('upstream_mirrors.connection', 'business_mirror')); abort_unless( $connection->table('fin_accounts')->where('account_short', $accountShort)->exists(), 422, '账户简称不存在,请刷新账户总表后重试。', ); $connection->table('fin_accounts')->where('account_short', $accountShort)->update([ 'account_should_keep' => $keep !== '' ? $keep : null, 'updated_at' => now(), ]); return response()->json([ 'ok' => true, 'account_short' => $accountShort, 'account_should_keep' => $keep, 'label' => $keep !== '' ? $keep : '未填写', ], 200, ['Cache-Control' => 'private, no-store']); } public function previewShopMapping(FinanceLedgerShopMappingBulk $bulk): JsonResponse { abort_unless(FinanceLedgerAccess::canEditMapping(user()), 403, '只有财务台账管理员可以批量修改账户映射。'); $preview = $bulk->preview(); return response()->json([ 'ok' => true, 'total' => $preview['total'], 'matched' => $preview['matched'], 'unmatched' => $preview['unmatched'], 'sample' => array_slice($preview['candidates'], 0, 10), 'unmatched_sample' => array_slice($preview['unmatched_values'], 0, 10), ], 200, ['Cache-Control' => 'private, no-store']); } public function applyShopMapping(FinanceLedgerShopMappingBulk $bulk): JsonResponse { abort_unless(FinanceLedgerAccess::canEditMapping(user()), 403, '只有财务台账管理员可以批量修改账户映射。'); $result = $bulk->apply(user()); return response()->json([ 'ok' => true, 'total' => $result['total'], 'mapped' => $result['mapped'], 'unmatched' => $result['unmatched'], ], 200, ['Cache-Control' => 'private, no-store']); } public function previewPayableMapping(FinanceLedgerPayableMappingBulk $bulk): JsonResponse { abort_unless(FinanceLedgerAccess::canEditMapping(user()), 403, '只有财务台账管理员可以批量修改账户映射。'); $preview = $bulk->preview(); return response()->json([ 'ok' => true, 'total' => $preview['total'], 'eligible' => $preview['eligible'], 'ambiguous' => $preview['ambiguous'], 'missing' => $preview['missing'], 'sample' => array_slice($preview['candidates'], 0, 10), 'ambiguous_sample' => array_slice($preview['ambiguous_values'], 0, 10), 'missing_sample' => array_slice($preview['missing_values'], 0, 10), ], 200, ['Cache-Control' => 'private, no-store']); } public function applyPayableMapping(FinanceLedgerPayableMappingBulk $bulk): JsonResponse { abort_unless(FinanceLedgerAccess::canEditMapping(user()), 403, '只有财务台账管理员可以批量修改账户映射。'); $result = $bulk->apply(user()); return response()->json([ 'ok' => true, 'total' => $result['total'], 'mapped' => $result['mapped'], 'ambiguous' => $result['ambiguous'], 'missing' => $result['missing'], ], 200, ['Cache-Control' => 'private, no-store']); } }