CaiWuXiTong/tests/Feature/Console/ProductMonthlyWeightedAverageAutomationTest.php

210 lines
8.7 KiB
PHP

<?php
namespace Tests\Feature\Console;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use Tests\TestCase;
class ProductMonthlyWeightedAverageAutomationTest extends TestCase
{
private string $connection = 'business_mirror';
/** @var list<string> */
private array $temporaryTables = [];
protected function setUp(): void
{
parent::setUp();
$schema = Schema::connection($this->connection);
$this->createTableIfMissing('ops_jst_sku', function (Blueprint $table): void {
$table->string('sku_id');
$table->decimal('cost_price', 20, 8)->nullable();
});
$this->createTableIfMissing('js_pm_price_adjustment', function (Blueprint $table): void {
$table->string('skuCode')->nullable();
$table->decimal('purchasePrice', 20, 8)->nullable();
$table->dateTime('effectTime')->nullable();
});
$this->createTableIfMissing('js_pm_price_adjustment_log', function (Blueprint $table): void {
$table->string('skucode')->nullable();
$table->decimal('afterPrice', 20, 8)->nullable();
$table->decimal('originalPrice', 20, 8)->nullable();
$table->dateTime('effectStartTime')->nullable();
$table->dateTime('createTime')->nullable();
});
$this->createTableIfMissing('jushuitan_cost_price_history_rows', function (Blueprint $table): void {
$table->string('sku_id')->nullable();
$table->string('sku_id_normalized')->nullable();
$table->dateTime('operated_at')->nullable();
});
DB::connection($this->connection)->table('ops_jst_sku')->insert([
'sku_id' => 'SKU-1',
'cost_price' => 9,
]);
}
protected function tearDown(): void
{
foreach (array_reverse($this->temporaryTables) as $table) {
Schema::connection($this->connection)->dropIfExists($table);
}
parent::tearDown();
}
public function test_compute_outputs_reconciliation_freeze_and_auto_skip(): void
{
$this->seedSourceMonth();
DB::connection($this->connection)->table('product_monthly_weighted_average_states')->insert([
'report_month' => '2026-09-01',
'status' => 'base_imported',
'created_at' => now(),
'updated_at' => now(),
]);
DB::connection($this->connection)->table('product_monthly_weighted_average_rows')->insert([
'report_month' => '2026-09-01',
'row_key' => str_repeat('a', 40),
'barcode_warehouse' => 'SKU-1测试仓',
'warehouse_short_name' => '测试仓',
'product_code' => 'SKU-1',
'opening_quantity' => 10,
'opening_unit_price' => 5,
'previous_pricing_status' => '已定价',
'base_source' => 'base',
'imported_at' => now(),
]);
DB::connection($this->connection)->table('inventory_inbound_detail_rows')->insert([
'report_month' => '2026-09-01',
'row_key' => hash('sha256', 'inbound-1'),
'barcode' => 'sku-1',
'warehouse_short_name' => '测试仓',
'quantity' => 2,
'amount' => 10,
'estimated_amount' => 2,
'imported_at' => now(),
]);
$resultPath = tempnam(sys_get_temp_dir(), 'weighted-result-') . '.json';
try {
$this->artisan('product:monthly-weighted-average:compute', [
'month' => '2026-09',
'--result-json' => $resultPath,
])->expectsOutputToContain('总行数 1')->assertSuccessful();
$row = DB::connection($this->connection)->table('product_monthly_weighted_average_rows')
->where('report_month', '2026-09-01')
->first();
$this->assertEquals(5.1667, (float) $row->weighted_average_unit_price);
$this->assertEquals(50, (float) $row->opening_amount);
$this->assertEquals(12, (float) $row->purchase_amount);
$result = json_decode((string) file_get_contents($resultPath), true);
$this->assertSame('computed', $result['status']);
$this->assertSame(1, $result['totals']['rows']);
$this->assertSame(1, $result['totals']['weighted_price_rows']);
$this->assertSame(1, $result['inbound']['matched_rows']);
$this->assertSame(0, $result['source']['row_difference']);
$this->assertSame(0, $result['anomalies']['affected_rows']);
$this->artisan('product:monthly-weighted-average:compute', [
'month' => '2026-09',
])->expectsOutputToContain('历史月份不自动覆盖')->assertFailed();
$autoPath = tempnam(sys_get_temp_dir(), 'weighted-auto-') . '.json';
$this->artisan('product:monthly-weighted-average:auto', [
'--month' => '2026-09',
'--result-json' => $autoPath,
])->assertSuccessful();
$auto = json_decode((string) file_get_contents($autoPath), true);
$this->assertSame('already_computed', $auto['status']);
$this->assertSame(1, $auto['totals']['rows']);
@unlink($autoPath);
} finally {
@unlink($resultPath);
}
}
public function test_auto_blocks_when_inbound_data_is_missing(): void
{
$this->seedSourceMonth();
DB::connection($this->connection)->table('product_monthly_weighted_average_states')->insert([
['report_month' => '2026-08-01', 'status' => 'computed', 'created_at' => now(), 'updated_at' => now()],
['report_month' => '2026-09-01', 'status' => 'base_imported', 'created_at' => now(), 'updated_at' => now()],
]);
DB::connection($this->connection)->table('product_monthly_weighted_average_rows')->insert([
'report_month' => '2026-08-01',
'row_key' => str_repeat('b', 40),
'barcode_warehouse' => 'SKU-1测试仓',
'warehouse_short_name' => '测试仓',
'product_code' => 'SKU-1',
'opening_quantity' => 1,
'weighted_average_unit_price' => 5,
'pricing_status' => '已定价',
'base_source' => 'base',
'imported_at' => now(),
'computed_at' => now(),
]);
$path = tempnam(sys_get_temp_dir(), 'weighted-blocked-') . '.json';
try {
$this->artisan('product:monthly-weighted-average:auto', [
'--month' => '2026-09',
'--result-json' => $path,
])->expectsOutputToContain('上游数据未更新或不完整')->assertFailed();
$result = json_decode((string) file_get_contents($path), true);
$this->assertSame('blocked', $result['status']);
$check = collect($result['checks'])->firstWhere('name', '入库明细已更新');
$this->assertFalse($check['passed']);
$this->assertSame(
'blocked',
DB::connection($this->connection)->table('product_monthly_weighted_average_states')
->where('report_month', '2026-09-01')->value('status'),
);
} finally {
@unlink($path);
}
}
private function seedSourceMonth(): void
{
$generation = (string) Str::uuid();
DB::connection($this->connection)->table('inventory_report_authoritative_months')->insert([
'dataset_key' => 'jiansun.inventory-report.authoritative-month',
'source_month' => '2026-09-01',
'status' => 'succeeded',
'active_generation' => $generation,
'source_filename' => 'inventory-2026-09.xlsx',
'source_row_count' => 1,
'last_succeeded_at' => now(),
'updated_at' => now(),
]);
DB::connection($this->connection)->table('inventory_report_authoritative_rows')->insert([
'generation' => $generation,
'row_key' => hash('sha256', 'source-row-1'),
'report_month' => '2026-09-01',
'owner' => '测试货主',
'warehouse_provider' => '测试仓',
'warehouse' => '测试仓主仓',
'product_code' => 'SKU-1',
'opening_quantity' => 10,
'closing_quantity' => 10,
'imported_at' => now(),
]);
}
private function createTableIfMissing(string $table, \Closure $definition): void
{
if (Schema::connection($this->connection)->hasTable($table)) {
return;
}
Schema::connection($this->connection)->create($table, $definition);
$this->temporaryTables[] = $table;
}
}