流程数据导航、OA流程改名及分页选项 #689
@ -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 [
|
||||
|
||||
@ -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'] ?? '');
|
||||
|
||||
|
||||
@ -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流程'],
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
@ -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',
|
||||
]);
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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' => '']);
|
||||
|
||||
@ -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<string,mixed> */
|
||||
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流程镜像尚未完成导入,请稍后重试。',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
<style>
|
||||
@ -356,6 +387,32 @@
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.process-details-page-size {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: .3rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.process-details-page-size select {
|
||||
width: 4.5rem;
|
||||
height: 2rem;
|
||||
padding: 0 .35rem;
|
||||
border: 1px solid var(--vip-border, #D8D2C7);
|
||||
border-radius: .45rem;
|
||||
background: #fff;
|
||||
color: var(--vip-content-ink, #171717);
|
||||
font: inherit;
|
||||
font-size: .75rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.process-details-page-size select:focus {
|
||||
border-color: var(--vip-content-accent-dark, #856632);
|
||||
outline: 0;
|
||||
box-shadow: 0 0 0 3px rgba(133, 102, 50, .2);
|
||||
}
|
||||
|
||||
.process-details-pagination {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@ -522,6 +579,10 @@
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.process-details-page-size {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.process-details-page-jump {
|
||||
margin-left: 0;
|
||||
}
|
||||
@ -551,7 +612,8 @@
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form method="GET" action="{{ $pageUrl }}" class="process-details-toolbar" aria-label="流程明细筛选">
|
||||
<form method="GET" action="{{ $pageUrl }}" class="process-details-toolbar" aria-label="OA流程筛选">
|
||||
<input type="hidden" name="per_page" value="{{ $processRows->perPage() }}">
|
||||
<div class="process-details-field">
|
||||
<label for="process-details-paid-date">打款日期</label>
|
||||
<input id="process-details-paid-date" type="date" name="paid_date" value="{{ $filters['paid_date'] ?? '' }}">
|
||||
@ -619,7 +681,7 @@
|
||||
金额合计按审批费用明细汇总,付款合计按结构化付款明细汇总,同一笔付款不会因关联多条费用而重复计算。
|
||||
付款状态按钉钉“出纳打款”节点判断;付款差额按“费用合计 − 已付款合计”单独计算,不参与付款状态判断。
|
||||
“出纳状态待同步”表示尚未取得出纳节点任务,不能据此推断已付款或未付款。
|
||||
当前展示的是当前账号权限范围内可读取的钉钉审批流程明细;审批中的金额和行项目仍可能随流程变化。
|
||||
当前展示的是当前账号权限范围内可读取的钉钉OA审批流程;审批中的金额和行项目仍可能随流程变化。
|
||||
企业级全量数据待领导开通 OA 权限后切换。
|
||||
</p>
|
||||
|
||||
@ -675,7 +737,7 @@
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="17" class="process-details-empty">
|
||||
当前筛选条件没有流程明细。可以调整月份、单据类型、状态或关键词后重试。
|
||||
当前筛选条件没有OA流程数据。可以调整月份、单据类型、状态或关键词后重试。
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
@ -692,9 +754,25 @@
|
||||
<span>共 <b>{{ number_format($processRows->total()) }}</b> 行</span>
|
||||
<span>第 <b>{{ number_format($processDetailsCurrentPage) }}</b> / {{ number_format($processDetailsLastPage) }} 页</span>
|
||||
<span>本页 <b>{{ number_format($processRows->count()) }}</b> 行</span>
|
||||
<form class="process-details-page-size" method="GET" action="{{ $pageUrl }}">
|
||||
@foreach ($filters as $filterName => $filterValue)
|
||||
@if ($filterValue !== '')
|
||||
<input type="hidden" name="{{ $filterName }}" value="{{ $filterValue }}">
|
||||
@endif
|
||||
@endforeach
|
||||
<label for="process-details-per-page">每页</label>
|
||||
<select id="process-details-per-page" name="per_page" onchange="this.form.submit()">
|
||||
@foreach ($perPageOptions as $perPageOption)
|
||||
<option value="{{ $perPageOption }}" @selected($processRows->perPage() === $perPageOption)>
|
||||
{{ $perPageOption }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<span>行</span>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<nav class="process-details-pagination" aria-label="流程明细分页">
|
||||
<nav class="process-details-pagination" aria-label="OA流程分页">
|
||||
@if ($processRows->onFirstPage())
|
||||
<span class="process-details-page-link is-disabled" aria-disabled="true" title="首页">
|
||||
<span class="material-icons-outlined" aria-hidden="true">first_page</span>
|
||||
@ -733,6 +811,7 @@
|
||||
|
||||
@if ($processDetailsLastPage > 1)
|
||||
<form class="process-details-page-jump" method="GET" action="{{ $pageUrl }}">
|
||||
<input type="hidden" name="per_page" value="{{ $processRows->perPage() }}">
|
||||
@foreach ($filters as $filterName => $filterValue)
|
||||
@if ($filterValue !== '')
|
||||
<input type="hidden" name="{{ $filterName }}" value="{{ $filterValue }}">
|
||||
|
||||
@ -32,14 +32,14 @@
|
||||
default => 'is-neutral',
|
||||
};
|
||||
$pageUrl = route('business.pages.show', [
|
||||
'section' => 'financial-operations',
|
||||
'page' => 'process-details',
|
||||
'section' => 'process-data',
|
||||
'page' => 'oa-process',
|
||||
]);
|
||||
$exportFilters = array_filter(
|
||||
$filters,
|
||||
static fn (mixed $value): bool => $value !== '' && $value !== null,
|
||||
);
|
||||
$exportUrl = url('/' . company_id() . '/workspace/financial-operations/process-details/export');
|
||||
$exportUrl = url('/' . company_id() . '/workspace/process-data/oa-process/export');
|
||||
if ($exportFilters !== []) {
|
||||
$exportUrl .= '?' . http_build_query($exportFilters);
|
||||
}
|
||||
@ -56,7 +56,7 @@
|
||||
@else
|
||||
<x-layouts.admin>
|
||||
<x-slot name="title">
|
||||
流程明细
|
||||
OA流程
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="content">
|
||||
|
||||
44
tests/Feature/Business/ProcessDetailsNavigationTest.php
Normal file
44
tests/Feature/Business/ProcessDetailsNavigationTest.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Business;
|
||||
|
||||
use App\Utilities\BusinessNavigation;
|
||||
use Tests\Feature\FeatureTestCase;
|
||||
|
||||
class ProcessDetailsNavigationTest extends FeatureTestCase
|
||||
{
|
||||
public function test_oa_process_is_in_dedicated_process_data_section(): void
|
||||
{
|
||||
$navigation = BusinessNavigation::all();
|
||||
$slugs = array_column($navigation, 'slug');
|
||||
|
||||
$inventoryIndex = array_search('inventory-management', $slugs, true);
|
||||
$processDataIndex = array_search('process-data', $slugs, true);
|
||||
$financeIndex = array_search('financial-operations', $slugs, true);
|
||||
|
||||
$this->assertIsInt($inventoryIndex);
|
||||
$this->assertIsInt($processDataIndex);
|
||||
$this->assertIsInt($financeIndex);
|
||||
$this->assertSame($inventoryIndex + 1, $processDataIndex);
|
||||
$this->assertSame($processDataIndex + 1, $financeIndex);
|
||||
|
||||
$section = $navigation[$processDataIndex];
|
||||
$this->assertSame('流程数据', $section['title']);
|
||||
$this->assertSame(
|
||||
[['slug' => 'oa-process', 'title' => 'OA流程']],
|
||||
array_map(
|
||||
static fn (array $page): array => [
|
||||
'slug' => $page['slug'],
|
||||
'title' => $page['title'],
|
||||
],
|
||||
$section['pages'],
|
||||
),
|
||||
);
|
||||
|
||||
$finance = $navigation[$financeIndex];
|
||||
$this->assertNotContains(
|
||||
'process-details',
|
||||
array_column($finance['pages'] ?? [], 'slug'),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -15,7 +15,9 @@ class ProcessDetailsPageTest extends FeatureTestCase
|
||||
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
|
||||
|
||||
$repository = Mockery::mock(DingProcessDetailMirrorRepository::class);
|
||||
$repository->shouldReceive('paginate')->once()->andReturn([
|
||||
$repository->shouldReceive('paginate')->once()
|
||||
->with(Mockery::type('array'), 1, 100)
|
||||
->andReturn([
|
||||
'data' => [
|
||||
(object) [
|
||||
'business_id' => '202601011200000000001',
|
||||
@ -65,18 +67,20 @@ class ProcessDetailsPageTest extends FeatureTestCase
|
||||
]);
|
||||
$this->app->instance(DingProcessDetailMirrorRepository::class, $repository);
|
||||
|
||||
$financeSection = collect(\App\Utilities\BusinessNavigation::all())
|
||||
->firstWhere('slug', 'financial-operations');
|
||||
$processSection = collect(\App\Utilities\BusinessNavigation::all())
|
||||
->firstWhere('slug', 'process-data');
|
||||
$this->assertContains(
|
||||
'process-details',
|
||||
array_column($financeSection['pages'] ?? [], 'slug'),
|
||||
'oa-process',
|
||||
array_column($processSection['pages'] ?? [], 'slug'),
|
||||
);
|
||||
|
||||
$this->loginAs()->get(route('business.pages.show', [
|
||||
'section' => 'financial-operations',
|
||||
'page' => 'process-details',
|
||||
'section' => 'process-data',
|
||||
'page' => 'oa-process',
|
||||
'per_page' => 100,
|
||||
]))->assertOk()
|
||||
->assertSee('流程明细')
|
||||
->assertSee('流程数据')
|
||||
->assertSee('OA流程')
|
||||
->assertSee('审批编号')
|
||||
->assertSee('发起人')
|
||||
->assertSee('单据类型')
|
||||
@ -99,6 +103,10 @@ class ProcessDetailsPageTest extends FeatureTestCase
|
||||
->assertSee('100.00')
|
||||
->assertSee('当前账号权限范围')
|
||||
->assertSee('同一笔付款不会因关联多条费用而重复计算')
|
||||
->assertSee('name="per_page"', false)
|
||||
->assertSee('value="50"', false)
|
||||
->assertSee('value="100" selected', false)
|
||||
->assertSee('value="200"', false)
|
||||
->assertSee('导出');
|
||||
}
|
||||
|
||||
@ -112,13 +120,37 @@ class ProcessDetailsPageTest extends FeatureTestCase
|
||||
$this->app->instance(DingProcessDetailMirrorRepository::class, $repository);
|
||||
|
||||
$this->loginAs()->get(route('business.pages.show', [
|
||||
'section' => 'financial-operations',
|
||||
'page' => 'process-details',
|
||||
'section' => 'process-data',
|
||||
'page' => 'oa-process',
|
||||
]))->assertOk()
|
||||
->assertSee('流程明细镜像尚未完成导入')
|
||||
->assertSee('OA流程镜像尚未完成导入')
|
||||
->assertSee('当前账号权限范围');
|
||||
}
|
||||
|
||||
public function test_legacy_process_details_url_redirects_to_oa_process(): void
|
||||
{
|
||||
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
|
||||
|
||||
$this->loginAs()->get(
|
||||
'/1/workspace/financial-operations/process-details?status=' . urlencode('已完成'),
|
||||
)->assertRedirect(route('business.pages.show', [
|
||||
'section' => 'process-data',
|
||||
'page' => 'oa-process',
|
||||
'status' => '已完成',
|
||||
]));
|
||||
}
|
||||
|
||||
public function test_legacy_process_details_export_url_redirects_to_oa_process(): void
|
||||
{
|
||||
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
|
||||
|
||||
$this->loginAs()->get(
|
||||
'/1/workspace/financial-operations/process-details/export?status=' . urlencode('已完成'),
|
||||
)->assertRedirect(route('business.process-details.export', [
|
||||
'status' => '已完成',
|
||||
]));
|
||||
}
|
||||
|
||||
public function test_export_streams_filtered_csv(): void
|
||||
{
|
||||
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
|
||||
|
||||
@ -194,6 +194,32 @@ class DingProcessDetailMirrorRepositoryTest extends TestCase
|
||||
$this->assertSame('202601011200000000001', (string) $rows[0]['business_id']);
|
||||
}
|
||||
|
||||
public function test_paginate_supports_two_hundred_rows_per_page(): void
|
||||
{
|
||||
$rows = [];
|
||||
for ($index = 1; $index <= 97; $index++) {
|
||||
$rows[] = $this->row(
|
||||
'20260301' . str_pad((string) $index, 14, '0', STR_PAD_LEFT),
|
||||
1,
|
||||
'202603',
|
||||
'批量测试',
|
||||
'审批中',
|
||||
'公摊日常报销',
|
||||
'分页容量测试',
|
||||
1.00,
|
||||
'2026-03-01 00:00',
|
||||
);
|
||||
}
|
||||
DB::connection('business_mirror')
|
||||
->table('ding_process_detail_mirror')
|
||||
->insert($rows);
|
||||
|
||||
$result = $this->repository->paginate([], 1, 200);
|
||||
|
||||
$this->assertSame(101, $result['total']);
|
||||
$this->assertCount(101, $result['data']);
|
||||
}
|
||||
|
||||
public function test_not_ready_when_state_is_missing(): void
|
||||
{
|
||||
DB::connection('business_mirror')
|
||||
|
||||
@ -6,6 +6,7 @@ use Illuminate\Http\Request;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Modules\EcommerceFinance\Repositories\DingProcessDetailMirrorRepository;
|
||||
use Modules\EcommerceFinance\View\ProcessDetailsPageData;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ProcessDetailsPageDataTest extends TestCase
|
||||
@ -30,6 +31,9 @@ class ProcessDetailsPageDataTest extends TestCase
|
||||
$this->assertStringContainsString('class="process-details-table-card"', $content);
|
||||
$this->assertStringContainsString('class="process-details-pagination"', $content);
|
||||
$this->assertStringContainsString('class="process-details-page-jump"', $content);
|
||||
$this->assertStringContainsString('$pageUrl ??=', $content);
|
||||
$this->assertStringContainsString('$exportUrl ??=', $content);
|
||||
$this->assertStringContainsString('$statusClass ??=', $content);
|
||||
$this->assertStringNotContainsString('$processRows->links()', $content);
|
||||
}
|
||||
|
||||
@ -80,7 +84,7 @@ class ProcessDetailsPageDataTest extends TestCase
|
||||
]);
|
||||
|
||||
$data = (new ProcessDetailsPageData($repository))->page(Request::create(
|
||||
'/1/workspace/financial-operations/process-details',
|
||||
'/1/workspace/process-data/oa-process',
|
||||
'GET',
|
||||
['paid_date' => '2026-10-24', 'payment_state' => '出纳已打款', 'page' => 2],
|
||||
));
|
||||
@ -111,4 +115,52 @@ class ProcessDetailsPageDataTest extends TestCase
|
||||
$this->assertArrayNotHasKey('months', $data);
|
||||
$this->assertNull($data['error']);
|
||||
}
|
||||
|
||||
#[DataProvider('perPageProvider')]
|
||||
public function test_page_size_only_accepts_supported_options(mixed $requested, int $expected): void
|
||||
{
|
||||
$repository = $this->createMock(DingProcessDetailMirrorRepository::class);
|
||||
$repository->expects($this->once())
|
||||
->method('paginate')
|
||||
->with($this->isType('array'), 1, $expected)
|
||||
->willReturn([
|
||||
'data' => [],
|
||||
'total' => 0,
|
||||
'summary' => [
|
||||
'rows' => 0,
|
||||
'approvals' => 0,
|
||||
'amount_total' => 0,
|
||||
'bank_amount_total' => 0,
|
||||
],
|
||||
'status' => 'succeeded',
|
||||
'last_synced_at' => null,
|
||||
'scope' => 'personal_permission',
|
||||
]);
|
||||
$repository->expects($this->once())
|
||||
->method('filterOptions')
|
||||
->willReturn([
|
||||
'document_types' => [],
|
||||
'statuses' => [],
|
||||
'payment_states' => [],
|
||||
]);
|
||||
|
||||
$data = (new ProcessDetailsPageData($repository))->page(Request::create(
|
||||
'/1/workspace/process-data/oa-process',
|
||||
'GET',
|
||||
['per_page' => $requested],
|
||||
));
|
||||
|
||||
$this->assertSame($expected, $data['processRows']->perPage());
|
||||
}
|
||||
|
||||
/** @return array<string,array{mixed,int}> */
|
||||
public static function perPageProvider(): array
|
||||
{
|
||||
return [
|
||||
'50 条' => ['50', 50],
|
||||
'100 条' => ['100', 100],
|
||||
'200 条' => ['200', 200],
|
||||
'非法值回退' => ['500', 50],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user