From 872cec7236f6d631e8963c437f4c52b742978752 Mon Sep 17 00:00:00 2001 From: shanshi <3316401569@qq.com> Date: Tue, 20 Oct 2026 11:17:36 +0800 Subject: [PATCH 01/24] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E8=B4=A2?= =?UTF-8?q?=E5=8A=A1=E5=8F=B0=E8=B4=A6=E8=B4=A6=E6=88=B7=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E7=BB=B4=E6=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/Business/FinanceLedger.php | 50 +++++++- app/Utilities/FinanceLedgerAccess.php | 8 ++ app/Utilities/FinanceLedgerAccountStatus.php | 30 +++++ ...create_finance_ledger_account_statuses.php | 41 ++++++ docs/财务台账开发说明.md | 9 +- .../View/FinanceLedgerPageData.php | 18 ++- .../pages/financial-ledger/ledger.blade.php | 78 ++++++++++- routes/admin.php | 4 + .../FinanceLedgerAccountStatusTest.php | 121 ++++++++++++++++++ 9 files changed, 352 insertions(+), 7 deletions(-) create mode 100644 app/Utilities/FinanceLedgerAccountStatus.php create mode 100644 database/migrations/2026_10_20_120000_create_finance_ledger_account_statuses.php create mode 100644 tests/Feature/Business/FinanceLedgerAccountStatusTest.php diff --git a/app/Http/Controllers/Business/FinanceLedger.php b/app/Http/Controllers/Business/FinanceLedger.php index bc74fd42..854ffd86 100644 --- a/app/Http/Controllers/Business/FinanceLedger.php +++ b/app/Http/Controllers/Business/FinanceLedger.php @@ -2,11 +2,13 @@ namespace App\Http\Controllers\Business; -use Illuminate\Routing\Controller; +use App\Utilities\FinanceLedgerAccountStatus; use App\Utilities\FinanceLedgerAccess; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Routing\Controller; use Illuminate\Support\Facades\DB; +use Illuminate\Validation\Rule; use Modules\EcommerceFinance\Services\FinanceLedgerCalculator; class FinanceLedger extends Controller @@ -59,4 +61,50 @@ class FinanceLedger extends Controller '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']); + } } diff --git a/app/Utilities/FinanceLedgerAccess.php b/app/Utilities/FinanceLedgerAccess.php index 309fbbcf..9fb40a73 100644 --- a/app/Utilities/FinanceLedgerAccess.php +++ b/app/Utilities/FinanceLedgerAccess.php @@ -28,4 +28,12 @@ class FinanceLedgerAccess return in_array(SystemAccess::roleName($user), $roles, true); } + + /** + * 账户状态与账户映射使用同一组财务台账编辑角色。 + */ + public static function canEditAccountStatus(?User $user = null): bool + { + return self::canEditMapping($user); + } } diff --git a/app/Utilities/FinanceLedgerAccountStatus.php b/app/Utilities/FinanceLedgerAccountStatus.php new file mode 100644 index 00000000..1fd25926 --- /dev/null +++ b/app/Utilities/FinanceLedgerAccountStatus.php @@ -0,0 +1,30 @@ + '正常', + self::ONLINE_BANKING_ABNORMAL => '网银异常', + self::LIMITED => '限额', + self::BUSINESS_DEREGISTERED => '工商注销', + ]; + + public static function normalize(?string $status): string + { + $status = trim((string) $status); + + return array_key_exists($status, self::OPTIONS) ? $status : self::NORMAL; + } + + public static function label(?string $status): string + { + return self::OPTIONS[self::normalize($status)]; + } +} diff --git a/database/migrations/2026_10_20_120000_create_finance_ledger_account_statuses.php b/database/migrations/2026_10_20_120000_create_finance_ledger_account_statuses.php new file mode 100644 index 00000000..8b468a11 --- /dev/null +++ b/database/migrations/2026_10_20_120000_create_finance_ledger_account_statuses.php @@ -0,0 +1,41 @@ +connection()); + + if (! $schema->hasTable('fin_account_statuses')) { + $schema->create('fin_account_statuses', function (Blueprint $table): void { + $table->bigIncrements('id'); + $table->string('account_short', 100)->unique('fin_account_statuses_account_unique'); + $table->string('status', 40)->default('normal'); + $table->string('updated_by', 100)->nullable(); + $table->timestamps(); + + $table->index(['status'], 'fin_account_statuses_status_index'); + }); + } + } + + public function down(): void + { + Schema::connection($this->connection())->dropIfExists('fin_account_statuses'); + } +}; diff --git a/docs/财务台账开发说明.md b/docs/财务台账开发说明.md index 0821a212..05f5d291 100644 --- a/docs/财务台账开发说明.md +++ b/docs/财务台账开发说明.md @@ -16,8 +16,10 @@ | `fin_daily_flows` | 每日资金流水 | | `fin_account_map` | 来源值 → 账户简称 | | `fin_account_map_events` | 映射修改审计 | +| `fin_account_statuses` | 账户状态维护(正常、网银异常、限额、工商注销) | -迁移:`database/migrations/2026_10_19_160000_create_finance_ledger_tables.php`。 +迁移:`database/migrations/2026_10_19_160000_create_finance_ledger_tables.php`、 +`database/migrations/2026_10_20_120000_create_finance_ledger_account_statuses.php`。 ## 本地导入 @@ -59,9 +61,12 @@ docker compose -f docker-compose.local.yml exec -u www-data app \ - 路由:`/workspace/financial-ledger/ledger` - 页面:账户总表、店铺台账、每日资金流水、动账明细、账户映射、数据来源 -- 写权限:账户映射修改仅系统管理员;其他账号只读 +- 写权限:账户映射和账户状态修改仅系统管理员及配置的财务台账管理员角色;其他账号只读 - 调度:`finance-ledger:sync-rpt` 每天 02:30(Asia/Shanghai) +账户状态独立保存在 `fin_account_statuses`,重新导入账户基础数据不会覆盖; +页面修改使用异步保存,不触发整页刷新。 + 生产宿主机 `/etc/cron.d` 仍需增加每日调用入口;本项目当前没有运行 `schedule:run` 的容器,调度登记不能替代宿主机 cron。 diff --git a/modules/EcommerceFinance/View/FinanceLedgerPageData.php b/modules/EcommerceFinance/View/FinanceLedgerPageData.php index 4b014023..3d4277ee 100644 --- a/modules/EcommerceFinance/View/FinanceLedgerPageData.php +++ b/modules/EcommerceFinance/View/FinanceLedgerPageData.php @@ -2,6 +2,8 @@ namespace Modules\EcommerceFinance\View; +use App\Utilities\FinanceLedgerAccountStatus; +use App\Utilities\FinanceLedgerAccess; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Query\Builder; use Illuminate\Support\Facades\DB; @@ -51,7 +53,9 @@ class FinanceLedgerPageData 'ledgerSourceSummary' => $this->sourceSummary(), 'ledgerMeta' => $this->meta(), 'ledgerAccountOptions' => $this->accountOptions(), - 'ledgerCanEditMapping' => \App\Utilities\FinanceLedgerAccess::canEditMapping(user()), + 'ledgerAccountStatusOptions' => FinanceLedgerAccountStatus::OPTIONS, + 'ledgerCanEditMapping' => FinanceLedgerAccess::canEditMapping(user()), + 'ledgerCanEditAccountStatus' => FinanceLedgerAccess::canEditAccountStatus(user()), ]; } @@ -80,6 +84,18 @@ class FinanceLedgerPageData ->paginate(100, ['*'], 'accounts_page') ->withQueryString(); + $accountShorts = $rows->pluck('account_short')->filter()->unique()->values()->all(); + $statuses = $accountShorts === [] + ? collect() + : $this->db()->table('fin_account_statuses') + ->whereIn('account_short', $accountShorts) + ->pluck('status', 'account_short'); + $rows->getCollection()->transform(static function ($row) use ($statuses) { + $row->account_status = FinanceLedgerAccountStatus::normalize($statuses[$row->account_short] ?? null); + + return $row; + }); + return ['rows' => $rows, 'summary' => $summary]; } diff --git a/resources/views/business/pages/financial-ledger/ledger.blade.php b/resources/views/business/pages/financial-ledger/ledger.blade.php index a1c52686..a45cfa8a 100644 --- a/resources/views/business/pages/financial-ledger/ledger.blade.php +++ b/resources/views/business/pages/financial-ledger/ledger.blade.php @@ -3,6 +3,8 @@ $tabs = $ledgerTabs ?? []; $activeTab = $ledgerTab ?? 'accounts'; $canEdit = (bool) ($ledgerCanEditMapping ?? false); + $canEditAccountStatus = (bool) ($ledgerCanEditAccountStatus ?? false); + $accountStatusOptions = $ledgerAccountStatusOptions ?? \App\Utilities\FinanceLedgerAccountStatus::OPTIONS; $money = static fn ($value): string => $value === null || $value === '' ? '' : number_format((float) $value, 2); $date = static fn ($value): string => $value ? \Illuminate\Support\Carbon::parse($value)->format('Y-m-d') : ''; $tabUrl = static fn (string $tab): string => route('business.pages.show', ['section' => 'financial-ledger', 'page' => 'ledger', 'tab' => $tab]); @@ -64,6 +66,19 @@ .ledger-map-input:focus { border-color:var(--vip-content-accent-dark, #856632); outline:none; box-shadow:0 0 0 3px rgba(133,102,50,.16); } .ledger-map-input.is-saving { border-color:var(--vip-content-accent, #B9954F); background:#fffdf6; } .ledger-map-input.is-error { border-color:var(--vip-danger, #B80000); background:#fff5f5; } + .ledger-status-select { width:7.4rem; height:2rem; padding:0 .45rem; border:1px solid var(--vip-border, #D8D2C7); border-radius:.4rem; background:#fff; color:var(--vip-content-ink, #171717); font:inherit; font-weight:600; cursor:pointer; } + .ledger-status-select:focus { border-color:var(--vip-content-accent-dark, #856632); outline:none; box-shadow:0 0 0 3px rgba(133,102,50,.16); } + .ledger-status-select[data-status="normal"] { border-color:rgba(83,121,62,.38); background:#f3f7ee; color:var(--vip-success, #53793E); } + .ledger-status-select[data-status="online_banking_abnormal"] { border-color:rgba(184,0,0,.34); background:#fff5f5; color:var(--vip-danger, #B80000); } + .ledger-status-select[data-status="limited"] { border-color:rgba(147,95,7,.36); background:#fff8eb; color:var(--vip-warning, #935F07); } + .ledger-status-select[data-status="business_deregistered"] { border-color:rgba(89,89,89,.32); background:#f3f1ec; color:var(--vip-content-muted, #595959); } + .ledger-status-select.is-saving { opacity:.62; cursor:wait; } + .ledger-status-select.is-error { border-color:var(--vip-danger, #B80000); box-shadow:0 0 0 3px rgba(184,0,0,.12); } + .ledger-status-badge { display:inline-flex; align-items:center; min-height:1.65rem; padding:.1rem .48rem; border:1px solid transparent; border-radius:999px; font-size:.68rem; font-weight:700; } + .ledger-status-badge[data-status="normal"] { border-color:rgba(83,121,62,.28); background:#f3f7ee; color:var(--vip-success, #53793E); } + .ledger-status-badge[data-status="online_banking_abnormal"] { border-color:rgba(184,0,0,.24); background:#fff5f5; color:var(--vip-danger, #B80000); } + .ledger-status-badge[data-status="limited"] { border-color:rgba(147,95,7,.26); background:#fff8eb; color:var(--vip-warning, #935F07); } + .ledger-status-badge[data-status="business_deregistered"] { border-color:rgba(89,89,89,.24); background:#f3f1ec; color:var(--vip-content-muted, #595959); } .ledger-toast { position:fixed; right:1.5rem; bottom:1.5rem; z-index:80; max-width:24rem; padding:.7rem 1rem; border:1px solid var(--vip-border, #D8D2C7); border-radius:.5rem; background:#fff; box-shadow:0 4px 12px rgba(23,23,23,.15); font-size:.78rem; display:none; } .ledger-toast.is-visible { display:block; } .ledger-toast.is-error { border-color:var(--vip-danger, #B80000); color:var(--vip-danger, #B80000); } @@ -113,14 +128,27 @@
| 公司名称 | 账户简称 | 地区 | 账号 | 开户行 | 开户城市/网点 | 账户类型 | 公司规划 | 后4位 | 单笔限额 | 日限额 | 月限额 | 年限额 | 当前余额 | 卡住金额 | 每日可出 | 预计出清天数 | 提额目标 | 提额状态 | 提额动作 | 已做动作 | 下一步 | 备注 | 责任人 | |||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 公司名称 | 账户简称 | 账户状态 | 地区 | 账号 | 开户行 | 开户城市/网点 | 账户类型 | 公司规划 | 后4位 | 单笔限额 | 日限额 | 月限额 | 年限额 | 当前余额 | 卡住金额 | 每日可出 | 预计出清天数 | 提额目标 | 提额状态 | 提额动作 | 已做动作 | 下一步 | 备注 | 责任人 | ||||||||||||||||||||||||
| {{ $row->company_name }} | {{ $row->account_short }} | {{ $row->region }} | {{ $row->account_no }} | {{ $row->bank_name }} | {{ $row->bank_city_branch }} | {{ $row->account_type }} | {{ $row->company_plan }} | {{ $row->account_last4 }} | {{ $row->single_limit }} | {{ $row->daily_limit }} | {{ $row->monthly_limit }} | {{ $row->yearly_limit }} | {{ $money($row->current_balance) }} | {{ $money($row->blocked_amount) }} | {{ $money($row->daily_available) }} | {{ $row->clearing_days !== null ? number_format((float) $row->clearing_days, 2) : '' }} | {{ $row->raise_target }} | {{ $row->raise_status }} | {{ $row->raise_actions }} | {{ $row->done_actions }} | {{ $row->next_action }} | {{ $row->remark }} | {{ $row->owner }} | +{{ $row->company_name }} | +{{ $row->account_short }} | ++ @if ($canEditAccountStatus) + + @else + {{ $accountStatusOptions[$row->account_status] ?? '正常' }} + @endif + | +{{ $row->region }} | {{ $row->account_no }} | {{ $row->bank_name }} | {{ $row->bank_city_branch }} | {{ $row->account_type }} | {{ $row->company_plan }} | {{ $row->account_last4 }} | {{ $row->single_limit }} | {{ $row->daily_limit }} | {{ $row->monthly_limit }} | {{ $row->yearly_limit }} | {{ $money($row->current_balance) }} | {{ $money($row->blocked_amount) }} | {{ $money($row->daily_available) }} | {{ $row->clearing_days !== null ? number_format((float) $row->clearing_days, 2) : '' }} | {{ $row->raise_target }} | {{ $row->raise_status }} | {{ $row->raise_actions }} | {{ $row->done_actions }} | {{ $row->next_action }} | {{ $row->remark }} | {{ $row->owner }} |
没有匹配的账户数据。 | ||||||||||||||||||||||||||||||||||||||||||||||||
没有匹配的账户数据。 | ||||||||||||||||||||||||||||||||||||||||||||||||
| 公司名称 | 账户简称 | 账户状态 | 地区 | 账号 | 开户行 | 开户城市/网点 | 账户类型 | 公司规划 | 后4位 | 单笔限额 | 日限额 | 月限额 | 年限额 | 当前余额 | 卡住金额 | 每日可出 | 预计出清天数 | 提额目标 | 提额状态 | 提额动作 | 已做动作 | 下一步 | 备注 | 责任人 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 公司名称 | 账户简称 | 账户状态 | 地区 | 账号 | 开户行 | 开户城市/网点 | 账户类型 | 公司规划 | 后4位 | 单笔限额 | 日限额 | 月限额 | 年限额 | 当前余额 | 卡住金额 | 每日可出 | 预计出清天数 | 提额目标 | 提额状态 | 提额动作 | 已做动作 | 下一步 | 备注 | 责任人 |
| {{ $row->company_name }} | {{ $row->account_short }} | -+ | @if ($canEditAccountStatus) |