CaiWuXiTong/tests/Feature/Business/JstOrderLabelsPageTest.php
qiuxiang 4bcfde58f7 按文件导入改为两遍:先挑出真正有标签的订单,再拆行写入
聚水潭导出是一单多行,实测有 97 单是「部分明细行带标签、部分为空」;单遍处理会给这些单多写一行「(无标签)」。改成第一遍收集有标签的订单号,第二遍只对整单无标签的订单写「(无标签)」,并补上对应断言。
2026-10-07 16:59:01 +08:00

240 lines
12 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\JstOrderLabelImporter;
use App\Models\Auth\Permission;
use App\Models\Business\JstOrderLabel;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use Tests\Feature\FeatureTestCase;
/**
* 账单流水数据 › 聚水潭订单标签明细(按标签拆行导入 + 页面 + 筛选 + 导出)。
*/
class JstOrderLabelsPageTest extends FeatureTestCase
{
protected function setUp(): void
{
parent::setUp();
config(['database.connections.business_mirror' => [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
]]);
DB::purge('business_mirror');
Schema::connection('business_mirror')->create('ops_jst_orders', function ($table): void {
$table->integer('Id')->primary();
$table->string('so_id')->nullable();
$table->string('shop_name')->nullable();
$table->dateTime('order_date')->nullable();
$table->text('labels')->nullable();
});
DB::connection('business_mirror')->table('ops_jst_orders')->insert([
['Id' => 1, 'so_id' => '260901-391328633273048', 'shop_name' => '拼多多-优乐猫家具旗舰店', 'order_date' => '2026-09-01 00:00:13', 'labels' => '发货前售后,店铺-拼多多'],
['Id' => 2, 'so_id' => '3608400015164657', 'shop_name' => '京喜-久伴吾家具京喜自营工厂店', 'order_date' => '2026-09-01 00:00:44', 'labels' => '京东供销订单, 京喜'],
['Id' => 3, 'so_id' => '110211297173802010', 'shop_name' => '得物-全米', 'order_date' => '2026-08-31 23:59:00', 'labels' => '品牌直发'],
['Id' => 4, 'so_id' => '260915-000000000000001', 'shop_name' => '得物-全米', 'order_date' => '2026-09-15 10:00:00', 'labels' => ''],
]);
}
public function test_import_splits_labels_without_deduping_orders(): void
{
$this->allowPage();
$result = app(JstOrderLabelImporter::class)->import('2026-09-01', '2026-09-15', $this->company->id, true);
$this->assertSame(3, $result['orders']);
$this->assertSame(5, $result['rows']);
$this->assertSame(1, $result['no_label_orders']);
$rows = JstOrderLabel::query()->where('company_id', $this->company->id)->orderBy('id')->get();
$this->assertSame(['发货前售后', '店铺-拼多多', '京东供销订单', '京喜', '(无标签)'], $rows->pluck('label')->all());
// 同一订单拆成多行,订单不去重
$this->assertSame(2, $rows->where('order_no', '260901-391328633273048')->count());
$this->assertSame('2026-09-15 10:00:00', $rows->last()->ordered_at->format('Y-m-d H:i:s'));
}
public function test_import_window_excludes_out_of_range_orders(): void
{
$this->allowPage();
// 8-31 的订单不在 9 月窗口内
$result = app(JstOrderLabelImporter::class)->import('2026-09-01', '2026-09-15', $this->company->id, true);
$orders = JstOrderLabel::query()->where('company_id', $this->company->id)->pluck('order_no')->unique()->all();
$this->assertNotContains('110211297173802010', $orders);
$this->assertSame(3, $result['orders']);
}
public function test_page_filters_and_export(): void
{
$this->allowPage();
app(JstOrderLabelImporter::class)->import('2026-09-01', '2026-09-15', $this->company->id, true);
$page = $this->loginAs()->get(route('business.pages.show', [
'section' => 'planting-data',
'page' => 'jst-order-labels',
]));
$page->assertOk()
->assertSee('聚水潭订单标签明细')
->assertSee('260901-391328633273048')
->assertSee('店铺-拼多多')
->assertSee('(无标签)')
->assertSee('id="jst-label-date-from"', false)
->assertSee('data-jst-labels-clear="jst-label-date-to"', false);
$filtered = $this->loginAs()->get(route('business.pages.show', [
'section' => 'planting-data',
'page' => 'jst-order-labels',
'label' => '京喜',
]));
$filtered->assertOk()
->assertSee('3608400015164657')
->assertDontSee('260901-391328633273048');
$export = $this->loginAs()->get(route('business.planting-data.jst-order-labels.export', [
'label' => '京喜',
]));
$export->assertOk()->assertDownload();
$csv = $export->streamedContent();
$this->assertStringContainsString('线上订单号,店铺名称,下单时间,标签', explode("\n", $csv)[0]);
$this->assertStringContainsString('3608400015164657', $csv);
$this->assertStringNotContainsString('260901-391328633273048', $csv);
}
private function allowPage(): void
{
$this->user->attachPermission(
Permission::where('name', 'read-admin-panel')->firstOrFail()
);
}
public function test_import_from_erp_export_file_dedupes_lines_and_labels(): void
{
$this->allowPage();
$path = $this->makeExportFile([
['内部订单号', '线上订单号', '店铺名称', '下单时间', '标签'],
['6476896', '3424493003753167', '京东-柯诺苏家具旗舰店', '2026-03-01 01:30:52', '发货后售后,店铺-京东,晚发赔'],
['6476897', '3424493003753167', '京东-柯诺苏家具旗舰店', '2026-03-01 01:30:52', '发货后售后,店铺-京东,晚发赔'],
['6476898', '3424493003753167', '京东-柯诺苏家具旗舰店', '2026-03-01 01:30:52', '发货后售后'],
// 同一单里只有部分明细行带标签:不能因此多写一行「(无标签)」
['6476899', '3424493003753167', '京东-柯诺苏家具旗舰店', '2026-03-01 01:30:52', ''],
['6477350', '110197275064171099', '得物-米客米家家居旗舰店', '2026-03-01 08:05:36', ''],
['9535498', '3290154852795260451', '淘宝-亿家达家居鞋柜电脑桌', '2026-03-11 19:01:30', '历史订单'],
]);
try {
$result = app(JstOrderLabelImporter::class)->importFromFile($path, null, null, $this->company->id, false);
} finally {
@unlink($path);
}
// 同一订单的多条明细行只算一单,重复标签只写一行,
// 且「部分明细行没标签」不会污染成「(无标签)」
$this->assertSame(3, $result['orders']);
$this->assertSame(5, $result['rows']);
$this->assertSame(1, $result['no_label_orders']);
$this->assertSame(0, JstOrderLabel::query()->where('company_id', $this->company->id)
->where('order_no', '3424493003753167')
->where('label', JstOrderLabelImporter::NO_LABEL)->count());
$labels = JstOrderLabel::query()->where('company_id', $this->company->id)
->where('order_no', '3424493003753167')->pluck('label')->sort()->values()->all();
$this->assertSame(['发货后售后', '店铺-京东', '晚发赔'], $labels);
$noLabel = JstOrderLabel::query()->where('company_id', $this->company->id)
->where('order_no', '110197275064171099')->firstOrFail();
$this->assertSame(JstOrderLabelImporter::NO_LABEL, $noLabel->label);
$this->assertSame('得物-米客米家家居旗舰店', $noLabel->shop_name);
}
public function test_import_from_file_respects_window_and_replace(): void
{
$this->allowPage();
app(JstOrderLabelImporter::class)->import('2026-09-01', '2026-09-15', $this->company->id, true);
$path = $this->makeExportFile([
['线上订单号', '店铺名称', '下单时间', '标签'],
['A-1', '店铺甲', '2026-03-05 10:00:00', '刷单待处理'],
['A-2', '店铺甲', '2026-03-06 10:00:00', '刷单待处理'],
]);
try {
$result = app(JstOrderLabelImporter::class)->importFromFile(
$path, '2026-03-06', '2026-03-31', $this->company->id, true, 'test'
);
} finally {
@unlink($path);
}
// 窗口外的 3-05 被跳过;9 月的老数据在窗口外因此不受 --replace 影响
$this->assertSame(1, $result['orders']);
$this->assertSame(1, $result['skipped']);
$this->assertSame(['A-2'], JstOrderLabel::query()->where('company_id', $this->company->id)
->where('order_no', 'like', 'A-%')->pluck('order_no')->all());
// 原有 9 月 5 行 + 新增 A-2 一行
$this->assertSame(6, JstOrderLabel::query()->where('company_id', $this->company->id)->count());
}
/**
* 按聚水潭「导出订单」的真实形态造一个最小 xlsx:
* 字符串走 inlineStr、dimension 只写 A1(尺寸不可信)、一单多行。
*
* @param list<list<string>> $rows
*/
private function makeExportFile(array $rows): string
{
$path = tempnam(sys_get_temp_dir(), 'jst-export-') . '.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/sharedStrings.xml', '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>'
. '<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="0" uniqueCount="0"></sst>');
$zip->addFromString('xl/worksheets/sheet1.xml', "\xEF\xBB\xBF" . $sheet);
$zip->close();
return $path;
}
}