199 lines
8.9 KiB
PHP
199 lines
8.9 KiB
PHP
<?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;
|
||
}
|
||
}
|