CaiWuXiTong/tests/Feature/Business/KukaPostingQuotationPageTest.php
chenxi c32dbe57b2 feat: 接入顾家报价单导入 (#1070)
feat: 接入顾家报价单导入 (#1070)
2026-11-11 13:20:51 +08:00

237 lines
9.5 KiB
PHP

<?php
namespace Tests\Feature\Business;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Tests\Feature\FeatureTestCase;
use ZipArchive;
class KukaPostingQuotationPageTest extends FeatureTestCase
{
protected function setUp(): void
{
parent::setUp();
config([
'database.connections.business_mirror' => [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
],
'upstream_mirrors.connection' => 'business_mirror',
]);
DB::purge('business_mirror');
$schema = Schema::connection('business_mirror');
$schema->create('kuka_posting_quotation_imports', function (Blueprint $table): void {
$table->increments('id');
$table->unsignedBigInteger('company_id');
$table->unsignedInteger('row_count')->default(0);
$table->unsignedInteger('material_count')->default(0);
$table->text('status_counts')->nullable();
$table->string('file_name');
$table->char('file_sha256', 64);
$table->string('source_sheet');
$table->timestamp('completed_at')->nullable();
$table->timestamps();
$table->unique('company_id');
});
$schema->create('kuka_posting_quotation_rows', function (Blueprint $table): void {
$table->increments('id');
$table->unsignedBigInteger('company_id');
$table->string('material_number');
$table->string('material_desc')->nullable();
$table->string('supplier_account')->nullable();
$table->string('supplier_name')->nullable();
$table->string('record_status')->nullable();
$table->decimal('price_including_tax', 20, 4)->nullable();
$table->decimal('price_excluding_tax', 20, 4)->nullable();
$table->dateTime('price_valid_date')->nullable();
$table->dateTime('price_expiry_date')->nullable();
$table->char('row_hash', 64);
$table->text('source_values');
$table->unsignedInteger('source_row_number');
$table->timestamp('imported_at');
$table->unique(['company_id', 'material_number']);
});
}
public function test_imports_quotation_workbook_and_renders_paginated_price_rows(): void
{
$path = $this->workbook();
$this->artisan('business-mirror:import-kuka-posting-quotation', [
'path' => $path,
'--company' => (string) $this->company->id,
])->assertExitCode(0);
$connection = DB::connection('business_mirror');
$this->assertSame(2, $connection->table('kuka_posting_quotation_rows')->count());
$this->assertSame(2, $connection->table('kuka_posting_quotation_rows')->distinct()->count('material_number'));
$this->assertDatabaseHas('kuka_posting_quotation_imports', [
'company_id' => $this->company->id,
'row_count' => 2,
'material_count' => 2,
'source_sheet' => 'Sheet1',
], 'business_mirror');
$row = $connection->table('kuka_posting_quotation_rows')
->where('material_number', '91.C52798.01')
->first();
$values = json_decode((string) $row->source_values, true);
$this->assertSame('DS5145BG-G192~升降桌-柚木色-1.4米', $values['material_desc']);
$this->assertSame('常州欧伯娜家居有限公司', $values['supplier_name']);
$this->assertSame('生效', $values['record_status']);
$this->loginAs()
->get(route('business.pages.show', [
'section' => 'posting-data',
'page' => 'kuka-basic-data',
'submenu' => 'quotation',
]))
->assertOk()
->assertSeeText('顾家报价单 · 价格明细')
->assertSeeText('91.C52798.01')
->assertSeeText('升降桌-柚木色-1.4米')
->assertSeeText('常州欧伯娜家居有限公司')
->assertSeeText('989.10')
->assertSeeText('生效')
->assertSee('data-directory-table-key="kuka-quotation"', false);
@unlink($path);
}
private function workbook(): string
{
$fields = config('kuka_posting.quotation_fields');
$headers = array_column($fields, 'source');
$rows = [
$headers,
array_map(static fn (array $field): string => $field['label'], $fields),
$this->row([
'Fbk1' => 'DS5145BG-G192',
'MaterialNumber' => '91.C52798.01',
'MaterialDesc' => 'DS5145BG-G192~升降桌-柚木色-1.4米',
'ToElsAccount' => '435421',
'ToElsAccountName' => '常州欧伯娜家居有限公司',
'OrderUnit' => 'PC',
'Currency' => 'CNY',
'TaxRate' => '13',
'PriceIncludingTax' => '989.10',
'PriceExcludingTax' => '875.31',
'PurchaseOrganization' => '1090',
'Factory' => '1090',
'PurchaseGroup' => 'G32',
'PriceValidDate' => '2026-06-24 12:00:00',
'PriceExpiryDate' => '2027-06-16 12:00:00',
'CreateDate' => '2026-06-24 02:00:42',
'SourceType' => 'Excel',
'OverTolerance' => '0',
'LackTolerance' => '0',
'RecordType' => '标准',
'Fbk6' => '0004',
'Fbk14' => '1',
'RecordStatus' => '生效',
'Fbk3' => 'Z9',
'CreateUser' => 'config-center_系统账号',
'LastUpdateUser' => 'config-center_系统账号',
'LastUpdateDate' => '2026-06-24 02:00:42',
]),
$this->row([
'Fbk1' => 'DS5166BG-B4',
'MaterialNumber' => '91.C52597.01',
'MaterialDesc' => 'DS5166BG-B4~妆台-白色-0.28米',
'ToElsAccount' => '435421',
'ToElsAccountName' => '常州欧伯娜家居有限公司',
'OrderUnit' => 'PC',
'Currency' => 'CNY',
'TaxRate' => '13',
'PriceIncludingTax' => '199.50',
'PriceExcludingTax' => '176.55',
'PurchaseOrganization' => '1090',
'Factory' => '1090',
'PurchaseGroup' => 'G32',
'PriceValidDate' => '2026-06-24 12:00:00',
'PriceExpiryDate' => '2027-06-16 12:00:00',
'CreateDate' => '2026-06-24 02:00:42',
'SourceType' => 'Excel',
'RecordType' => '标准',
'Fbk6' => '0004',
'Fbk14' => '1',
'RecordStatus' => '生效',
'Fbk3' => 'Z9',
'CreateUser' => 'config-center_系统账号',
'LastUpdateUser' => 'config-center_系统账号',
'LastUpdateDate' => '2026-06-24 02:00:42',
]),
];
$path = tempnam(sys_get_temp_dir(), 'kuka-quotation-') . '.xlsx';
$zip = new ZipArchive();
$zip->open($path, ZipArchive::CREATE | ZipArchive::OVERWRITE);
$zip->addFromString('xl/workbook.xml', <<<'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>
XML);
$zip->addFromString('xl/_rels/workbook.xml.rels', <<<'XML'
<?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>
XML);
$zip->addFromString('xl/worksheets/sheet1.xml', $this->worksheetXml($rows, $headers));
$zip->close();
return $path;
}
/**
* @param list<string> $headers
*/
private function row(array $values): array
{
return array_map(
static fn (string $header): string => (string) ($values[$header] ?? ''),
array_column(config('kuka_posting.quotation_fields'), 'source'),
);
}
private function worksheetXml(array $rows, array $headers): string
{
$xmlRows = [];
foreach ($rows as $rowIndex => $row) {
$cells = [];
foreach ($row as $columnIndex => $value) {
$reference = $this->columnName($columnIndex + 1) . ($rowIndex + 1);
$cells[] = '<c r="' . $reference . '" t="inlineStr"><is><t>'
. htmlspecialchars((string) $value, ENT_XML1 | ENT_QUOTES, 'UTF-8')
. '</t></is></c>';
}
$xmlRows[] = '<row r="' . ($rowIndex + 1) . '">' . implode('', $cells) . '</row>';
}
return '<?xml version="1.0" encoding="UTF-8"?>'
. '<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">'
. '<sheetData>' . implode('', $xmlRows) . '</sheetData></worksheet>';
}
private function columnName(int $index): string
{
$name = '';
while ($index > 0) {
$index--;
$name = chr(65 + ($index % 26)) . $name;
$index = intdiv($index, 26);
}
return $name;
}
}