253 lines
10 KiB
PHP
253 lines
10 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Console;
|
|
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Modules\EcommerceFinance\Services\InventoryReportAuthoritativeImporter;
|
|
use PhpOffice\PhpSpreadsheet\Spreadsheet;
|
|
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
|
|
use Tests\TestCase;
|
|
|
|
class InventoryReportAuthoritativeImportTest extends TestCase
|
|
{
|
|
private array $temporaryFiles = [];
|
|
|
|
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');
|
|
$this->createTables();
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
foreach ($this->temporaryFiles as $file) {
|
|
@unlink($file);
|
|
}
|
|
parent::tearDown();
|
|
}
|
|
|
|
public function test_it_imports_monthly_warehouse_rows_and_preserves_opening_quantity(): void
|
|
{
|
|
$path = $this->workbook([
|
|
$this->headers(),
|
|
$this->row('2026-08-01', '简尚-崔桥工厂仓', '简尚-崔桥工厂仓主仓', '15962G', 14, 6, [
|
|
'销售出仓' => -28,
|
|
'调拨入' => 20,
|
|
]),
|
|
$this->row('2026-08-01', '简尚-崔桥工厂仓', '简尚-崔桥工厂仓进货仓', '15962G', 0, 0, [
|
|
'采购进仓' => 20,
|
|
'调拨出' => -20,
|
|
]),
|
|
]);
|
|
|
|
$this->artisan('business-mirror:import-inventory-report-month', ['path' => $path])
|
|
->assertSuccessful();
|
|
|
|
$state = DB::connection('business_mirror')->table('inventory_report_authoritative_states')->first();
|
|
$this->assertSame(InventoryReportAuthoritativeImporter::DATASET_KEY, $state->dataset_key);
|
|
$this->assertSame('2026-08-01', $state->source_month);
|
|
$this->assertSame(2, (int) $state->source_row_count);
|
|
$this->assertDatabaseHas('inventory_report_authoritative_rows', [
|
|
'generation' => $state->active_generation,
|
|
'warehouse' => '简尚-崔桥工厂仓主仓',
|
|
'product_code' => '15962G',
|
|
'opening_quantity' => 14,
|
|
'closing_quantity' => 6,
|
|
'sales_out' => -28,
|
|
'transfer_in' => 20,
|
|
], 'business_mirror');
|
|
}
|
|
|
|
public function test_it_keeps_authoritative_generations_for_multiple_months(): void
|
|
{
|
|
$august = app(InventoryReportAuthoritativeImporter::class)->import($this->workbook([
|
|
$this->headers(),
|
|
$this->row('2026-08-01', '仓储方', '主仓', 'SKU-A', 10, 12, ['采购进仓' => 2]),
|
|
]));
|
|
$july = app(InventoryReportAuthoritativeImporter::class)->import($this->workbook([
|
|
$this->headers(),
|
|
$this->row('2026-07-01', '仓储方', '主仓', 'SKU-A', 8, 10, ['采购进仓' => 2]),
|
|
]));
|
|
|
|
$months = DB::connection('business_mirror')
|
|
->table('inventory_report_authoritative_months')
|
|
->where('dataset_key', InventoryReportAuthoritativeImporter::DATASET_KEY)
|
|
->orderBy('source_month')
|
|
->get();
|
|
|
|
$this->assertCount(2, $months);
|
|
$this->assertSame('2026-07-01', (string) $months[0]->source_month);
|
|
$this->assertSame($july['generation'], (string) $months[0]->active_generation);
|
|
$this->assertSame('2026-08-01', (string) $months[1]->source_month);
|
|
$this->assertSame($august['generation'], (string) $months[1]->active_generation);
|
|
$this->assertSame(2, DB::connection('business_mirror')
|
|
->table('inventory_report_authoritative_rows')->count());
|
|
}
|
|
|
|
public function test_invalid_replacement_keeps_previous_generation(): void
|
|
{
|
|
$valid = app(InventoryReportAuthoritativeImporter::class)->import($this->workbook([
|
|
$this->headers(),
|
|
$this->row('2026-08-01', '仓储方', '主仓', 'SKU-A', 1, 1),
|
|
]));
|
|
$invalid = $this->workbook([
|
|
$this->headers(),
|
|
$this->row('2026-08-01', '仓储方', '主仓', 'SKU-A', 1, 2),
|
|
]);
|
|
|
|
$this->artisan('business-mirror:import-inventory-report-month', ['path' => $invalid])
|
|
->expectsOutputToContain('已保留上一版有效数据')
|
|
->assertFailed();
|
|
|
|
$state = DB::connection('business_mirror')->table('inventory_report_authoritative_states')->first();
|
|
$this->assertSame($valid['generation'], $state->active_generation);
|
|
$this->assertSame(1, DB::connection('business_mirror')->table('inventory_report_authoritative_rows')
|
|
->where('generation', $valid['generation'])->count());
|
|
}
|
|
|
|
public function test_it_finds_the_detail_sheet_when_a_pivot_sheet_comes_first(): void
|
|
{
|
|
$temporary = tempnam(sys_get_temp_dir(), 'inventory-report-multi-sheet-');
|
|
$path = $temporary . '.xlsx';
|
|
rename($temporary, $path);
|
|
$spreadsheet = new Spreadsheet();
|
|
$spreadsheet->getActiveSheet()->setTitle('汇总');
|
|
$spreadsheet->getActiveSheet()->fromArray([
|
|
['条码+仓库', '求和项:期初数量'],
|
|
['SKU-A主仓', 99],
|
|
]);
|
|
$detail = $spreadsheet->createSheet();
|
|
$detail->setTitle('明细');
|
|
$detail->fromArray([
|
|
$this->headers(),
|
|
$this->row('2026-08-01', '仓储方', '主仓', 'SKU-A', 1, 1),
|
|
]);
|
|
(new Xlsx($spreadsheet))->save($path);
|
|
$spreadsheet->disconnectWorksheets();
|
|
$this->temporaryFiles[] = $path;
|
|
|
|
$result = app(InventoryReportAuthoritativeImporter::class)->import($path);
|
|
|
|
$this->assertSame('2026-08-01', $result['source_month']);
|
|
$this->assertSame(1, $result['source_rows']);
|
|
$this->assertDatabaseHas('inventory_report_authoritative_rows', [
|
|
'generation' => $result['generation'],
|
|
'product_code' => 'SKU-A',
|
|
'warehouse' => '主仓',
|
|
], 'business_mirror');
|
|
}
|
|
|
|
private function createTables(): void
|
|
{
|
|
$schema = Schema::connection('business_mirror');
|
|
$schema->create('inventory_report_authoritative_states', function (Blueprint $table): void {
|
|
$table->string('dataset_key')->primary();
|
|
$table->string('status');
|
|
$table->string('active_generation')->nullable();
|
|
$table->string('pending_generation')->nullable();
|
|
$table->string('source_filename')->nullable();
|
|
$table->string('source_sha256')->nullable();
|
|
$table->date('source_month')->nullable();
|
|
$table->unsignedBigInteger('source_row_count')->default(0);
|
|
$table->dateTime('last_attempted_at')->nullable();
|
|
$table->dateTime('last_succeeded_at')->nullable();
|
|
$table->string('last_error_code')->nullable();
|
|
$table->timestamps();
|
|
});
|
|
$schema->create('inventory_report_authoritative_rows', function (Blueprint $table): void {
|
|
$table->string('generation');
|
|
$table->string('row_key');
|
|
$table->date('report_month');
|
|
$table->string('owner');
|
|
$table->string('warehouse_provider');
|
|
$table->string('warehouse');
|
|
$table->string('product_code');
|
|
$table->string('category')->nullable();
|
|
foreach ([
|
|
'opening_quantity', 'closing_quantity', 'processing_in', 'processing_out',
|
|
'purchase_in', 'purchase_return', 'other_in', 'other_return', 'sales_out',
|
|
'sales_return', 'stocktake', 'transfer_out', 'transfer_in', 'opening', 'other_out',
|
|
] as $column) {
|
|
$table->decimal($column, 20, 4)->default(0);
|
|
}
|
|
$table->dateTime('imported_at');
|
|
$table->primary(['generation', 'row_key']);
|
|
});
|
|
$schema->create('inventory_report_authoritative_months', function (Blueprint $table): void {
|
|
$table->string('dataset_key');
|
|
$table->date('source_month');
|
|
$table->string('status')->default('succeeded');
|
|
$table->string('active_generation')->nullable();
|
|
$table->string('source_filename')->nullable();
|
|
$table->string('source_sha256')->nullable();
|
|
$table->unsignedBigInteger('source_row_count')->default(0);
|
|
$table->dateTime('last_succeeded_at')->nullable();
|
|
$table->timestamps();
|
|
$table->primary(['dataset_key', 'source_month']);
|
|
});
|
|
}
|
|
|
|
private function headers(): array
|
|
{
|
|
return [
|
|
'货主', '月份', '仓储方', '仓库', '商品编码', '图片', '商品名称', '商品简称',
|
|
'分类', '虚拟分类', '供应商款号', '颜色规格', '品牌', '单位', '期初数量', '期末数量',
|
|
'加工进仓', '加工出仓', '采购进仓', '采购退货', '其它进仓', '其它退货',
|
|
'销售出仓', '销售退货', '盘点', '调拨出', '调拨入', '期初', '其它出库',
|
|
];
|
|
}
|
|
|
|
private function row(
|
|
string $month,
|
|
string $provider,
|
|
string $warehouse,
|
|
string $sku,
|
|
float $opening,
|
|
float $closing,
|
|
array $movements = [],
|
|
): array {
|
|
$row = array_fill(0, 29, null);
|
|
$row[0] = '常州简尚家居有限公司';
|
|
$row[1] = $month;
|
|
$row[2] = $provider;
|
|
$row[3] = $warehouse;
|
|
$row[4] = $sku;
|
|
$row[8] = '测试分类';
|
|
$row[14] = $opening;
|
|
$row[15] = $closing;
|
|
foreach ([
|
|
'加工进仓', '加工出仓', '采购进仓', '采购退货', '其它进仓', '其它退货',
|
|
'销售出仓', '销售退货', '盘点', '调拨出', '调拨入', '期初', '其它出库',
|
|
] as $offset => $header) {
|
|
$row[16 + $offset] = $movements[$header] ?? 0;
|
|
}
|
|
|
|
return $row;
|
|
}
|
|
|
|
private function workbook(array $rows): string
|
|
{
|
|
$temporary = tempnam(sys_get_temp_dir(), 'inventory-report-month-');
|
|
$path = $temporary . '.xlsx';
|
|
rename($temporary, $path);
|
|
$spreadsheet = new Spreadsheet();
|
|
$spreadsheet->getActiveSheet()->fromArray($rows);
|
|
(new Xlsx($spreadsheet))->save($path);
|
|
$spreadsheet->disconnectWorksheets();
|
|
$this->temporaryFiles[] = $path;
|
|
|
|
return $path;
|
|
}
|
|
}
|