CaiWuXiTong/tests/Feature/Business/CourierFreightPageTest.php
qiuxiang 66d6146c19 快递运费:导入兼容「店铺」列名(财务系统提供成本5-6.xlsx 用)
列名别名扩展:店铺名称/店铺;csv 与 xlsx 两条路径的用例同步覆盖。
2026-10-12 14:55:01 +08:00

199 lines
8.9 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\CourierFreightImporter;
use App\Models\Auth\Permission;
use App\Models\Business\CourierFreightRow;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use Tests\Feature\FeatureTestCase;
/**
* 财务经营报表 › 快递运费(导入 + 页面列)。
*
* 来源 / 成品配件 / 交易单号 / 提供成本运费 来自导入文件;
* 快递单号 / 条形码 / 店铺名称 为「财务系统运费整理.xlsx」起新增的可选列(老文件没有也能导)。
*/
class CourierFreightPageTest extends FeatureTestCase
{
protected function setUp(): void
{
parent::setUp();
$this->user->attachPermission(
Permission::where('name', 'read-admin-panel')->firstOrFail()
);
}
public function test_import_reads_new_optional_columns(): void
{
$path = $this->makeCsv([
// 「店铺」是「店铺名称」的别名写法(财务系统提供成本5-6.xlsx 用的列名)
['来源', '快递单号', '交易单号', '条形码', '成品配件', '提供成本运费', '店铺'],
['提供成本7月汇总', '79015585514691', '5121853455486000631', 'cbz000203N1221V01', '成品运费', '6.7', '淘宝-亿家达家居鞋柜电脑桌'],
['提供成本7月汇总', '', '5121853455486000632', 'DNZ023905N1221V01', '配件运费', '1.5', ''],
]);
try {
$result = app(CourierFreightImporter::class)->import($path, $this->company->id, true, 'test');
} finally {
@unlink($path);
}
$this->assertSame(2, $result['inserted']);
$row = CourierFreightRow::query()->where('company_id', $this->company->id)
->where('transaction_no', '5121853455486000631')->firstOrFail();
$this->assertSame('提供成本7月汇总', $row->source);
$this->assertSame('79015585514691', $row->express_no);
$this->assertSame('cbz000203N1221V01', $row->barcode);
$this->assertSame('淘宝-亿家达家居鞋柜电脑桌', $row->shop_name);
$this->assertSame('成品运费', $row->part);
$this->assertSame(6.7, (float) $row->freight_amount);
$blank = CourierFreightRow::query()->where('company_id', $this->company->id)
->where('transaction_no', '5121853455486000632')->firstOrFail();
$this->assertNull($blank->express_no);
$this->assertNull($blank->shop_name);
}
public function test_import_old_file_without_optional_columns_still_works(): void
{
$path = $this->makeCsv([
['来源', '成品配件', '交易单号', '提供成本运费'],
['8月提供成本', '成品运费', 'X-1', '2.5'],
]);
try {
$result = app(CourierFreightImporter::class)->import($path, $this->company->id, true, 'test');
} finally {
@unlink($path);
}
$this->assertSame(1, $result['inserted']);
$row = CourierFreightRow::query()->where('company_id', $this->company->id)->firstOrFail();
$this->assertNull($row->express_no);
$this->assertNull($row->barcode);
$this->assertNull($row->shop_name);
}
public function test_page_shows_new_stored_columns(): void
{
$path = $this->makeCsv([
['来源', '快递单号', '交易单号', '条形码', '成品配件', '提供成本运费', '店铺名称'],
['提供成本7月汇总', '79015585514691', 'T-0001', 'cbz000203N1221V01', '成品运费', '6.7', '淘宝-亿家达家居鞋柜电脑桌'],
]);
try {
app(CourierFreightImporter::class)->import($path, $this->company->id, true, 'test');
} finally {
@unlink($path);
}
$page = $this->loginAs()->get(route('business.pages.show', [
'section' => 'financial-operations',
'page' => 'courier-freight',
]));
$page->assertOk()
->assertSee('快递运费')
->assertSee('快递单号')
->assertSee('条形码')
->assertSee('店铺名称')
->assertSee('T-0001')
->assertSee('79015585514691')
->assertSee('cbz000203N1221V01')
->assertSee('淘宝-亿家达家居鞋柜电脑桌');
}
public function test_import_from_xlsx_reads_optional_columns(): void
{
// xlsx 走 XlsxSheetReader 流式读取,和 csv 分两条代码路径,单独覆盖
$path = $this->makeXlsx([
// 列名用「店铺」别名,与「财务系统提供成本5-6.xlsx」一致
['来源', '快递单号', '交易单号', '条形码', '成品配件', '提供成本运费', '店铺'],
['提供成本8月汇总', '79015585514648', '3310332098698014581', 'cbz000204N1221V01', '成品运费', '9.4', '新佰广1688'],
]);
try {
$result = app(CourierFreightImporter::class)->import($path, $this->company->id, true, 'test');
} finally {
@unlink($path);
}
$this->assertSame(1, $result['inserted']);
$row = CourierFreightRow::query()->where('company_id', $this->company->id)->firstOrFail();
$this->assertSame('提供成本8月汇总', $row->source);
$this->assertSame('79015585514648', $row->express_no);
$this->assertSame('cbz000204N1221V01', $row->barcode);
$this->assertSame('新佰广1688', $row->shop_name);
$this->assertSame(9.4, (float) $row->freight_amount);
}
/** @param list<list<string>> $rows */
private function makeCsv(array $rows): string
{
$path = tempnam(sys_get_temp_dir(), 'courier-') . '.csv';
$handle = fopen($path, 'wb');
fwrite($handle, "\xEF\xBB\xBF");
foreach ($rows as $row) {
fputcsv($handle, $row);
}
fclose($handle);
return $path;
}
/**
* 造一个最小 xlsx:字符串走 inlineStr、dimension 只写 A1(与聚水潭导出同形态)。
*
* @param list<list<string>> $rows
*/
private function makeXlsx(array $rows): string
{
$path = tempnam(sys_get_temp_dir(), 'courier-') . '.xlsx';
$sheet = '<?xml version="1.0" encoding="UTF-8"?>'
. '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'
. '<dimension ref="A1"/><sheetData>';
foreach ($rows as $rowIndex => $values) {
$sheet .= '<row r="' . ($rowIndex + 1) . '">';
foreach ($values as $columnIndex => $value) {
$reference = Coordinate::stringFromColumnIndex($columnIndex + 1) . ($rowIndex + 1);
$sheet .= '<c r="' . $reference . '" t="inlineStr"><is><t>'
. htmlspecialchars((string) $value, ENT_XML1) . '</t></is></c>';
}
$sheet .= '</row>';
}
$sheet .= '</sheetData></worksheet>';
$zip = new \ZipArchive();
$this->assertTrue($zip->open($path, \ZipArchive::CREATE | \ZipArchive::OVERWRITE) === true);
$zip->addFromString('[Content_Types].xml', '<?xml version="1.0" encoding="UTF-8"?>'
. '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
. '<Default Extension="xml" ContentType="application/xml"/>'
. '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'
. '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>'
. '<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>'
. '</Types>');
$zip->addFromString('_rels/.rels', '<?xml version="1.0" encoding="UTF-8"?>'
. '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
. '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>'
. '</Relationships>');
$zip->addFromString('xl/workbook.xml', '<?xml version="1.0" encoding="UTF-8"?>'
. '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" '
. 'xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">'
. '<sheets><sheet name="Sheet1" sheetId="1" r:id="rId1"/></sheets></workbook>');
$zip->addFromString('xl/_rels/workbook.xml.rels', '<?xml version="1.0" encoding="UTF-8"?>'
. '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'
. '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>'
. '</Relationships>');
$zip->addFromString('xl/worksheets/sheet1.xml', "\xEF\xBB\xBF" . $sheet);
$zip->close();
return $path;
}
}