diff --git a/app/Http/Controllers/Business/FinanceLedger.php b/app/Http/Controllers/Business/FinanceLedger.php index 37737c0d..6fd7ce12 100644 --- a/app/Http/Controllers/Business/FinanceLedger.php +++ b/app/Http/Controllers/Business/FinanceLedger.php @@ -188,6 +188,229 @@ class FinanceLedger extends Controller ]); } + public function unretainedAccountTasks(Request $request) + { + $connection = DB::connection((string) config('upstream_mirrors.connection', 'business_mirror')); + $handlingMethods = [ + 'online' => '线上', + 'offline' => '线下', + ]; + $completionStatuses = [ + 'completed' => '已完成', + 'in_progress' => '进行中', + 'not_started' => '待开始', + ]; + $filters = [ + 'keep_short' => trim((string) $request->query('keep_short', '')), + 'keep_type' => trim((string) $request->query('keep_type', '')), + 'keep_status' => trim((string) $request->query('keep_status', '')), + 'keep_method' => trim((string) $request->query('keep_method', '')), + 'keep_owner' => trim((string) $request->query('keep_owner', '')), + 'keep_progress' => trim((string) $request->query('keep_progress', '')), + ]; + + $query = $connection->table('fin_accounts as accounts') + ->leftJoin('fin_account_statuses as account_status', 'account_status.account_short', '=', 'accounts.account_short') + ->leftJoin('fin_account_type_overrides as type_override', 'type_override.account_short', '=', 'accounts.account_short') + ->leftJoin('fin_account_keep_todos as account_todo', 'account_todo.account_short', '=', 'accounts.account_short') + ->select([ + 'accounts.*', + 'account_status.status as effective_account_status', + 'type_override.id as type_override_id', + 'type_override.account_type as effective_account_type', + 'account_todo.handling_method as todo_handling_method', + 'account_todo.owner_name as todo_owner_name', + 'account_todo.started_at as todo_started_at', + 'account_todo.completed_at as todo_completed_at', + 'account_todo.current_progress as todo_current_progress', + ]); + FinanceLedgerAccountShouldKeep::applyUnretainedCondition( + $query, + 'accounts.account_should_keep', + 'accounts.company_plan', + ); + + if ($filters['keep_short'] !== '') { + $query->where('accounts.account_short', 'like', '%' . $filters['keep_short'] . '%'); + } + if ($filters['keep_type'] !== '') { + $query->where(function ($effectiveType) use ($filters): void { + $effectiveType->where(function ($baseType) use ($filters): void { + $baseType->whereNull('type_override.id'); + if ($filters['keep_type'] === '__empty__') { + $baseType->where(function ($blank): void { + $blank->whereNull('accounts.account_type')->orWhere('accounts.account_type', ''); + }); + } else { + $baseType->where('accounts.account_type', $filters['keep_type']); + } + })->orWhere(function ($overrideType) use ($filters): void { + $overrideType->whereNotNull('type_override.id'); + if ($filters['keep_type'] === '__empty__') { + $overrideType->where(function ($blank): void { + $blank->whereNull('type_override.account_type')->orWhere('type_override.account_type', ''); + }); + } else { + $overrideType->where('type_override.account_type', $filters['keep_type']); + } + }); + }); + } + if ($filters['keep_status'] !== '' && array_key_exists($filters['keep_status'], FinanceLedgerAccountStatus::OPTIONS)) { + if ($filters['keep_status'] === FinanceLedgerAccountStatus::NORMAL) { + $query->where(function ($normalStatus): void { + $normalStatus->whereNull('account_status.status') + ->orWhere('account_status.status', FinanceLedgerAccountStatus::NORMAL); + }); + } else { + $query->where('account_status.status', $filters['keep_status']); + } + } + if ($filters['keep_method'] !== '' && array_key_exists($filters['keep_method'], $handlingMethods)) { + $query->where('account_todo.handling_method', $filters['keep_method']); + } + if ($filters['keep_owner'] !== '') { + $query->where('account_todo.owner_name', 'like', '%' . $filters['keep_owner'] . '%'); + } + if ($filters['keep_progress'] !== '' && array_key_exists($filters['keep_progress'], $completionStatuses)) { + if ($filters['keep_progress'] === 'not_started') { + $query->where(function ($notStarted): void { + $notStarted->whereNull('account_todo.current_progress') + ->orWhere('account_todo.current_progress', 'not_started'); + }); + } else { + $query->where('account_todo.current_progress', $filters['keep_progress']); + } + } + + $summaryQuery = clone $query; + $summary = [ + 'count' => (clone $query)->count(), + 'in_progress' => (clone $summaryQuery)->where('account_todo.current_progress', 'in_progress')->count(), + 'not_started' => (clone $summaryQuery)->where(function ($notStarted): void { + $notStarted->whereNull('account_todo.current_progress') + ->orWhere('account_todo.current_progress', 'not_started'); + })->count(), + 'completed' => (clone $summaryQuery)->where('account_todo.current_progress', 'completed')->count(), + ]; + + $sortField = (string) $request->query('keep_sort', ''); + $sortField = in_array($sortField, ['started_at', 'completed_at'], true) ? $sortField : ''; + $sortDirection = strtolower((string) $request->query('keep_direction', 'asc')); + $sortDirection = $sortDirection === 'desc' ? 'desc' : 'asc'; + if ($sortField !== '') { + $query->orderBy('account_todo.' . $sortField, $sortDirection); + } + + $rows = $query + ->orderBy('accounts.company_name') + ->orderBy('accounts.account_short') + ->paginate(50) + ->appends($filters) + ->appends(array_filter([ + 'keep_sort' => $sortField, + 'keep_direction' => $sortField !== '' ? $sortDirection : '', + ])) + ->withQueryString(); + + $rows->getCollection()->transform(static function ($row) { + $row->account_status = FinanceLedgerAccountStatus::normalize($row->effective_account_status ?? null); + if ($row->type_override_id !== null) { + $row->account_type = (string) ($row->effective_account_type ?? ''); + } + $row->todo_current_progress = $row->todo_current_progress ?: 'not_started'; + + return $row; + }); + + $typeOptions = FinanceLedgerAccountType::options( + $connection->table('fin_accounts') + ->whereNotNull('account_type')->where('account_type', '<>', '') + ->distinct()->orderBy('account_type')->pluck('account_type')->all(), + $connection->table('fin_account_type_overrides') + ->whereNotNull('account_type')->where('account_type', '<>', '') + ->distinct()->orderBy('account_type')->pluck('account_type')->all(), + ); + $ownerOptions = $connection->table('fin_account_keep_todos') + ->whereNotNull('owner_name')->where('owner_name', '<>', '') + ->distinct()->orderBy('owner_name')->pluck('owner_name')->all(); + + return view('business.pages.financial-ledger.unretained-account-tasks', [ + 'rows' => $rows, + 'summary' => $summary, + 'filters' => $filters, + 'sortField' => $sortField, + 'sortDirection' => $sortDirection, + 'backUrl' => route('business.pages.show', [ + 'section' => 'financial-operations', + 'page' => 'ledger', + ]) . '?tab=accounts', + 'canEdit' => FinanceLedgerAccess::canEditAccountStatus(user()), + 'handlingMethods' => $handlingMethods, + 'completionStatuses' => $completionStatuses, + 'accountStatusOptions' => FinanceLedgerAccountStatus::OPTIONS, + 'accountTypeOptions' => $typeOptions, + 'ownerOptions' => $ownerOptions, + ]); + } + + public function updateAccountKeepTodo(Request $request) + { + abort_unless(FinanceLedgerAccess::canEditAccountStatus(user()), 403, '只有财务台账管理员可以修改不保留账户待办。'); + + $data = $request->validate([ + 'account_short' => ['required', 'string', 'max:100'], + 'handling_method' => ['nullable', 'string', Rule::in(['online', 'offline'])], + 'owner_name' => ['nullable', 'string', 'max:100'], + 'started_at' => ['nullable', 'date'], + 'completed_at' => ['nullable', 'date'], + 'current_progress' => ['required', 'string', Rule::in(['completed', 'in_progress', 'not_started'])], + ]); + + $accountShort = trim((string) $data['account_short']); + $connection = DB::connection((string) config('upstream_mirrors.connection', 'business_mirror')); + $account = $connection->table('fin_accounts') + ->where('account_short', $accountShort) + ->first(['account_should_keep', 'company_plan']); + abort_if($account === null, 422, '账户简称不存在,请刷新待办清单后重试。'); + abort_unless( + FinanceLedgerAccountShouldKeep::display( + $account->account_should_keep ?? null, + $account->company_plan ?? null, + ) === FinanceLedgerAccountShouldKeep::NO, + 422, + '账户当前已设置为保留,无需继续填写不保留账户待办。', + ); + + $now = now(); + $values = [ + 'handling_method' => $data['handling_method'] ?? null, + 'owner_name' => $data['owner_name'] ?? null, + 'started_at' => filled($data['started_at'] ?? null) + ? Carbon::parse($data['started_at'])->format('Y-m-d H:i:s') + : null, + 'completed_at' => filled($data['completed_at'] ?? null) + ? Carbon::parse($data['completed_at'])->format('Y-m-d H:i:s') + : null, + 'current_progress' => (string) $data['current_progress'], + 'updated_by' => (string) user()->name, + 'updated_at' => $now, + ]; + + $todos = $connection->table('fin_account_keep_todos'); + $existing = $todos->where('account_short', $accountShort)->exists(); + if ($existing) { + $todos->where('account_short', $accountShort)->update($values); + } else { + $todos->insert($values + [ + 'account_short' => $accountShort, + 'created_at' => $now, + ]); + } + + return redirect()->back()->with('success', '不保留账户待办已保存'); + } + public function updateAccountTodo(Request $request, FinanceLedgerCalculator $calculator) { abort_unless(FinanceLedgerAccess::canEditAccountStatus(user()), 403, '只有财务台账管理员可以修改待办事项。'); diff --git a/app/Utilities/FinanceLedgerAccountShouldKeep.php b/app/Utilities/FinanceLedgerAccountShouldKeep.php index bf928203..9efe79ea 100644 --- a/app/Utilities/FinanceLedgerAccountShouldKeep.php +++ b/app/Utilities/FinanceLedgerAccountShouldKeep.php @@ -2,6 +2,8 @@ namespace App\Utilities; +use Illuminate\Database\Query\Builder; + class FinanceLedgerAccountShouldKeep { public const YES = '是'; @@ -42,4 +44,22 @@ class FinanceLedgerAccountShouldKeep return str_contains($plan, '保留') ? self::YES : self::NO; } + + public static function applyUnretainedCondition( + Builder $query, + string $explicitColumn = 'account_should_keep', + string $companyPlanColumn = 'company_plan', + ): Builder { + return $query->where(function (Builder $keep) use ($explicitColumn, $companyPlanColumn): void { + $keep->where($explicitColumn, self::NO) + ->orWhere(function (Builder $inferred) use ($explicitColumn, $companyPlanColumn): void { + $inferred->where(function (Builder $blank) use ($explicitColumn): void { + $blank->whereNull($explicitColumn)->orWhere($explicitColumn, ''); + }) + ->whereNotNull($companyPlanColumn) + ->where($companyPlanColumn, '<>', '') + ->where($companyPlanColumn, 'not like', '%保留%'); + }); + }); + } } diff --git a/database/migrations/2026_10_21_170000_create_finance_ledger_account_keep_todos.php b/database/migrations/2026_10_21_170000_create_finance_ledger_account_keep_todos.php new file mode 100644 index 00000000..d9058edc --- /dev/null +++ b/database/migrations/2026_10_21_170000_create_finance_ledger_account_keep_todos.php @@ -0,0 +1,39 @@ +connection()); + if (! $schema->hasTable('fin_accounts') || $schema->hasTable('fin_account_keep_todos')) { + return; + } + + $schema->create('fin_account_keep_todos', function (Blueprint $table): void { + $table->bigIncrements('id'); + $table->string('account_short', 100)->unique('fin_account_keep_todos_account_unique'); + $table->string('handling_method', 20)->nullable(); + $table->string('owner_name', 100)->nullable(); + $table->dateTime('started_at')->nullable(); + $table->dateTime('completed_at')->nullable(); + $table->string('current_progress', 20)->default('not_started'); + $table->string('updated_by', 100)->nullable(); + $table->timestamps(); + $table->index(['current_progress'], 'fin_account_keep_todos_progress_index'); + }); + } + + public function down(): void + { + Schema::connection($this->connection())->dropIfExists('fin_account_keep_todos'); + } +}; diff --git a/modules/EcommerceFinance/View/FinanceLedgerPageData.php b/modules/EcommerceFinance/View/FinanceLedgerPageData.php index f9ad2be5..2c69a6a6 100644 --- a/modules/EcommerceFinance/View/FinanceLedgerPageData.php +++ b/modules/EcommerceFinance/View/FinanceLedgerPageData.php @@ -159,6 +159,7 @@ class FinanceLedgerPageData $summaryQuery = clone $query; $summary = [ 'count' => (clone $query)->count(), + 'unretained' => FinanceLedgerAccountShouldKeep::applyUnretainedCondition(clone $query)->count(), '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 78f06150..b64c3d88 100644 --- a/resources/views/business/pages/financial-ledger/ledger.blade.php +++ b/resources/views/business/pages/financial-ledger/ledger.blade.php @@ -263,6 +263,7 @@
账户总表中“账户是否保留”为“否”的账户会自动进入本清单;不与其他页面的待办事项合并。
+拖动表头右侧边界可调整列宽,双击边界恢复默认
+| 公司名称 | +账户简称 | +账户类型 | +账户状态 | +账号 | +开户行 | +开户城市/网点 | +账户是否保留 | +办理方式 | +责任人员 | ++ + 开始时间 + + + | ++ + 完成时间 + + + | +当前进度 | +
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| + + {{ $row->company_name ?: '未填写' }} + | +{{ $row->account_short ?: '未填写' }} | +{{ $row->account_type ?: '未填写' }} | +{{ $accountStatusOptions[$row->account_status] ?? '正常' }} | +{{ $row->account_no ?: '未填写' }} | +{{ $row->bank_name ?: '未填写' }} | +{{ $row->bank_city_branch ?: '未填写' }} | +否 | ++ + | ++ | + | + |
+
+
+ @if ($canEdit)
+
+ @endif
+
+ |
+
| 当前没有需要处理的不保留账户 | ||||||||||||