diff --git a/app/Http/Controllers/Business/Pages.php b/app/Http/Controllers/Business/Pages.php index f6abd339..4b1e236f 100644 --- a/app/Http/Controllers/Business/Pages.php +++ b/app/Http/Controllers/Business/Pages.php @@ -4693,6 +4693,7 @@ class Pages extends Controller 'online-daifa-price' => $this->contentForOnlineDaifaPrice(), 'product-costs' => $this->contentForProductCosts(), 'financial-analysis' => $this->contentForFinancialAnalysis(), + 'oa-process' => $this->contentForProcessDetails(), 'labor-expenses' => $this->contentForLaborExpenses(), 'courier-surcharge' => $this->contentForCourierSurcharge($tab), 'courier-surcharge-settlement' => $this->contentForCourierSurchargeSettlementPage(), @@ -4722,6 +4723,17 @@ class Pages extends Controller }; } + protected function contentForProcessDetails(): array + { + $data = app(\Modules\EcommerceFinance\View\ProcessDetailsPageData::class)->page(request()); + $data['view'] = 'business.pages.financial-operations.process-details-content'; + $data['scopeLabel'] = ($data['scope'] ?? null) === 'personal_permission' + ? '当前账号权限范围' + : ($data['scope'] ?? ''); + + return $data; + } + protected function contentForLaborExpenses(): array { return [ diff --git a/app/Utilities/BusinessNavigation.php b/app/Utilities/BusinessNavigation.php index 556b645b..49517a36 100644 --- a/app/Utilities/BusinessNavigation.php +++ b/app/Utilities/BusinessNavigation.php @@ -12,6 +12,30 @@ class BusinessNavigation $navigation = config('business_navigation', []); $pageExtensions = (array) config('business_navigation_page_extensions', []); + foreach ((array) ($pageExtensions['module_sections'] ?? []) as $definition) { + $section = (array) ($definition['section'] ?? []); + $sectionSlug = trim((string) ($section['slug'] ?? '')); + if ($sectionSlug === '' + || collect($navigation)->contains( + static fn (array $existing): bool => ($existing['slug'] ?? null) === $sectionSlug, + )) { + continue; + } + + $positionAfter = trim((string) ($definition['position_after'] ?? '')); + $anchorIndex = $positionAfter === '' + ? false + : collect($navigation)->search( + static fn (array $existing): bool => ($existing['slug'] ?? null) === $positionAfter, + ); + array_splice( + $navigation, + is_int($anchorIndex) ? $anchorIndex + 1 : count($navigation), + 0, + [$section], + ); + } + foreach ($navigation as &$section) { $sectionSlug = (string) ($section['slug'] ?? ''); diff --git a/config/business_navigation_page_extensions.php b/config/business_navigation_page_extensions.php index 981e6a08..c61ac703 100644 --- a/config/business_navigation_page_extensions.php +++ b/config/business_navigation_page_extensions.php @@ -10,10 +10,17 @@ return [ | Entries are appended to an existing section in configuration order. | */ - 'financial-operations' => [ + 'module_sections' => [ [ - 'slug' => 'process-details', - 'title' => '流程明细', + 'position_after' => 'inventory-management', + 'section' => [ + 'slug' => 'process-data', + 'title' => '流程数据', + 'icon' => 'account_tree', + 'pages' => [ + ['slug' => 'oa-process', 'title' => 'OA流程'], + ], + ], ], ], ]; diff --git a/modules/EcommerceFinance/Http/Controllers/ProcessDetails.php b/modules/EcommerceFinance/Http/Controllers/ProcessDetails.php index bdb0f0f4..527436ca 100644 --- a/modules/EcommerceFinance/Http/Controllers/ProcessDetails.php +++ b/modules/EcommerceFinance/Http/Controllers/ProcessDetails.php @@ -8,6 +8,7 @@ use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Routing\Controller; use Modules\EcommerceFinance\Repositories\DingProcessDetailMirrorRepository; use Modules\EcommerceFinance\Services\DingProcessDetailMirrorImporter; +use Modules\EcommerceFinance\View\ProcessDetailsPageData; use Symfony\Component\HttpFoundation\StreamedResponse; use Throwable; @@ -21,15 +22,15 @@ class ProcessDetails extends Controller { $filters = $this->filters($request); $page = max((int) $request->query('page', 1), 1); - $perPage = 50; + $perPage = ProcessDetailsPageData::normalizePerPage($request->query('per_page')); try { $result = $this->repository->paginate($filters, $page, $perPage); $options = $this->repository->filterOptions(); return view('business.pages.financial-operations.process-details', [ - 'section' => ['slug' => 'financial-operations', 'title' => '财务经营报表'], - 'page' => ['slug' => 'process-details', 'title' => '流程明细'], + 'section' => ['slug' => 'process-data', 'title' => '流程数据'], + 'page' => ['slug' => 'oa-process', 'title' => 'OA流程'], 'processRows' => new LengthAwarePaginator( $result['data'], $result['total'], @@ -53,8 +54,8 @@ class ProcessDetails extends Controller report($exception); return view('business.pages.financial-operations.process-details', [ - 'section' => ['slug' => 'financial-operations', 'title' => '财务经营报表'], - 'page' => ['slug' => 'process-details', 'title' => '流程明细'], + 'section' => ['slug' => 'process-data', 'title' => '流程数据'], + 'page' => ['slug' => 'oa-process', 'title' => 'OA流程'], 'processRows' => new LengthAwarePaginator([], 0, $perPage, $page, [ 'path' => $request->url(), 'query' => $request->except('page'), @@ -74,7 +75,7 @@ class ProcessDetails extends Controller 'mirrorStale' => false, 'mirrorStaleAfterMinutes' => 15, 'scope' => $this->repository->scope(), - 'error' => '流程明细镜像尚未完成导入,请稍后重试。', + 'error' => 'OA流程镜像尚未完成导入,请稍后重试。', ]); } } @@ -84,10 +85,10 @@ class ProcessDetails extends Controller if (! $this->repository->ready()) { return redirect() ->route('business.pages.show', [ - 'section' => 'financial-operations', - 'page' => 'process-details', + 'section' => 'process-data', + 'page' => 'oa-process', ]) - ->withErrors(['export' => '流程明细镜像尚未完成导入,暂时不能导出。']); + ->withErrors(['export' => 'OA流程镜像尚未完成导入,暂时不能导出。']); } $filters = $this->filters($request); @@ -124,7 +125,7 @@ class ProcessDetails extends Controller } fclose($stream); - }, '流程明细_' . now()->format('Ymd_His') . '.csv', [ + }, 'OA流程_' . now()->format('Ymd_His') . '.csv', [ 'Content-Type' => 'text/csv; charset=UTF-8', ]); } diff --git a/modules/EcommerceFinance/Repositories/DingProcessDetailMirrorRepository.php b/modules/EcommerceFinance/Repositories/DingProcessDetailMirrorRepository.php index d1d42991..c8cd2208 100644 --- a/modules/EcommerceFinance/Repositories/DingProcessDetailMirrorRepository.php +++ b/modules/EcommerceFinance/Repositories/DingProcessDetailMirrorRepository.php @@ -103,7 +103,7 @@ class DingProcessDetailMirrorRepository } $page = max($page, 1); - $perPage = min(max($perPage, 1), 100); + $perPage = min(max($perPage, 1), 200); $query = $this->filteredQuery($filters); $total = (clone $query)->count(); diff --git a/modules/EcommerceFinance/Routes/admin.php b/modules/EcommerceFinance/Routes/admin.php index d128dda9..0ac456fd 100644 --- a/modules/EcommerceFinance/Routes/admin.php +++ b/modules/EcommerceFinance/Routes/admin.php @@ -11,15 +11,34 @@ Route::admin('ecommerce-finance', function () { // 模块路由在 register 阶段先注册,因此这里的精确路由会优先命中。沿用 // business.pages.show 名称,侧边栏仍能生成与通用页面一致的可访问 URL。 Route::admin('ecommerce-finance', function (): void { - Route::get('workspace/financial-operations/process-details', [ProcessDetails::class, 'show']) + Route::get('workspace/process-data/oa-process', [ProcessDetails::class, 'show']) ->middleware('business.page.viewer') - ->defaults('section', 'financial-operations') - ->defaults('page', 'process-details') + ->defaults('section', 'process-data') + ->defaults('page', 'oa-process') ->name('business.pages.show'); - Route::get('workspace/financial-operations/process-details/export', [ProcessDetails::class, 'export']) + Route::get('workspace/process-data/oa-process/export', [ProcessDetails::class, 'export']) + ->middleware('business.page.viewer') + ->defaults('section', 'process-data') + ->defaults('page', 'oa-process') + ->name('business.process-details.export'); + + Route::get('workspace/financial-operations/process-details', function (\Illuminate\Http\Request $request) { + return redirect()->route('business.pages.show', array_merge($request->query(), [ + 'section' => 'process-data', + 'page' => 'oa-process', + ])); + }) ->middleware('business.page.viewer') ->defaults('section', 'financial-operations') ->defaults('page', 'process-details') - ->name('business.process-details.export'); + ->name('business.process-details.legacy'); + + Route::get('workspace/financial-operations/process-details/export', function (\Illuminate\Http\Request $request) { + return redirect()->route('business.process-details.export', $request->query()); + }) + ->middleware('business.page.viewer') + ->defaults('section', 'financial-operations') + ->defaults('page', 'process-details') + ->name('business.process-details.legacy.export'); }, ['prefix' => '', 'as' => '']); diff --git a/modules/EcommerceFinance/View/ProcessDetailsPageData.php b/modules/EcommerceFinance/View/ProcessDetailsPageData.php index eaaa432e..6a8330f7 100644 --- a/modules/EcommerceFinance/View/ProcessDetailsPageData.php +++ b/modules/EcommerceFinance/View/ProcessDetailsPageData.php @@ -9,16 +9,29 @@ use Throwable; class ProcessDetailsPageData { + public const PER_PAGE_OPTIONS = [50, 100, 200]; + + public const DEFAULT_PER_PAGE = 50; + public function __construct( private readonly DingProcessDetailMirrorRepository $repository, ) {} + public static function normalizePerPage(mixed $value): int + { + $perPage = (int) $value; + + return in_array($perPage, self::PER_PAGE_OPTIONS, true) + ? $perPage + : self::DEFAULT_PER_PAGE; + } + /** @return array */ public function page(Request $request): array { $filters = $this->filters($request); $page = max((int) $request->query('page', 1), 1); - $perPage = 50; + $perPage = self::normalizePerPage($request->query('per_page')); try { $result = $this->repository->paginate($filters, $page, $perPage); @@ -67,7 +80,7 @@ class ProcessDetailsPageData 'mirrorStale' => false, 'mirrorStaleAfterMinutes' => 15, 'scope' => $this->repository->scope(), - 'error' => '流程明细镜像尚未完成导入,请稍后重试。', + 'error' => 'OA流程镜像尚未完成导入,请稍后重试。', ]; } } diff --git a/resources/views/business/pages/financial-operations/process-details-content.blade.php b/resources/views/business/pages/financial-operations/process-details-content.blade.php index a8ac6126..18ebad1b 100644 --- a/resources/views/business/pages/financial-operations/process-details-content.blade.php +++ b/resources/views/business/pages/financial-operations/process-details-content.blade.php @@ -1,7 +1,28 @@ @php + $filters ??= []; + $summary ??= []; + $documentTypes ??= []; + $statuses ??= []; + $error ??= null; $paymentStates = $paymentStates ?? ($pageData['paymentStates'] ?? []); $mirrorStale = $mirrorStale ?? ($pageData['mirrorStale'] ?? false); $mirrorStaleAfterMinutes = $mirrorStaleAfterMinutes ?? ($pageData['mirrorStaleAfterMinutes'] ?? 15); + $perPageOptions = \Modules\EcommerceFinance\View\ProcessDetailsPageData::PER_PAGE_OPTIONS; + $scopeLabel ??= (($scope ?? null) === 'personal_permission' + ? '当前账号权限范围' + : ($scope ?? '')); + $money ??= static fn (mixed $value): string => $value === null || $value === '' + ? '-' + : number_format((float) $value, 2); + $text ??= static fn (mixed $value): string => $value === null || trim((string) $value) === '' + ? '-' + : (string) $value; + $statusClass ??= static fn (string $status): string => match ($status) { + '已完成' => 'is-success', + '审批中' => 'is-warning', + '已终止', '已撤销' => 'is-danger', + default => 'is-neutral', + }; $paymentStateClass = static fn (string $state): string => match ($state) { '出纳已打款' => 'is-success', '出纳待打款', '出纳退回' => 'is-warning', @@ -9,6 +30,16 @@ '出纳未开始', '流程终止', '不涉及付款' => 'is-neutral', default => 'is-neutral', }; + $pageUrl ??= route('business.pages.show', [ + 'section' => 'process-data', + 'page' => 'oa-process', + ]); + $exportFilters = array_filter( + $filters, + static fn (mixed $value): bool => $value !== '' && $value !== null, + ); + $exportUrl ??= url('/' . company_id() . '/workspace/process-data/oa-process/export') + . ($exportFilters === [] ? '' : '?' . http_build_query($exportFilters)); @endphp