Files
xin-procurement/tests/Feature/PurchaseGenerateTest.php
T
2026-08-11 21:08:01 +08:00

184 lines
8.6 KiB
PHP
Raw 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\ProductPriceModel;
use App\Models\PurchaseOrderModel;
use App\Models\StoreModel;
use App\Models\StoreOrderModel;
use App\Models\UserModel;
/**
* C1 订单汇总生成采购单:多门店聚合、订单状态回写、无订单/重复生成防护
*/
class PurchaseGenerateTest extends ProcurementTestCase
{
/**
* 造当日已接单订单:门店A 两单各 3 件 + 门店B 一单 4 件(同一商品),下单后统一接单
* 商品设两个等级价 5.00 / 4.00,估算单价应取最低 4.00
*/
private function seedAcceptedOrders(): ProductModel
{
$level = CustomerLevelModel::factory()->create();
$levelLow = 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]);
ProductPriceModel::factory()->create(['product_id' => $product->id, 'level_id' => $level->id, 'price' => 5.00]);
ProductPriceModel::factory()->create(['product_id' => $product->id, 'level_id' => $levelLow->id, 'price' => 4.00]);
foreach ([[$storeA, 3], [$storeA, 3], [$storeB, 4]] as [$store, $qty]) {
$this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create());
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => $qty]]])
->assertJsonPath('success', true);
}
// 后台接单:待接单 → 已接单(生成采购单的来源状态)
StoreOrderModel::query()->update(['status' => StoreOrderModel::STATUS_SUMMARIZED]);
return $product;
}
/** 多门店多订单按商品聚合,估算单价取最低等级价 */
public function test_generate_aggregates_orders_by_product(): 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->assertCount(1, $purchase->items, '单一商品应聚合为一行');
$item = $purchase->items->first();
$this->assertSame('10.00', (string) $item->quantity, '3+3+4');
$this->assertSame('4.00', (string) $item->price, '估算单价取最低等级价');
$this->assertSame('40.00', (string) $item->amount, '10 × 4.00');
$this->assertSame('40.00', (string) $purchase->estimate_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());
}
/** 仅有待接单订单时不可生成(待接单不再被归集)→ 报错 */
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]);
ProductPriceModel::factory()->create(['product_id' => $product->id, 'level_id' => $level->id, 'price' => 5.00]);
$this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create());
$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_uses_min_actual_price_across_types(): void
{
$level = CustomerLevelModel::factory()->create();
$levelFixed = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create([
'status' => ProductModel::STATUS_ON,
'cost_price' => 10,
]);
// 百分比行:10 × (1+40%) = 14.00;固定价行 5.00 → 估算应取 5.00
ProductPriceModel::factory()->create([
'product_id' => $product->id,
'level_id' => $level->id,
'price_type' => ProductPriceModel::PRICE_TYPE_PERCENT,
'percent' => 40,
'price' => 14.00,
]);
ProductPriceModel::factory()->create([
'product_id' => $product->id,
'level_id' => $levelFixed->id,
'price' => 5.00,
]);
$this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create());
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 3]]])
->assertJsonPath('success', true);
// 接单后生成
StoreOrderModel::query()->update(['status' => StoreOrderModel::STATUS_SUMMARIZED]);
$this->actingAsSysUser();
$this->postJson('/purchase/order/generate', ['purchase_date' => now()->toDateString()])
->assertJsonPath('success', true);
$item = PurchaseOrderModel::first()->items->first();
$this->assertSame('5.00', (string) $item->price, '估算单价应取换算后的最低实际价');
$this->assertSame('15.00', (string) $item->amount, '3 × 5.00');
}
}