210 lines
8.7 KiB
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;
|
|
}
|
|
}
|