安装明细创建日期筛选与导出 #94

Merged
chenxi merged 1 commits from fix/codex-installation-filter-export into main 2026-09-01 16:10:07 +08:00
4 changed files with 71 additions and 2 deletions

View File

@ -10,6 +10,7 @@ use App\Business\StoreReceivingAccountWorkbookImporter;
use App\Models\Banking\MarketingAccountBill;
use App\Models\Business\CourierAgreementSignature;
use App\Models\Business\CourierSigningSubject;
use App\Models\Business\InstallationDetail;
use App\Models\StoreReceivables\StoreZhongcaiReconciliation;
use App\Utilities\SidebarPermissions;
use Illuminate\Http\Request;
@ -184,6 +185,49 @@ class Pages extends Controller
return response()->json(['data' => $payload['data'], 'has_more' => $payload['has_more'] ?? false]);
}
public function exportInstallationDetails(Request $request): StreamedResponse
{
$section = collect(SidebarPermissions::navigationFor(user()))->firstWhere('slug', 'financial-operations');
abort_unless($section && collect($section['pages'] ?? [])->contains('slug', 'installation-details'), 403, '当前账号没有该页面权限。');
$filters = InstallationDetailMirrorRepository::requestFilters();
$firstPage = $this->installationDetails->paginate(1, 100, $filters);
abort_if(! $firstPage['ready'], 503, '安装明细镜像尚未完成首次同步');
$labels = InstallationDetail::columnLabels();
return response()->streamDownload(function () use ($filters, $firstPage, $labels): void {
$csv = fopen('php://output', 'w');
if ($csv === false) {
throw new \RuntimeException('无法创建安装明细导出流。');
}
fputs($csv, chr(0xEF) . chr(0xBB) . chr(0xBF));
fputcsv($csv, array_values($labels));
$page = 1;
$result = $firstPage;
do {
foreach ($result['data'] as $row) {
fputcsv($csv, array_map(
fn (string $name): string => $this->csvText($row[$name] ?? ''),
array_keys($labels),
));
}
$page++;
$hasMore = ($page - 1) * 100 < $result['total'];
if ($hasMore) {
$result = $this->installationDetails->paginate($page, 100, $filters);
}
} while ($hasMore);
fclose($csv);
}, '安装明细_' . now()->format('Ymd_His') . '.csv', [
'Content-Type' => 'text/csv; charset=UTF-8',
'Cache-Control' => 'private, no-store',
]);
}
protected function viewData(
array $section,
?array $page = null,

View File

@ -636,7 +636,7 @@
<form method="GET" action="{{ request()->url() }}" class="installation-filter-bar" aria-label="安装明细筛选查询栏">
@php
$filterFields = [
['name' => 'created_from', 'label' => '提交日期:', 'type' => 'date'], ['name' => 'created_to', 'label' => '至:', 'type' => 'date'],
['name' => 'created_from', 'label' => '创建日期:', 'type' => 'date'], ['name' => 'created_to', 'label' => '至:', 'type' => 'date'],
['name' => 'customer_review_from', 'label' => '客服审核时间:', 'type' => 'date'], ['name' => 'customer_review_to', 'label' => '至:', 'type' => 'date'],
['name' => 'finance_review_from', 'label' => '财务审核时间:', 'type' => 'date'], ['name' => 'finance_review_to', 'label' => '至:', 'type' => 'date'],
['name' => 'push_status', 'label' => '推送状态:'], ['name' => 'order_no', 'label' => '订单编号:'],
@ -661,6 +661,7 @@
<div class="installation-filter-actions">
<button type="submit" class="biz-btn biz-btn-primary">查询</button>
<a href="{{ request()->url() }}" class="biz-btn biz-btn-ghost">清除</a>
<a href="{{ route('business.installation-details.export', request()->query()) }}" class="biz-btn biz-btn-ghost">导出</a>
</div>
</form>
@endif

View File

@ -124,6 +124,9 @@ Route::get('workspace/financial-operations/installation-details/details/{guid}',
->where('guid', '[A-Za-z0-9-]+')
->name('business.installation-details.details');
Route::get('workspace/financial-operations/installation-details/export', 'Business\Pages@exportInstallationDetails')
->name('business.installation-details.export');
Route::get('workspace/{section}/{page?}/{tab?}', 'Business\Pages@show')
->name('business.pages.show');

View File

@ -79,7 +79,7 @@ class ManagementCrudTest extends FeatureTestCase
->assertSeeText('安装单号')
->assertSeeText('申请总金额')
->assertSeeText('商品名称(1)')
->assertSeeText('提交日期:')
->assertSeeText('创建日期:')
->assertSeeText('客服审核时间:')
->assertSeeText('财务审核时间:')
->assertSeeText('推送状态:')
@ -87,12 +87,33 @@ class ManagementCrudTest extends FeatureTestCase
->assertSeeText('选择删除:')
->assertSeeText('订单类型:')
->assertSeeText('商品ID:')
->assertSeeText('导出')
->assertSeeText('INSTALL-TEST-001')
->assertSeeText('测试安装公司')
->assertSeeText('120.50')
->assertSeeText('共 1 条');
}
public function testInstallationDetailsExportUsesFiltersAndMirrorRows(): void
{
$row = array_fill_keys(array_keys(InstallationDetail::columnLabels()), null);
$row['installation_no'] = 'INSTALL-EXPORT-001';
$mirror = Mockery::mock(InstallationDetailMirrorRepository::class);
$mirror->shouldReceive('paginate')->once()->with(1, 100, ['created_from' => '2026-08-01'])->andReturn([
'ready' => true,
'total' => 1,
'data' => [$row],
]);
$this->app->instance(InstallationDetailMirrorRepository::class, $mirror);
$response = $this->loginAs()->get(route('business.installation-details.export', [
'created_from' => '2026-08-01',
]));
$response->assertOk()->assertHeader('content-type', 'text/csv; charset=UTF-8');
$this->assertStringContainsString('INSTALL-EXPORT-001', $response->streamedContent());
}
public function testInstallationDetailsEndpointReturnsChildItemsForMainRecord(): void
{
$mirror = Mockery::mock(InstallationDetailMirrorRepository::class);