144 lines
6.7 KiB
PHP
144 lines
6.7 KiB
PHP
<?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;
|
||
use App\Models\UserModel;
|
||
|
||
/**
|
||
* 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->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;
|
||
}
|
||
|
||
/** 头部汇总取订货明细合计: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->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(), '第二次生成应被拒绝,不产生新采购单');
|
||
}
|
||
}
|