From 22ad0f037892a556b80b91cf590d456497643d8a Mon Sep 17 00:00:00 2001 From: DoDo <1060330844@qq.com> Date: Wed, 21 Oct 2026 11:38:45 +0800 Subject: [PATCH 1/7] =?UTF-8?q?feat(=E8=B4=A2=E5=8A=A1=E5=8F=B0=E8=B4=A6):?= =?UTF-8?q?=20=E7=A7=BB=E5=85=A5=E8=B4=A2=E5=8A=A1=E7=BB=8F=E8=90=A5?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E5=B9=B6=E5=85=BC=E5=AE=B9=E6=97=A7=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=20-=20app/Utilities/SidebarPermissions.php?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Utilities/SidebarPermissions.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Utilities/SidebarPermissions.php b/app/Utilities/SidebarPermissions.php index aaea8c18..7120b6e5 100644 --- a/app/Utilities/SidebarPermissions.php +++ b/app/Utilities/SidebarPermissions.php @@ -10,6 +10,8 @@ class SidebarPermissions { private const LEGACY_PAGE_KEYS = [ 'refunds/small-payment-work-orders' => 'financial-operations/small-payment-work-orders', + 'financial-ledger' => 'financial-operations/ledger', + 'financial-ledger/ledger' => 'financial-operations/ledger', ]; private const FINAL_SECTION_KEYS = [ -- 2.47.2 From e18e003eb77b99a6e6472b37c6d7ce170add88f4 Mon Sep 17 00:00:00 2001 From: DoDo <1060330844@qq.com> Date: Wed, 21 Oct 2026 11:38:49 +0800 Subject: [PATCH 2/7] =?UTF-8?q?feat(=E8=B4=A2=E5=8A=A1=E5=8F=B0=E8=B4=A6):?= =?UTF-8?q?=20=E7=A7=BB=E5=85=A5=E8=B4=A2=E5=8A=A1=E7=BB=8F=E8=90=A5?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E5=B9=B6=E5=85=BC=E5=AE=B9=E6=97=A7=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=20-=20config/business=5Fnavigation.php?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/business_navigation.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/config/business_navigation.php b/config/business_navigation.php index 018e4b30..402be577 100644 --- a/config/business_navigation.php +++ b/config/business_navigation.php @@ -881,6 +881,7 @@ $navigation = [ ['slug' => 'bookkeeping-details', 'title' => '记账明细'], ['slug' => 'non-operating-expenses', 'title' => '营业外支出'], ['slug' => 'financial-analysis', 'title' => '财务经营分析表'], + ['slug' => 'ledger', 'title' => '财务台账'], ], ], [ @@ -943,14 +944,6 @@ $navigation = [ ['slug' => 'account-mapping', 'title' => '科目映射表'], ], ], - [ - 'slug' => 'financial-ledger', - 'title' => '财务台账', - 'icon' => 'menu_book', - 'pages' => [ - ['slug' => 'ledger', 'title' => '财务台账'], - ], - ], [ 'slug' => 'data-monitoring', 'title' => '数据监控', -- 2.47.2 From ce8a9f47f4c4180ef894d2e97471be0007ec8c83 Mon Sep 17 00:00:00 2001 From: DoDo <1060330844@qq.com> Date: Wed, 21 Oct 2026 11:38:52 +0800 Subject: [PATCH 3/7] =?UTF-8?q?feat(=E8=B4=A2=E5=8A=A1=E5=8F=B0=E8=B4=A6):?= =?UTF-8?q?=20=E7=A7=BB=E5=85=A5=E8=B4=A2=E5=8A=A1=E7=BB=8F=E8=90=A5?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E5=B9=B6=E5=85=BC=E5=AE=B9=E6=97=A7=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=20-=20docs/=E8=B4=A2=E5=8A=A1=E5=8F=B0=E8=B4=A6?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E8=AF=B4=E6=98=8E.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/财务台账开发说明.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/财务台账开发说明.md b/docs/财务台账开发说明.md index eb46ab7e..0e6d6ed5 100644 --- a/docs/财务台账开发说明.md +++ b/docs/财务台账开发说明.md @@ -61,7 +61,7 @@ docker compose -f docker-compose.local.yml exec -u www-data app \ ## 页面 -- 路由:`/workspace/financial-ledger/ledger` +- 路由:`/workspace/financial-operations/ledger`(旧地址会自动跳转) - 页面:账户总表、店铺台账、每日资金流水、动账明细、账户映射、数据来源 - 账户总表支持导出空白 Excel 录入模板,包含页面列、状态和账户类型选项及填写说明 - 写权限:账户映射、账户状态和账户类型修改仅系统管理员及配置的财务台账管理员角色;其他账号只读 -- 2.47.2 From 5ab7eb97794ded60907bf637c19ced74c65794e4 Mon Sep 17 00:00:00 2001 From: DoDo <1060330844@qq.com> Date: Wed, 21 Oct 2026 11:38:55 +0800 Subject: [PATCH 4/7] =?UTF-8?q?feat(=E8=B4=A2=E5=8A=A1=E5=8F=B0=E8=B4=A6):?= =?UTF-8?q?=20=E7=A7=BB=E5=85=A5=E8=B4=A2=E5=8A=A1=E7=BB=8F=E8=90=A5?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E5=B9=B6=E5=85=BC=E5=AE=B9=E6=97=A7=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=20-=20resources/views/business/pages/financial-ledger?= =?UTF-8?q?/ledger.blade.php?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/business/pages/financial-ledger/ledger.blade.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/business/pages/financial-ledger/ledger.blade.php b/resources/views/business/pages/financial-ledger/ledger.blade.php index ae08b65d..e1d42910 100644 --- a/resources/views/business/pages/financial-ledger/ledger.blade.php +++ b/resources/views/business/pages/financial-ledger/ledger.blade.php @@ -17,7 +17,7 @@ ], static fn ($value): bool => trim((string) $value) !== '')); $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') : ''; - $ledgerPageUrl = route('business.pages.show', ['section' => 'financial-ledger', 'page' => 'ledger']); + $ledgerPageUrl = route('business.pages.show', ['section' => 'financial-operations', 'page' => 'ledger']); $tabUrl = static fn (string $tab): string => $ledgerPageUrl . '?' . http_build_query(['tab' => $tab]); $sourceLabels = ['dingtalk' => '钉钉审批', 'shop' => '店铺提现', 'payable' => '供应商应付', 'store' => '店铺开店', 'workorder' => '聚工单']; @endphp @@ -345,7 +345,7 @@
- @forelse (($mapping['rows'] ?? collect()) as $row)@empty@endforelse + @forelse (($mapping['rows'] ?? collect()) as $row)@empty@endforelse
来源原始值笔数收入合计支出合计映射账户简称状态
{{ $sourceLabels[$row->source] ?? $row->source }}{{ $row->source_value }}{{ number_format((int) $row->txn_count) }}{{ $money($row->income_sum) }}{{ $money($row->expense_sum) }}@if ($canEdit)@else{{ $row->account_short ?: '未匹配' }}@endif{{ $row->account_short ? '已映射' : '未匹配' }}
没有匹配的映射记录。
{{ $sourceLabels[$row->source] ?? $row->source }}{{ $row->source_value }}{{ number_format((int) $row->txn_count) }}{{ $money($row->income_sum) }}{{ $money($row->expense_sum) }}@if ($canEdit)@else{{ $row->account_short ?: '未匹配' }}@endif{{ $row->account_short ? '已映射' : '未匹配' }}
没有匹配的映射记录。
共 {{ number_format($mapping['total'] ?? 0) }} 条 · 第 {{ ($mapping['rows'] ?? null)?->currentPage() ?? 1 }} / {{ ($mapping['rows'] ?? null)?->lastPage() ?? 1 }} 页@if (($mapping['rows'] ?? null) && !$mapping['rows']->onFirstPage())上一页@endif @if (($mapping['rows'] ?? null) && $mapping['rows']->hasMorePages())下一页@endif
-- 2.47.2 From cb8646b8bd9bed7372e525598783c372f9ff61b8 Mon Sep 17 00:00:00 2001 From: DoDo <1060330844@qq.com> Date: Wed, 21 Oct 2026 11:38:59 +0800 Subject: [PATCH 5/7] =?UTF-8?q?feat(=E8=B4=A2=E5=8A=A1=E5=8F=B0=E8=B4=A6):?= =?UTF-8?q?=20=E7=A7=BB=E5=85=A5=E8=B4=A2=E5=8A=A1=E7=BB=8F=E8=90=A5?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E5=B9=B6=E5=85=BC=E5=AE=B9=E6=97=A7=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=20-=20routes/admin.php?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routes/admin.php | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/routes/admin.php b/routes/admin.php index d3650580..e21de198 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -15,6 +15,7 @@ use App\Http\Controllers\Business\SupplierPayableActions; use App\Http\Controllers\Business\PayablesFinalActions; use App\Http\Controllers\Business\SupplierReconciliation as SupplierReconciliationActions; use App\Http\Controllers\Business\WithdrawalManagement; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; use Modules\EcommerceFinance\Http\Controllers\AccountBills; use Modules\EcommerceFinance\Http\Controllers\AlipayBills; @@ -649,39 +650,60 @@ Route::patch('workspace/supplier-payables/supplier-management/free-shipping-manu ->middleware('business.page:supplier-payables,supplier-management') ->name('business.supplier-management.free-shipping.update'); +Route::post('workspace/financial-operations/ledger/mapping/{id}', [\App\Http\Controllers\Business\FinanceLedger::class, 'updateMapping']) + ->whereNumber('id') + ->middleware('business.page:financial-operations,ledger') + ->name('business.financial-operations.ledger.mapping.update'); + +// Keep already-open ledger pages working while they post to the previous URL. Route::post('workspace/financial-ledger/mapping/{id}', [\App\Http\Controllers\Business\FinanceLedger::class, 'updateMapping']) ->whereNumber('id') - ->middleware('business.page:financial-ledger,ledger') + ->middleware('business.page:financial-operations,ledger') ->name('business.financial-ledger.mapping.update'); Route::post('workspace/financial-ledger/accounts/status', [\App\Http\Controllers\Business\FinanceLedger::class, 'updateAccountStatus']) - ->middleware('business.page:financial-ledger,ledger') + ->middleware('business.page:financial-operations,ledger') ->name('business.financial-ledger.accounts.status'); Route::post('workspace/financial-ledger/accounts/type', [\App\Http\Controllers\Business\FinanceLedger::class, 'updateAccountType']) - ->middleware('business.page:financial-ledger,ledger') + ->middleware('business.page:financial-operations,ledger') ->name('business.financial-ledger.accounts.type'); Route::get('workspace/financial-ledger/accounts/template', [\App\Http\Controllers\Business\FinanceLedger::class, 'downloadAccountTemplate']) - ->middleware('business.page:financial-ledger,ledger') + ->middleware('business.page:financial-operations,ledger') ->name('business.financial-ledger.accounts.template'); Route::post('workspace/financial-ledger/mapping/shop/preview', [\App\Http\Controllers\Business\FinanceLedger::class, 'previewShopMapping']) - ->middleware('business.page:financial-ledger,ledger') + ->middleware('business.page:financial-operations,ledger') ->name('business.financial-ledger.mapping.shop.preview'); Route::post('workspace/financial-ledger/mapping/shop/apply', [\App\Http\Controllers\Business\FinanceLedger::class, 'applyShopMapping']) - ->middleware('business.page:financial-ledger,ledger') + ->middleware('business.page:financial-operations,ledger') ->name('business.financial-ledger.mapping.shop.apply'); Route::post('workspace/financial-ledger/mapping/payable/preview', [\App\Http\Controllers\Business\FinanceLedger::class, 'previewPayableMapping']) - ->middleware('business.page:financial-ledger,ledger') + ->middleware('business.page:financial-operations,ledger') ->name('business.financial-ledger.mapping.payable.preview'); Route::post('workspace/financial-ledger/mapping/payable/apply', [\App\Http\Controllers\Business\FinanceLedger::class, 'applyPayableMapping']) - ->middleware('business.page:financial-ledger,ledger') + ->middleware('business.page:financial-operations,ledger') ->name('business.financial-ledger.mapping.payable.apply'); +Route::get('workspace/financial-ledger/{page?}/{tab?}', function (Request $request, ?string $page = null, ?string $tab = null) { + abort_unless($page === null || $page === 'ledger', 404); + + $query = $request->query(); + if ($tab !== null) { + $query['tab'] = $tab; + } + $target = route('business.pages.show', [ + 'section' => 'financial-operations', + 'page' => 'ledger', + ]); + + return redirect()->to($query === [] ? $target : $target . '?' . http_build_query($query)); +})->name('business.financial-ledger.legacy'); + Route::get('workspace/{section}/{page?}/{tab?}', 'Business\Pages@show') ->name('business.pages.show'); -- 2.47.2 From 4b0e829ef4473a52ad51a195e1440c67da0d4f9f Mon Sep 17 00:00:00 2001 From: DoDo <1060330844@qq.com> Date: Wed, 21 Oct 2026 11:39:03 +0800 Subject: [PATCH 6/7] =?UTF-8?q?feat(=E8=B4=A2=E5=8A=A1=E5=8F=B0=E8=B4=A6):?= =?UTF-8?q?=20=E7=A7=BB=E5=85=A5=E8=B4=A2=E5=8A=A1=E7=BB=8F=E8=90=A5?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E5=B9=B6=E5=85=BC=E5=AE=B9=E6=97=A7=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=20-=20tests/Feature/Business/FinanceLedgerAccountStat?= =?UTF-8?q?usTest.php?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Business/FinanceLedgerAccountStatusTest.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/Feature/Business/FinanceLedgerAccountStatusTest.php b/tests/Feature/Business/FinanceLedgerAccountStatusTest.php index 085aa02f..7de58a29 100644 --- a/tests/Feature/Business/FinanceLedgerAccountStatusTest.php +++ b/tests/Feature/Business/FinanceLedgerAccountStatusTest.php @@ -28,7 +28,7 @@ class FinanceLedgerAccountStatusTest extends FeatureTestCase { return route('business.pages.show', [ 'company_id' => $this->company->id, - 'section' => 'financial-ledger', + 'section' => 'financial-operations', 'page' => 'ledger', ]); } @@ -110,7 +110,7 @@ class FinanceLedgerAccountStatusTest extends FeatureTestCase $this->loginAs()->get(route('business.pages.show', [ 'company_id' => $this->company->id, - 'section' => 'financial-ledger', + 'section' => 'financial-operations', 'page' => 'ledger', 'accounts_short' => 'page-test', ])) @@ -146,7 +146,7 @@ class FinanceLedgerAccountStatusTest extends FeatureTestCase $this->loginAs()->get(route('business.pages.show', [ 'company_id' => $this->company->id, - 'section' => 'financial-ledger', + 'section' => 'financial-operations', 'page' => 'ledger', 'accounts_short' => '筛选账户', 'accounts_status' => 'limited', @@ -162,7 +162,7 @@ class FinanceLedgerAccountStatusTest extends FeatureTestCase $this->loginAs()->get(route('business.pages.show', [ 'company_id' => $this->company->id, - 'section' => 'financial-ledger', + 'section' => 'financial-operations', 'page' => 'ledger', 'accounts_short' => '筛选账户', 'accounts_status' => 'normal', @@ -173,7 +173,7 @@ class FinanceLedgerAccountStatusTest extends FeatureTestCase $this->loginAs()->get(route('business.pages.show', [ 'company_id' => $this->company->id, - 'section' => 'financial-ledger', + 'section' => 'financial-operations', 'page' => 'ledger', 'accounts_short' => '测试账户', 'accounts_status' => 'normal', @@ -227,7 +227,7 @@ class FinanceLedgerAccountStatusTest extends FeatureTestCase $this->loginAs()->get(route('business.pages.show', [ 'company_id' => $this->company->id, - 'section' => 'financial-ledger', + 'section' => 'financial-operations', 'page' => 'ledger', 'accounts_type' => '个人卡', ])) @@ -239,7 +239,7 @@ class FinanceLedgerAccountStatusTest extends FeatureTestCase $this->loginAs()->get(route('business.pages.show', [ 'company_id' => $this->company->id, - 'section' => 'financial-ledger', + 'section' => 'financial-operations', 'page' => 'ledger', 'accounts_type' => '公户', ])) -- 2.47.2 From 042234b06d587d0f918edd27adc789b68b7488d1 Mon Sep 17 00:00:00 2001 From: DoDo <1060330844@qq.com> Date: Wed, 21 Oct 2026 11:39:04 +0800 Subject: [PATCH 7/7] =?UTF-8?q?feat(=E8=B4=A2=E5=8A=A1=E5=8F=B0=E8=B4=A6):?= =?UTF-8?q?=20=E7=A7=BB=E5=85=A5=E8=B4=A2=E5=8A=A1=E7=BB=8F=E8=90=A5?= =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E5=B9=B6=E5=85=BC=E5=AE=B9=E6=97=A7=E9=93=BE?= =?UTF-8?q?=E6=8E=A5=20-=20tests/Feature/Business/FinanceLedgerNavigationT?= =?UTF-8?q?est.php?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Business/FinanceLedgerNavigationTest.php | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 tests/Feature/Business/FinanceLedgerNavigationTest.php diff --git a/tests/Feature/Business/FinanceLedgerNavigationTest.php b/tests/Feature/Business/FinanceLedgerNavigationTest.php new file mode 100644 index 00000000..f396fc1f --- /dev/null +++ b/tests/Feature/Business/FinanceLedgerNavigationTest.php @@ -0,0 +1,122 @@ +keyBy('slug'); + $financialOperations = $navigation->get('financial-operations'); + + $this->assertNotNull($financialOperations); + $this->assertSame('ledger', collect($financialOperations['pages'])->last()['slug']); + $this->assertFalse($navigation->has('financial-ledger')); + } + + public function test_ledger_page_and_legacy_link_open_under_financial_operations(): void + { + $this->loginAs(); + + $target = route('business.pages.show', [ + 'section' => 'financial-operations', + 'page' => 'ledger', + ]); + + $this->get($target . '?tab=flows') + ->assertOk() + ->assertSeeText('财务经营报表') + ->assertSeeText('财务台账') + ->assertSee('data-ledger-page', false) + ->assertSee('data-ledger-tab="flows"', false) + ->assertSee($target . '?tab=accounts', false); + + $legacy = route('business.pages.show', [ + 'section' => 'financial-ledger', + 'page' => 'ledger', + ]); + + $this->get($legacy . '?tab=flows')->assertRedirect($target . '?tab=flows'); + $this->get($legacy . '/flows')->assertRedirect($target . '?tab=flows'); + $this->get(route('business.financial-ledger.accounts.template'))->assertOk(); + } + + public function test_ledger_actions_use_financial_operations_permission(): void + { + foreach ([ + 'business.financial-operations.ledger.mapping.update', + 'business.financial-ledger.mapping.update', + 'business.financial-ledger.accounts.status', + 'business.financial-ledger.accounts.type', + 'business.financial-ledger.accounts.template', + 'business.financial-ledger.mapping.shop.preview', + 'business.financial-ledger.mapping.shop.apply', + 'business.financial-ledger.mapping.payable.preview', + 'business.financial-ledger.mapping.payable.apply', + ] as $routeName) { + $route = app('router')->getRoutes()->getByName($routeName); + + $this->assertNotNull($route, $routeName); + $this->assertContains( + 'business.page:financial-operations,ledger', + $route->gatherMiddleware(), + $routeName + ); + } + } + + public function test_legacy_ledger_permissions_follow_the_page(): void + { + $regular = $this->regularAccount(); + $subject = 'user_' . $regular->id; + + foreach (['financial-ledger', 'financial-ledger/ledger'] as $legacyKey) { + setting()->set('system.sidebar_permissions', [ + 'configured' => [$subject], + 'subjects' => [$subject => [$legacyKey]], + ]); + + $this->assertSame( + ['financial-operations/ledger'], + SidebarPermissions::allowedKeys($regular) + ); + + $navigation = SidebarPermissions::navigationFor($regular); + $this->assertSame(['financial-operations'], array_column($navigation, 'slug')); + $this->assertSame(['ledger'], array_column($navigation[0]['pages'], 'slug')); + } + + $this->loginAs($regular) + ->get(route('business.pages.show', [ + 'section' => 'financial-operations', + 'page' => 'ledger', + ])) + ->assertOk() + ->assertSee('data-ledger-page', false); + } + + private function regularAccount(): User + { + $name = 'fixture-finance-ledger-' . User::count(); + $user = User::create([ + 'name' => $name, + 'email' => $name . '@example.test', + 'password' => 'fixture-unused-password', + 'enabled' => true, + 'locale' => 'zh-CN', + 'landing_page' => 'dashboard', + ]); + $user->roles()->attach(Role::where('name', 'user')->firstOrFail()->id); + $user->permissions()->attach(Permission::where('name', 'read-admin-panel')->firstOrFail()->id); + $user->companies()->attach($this->company->id); + + return $user; + } +} -- 2.47.2