增加其他附加费导入模板 #211

Merged
renshen merged 1 commits from feature/renshen-other-surcharge-template into main 2026-09-20 11:39:51 +08:00
5 changed files with 167 additions and 27 deletions

View File

@ -13,6 +13,19 @@ use Symfony\Component\HttpFoundation\StreamedResponse;
class CourierPayableSummaries extends Controller
{
private const IMPORT_TEMPLATES = [
'freight-difference' => [
'path' => 'templates/courier-payables/freight-difference-import-template.xlsx',
'ascii_name' => 'freight_difference_import_template.xlsx',
'download_name' => '运费差导入模板.xlsx',
],
'other-surcharge-summary' => [
'path' => 'templates/courier-payables/other-surcharge-import-template.xlsx',
'ascii_name' => 'other_surcharge_import_template.xlsx',
'download_name' => '其他附加费导入模板.xlsx',
],
];
public function __construct(private readonly CourierPayableSummaryStore $store)
{
$this->middleware('auth');
@ -49,20 +62,21 @@ class CourierPayableSummaries extends Controller
public function template(string $summaryPage): BinaryFileResponse
{
$this->authorizePage($summaryPage);
abort_unless($summaryPage === 'freight-difference', 404);
$template = self::IMPORT_TEMPLATES[$summaryPage] ?? null;
abort_unless(is_array($template), 404);
$path = resource_path('templates/courier-payables/freight-difference-import-template.xlsx');
$path = resource_path($template['path']);
abort_unless(is_file($path), 404);
$response = response()->download($path, 'freight_difference_import_template.xlsx', [
$response = response()->download($path, $template['ascii_name'], [
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'Cache-Control' => 'private, no-store',
]);
return $response->setContentDisposition(
'attachment',
'运费差导入模板.xlsx',
'freight_difference_import_template.xlsx',
$template['download_name'],
$template['ascii_name'],
);
}

View File

@ -1,3 +1,18 @@
@php
$courierSummaryImportTemplate = [
'freight-difference' => [
'dialog_title' => '导入运费差',
'template_name' => '运费差导入模板',
'field_count' => 11,
],
'other-surcharge-summary' => [
'dialog_title' => '导入其他附加费汇总',
'template_name' => '其他附加费导入模板',
'field_count' => 11,
],
][$courierSummaryPage] ?? null;
@endphp
<section class="courier-summary" data-courier-payable-summary="{{ $courierSummaryPage }}" aria-label="{{ $courierSummaryTitle }}">
<header class="courier-summary__toolbar">
<div class="courier-summary__heading">
@ -9,20 +24,11 @@
@endif
</div>
<div class="courier-summary__actions">
@if ($courierSummaryPage === 'freight-difference')
@if ($courierSummaryImportTemplate)
<button type="button" class="courier-summary__button" data-courier-summary-import-open>
<span class="material-icons-outlined" aria-hidden="true">upload_file</span>
<span>导入</span>
</button>
@else
<form method="POST" enctype="multipart/form-data" action="{{ route('business.courier-payable-summaries.import', ['summaryPage' => $courierSummaryPage]) }}">
@csrf
<input id="courier-summary-import-{{ $courierSummaryPage }}" type="file" name="import" accept=".xlsx,.xls,.csv" required hidden onchange="this.form.submit()">
<label class="courier-summary__button" for="courier-summary-import-{{ $courierSummaryPage }}">
<span class="material-icons-outlined" aria-hidden="true">upload_file</span>
<span>导入</span>
</label>
</form>
@endif
<a
href="{{ route('business.courier-payable-summaries.export', ['summaryPage' => $courierSummaryPage]) }}"
@ -85,7 +91,7 @@
</footer>
@endif
@if ($courierSummaryPage === 'freight-difference')
@if ($courierSummaryImportTemplate)
<dialog class="courier-summary__import-dialog" data-courier-summary-import-dialog aria-labelledby="courier-summary-import-title">
<form
method="POST"
@ -96,7 +102,7 @@
@csrf
<header class="courier-summary__import-header">
<div>
<strong id="courier-summary-import-title">导入运费差</strong>
<strong id="courier-summary-import-title">{{ $courierSummaryImportTemplate['dialog_title'] }}</strong>
<span>支持 .xlsx、.xls、.csv,单个文件不超过 10 MB</span>
</div>
<button type="button" class="courier-summary__icon-button" data-courier-summary-import-close aria-label="关闭导入窗口" title="关闭">
@ -107,8 +113,8 @@
<div class="courier-summary__template-row">
<span class="material-icons-outlined" aria-hidden="true">description</span>
<div>
<strong>运费差导入模板</strong>
<span>.xlsx · 11 个字段</span>
<strong>{{ $courierSummaryImportTemplate['template_name'] }}</strong>
<span>.xlsx · {{ $courierSummaryImportTemplate['field_count'] }} 个字段</span>
</div>
<a class="courier-summary__button" href="{{ route('business.courier-payable-summaries.template', ['summaryPage' => $courierSummaryPage]) }}">
<span class="material-icons-outlined" aria-hidden="true">download</span>
@ -191,10 +197,10 @@
@media (max-width:700px) { .courier-summary__toolbar { align-items:flex-start; flex-direction:column; } .courier-summary__actions { width:100%; } .courier-summary__template-row { grid-template-columns:28px minmax(0,1fr); } .courier-summary__template-row .courier-summary__button { grid-column:1/-1; justify-content:center; } }
</style>
@if ($courierSummaryPage === 'freight-difference')
@if ($courierSummaryImportTemplate)
<script>
(() => {
const root = document.querySelector('[data-courier-payable-summary="freight-difference"]');
const root = document.querySelector('[data-courier-payable-summary="{{ $courierSummaryPage }}"]');
if (!root) return;
const dialog = root.querySelector('[data-courier-summary-import-dialog]');

View File

@ -201,7 +201,7 @@ Route::post('workspace/courier-payables/{summaryPage}/summary-import', [CourierP
->where('summaryPage', 'freight-difference|other-surcharge-summary')
->name('business.courier-payable-summaries.import');
Route::get('workspace/courier-payables/{summaryPage}/summary-template', [CourierPayableSummaries::class, 'template'])
->where('summaryPage', 'freight-difference')
->where('summaryPage', 'freight-difference|other-surcharge-summary')
->name('business.courier-payable-summaries.template');
Route::get('workspace/courier-payables/{summaryPage}/summary-export', [CourierPayableSummaries::class, 'export'])
->where('summaryPage', 'freight-difference|other-surcharge-summary')

View File

@ -106,15 +106,64 @@ class CourierPayableSummaryPagesTest extends FeatureTestCase
$spreadsheet->disconnectWorksheets();
}
public function test_other_surcharge_page_does_not_offer_the_freight_difference_template(): void
public function test_other_surcharge_import_dialog_downloads_the_supplied_xlsx_template(): void
{
$this->loginAs()->get(route('business.pages.show', [
$pageResponse = $this->loginAs()->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'other-surcharge-summary',
]))
]));
$pageResponse
->assertOk()
->assertDontSeeText('下载模板')
->assertDontSee('data-courier-summary-import-dialog', false);
->assertSee('data-courier-summary-import-dialog', false)
->assertSeeText('导入其他附加费汇总')
->assertSeeText('其他附加费导入模板')
->assertSeeText('.xlsx · 11 个字段')
->assertSeeText('下载模板')
->assertSee(route('business.courier-payable-summaries.template', [
'summaryPage' => 'other-surcharge-summary',
]), false);
$templateResponse = $this->loginAs()->get(route('business.courier-payable-summaries.template', [
'summaryPage' => 'other-surcharge-summary',
]));
$templateResponse
->assertOk()
->assertDownload('other_surcharge_import_template.xlsx')
->assertHeader('content-type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
$this->assertStringContainsString(
"filename*=utf-8''" . rawurlencode('其他附加费导入模板.xlsx'),
(string) $templateResponse->headers->get('Content-Disposition'),
);
$spreadsheet = IOFactory::load(resource_path('templates/courier-payables/other-surcharge-import-template.xlsx'));
$worksheet = $spreadsheet->getActiveSheet();
$this->assertSame([
'日期',
'负责人',
'快递公司',
'快递单号',
'商品ID',
'条码',
'店铺',
'交易单号',
'金额',
'备注',
'成品条码',
], $worksheet->rangeToArray('A1:K1', null, false, false)[0]);
$this->assertSame('@', $worksheet->getStyle('A2')->getNumberFormat()->getFormatCode());
$this->assertSame('@', $worksheet->getStyle('D2')->getNumberFormat()->getFormatCode());
$this->assertSame('@', $worksheet->getStyle('E2')->getNumberFormat()->getFormatCode());
$this->assertSame('@', $worksheet->getStyle('F2')->getNumberFormat()->getFormatCode());
$this->assertSame('@', $worksheet->getStyle('H2')->getNumberFormat()->getFormatCode());
$this->assertSame('0.00########', $worksheet->getStyle('I2')->getNumberFormat()->getFormatCode());
$this->assertSame('@', $worksheet->getStyle('K2')->getNumberFormat()->getFormatCode());
$this->assertSame('', (string) $worksheet->getCell('A2')->getValue());
$this->assertSame('', (string) $worksheet->getCell('K18')->getValue());
$spreadsheet->disconnectWorksheets();
}
public function test_completed_freight_difference_template_preserves_long_identifiers_on_import(): void
@ -183,6 +232,73 @@ class CourierPayableSummaryPagesTest extends FeatureTestCase
}
}
public function test_completed_other_surcharge_template_preserves_long_identifiers_on_import(): void
{
$spreadsheet = IOFactory::load(resource_path('templates/courier-payables/other-surcharge-import-template.xlsx'));
$worksheet = $spreadsheet->getActiveSheet();
$worksheet->fromArray([
'2026-07',
'苏木',
'测试快递',
'SF5199125637478',
'6041543429417',
'SJ038017N1410V01',
'测试店铺',
'5110751415866048404',
-60,
'安装费',
'CP001',
], null, 'A2');
$worksheet->getCell('E2')->setValueExplicit('6041543429417', DataType::TYPE_STRING);
$worksheet->getCell('H2')->setValueExplicit('5110751415866048404', DataType::TYPE_STRING);
$path = tempnam(sys_get_temp_dir(), 'other-surcharge-template-');
$this->assertNotFalse($path);
$xlsxPath = $path . '.xlsx';
try {
IOFactory::createWriter($spreadsheet, 'Xlsx')->save($xlsxPath);
$spreadsheet->disconnectWorksheets();
$file = new UploadedFile(
$xlsxPath,
'其他附加费导入模板.xlsx',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
null,
true,
);
$this->loginAs()->post(route('business.courier-payable-summaries.import', [
'summaryPage' => 'other-surcharge-summary',
]), ['import' => $file])
->assertRedirect(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'other-surcharge-summary',
]))
->assertSessionHas('success', '导入完成:1 条数据,11 个字段。');
$snapshot = app(CourierPayableSummaryStore::class)->snapshot(
(int) $this->company->id,
'other-surcharge-summary',
);
$this->assertSame('2026-07', $snapshot['rows'][0][0]);
$this->assertSame('6041543429417', $snapshot['rows'][0][4]);
$this->assertSame('5110751415866048404', $snapshot['rows'][0][7]);
$this->assertSame('-60', $snapshot['rows'][0][8]);
$this->assertSame('安装费', $snapshot['rows'][0][9]);
} finally {
if (isset($spreadsheet)) {
$spreadsheet->disconnectWorksheets();
}
if ($path !== false && is_file($path)) {
unlink($path);
}
if (isset($xlsxPath) && is_file($xlsxPath)) {
unlink($xlsxPath);
}
}
}
public function test_import_replaces_the_page_snapshot_and_export_returns_all_rows(): void
{
$file = UploadedFile::fake()->createWithContent(
@ -242,5 +358,9 @@ class CourierPayableSummaryPagesTest extends FeatureTestCase
$this->loginAs()->get(route('business.courier-payable-summaries.template', [
'summaryPage' => 'freight-difference',
]))->assertForbidden();
$this->loginAs()->get(route('business.courier-payable-summaries.template', [
'summaryPage' => 'other-surcharge-summary',
]))->assertForbidden();
}
}