CaiWuXiTong/tests/Feature/Business/CourierPayableSummaryPagesTest.php
renshen 56949d6d29 增加其他附加费导入模板 (#211)
## 实际改动
- 为“其他附加费汇总”增加与“运费差”一致的导入弹窗和模板下载入口。
- 新增空白 Excel 模板,字段为日期、负责人、快递公司、快递单号、商品ID、条码、店铺、交易单号、金额、备注、成品条码。
- 日期及编号列按文本保存,金额列保留数值格式;模板不包含用户提供文件中的真实业务数据。
- 扩展模板下载路由与控制器映射,并补充权限、格式、长编号和实际导入回归测试。

## 修改文件
- app/Http/Controllers/Business/CourierPayableSummaries.php
- resources/templates/courier-payables/other-surcharge-import-template.xlsx
- resources/views/business/pages/courier-payables/summary-table.blade.php
- routes/admin.php
- tests/Feature/Business/CourierPayableSummaryPagesTest.php

## 验证结果
- CourierPayableSummaryPagesTest:8 passed,88 assertions。
- Blade 编译:php artisan view:cache 通过。
- git diff --check 通过。
- 模板已核对为空白,且未提交真实财务数据。

## 风险检查
- 涉及导入和页面权限,已增加对应回归测试。
- 不涉及数据库迁移、生产配置或数据修复。
- 前端复用现有 VIP 黑金组件样式,符合 DESIGN.md。

Reviewed-on: http://git.1080p.hk/WuYongJie/CaiWuXiTong/pulls/211
2026-09-20 11:39:50 +08:00

367 lines
15 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Tests\Feature\Business;
use App\Business\CourierPayableSummaryStore;
use App\Models\Auth\Permission;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\IOFactory;
use Tests\Feature\FeatureTestCase;
class CourierPayableSummaryPagesTest extends FeatureTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->user->attachPermission(
Permission::where('name', 'read-admin-panel')->firstOrFail()
);
Storage::fake('uploads');
}
public function test_final_workspace_places_both_summary_pages_after_courier_payables_report(): void
{
$response = $this->loginAs()->get(route('business.pages.show', [
'section' => 'courier-payables-final',
]));
$response->assertOk()->assertSeeInOrder([
route('business.pages.show', ['section' => 'courier-payables', 'page' => 'courier-payables-report']),
route('business.pages.show', ['section' => 'courier-payables', 'page' => 'freight-difference']),
route('business.pages.show', ['section' => 'courier-payables', 'page' => 'other-surcharge-summary']),
], false);
}
public function test_summary_pages_render_import_and_export_buttons(): void
{
foreach (CourierPayableSummaryStore::PAGES as $page => $title) {
$this->loginAs()->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => $page,
]))
->assertOk()
->assertSee("data-courier-payable-summary=\"{$page}\"", false)
->assertSeeText($title)
->assertSeeText('导入')
->assertSeeText('导出')
->assertSeeText('暂无数据,请先点击“导入”上传汇总表。');
}
}
public function test_freight_difference_import_dialog_downloads_the_supplied_xlsx_template(): void
{
$pageResponse = $this->loginAs()->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'freight-difference',
]));
$pageResponse
->assertOk()
->assertSee('data-courier-summary-import-dialog', false)
->assertSeeText('下载模板')
->assertSee(route('business.courier-payable-summaries.template', [
'summaryPage' => 'freight-difference',
]), false);
$templateResponse = $this->loginAs()->get(route('business.courier-payable-summaries.template', [
'summaryPage' => 'freight-difference',
]));
$templateResponse
->assertOk()
->assertDownload('freight_difference_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/freight-difference-import-template.xlsx'));
$worksheet = $spreadsheet->getActiveSheet();
$this->assertSame([
'日期',
'负责人',
'快递公司',
'快递单号',
'店铺',
'id',
'条码',
'交易单号',
'差异(结算运费-提供成本运费)',
'是否配件',
'配件成品条码',
], $worksheet->rangeToArray('A1:K1', null, false, false)[0]);
$this->assertSame('FFFFC000', $worksheet->getStyle('E1')->getFill()->getStartColor()->getARGB());
$this->assertSame('FFFFC000', $worksheet->getStyle('F1')->getFill()->getStartColor()->getARGB());
$this->assertSame('FFFFC000', $worksheet->getStyle('H1')->getFill()->getStartColor()->getARGB());
$this->assertSame('@', $worksheet->getStyle('A2')->getNumberFormat()->getFormatCode());
$this->assertSame('@', $worksheet->getStyle('F2')->getNumberFormat()->getFormatCode());
$this->assertSame('@', $worksheet->getStyle('H2')->getNumberFormat()->getFormatCode());
$this->assertSame('', (string) $worksheet->getCell('A2')->getValue());
$spreadsheet->disconnectWorksheets();
}
public function test_other_surcharge_import_dialog_downloads_the_supplied_xlsx_template(): void
{
$pageResponse = $this->loginAs()->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'other-surcharge-summary',
]));
$pageResponse
->assertOk()
->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
{
$spreadsheet = IOFactory::load(resource_path('templates/courier-payables/freight-difference-import-template.xlsx'));
$worksheet = $spreadsheet->getActiveSheet();
$worksheet->fromArray([
'2026-07',
'苏木',
'测试快递',
'YT7632543323146',
'测试店铺',
'855226246304',
'DNZ0117123N1210V01',
'3312228147193110069',
0.1827,
'成品',
'',
], null, 'A2');
$worksheet->getCell('F2')->setValueExplicit('855226246304', DataType::TYPE_STRING);
$worksheet->getCell('H2')->setValueExplicit('3312228147193110069', DataType::TYPE_STRING);
$path = tempnam(sys_get_temp_dir(), 'freight-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' => 'freight-difference',
]), ['import' => $file])
->assertRedirect(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'freight-difference',
]))
->assertSessionHas('success', '导入完成:1 条数据,11 个字段。');
$snapshot = app(CourierPayableSummaryStore::class)->snapshot(
(int) $this->company->id,
'freight-difference',
);
$this->assertSame('2026-07', $snapshot['rows'][0][0]);
$this->assertSame('855226246304', $snapshot['rows'][0][5]);
$this->assertSame('3312228147193110069', $snapshot['rows'][0][7]);
$this->assertSame('0.1827', $snapshot['rows'][0][8]);
} 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_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(
'运费差.csv',
"快递公司,结算月份,运费差,备注\n测试快递,2026-08,-18.50,复核\n第二快递,2026-08,20,=待确认\n",
);
$this->loginAs()->post(route('business.courier-payable-summaries.import', [
'summaryPage' => 'freight-difference',
]), ['import' => $file])
->assertRedirect(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'freight-difference',
]))
->assertSessionHas('success', '导入完成:2 条数据,4 个字段。');
Storage::disk('uploads')->assertExists(
app(CourierPayableSummaryStore::class)->path((int) $this->company->id, 'freight-difference')
);
$this->loginAs()->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'freight-difference',
]))
->assertOk()
->assertSeeText('导入完成:2 条数据,4 个字段。')
->assertSeeText('测试快递')
->assertSeeText('-18.50')
->assertSeeText('第二快递');
$response = $this->loginAs()->get(route('business.courier-payable-summaries.export', [
'summaryPage' => 'freight-difference',
]));
$content = $response->streamedContent();
$response->assertOk()->assertHeader('content-type', 'text/csv; charset=UTF-8');
$this->assertStringContainsString('快递公司,结算月份,运费差,备注', $content);
$this->assertStringContainsString('测试快递,2026-08,-18.50,复核', $content);
$this->assertStringContainsString("\t=待确认", $content);
}
public function test_summary_actions_require_the_matching_page_permission(): void
{
$this->withExceptionHandling();
$this->user->roles()->sync([
\App\Models\Auth\Role::where('name', 'admin')->firstOrFail()->id,
]);
setting()->set('system.sidebar_permissions', [
'configured' => ['user_' . $this->user->id],
'subjects' => ['user_' . $this->user->id => ['courier-payables/courier-information']],
]);
$this->loginAs()->get(route('business.courier-payable-summaries.export', [
'summaryPage' => 'freight-difference',
]))->assertForbidden();
$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();
}
}