Files
xin-procurement/tests/Feature/PurchaseGenerateTest.php
2026-09-05 14:37:22 +08:00

169 lines
7.7 KiB
PHP
Raw Permalink 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;
use App\Models\CustomerLevelModel;
use App\Models\ProductModel;
use App\Models\PurchaseOrderModel;
use App\Models\StoreModel;
use App\Models\StoreOrderItemModel;
use App\Models\StoreOrderModel;
/**
* C1 订单汇总生成采购单:订单/明细归集回写、头部汇总、无订单/重复生成防护
* (采购单无独立明细表,明细直接溯源订货明细 store_order_item.purchase_id
*/
class PurchaseGenerateTest extends ProcurementTestCase
{
/**
* 造当日已接单订单:门店A 两单各 3 件 + 门店B 一单 4 件(同一商品),下单后统一接单
* 门店等级上浮 0%(售价=成本价 6.00),预估成本按成本价合计
*/
private function seedAcceptedOrders(): ProductModel
{
$level = CustomerLevelModel::factory()->create();
$storeA = StoreModel::factory()->create(['level_id' => $level->id]);
$storeB = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '6.00']);
foreach ([[$storeA, 3], [$storeA, 3], [$storeB, 4]] as [$store, $qty]) {
$this->actingAsMiniStore($store);
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => $qty]]])
->assertJsonPath('success', true);
}
// 后台接单:待接单 → 已接单(生成采购单的来源状态)
StoreOrderModel::query()->update(['status' => StoreOrderModel::STATUS_SUMMARIZED]);
return $product;
}
/** 头部汇总取订货明细合计:total_quantity = Σ数量,estimate_amount = Σ数量×成本价(预估成本) */
public function test_generate_aggregates_order_items_into_header(): void
{
$this->seedAcceptedOrders();
$admin = $this->actingAsSysUser();
$this->postJson('/purchase/order/generate', ['purchase_date' => now()->toDateString()])
->assertOk()
->assertJsonPath('success', true);
$purchase = PurchaseOrderModel::first();
$this->assertNotNull($purchase);
$this->assertStringStartsWith('PO', $purchase->purchase_no);
$this->assertSame(now()->toDateString(), $purchase->purchase_date->toDateString());
$this->assertSame($admin->id, $purchase->operator_id);
$this->assertSame(PurchaseOrderModel::STATUS_PENDING, $purchase->status);
$this->assertSame('10.00', (string) $purchase->total_quantity, '3+3+4');
$this->assertSame('60.00', (string) $purchase->estimate_amount, '10 件 × 每包成本 6.00');
$this->assertSame('0.00', (string) $purchase->actual_amount);
}
/** 源订单状态回写为采购中,订单与订货明细均回写 purchase_id */
public function test_generate_writes_back_order_status(): void
{
$this->seedAcceptedOrders();
$this->actingAsSysUser();
$this->postJson('/purchase/order/generate', ['purchase_date' => now()->toDateString()])
->assertJsonPath('success', true);
$purchase = PurchaseOrderModel::first();
$this->assertSame(0, StoreOrderModel::where('status', StoreOrderModel::STATUS_SUMMARIZED)->count());
$this->assertSame(3, StoreOrderModel::where('status', StoreOrderModel::STATUS_DELIVERING)->count());
$this->assertSame(3, StoreOrderModel::where('purchase_id', $purchase->id)->count());
$this->assertSame(
3,
StoreOrderItemModel::where('purchase_id', $purchase->id)->count(),
'订货明细应全部回写 purchase_id(采购明细溯源依据)'
);
$this->assertSame(0, StoreOrderItemModel::where('purchase_id', 0)->count());
}
/** 仅有待接单订单时不可生成(待接单不再被归集)→ 报错 */
public function test_generate_without_accepted_orders_fails(): void
{
$level = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '5.00']);
$this->actingAsMiniStore($store);
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]])
->assertJsonPath('success', true);
$this->actingAsSysUser();
$this->postJson('/purchase/order/generate', ['purchase_date' => now()->toDateString()])
->assertJsonPath('success', false);
$this->assertSame(0, PurchaseOrderModel::count());
$this->assertSame(1, StoreOrderModel::where('status', StoreOrderModel::STATUS_PENDING)->count());
}
/** 指定订单合并:仅归集 order_ids 内的已接单订单,混入非已接单则整批拒绝 */
public function test_generate_with_selected_order_ids(): void
{
$this->seedAcceptedOrders();
$this->actingAsSysUser();
$selected = StoreOrderModel::query()->orderBy('id')->limit(2)->pluck('id')->all();
$this->postJson('/purchase/order/generate', [
'purchase_date' => now()->toDateString(),
'order_ids' => $selected,
])->assertJsonPath('success', true);
$purchase = PurchaseOrderModel::first();
$this->assertSame(2, StoreOrderModel::where('purchase_id', $purchase->id)->count());
$this->assertSame(1, StoreOrderModel::where('status', StoreOrderModel::STATUS_SUMMARIZED)->count(), '未选中的订单保持已接单');
// 混入非已接单订单 → 整批拒绝
$remaining = StoreOrderModel::where('status', StoreOrderModel::STATUS_SUMMARIZED)->value('id');
$done = StoreOrderModel::where('status', StoreOrderModel::STATUS_DELIVERING)->value('id');
$this->postJson('/purchase/order/generate', [
'purchase_date' => now()->toDateString(),
'order_ids' => [$remaining, $done],
])->assertJsonPath('success', false);
$this->assertSame(1, PurchaseOrderModel::count(), '整批拒绝,不产生新采购单');
}
/** 幂等:已归集订单不会被重复归集 */
public function test_generate_is_idempotent(): void
{
$this->seedAcceptedOrders();
$this->actingAsSysUser();
$this->postJson('/purchase/order/generate', ['purchase_date' => now()->toDateString()])
->assertJsonPath('success', true);
$this->postJson('/purchase/order/generate', ['purchase_date' => now()->toDateString()])
->assertJsonPath('success', false);
$this->assertSame(1, PurchaseOrderModel::count(), '第二次生成应被拒绝,不产生新采购单');
}
/** 采购单总重量 = 订货明细参考重量合计(下单时已按订货量 × 规格预填) */
public function test_generate_sums_reference_weight(): void
{
$level = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create([
'status' => ProductModel::STATUS_ON,
'cost_price' => '6.00',
'spec' => '10斤/箱',
'unit' => '箱',
]);
$this->actingAsMiniStore($store);
foreach ([3, 4] as $qty) {
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => $qty]]])
->assertJsonPath('success', true);
}
StoreOrderModel::query()->update(['status' => StoreOrderModel::STATUS_SUMMARIZED]);
$this->actingAsSysUser();
$this->postJson('/purchase/order/generate', ['purchase_date' => now()->toDateString()])
->assertJsonPath('success', true);
$this->assertSame('70.000', (string) PurchaseOrderModel::first()->total_weight, '(3+4) 箱 × 10斤/箱');
}
}