Files
xin-procurement/tests/Feature/PurchaseEditTest.php
T
2026-08-13 12:34:32 +08:00

608 lines
28 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\StoreOrderItemModel;
use App\Models\StoreOrderModel;
use App\Models\SupplierModel;
use App\Models\UserModel;
use Illuminate\Support\Facades\Cache;
use Modules\SystemTool\Models\SysSiteConfigGroupModel;
use Modules\SystemTool\Models\SysSiteConfigItemsModel;
/**
* C4 采购单数据修改:详情矩阵(商品行 × 门店列)、门店单元格下钻编辑/同步、行级成本,
* 修改同步订货明细并重算订单/采购单汇总(金额口径:数量(包) × 每包价格,单价/包规不参与金额计算)
*/
class PurchaseEditTest extends ProcurementTestCase
{
/**
* 造采购链路:门店A 2 件 + 门店B 3 件(同一商品,等级价 10.00,成本 20,包规 10斤/箱 → 单价 2.00),
* 接单后生成采购单
*
* @return array{0: PurchaseOrderModel, 1: ProductModel, 2: array<int, StoreModel>}
*/
private function buildPurchase(): array
{
$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' => 20,
'spec' => '10斤/箱',
'unit' => '斤',
]);
ProductPriceModel::factory()->create(['product_id' => $product->id, 'level_id' => $level->id, 'price' => 10.00]);
foreach ([[$storeA, 2], [$storeB, 3]] 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]);
$this->actingAsSysUser();
$this->postJson('/purchase/order/generate', ['purchase_date' => now()->toDateString()])
->assertJsonPath('success', true);
return [PurchaseOrderModel::first(), $product, [$storeA, $storeB]];
}
/** 播种站点配置:周转框单价 2.00、周转托盘单价 10.00 */
private function seedContainerConfig(): void
{
$group = SysSiteConfigGroupModel::create(['title' => '业务配置', 'key' => 'services']);
foreach ([['box_amount', '周转框单价', '2.00'], ['tray_amount', '周转托盘单价', '10.00']] as [$key, $title, $value]) {
SysSiteConfigItemsModel::create([
'group_id' => $group->id,
'key' => $key,
'title' => $title,
'type' => 'InputNumber',
'values' => $value,
'sort' => 0,
]);
}
Cache::forget('site_config');
}
/** 详情返回「商品行 × 门店列」矩阵:cells 按门店聚合数量 */
public function test_detail_returns_store_matrix(): void
{
[$purchase, $product, $stores] = $this->buildPurchase();
$this->actingAsSysUser();
$response = $this->getJson("/purchase/order/{$purchase->id}")
->assertOk()
->assertJsonPath('success', true);
$data = $response->json('data');
$this->assertCount(2, $data['stores']);
$this->assertCount(1, $data['items']);
$row = $data['items'][0];
$this->assertSame($product->id, $row['product_id']);
$this->assertSame('10斤/箱', $row['product_spec']);
$this->assertSame('斤', $row['unit']);
$this->assertSame('20.00', (string) $row['cost_price']);
$this->assertSame(5, $row['quantity'], '2+3');
$cells = $row['cells'];
$this->assertSame(2, $cells[$stores[0]->id]);
$this->assertSame(3, $cells[$stores[1]->id]);
}
/** 门店单元格数量修改 → 明细金额重算(数量×单价),订单与采购单汇总同步 */
public function test_update_cell_syncs_order_item_and_totals(): void
{
[$purchase, , $stores] = $this->buildPurchase();
$this->actingAsSysUser();
$item = StoreOrderItemModel::where('store_id', $stores[0]->id)->first();
$this->putJson("/purchase/order/cell/{$item->id}", ['quantity' => 5])
->assertJsonPath('success', true)
->assertJsonPath('data.amount', 50);
$item = $item->fresh();
$this->assertSame(5, $item->quantity);
$this->assertSame('50.00', (string) $item->amount, '5 × 订货单价 10.00');
$order = StoreOrderModel::find($item->order_id);
$this->assertSame(5, $order->total_quantity);
$this->assertSame('50.00', (string) $order->product_amount);
$this->assertSame('50.00', (string) $order->total_amount);
$purchase = $purchase->fresh();
$this->assertSame('8.00', (string) $purchase->total_quantity, '5+3');
$this->assertSame('80.00', (string) $purchase->estimate_amount, '50+30');
$this->assertSame('160.00', (string) $purchase->actual_amount, '8 包 × 每包成本 20.00');
}
/** 行级成本修改 → 同步该商品全部订货明细,采购单实际金额按 数量×每包成本 重算 */
public function test_update_row_cost_syncs_all_order_items(): void
{
[$purchase, $product] = $this->buildPurchase();
$this->actingAsSysUser();
$this->putJson("/purchase/order/{$purchase->id}/row/{$product->id}", ['cost_price' => 30])
->assertJsonPath('success', true)
->assertJsonPath('data.count', 2);
$this->assertSame(2, StoreOrderItemModel::where('cost_price', '30.00')->count());
$purchase = $purchase->fresh();
$this->assertSame('150.00', (string) $purchase->actual_amount, '5 包 × 每包成本 30.00');
$this->assertSame('50.00', (string) $purchase->estimate_amount, '订货金额不受成本修改影响');
}
/** 行级属性修改:品名/供应商/包规/单位/成本一键同步该商品全部订货明细(重量为手动录入,不受行修改影响) */
public function test_update_row_does_not_touch_manual_weight(): void
{
[$purchase, $product] = $this->buildPurchase();
$this->actingAsSysUser();
$this->putJson("/purchase/order/{$purchase->id}/row/{$product->id}", ['cost_price' => 30])
->assertJsonPath('success', true);
$this->assertSame(0, StoreOrderItemModel::where('weight', '<>', 0)->count(), '重量保持手动录入值,不自动计算');
}
/** 行级属性修改:品名/供应商/包规/单位/成本一键同步该商品全部订货明细 */
public function test_update_row_syncs_product_attributes(): void
{
[$purchase, $product] = $this->buildPurchase();
$supplier = SupplierModel::factory()->create();
$this->actingAsSysUser();
$this->putJson("/purchase/order/{$purchase->id}/row/{$product->id}", [
'product_name' => '优选土豆',
'supplier_id' => $supplier->id,
'product_spec' => '5斤/袋',
'unit' => '袋',
'cost_price' => 25,
])->assertJsonPath('success', true)
->assertJsonPath('data.count', 2);
$items = StoreOrderItemModel::all();
$this->assertCount(2, $items);
foreach ($items as $item) {
$this->assertSame('优选土豆', $item->product_name);
$this->assertSame($supplier->id, $item->supplier_id);
$this->assertSame('5斤/袋', $item->product_spec);
$this->assertSame('袋', $item->unit);
$this->assertSame('25.00', (string) $item->cost_price);
}
// 未传 sync_product:商品档案保持原值
$product = $product->fresh();
$this->assertNotSame('优选土豆', $product->name);
$this->assertSame('125.00', (string) $purchase->fresh()->actual_amount, '5 包 × 每包成本 25.00');
}
/** sync_product=true:订货明细与商品档案同步更新;软删除商品拒绝 */
public function test_update_row_syncs_to_product_catalog(): void
{
[$purchase, $product] = $this->buildPurchase();
$supplier = SupplierModel::factory()->create();
$this->actingAsSysUser();
$this->putJson("/purchase/order/{$purchase->id}/row/{$product->id}", [
'product_name' => '优选土豆',
'supplier_id' => $supplier->id,
'product_spec' => '5斤/袋',
'unit' => '袋',
'cost_price' => 25,
'sync_product' => true,
])->assertJsonPath('success', true);
$product = $product->fresh();
$this->assertSame('优选土豆', $product->name);
$this->assertSame($supplier->id, $product->supplier_id);
$this->assertSame('5斤/袋', $product->spec);
$this->assertSame('袋', $product->unit);
$this->assertSame('25.00', (string) $product->cost_price);
// 软删除商品 → 拒绝并回滚(订货明细不同步)
$product->delete();
$this->putJson("/purchase/order/{$purchase->id}/row/{$product->id}", [
'product_name' => '再次改名',
'sync_product' => true,
])->assertJsonPath('success', false);
$this->assertSame('优选土豆', StoreOrderItemModel::first()->product_name, '回滚后明细保持原值');
}
/** 行级修改:属性与称重至少一项 */
public function test_update_row_requires_at_least_one_field(): void
{
[$purchase, $product] = $this->buildPurchase();
$this->actingAsSysUser();
$this->putJson("/purchase/order/{$purchase->id}/row/{$product->id}", [])
->assertJsonPath('success', false);
}
/** 单元格数量校验:负数拒绝 */
public function test_update_cell_rejects_negative_quantity(): void
{
[$purchase, , $stores] = $this->buildPurchase();
$this->actingAsSysUser();
$item = StoreOrderItemModel::where('store_id', $stores[0]->id)->first();
$this->putJson("/purchase/order/cell/{$item->id}", ['quantity' => -1])
->assertJsonPath('success', false);
}
/** 单元格下钻:返回该门店该商品的全部订货明细(订单号/状态/快照/可编辑标记),其他门店无明细 */
public function test_cell_detail_returns_items_with_order_info(): void
{
[$purchase, $product, $stores] = $this->buildPurchase();
$this->actingAsSysUser();
$response = $this->getJson("/purchase/order/{$purchase->id}/cell?product_id={$product->id}&store_id={$stores[0]->id}")
->assertOk()
->assertJsonPath('success', true);
$data = $response->json('data');
$this->assertSame($stores[0]->name, $data['store']['name']);
$this->assertSame($product->name, $data['product']['name']);
$this->assertCount(1, $data['items']);
$item = $data['items'][0];
$this->assertArrayHasKey('order_no', $item);
$this->assertSame(2, $item['quantity']);
$this->assertSame('20.00', (string) $item['amount']);
$this->assertSame(StoreOrderModel::STATUS_DELIVERING, $item['order_status'], '生成采购单后源订单为采购中');
$this->assertTrue($item['editable']);
// 其他门店 → 另一条明细
$this->getJson("/purchase/order/{$purchase->id}/cell?product_id={$product->id}&store_id={$stores[1]->id}")
->assertOk()
->assertJsonPath('data.items.0.quantity', 3);
}
/** 单元格下钻参数校验:缺少商品/门店参数拒绝 */
public function test_cell_detail_rejects_missing_params(): void
{
[$purchase] = $this->buildPurchase();
$this->actingAsSysUser();
$this->getJson("/purchase/order/{$purchase->id}/cell")
->assertJsonPath('success', false);
}
/** 单元格称重修改 → 明细/订货单/采购单重量与金额汇总级联重算 */
public function test_update_cell_weight_cascades_totals(): void
{
[$purchase, , $stores] = $this->buildPurchase();
$this->actingAsSysUser();
$item = StoreOrderItemModel::where('store_id', $stores[0]->id)->first();
$this->putJson("/purchase/order/cell/{$item->id}", ['quantity' => 5, 'weight' => 4.5])
->assertJsonPath('success', true)
->assertJsonPath('data.amount', 50);
$item = $item->fresh();
$this->assertSame('4.500', (string) $item->weight);
$this->assertSame('50.00', (string) $item->amount, '5 × 订货单价 10.00');
$order = StoreOrderModel::find($item->order_id);
$this->assertSame('4.500', (string) $order->total_weight);
$this->assertSame('50.00', (string) $order->product_amount);
$purchase = $purchase->fresh();
$this->assertSame('4.500', (string) $purchase->total_weight);
$this->assertSame('80.00', (string) $purchase->estimate_amount, '50+30 订货金额');
$this->assertSame('160.00', (string) $purchase->actual_amount, '8 包 × 每包成本 20.00(称重仅参考,不参与金额)');
}
/** 单元格一键同步:按商品ID同步档案 + 等级价重算,订货单与采购单汇总级联 */
public function test_cell_sync_refreshes_snapshot_and_cascades(): void
{
$level = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create([
'status' => ProductModel::STATUS_ON,
'cost_price' => '10.00',
'spec' => '1斤/袋',
'unit' => '斤',
]);
ProductPriceModel::factory()->create([
'product_id' => $product->id,
'level_id' => $level->id,
'price_type' => ProductPriceModel::PRICE_TYPE_PERCENT,
'percent' => 30,
]);
$this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create());
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 2]]])
->assertJsonPath('success', true);
StoreOrderModel::query()->update(['status' => StoreOrderModel::STATUS_SUMMARIZED]);
$this->actingAsSysUser();
$this->postJson('/purchase/order/generate', ['purchase_date' => now()->toDateString()])
->assertJsonPath('success', true);
$purchase = PurchaseOrderModel::first();
$item = StoreOrderItemModel::first();
$this->assertSame('13.00', (string) $item->price, '下单时 10 元上浮 30%');
$this->assertSame('26.00', (string) $purchase->estimate_amount);
// 成本上调后同步:单价按最新成本重算,金额与两级汇总级联
$product->update(['cost_price' => '20.00']);
$this->putJson("/purchase/order/cell/{$item->id}/sync")
->assertJsonPath('success', true)
->assertJsonPath('data.amount', 52);
$item->refresh();
$this->assertSame('20.00', (string) $item->cost_price);
$this->assertSame('26.00', (string) $item->price, '20 元上浮 30% = 26.00');
$this->assertSame('52.00', (string) $item->amount, '26.00 × 2');
$order = StoreOrderModel::first();
$this->assertSame('52.00', (string) $order->product_amount);
$this->assertSame('52.00', (string) $order->total_amount);
$purchase = $purchase->fresh();
$this->assertSame('52.00', (string) $purchase->estimate_amount);
$this->assertSame('40.00', (string) $purchase->actual_amount, '2 包 × 每包成本 20.00');
}
/** 门店购买详情:按商品聚合该门店采购汇总(数量/包规/单位/单价/预计金额),仅含该门店明细 */
public function test_store_summary_returns_aggregated_items(): void
{
[$purchase, $product, $stores] = $this->buildPurchase();
$this->actingAsSysUser();
$response = $this->getJson("/purchase/order/{$purchase->id}/store?store_id={$stores[0]->id}")
->assertOk()
->assertJsonPath('success', true);
$data = $response->json('data');
$this->assertSame($stores[0]->name, $data['store']['name']);
$this->assertCount(1, $data['items']);
$row = $data['items'][0];
$this->assertSame($product->id, $row['product_id']);
$this->assertSame('10斤/箱', $row['product_spec']);
$this->assertSame('斤', $row['unit']);
$this->assertSame('10.00', (string) $row['price']);
$this->assertSame(2, $row['quantity']);
$this->assertSame('20.00', (string) $row['amount']);
// 另一门店只见自身明细
$this->getJson("/purchase/order/{$purchase->id}/store?store_id={$stores[1]->id}")
->assertOk()
->assertJsonPath('data.items.0.quantity', 3)
->assertJsonPath('data.items.0.amount', '30.00');
}
/** 门店购买详情:同一商品多笔订单不同单价 → 数量合计、金额求和、单价为加权平均 */
public function test_store_summary_aggregates_multiple_orders_with_weighted_price(): void
{
$level = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create([
'status' => ProductModel::STATUS_ON,
'cost_price' => 20,
'spec' => '10斤/箱',
'unit' => '斤',
]);
ProductPriceModel::factory()->create(['product_id' => $product->id, 'level_id' => $level->id, 'price' => 10.00]);
$this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create());
foreach ([2, 3] 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);
$purchase = PurchaseOrderModel::first();
// 第二笔明细单价改为 20.00,构造同商品双单价:2×10 + 3×20
$secondItem = StoreOrderItemModel::orderBy('id')->skip(1)->first();
$this->putJson("/purchase/order/cell/{$secondItem->id}", ['quantity' => 3, 'price' => 20])
->assertJsonPath('success', true);
$this->getJson("/purchase/order/{$purchase->id}/store?store_id={$store->id}")
->assertOk()
->assertJsonPath('data.items.0.quantity', 5)
->assertJsonPath('data.items.0.price', '16.00', '(20+60) ÷ 5 加权平均')
->assertJsonPath('data.items.0.amount', '80.00');
}
/** 门店购买详情参数校验:缺少门店参数 / 采购单不存在均拒绝 */
public function test_store_summary_rejects_missing_or_invalid_params(): void
{
[$purchase] = $this->buildPurchase();
$this->actingAsSysUser();
$this->getJson("/purchase/order/{$purchase->id}/store")
->assertJsonPath('success', false)
->assertJsonPath('msg', '缺少门店参数');
$this->getJson('/purchase/order/99999/store?store_id=1')
->assertJsonPath('success', false)
->assertJsonPath('msg', '采购单不存在');
}
/** 采购单已完成:单元格编辑/同步、行修改均拒绝,明细不变 */
public function test_edits_rejected_when_purchase_completed(): void
{
[$purchase, $product, $stores] = $this->buildPurchase();
$item = StoreOrderItemModel::where('store_id', $stores[0]->id)->first();
$purchase->update(['status' => PurchaseOrderModel::STATUS_COMPLETED]);
$this->actingAsSysUser();
$this->putJson("/purchase/order/cell/{$item->id}", ['quantity' => 5])
->assertJsonPath('success', false)
->assertJsonPath('msg', '采购单已完成,不允许修改明细');
$this->putJson("/purchase/order/cell/{$item->id}/sync")
->assertJsonPath('success', false);
$this->putJson("/purchase/order/{$purchase->id}/row/{$product->id}", ['cost_price' => 30])
->assertJsonPath('success', false);
$this->assertSame(2, $item->fresh()->quantity, '被拒绝后明细不变');
$this->assertSame('20.00', (string) $item->fresh()->cost_price);
// 下钻明细同步标记不可编辑
$this->getJson("/purchase/order/{$purchase->id}/cell?product_id={$product->id}&store_id={$stores[0]->id}")
->assertOk()
->assertJsonPath('data.items.0.editable', false);
}
/** 详情返回周转框/托盘合并记录:按门店聚合全部订单(含底层订单明细与单价) */
public function test_detail_returns_container_summary(): void
{
$this->seedContainerConfig();
[$purchase, , $stores] = $this->buildPurchase();
StoreOrderModel::where('store_id', $stores[0]->id)->update(['box_num' => 2, 'tray_num' => 1]);
StoreOrderModel::where('store_id', $stores[1]->id)->update(['box_num' => 3, 'tray_num' => 0]);
$this->actingAsSysUser();
$containers = $this->getJson("/purchase/order/{$purchase->id}")
->assertOk()
->assertJsonPath('success', true)
->json('data.containers');
$this->assertCount(2, $containers);
$this->assertSame($stores[0]->id, $containers[0]['store_id'], '与门店列同序');
$row = $containers[0];
$this->assertSame($stores[0]->name, $row['store_name']);
$this->assertSame(2, $row['box_num']);
$this->assertSame(1, $row['tray_num']);
$this->assertSame('2.00', $row['box_price']);
$this->assertSame('10.00', $row['tray_price']);
$this->assertSame('14.00', $row['added_amount'], '2×2.00 + 1×10.00');
$this->assertSame(1, $row['order_count']);
$this->assertCount(1, $row['orders']);
$order = $row['orders'][0];
$this->assertStringStartsWith('SO', $order['order_no']);
$this->assertSame(2, $order['box_num']);
$this->assertSame(1, $order['tray_num']);
$this->assertSame(3, $containers[1]['box_num']);
$this->assertSame('6.00', $containers[1]['added_amount']);
}
/** 合并记录修改:逐笔更新订单数量并重算附加金额与订单总金额,其他门店不受影响 */
public function test_update_container_syncs_order_amounts(): void
{
$this->seedContainerConfig();
[$purchase, , $stores] = $this->buildPurchase();
$order = StoreOrderModel::where('store_id', $stores[0]->id)->first();
$this->assertSame('20.00', (string) $order->product_amount, '下单时应写入商品金额');
$this->putJson("/purchase/order/{$purchase->id}/container/{$stores[0]->id}", [
'orders' => [['order_id' => $order->id, 'box_num' => 3, 'tray_num' => 1]],
])->assertOk()->assertJsonPath('success', true);
$order->refresh();
$this->assertSame(3, $order->box_num);
$this->assertSame(1, $order->tray_num);
$this->assertSame('16.00', (string) $order->added_amount, '3×2.00 + 1×10.00');
$this->assertSame('20.00', (string) $order->product_amount);
$this->assertSame('36.00', (string) $order->total_amount, '20.00 + 16.00');
$other = StoreOrderModel::where('store_id', $stores[1]->id)->first();
$this->assertSame(0, $other->box_num, '其他门店订单不受影响');
$this->assertSame('30.00', (string) $other->total_amount);
}
/** 采购单已完成:合并记录修改拒绝,数量与金额不变 */
public function test_update_container_rejected_when_purchase_completed(): void
{
$this->seedContainerConfig();
[$purchase, , $stores] = $this->buildPurchase();
$order = StoreOrderModel::where('store_id', $stores[0]->id)->first();
$purchase->update(['status' => PurchaseOrderModel::STATUS_COMPLETED]);
$this->putJson("/purchase/order/{$purchase->id}/container/{$stores[0]->id}", [
'orders' => [['order_id' => $order->id, 'box_num' => 5, 'tray_num' => 5]],
])->assertJsonPath('success', false)
->assertJsonPath('msg', '采购单已完成,不允许修改周转框/托盘');
$this->assertSame(0, $order->fresh()->box_num, '被拒绝后数量不变');
}
/** 提交必须覆盖该门店全部订单;跨门店/跨采购单订单拒绝 */
public function test_update_container_rejects_incomplete_or_foreign_orders(): void
{
$this->seedContainerConfig();
$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' => 10.00]);
$this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create());
foreach ([1, 2] 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);
$purchase = PurchaseOrderModel::first();
$orders = StoreOrderModel::where('purchase_id', $purchase->id)->orderBy('id')->get();
$this->assertCount(2, $orders, '同一门店两笔订单');
// 只提交一笔 → 缺少另一笔,整批拒绝
$this->putJson("/purchase/order/{$purchase->id}/container/{$store->id}", [
'orders' => [['order_id' => $orders[0]->id, 'box_num' => 1, 'tray_num' => 0]],
])->assertJsonPath('success', false)
->assertJsonPath('msg', '提交不完整,缺少订单:' . $orders[1]->id);
// 混入其他门店订单(同一采购单另一门店)→ 拒绝
$otherStore = StoreModel::factory()->create(['level_id' => $level->id]);
$this->actingAsMiniUser(UserModel::factory()->forStore($otherStore->id)->create());
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]])
->assertJsonPath('success', true);
$foreign = StoreOrderModel::where('store_id', $otherStore->id)->first();
$this->actingAsSysUser();
$this->putJson("/purchase/order/{$purchase->id}/container/{$store->id}", [
'orders' => [
['order_id' => $orders[0]->id, 'box_num' => 1, 'tray_num' => 0],
['order_id' => $orders[1]->id, 'box_num' => 1, 'tray_num' => 0],
['order_id' => $foreign->id, 'box_num' => 1, 'tray_num' => 0],
],
])->assertJsonPath('success', false)
->assertJsonPath('msg', '包含不属于该采购单该门店的订单');
$this->assertSame(0, $orders[0]->fresh()->box_num, '被拒绝后数量不变');
}
/** 数量为负/重复订单时校验失败 */
public function test_update_container_validates_negative_and_duplicate_orders(): void
{
$this->seedContainerConfig();
[$purchase, , $stores] = $this->buildPurchase();
$order = StoreOrderModel::where('store_id', $stores[0]->id)->first();
$this->putJson("/purchase/order/{$purchase->id}/container/{$stores[0]->id}", [
'orders' => [['order_id' => $order->id, 'box_num' => -1, 'tray_num' => 0]],
])->assertJsonPath('success', false)
->assertJsonPath('msg', '周转框数量不能小于 0');
$this->putJson("/purchase/order/{$purchase->id}/container/{$stores[0]->id}", [
'orders' => [
['order_id' => $order->id, 'box_num' => 1, 'tray_num' => 0],
['order_id' => $order->id, 'box_num' => 1, 'tray_num' => 0],
],
])->assertJsonPath('success', false);
}
}