采购单优化
This commit is contained in:
@@ -4,14 +4,16 @@ 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\SupplierModel;
|
||||
use App\Models\UserModel;
|
||||
|
||||
/**
|
||||
* 门店订单明细(商品快照):
|
||||
* 下单快照完整商品档案、成本价防泄漏、后台明细修改重算总价、一键同步商品档案、按商品名称搜索订单
|
||||
* 下单快照完整商品档案、成本价防泄漏、采购单内门店单品增删改重算汇总、按商品名称搜索订单
|
||||
*/
|
||||
class StoreOrderItemTest extends ProcurementTestCase
|
||||
{
|
||||
@@ -50,6 +52,17 @@ class StoreOrderItemTest extends ProcurementTestCase
|
||||
return $order;
|
||||
}
|
||||
|
||||
/** 下单 → 接单 → 生成采购单,返回采购单 */
|
||||
private function generatePurchase(): PurchaseOrderModel
|
||||
{
|
||||
StoreOrderModel::query()->update(['status' => StoreOrderModel::STATUS_SUMMARIZED]);
|
||||
$this->actingAsSysUser();
|
||||
$this->postJson('/purchase/order/generate', ['purchase_date' => now()->toDateString()])
|
||||
->assertJsonPath('success', true);
|
||||
|
||||
return PurchaseOrderModel::first();
|
||||
}
|
||||
|
||||
/** 下单时明细快照完整商品档案(分类/供应商/单位/图片/图文/保质期/成本价) */
|
||||
public function test_place_order_snapshots_full_product_info(): void
|
||||
{
|
||||
@@ -114,33 +127,23 @@ class StoreOrderItemTest extends ProcurementTestCase
|
||||
->assertJsonPath('data.items.0.supplier.name', $supplier->name);
|
||||
}
|
||||
|
||||
/** 修改明细:重算单品金额与订单总量/总额(总额 = 商品金额,附加金额已迁入账单) */
|
||||
public function test_update_item_recalculates_order_totals(): void
|
||||
/** 采购单内修改门店单品:重算单品金额与订单/采购单汇总 */
|
||||
public function test_update_store_item_recalculates_totals(): void
|
||||
{
|
||||
[$store, $product, $user] = $this->makeStoreWithProduct('5.00');
|
||||
$order = $this->placeOrder($product, $user, 2, StoreOrderModel::STATUS_SUMMARIZED);
|
||||
$item = $order->items->first();
|
||||
$supplier = SupplierModel::factory()->create();
|
||||
$purchase = $this->generatePurchase();
|
||||
|
||||
$this->actingAsSysUser();
|
||||
$this->putJson("/order/store/item/{$item->id}", [
|
||||
'supplier_id' => $supplier->id,
|
||||
'product_name' => '改名后的商品',
|
||||
'product_spec' => '新规格',
|
||||
'unit' => '箱',
|
||||
$this->putJson("/purchase/order/{$purchase->id}/store/{$store->id}/item/{$product->id}", [
|
||||
'price' => '6.00',
|
||||
'cost_price' => '3.50',
|
||||
'quantity' => 5,
|
||||
'weight' => '2.5',
|
||||
])->assertOk()->assertJsonPath('success', true);
|
||||
|
||||
$item->refresh();
|
||||
$this->assertSame($supplier->id, $item->supplier_id);
|
||||
$this->assertSame('改名后的商品', $item->product_name);
|
||||
$this->assertSame('新规格', $item->product_spec);
|
||||
$this->assertSame('箱', $item->unit);
|
||||
$this->assertSame('6.00', (string) $item->price);
|
||||
$this->assertSame('3.50', (string) $item->cost_price);
|
||||
$this->assertSame(5, $item->quantity);
|
||||
$this->assertSame('2.500', (string) $item->weight);
|
||||
$this->assertSame('30.00', (string) $item->amount, '6.00 × 5');
|
||||
@@ -148,139 +151,106 @@ class StoreOrderItemTest extends ProcurementTestCase
|
||||
$order->refresh();
|
||||
$this->assertSame(5, $order->total_quantity, '订货总量 = 明细合计');
|
||||
$this->assertSame('2.500', (string) $order->total_weight, '总重量 = 明细合计');
|
||||
$this->assertSame('30.00', (string) $order->product_amount, '商品总金额 = 明细金额合计');
|
||||
$this->assertSame('30.00', (string) $order->total_amount, '订单总金额 = 商品金额');
|
||||
$this->assertSame('30.00', (string) $order->total_amount, '订单总金额 = 明细金额合计');
|
||||
|
||||
$purchase->refresh();
|
||||
$this->assertSame('5.00', (string) $purchase->total_quantity);
|
||||
$this->assertSame('2.500', (string) $purchase->total_weight);
|
||||
$this->assertSame('20.00', (string) $purchase->estimate_amount, '5 包 × 成本 4.00');
|
||||
}
|
||||
|
||||
/** 已完成/已取消订单不允许修改明细 */
|
||||
public function test_update_item_rejected_when_completed_or_cancelled(): void
|
||||
/** 采购单已完成:门店单品增删改均拒绝,明细不变 */
|
||||
public function test_store_item_edits_rejected_when_purchase_completed(): void
|
||||
{
|
||||
[, $product, $user] = $this->makeStoreWithProduct('5.00');
|
||||
$order = $this->placeOrder($product, $user, 1);
|
||||
$item = $order->items->first();
|
||||
$payload = [
|
||||
'supplier_id' => 0,
|
||||
'product_name' => '不应生效',
|
||||
'product_spec' => '',
|
||||
'unit' => '斤',
|
||||
'price' => '1.00',
|
||||
'cost_price' => '1.00',
|
||||
'quantity' => 1,
|
||||
'weight' => 0,
|
||||
];
|
||||
$order = $this->placeOrder($product, $user, 1, StoreOrderModel::STATUS_SUMMARIZED);
|
||||
$purchase = $this->generatePurchase();
|
||||
$store = StoreModel::find($order->store_id);
|
||||
|
||||
$purchase->update(['status' => PurchaseOrderModel::STATUS_COMPLETED]);
|
||||
$this->actingAsSysUser();
|
||||
foreach ([StoreOrderModel::STATUS_COMPLETED, StoreOrderModel::STATUS_CANCELLED] as $status) {
|
||||
$order->update(['status' => $status]);
|
||||
$this->putJson("/order/store/item/{$item->id}", $payload)
|
||||
->assertOk()->assertJsonPath('success', false);
|
||||
}
|
||||
|
||||
$this->assertNotSame('不应生效', $item->fresh()->product_name, '被拒绝后明细不变');
|
||||
$this->putJson("/purchase/order/{$purchase->id}/store/{$store->id}/item/{$product->id}", [
|
||||
'quantity' => 9,
|
||||
])->assertOk()->assertJsonPath('success', false)
|
||||
->assertJsonPath('msg', '采购单已完成,不允许修改明细');
|
||||
$this->postJson("/purchase/order/{$purchase->id}/store/{$store->id}/item", [
|
||||
'product_id' => $product->id,
|
||||
'quantity' => 1,
|
||||
])->assertJsonPath('success', false);
|
||||
$this->deleteJson("/purchase/order/{$purchase->id}/store/{$store->id}/item/{$product->id}")
|
||||
->assertJsonPath('success', false);
|
||||
|
||||
$item = $order->items->first();
|
||||
$this->assertSame(1, $item->fresh()->quantity, '被拒绝后明细不变');
|
||||
$this->assertSame('5.00', (string) $order->fresh()->total_amount, '被拒绝后总金额不变');
|
||||
}
|
||||
|
||||
/** 修改明细参数校验:负单价被拦截 */
|
||||
public function test_update_item_validates_negative_price(): void
|
||||
/** 修改门店单品参数校验:负单价/负数量被拦截 */
|
||||
public function test_update_store_item_validates_negative_values(): void
|
||||
{
|
||||
[, $product, $user] = $this->makeStoreWithProduct('5.00');
|
||||
$order = $this->placeOrder($product, $user, 1, StoreOrderModel::STATUS_SUMMARIZED);
|
||||
$item = $order->items->first();
|
||||
[$store, $product, $user] = $this->makeStoreWithProduct('5.00');
|
||||
$this->placeOrder($product, $user, 1, StoreOrderModel::STATUS_SUMMARIZED);
|
||||
$purchase = $this->generatePurchase();
|
||||
|
||||
$this->actingAsSysUser();
|
||||
$this->putJson("/order/store/item/{$item->id}", [
|
||||
'supplier_id' => 0,
|
||||
'product_name' => $item->product_name,
|
||||
'unit' => '斤',
|
||||
'price' => -1,
|
||||
'cost_price' => 0,
|
||||
$this->putJson("/purchase/order/{$purchase->id}/store/{$store->id}/item/{$product->id}", [
|
||||
'quantity' => 1,
|
||||
'weight' => 0,
|
||||
'price' => -1,
|
||||
])->assertOk()
|
||||
->assertJsonPath('success', false)
|
||||
->assertJsonPath('msg', '单价不能小于 0');
|
||||
|
||||
$this->putJson("/purchase/order/{$purchase->id}/store/{$store->id}/item/{$product->id}", [
|
||||
'quantity' => -1,
|
||||
])->assertOk()
|
||||
->assertJsonPath('success', false)
|
||||
->assertJsonPath('msg', '采购数量不能小于 0');
|
||||
}
|
||||
|
||||
/** 一键同步:按商品ID同步最新档案(固定价等级保留原单价) */
|
||||
public function test_sync_item_updates_snapshot_from_product(): void
|
||||
/** 新增单品:单价按门店等级上浮比例换算(成本 20 上浮 30% = 26.00) */
|
||||
public function test_add_store_item_uses_level_percent_price(): void
|
||||
{
|
||||
[$store, $product, $user] = $this->makeStoreWithProduct('5.50', '4.00');
|
||||
$order = $this->placeOrder($product, $user, 3, StoreOrderModel::STATUS_SUMMARIZED);
|
||||
$item = $order->items->first();
|
||||
$this->assertSame('16.50', (string) $item->amount);
|
||||
|
||||
// 下单后商品档案变更:改名/改规格/改单位/换供应商/调成本价
|
||||
$supplier = SupplierModel::factory()->create();
|
||||
$product->update([
|
||||
'name' => '同步后的品名',
|
||||
'spec' => '同步后的规格',
|
||||
'unit' => '箱',
|
||||
'supplier_id' => $supplier->id,
|
||||
'cost_price' => '9.99',
|
||||
]);
|
||||
|
||||
$this->actingAsSysUser();
|
||||
$this->putJson("/order/store/item/{$item->id}/sync")
|
||||
->assertOk()->assertJsonPath('success', true);
|
||||
|
||||
$item->refresh();
|
||||
$this->assertSame('同步后的品名', $item->product_name);
|
||||
$this->assertSame('同步后的规格', $item->product_spec);
|
||||
$this->assertSame('箱', $item->unit);
|
||||
$this->assertSame($supplier->id, $item->supplier_id);
|
||||
$this->assertSame('9.99', (string) $item->cost_price);
|
||||
$this->assertSame('5.50', (string) $item->price, '固定价等级单价不随档案变化');
|
||||
$this->assertSame('16.50', (string) $item->amount, '单价未变,金额不变');
|
||||
}
|
||||
|
||||
/** 一键同步:百分比计价等级按最新成本价重算单价与订单总价 */
|
||||
public function test_sync_item_recalculates_percent_price_with_latest_cost(): void
|
||||
{
|
||||
// 等级上浮 30%:下单时成本 10.00 → 售价 13.00
|
||||
$level = CustomerLevelModel::factory()->create(['percent' => 30]);
|
||||
$store = StoreModel::factory()->create(['level_id' => $level->id]);
|
||||
$product = ProductModel::factory()->create([
|
||||
'status' => ProductModel::STATUS_ON,
|
||||
'cost_price' => '10.00',
|
||||
]);
|
||||
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '10.00']);
|
||||
$extra = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '20.00']);
|
||||
$user = UserModel::factory()->forStore($store->id)->create();
|
||||
|
||||
$order = $this->placeOrder($product, $user, 2, StoreOrderModel::STATUS_SUMMARIZED);
|
||||
$item = $order->items->first();
|
||||
$this->assertSame('13.00', (string) $item->price, '下单时 10 元上浮 30%');
|
||||
$this->assertSame('26.00', (string) $order->total_amount);
|
||||
|
||||
// 成本价上调后同步:单价应按最新成本重算
|
||||
$product->update(['cost_price' => '20.00']);
|
||||
$this->placeOrder($product, $user, 2, StoreOrderModel::STATUS_SUMMARIZED);
|
||||
$purchase = $this->generatePurchase();
|
||||
|
||||
$this->actingAsSysUser();
|
||||
$this->putJson("/order/store/item/{$item->id}/sync")
|
||||
->assertOk()->assertJsonPath('success', true);
|
||||
$this->postJson("/purchase/order/{$purchase->id}/store/{$store->id}/item", [
|
||||
'product_id' => $extra->id,
|
||||
'quantity' => 2,
|
||||
])->assertOk()->assertJsonPath('success', true);
|
||||
|
||||
$item->refresh();
|
||||
$this->assertSame('20.00', (string) $item->cost_price);
|
||||
$item = StoreOrderItemModel::where('product_id', $extra->id)->first();
|
||||
$this->assertNotNull($item);
|
||||
$this->assertSame('26.00', (string) $item->price, '20 元上浮 30% = 26.00');
|
||||
$this->assertSame('52.00', (string) $item->amount, '26.00 × 2');
|
||||
|
||||
$order->refresh();
|
||||
$this->assertSame('52.00', (string) $order->product_amount);
|
||||
$this->assertSame('52.00', (string) $order->total_amount);
|
||||
$this->assertSame('20.00', (string) $item->cost_price, '快照成本价');
|
||||
$this->assertSame($extra->name, $item->product_name, '快照品名取自商品档案');
|
||||
}
|
||||
|
||||
/** 一键同步:商品已删除时拒绝同步 */
|
||||
public function test_sync_item_rejected_when_product_deleted(): void
|
||||
/** 新增单品:商品已删除时拒绝 */
|
||||
public function test_add_store_item_rejected_when_product_deleted(): void
|
||||
{
|
||||
[, $product, $user] = $this->makeStoreWithProduct('5.00');
|
||||
$order = $this->placeOrder($product, $user, 1, StoreOrderModel::STATUS_SUMMARIZED);
|
||||
$item = $order->items->first();
|
||||
[$store, $product, $user] = $this->makeStoreWithProduct('5.00');
|
||||
$this->placeOrder($product, $user, 1, StoreOrderModel::STATUS_SUMMARIZED);
|
||||
$purchase = $this->generatePurchase();
|
||||
|
||||
$product->delete(); // 软删除
|
||||
$extra = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '9.00']);
|
||||
$extra->delete(); // 软删除
|
||||
|
||||
$this->actingAsSysUser();
|
||||
$this->putJson("/order/store/item/{$item->id}/sync")
|
||||
->assertOk()
|
||||
$this->postJson("/purchase/order/{$purchase->id}/store/{$store->id}/item", [
|
||||
'product_id' => $extra->id,
|
||||
'quantity' => 1,
|
||||
])->assertOk()
|
||||
->assertJsonPath('success', false)
|
||||
->assertJsonPath('msg', '商品不存在或已被删除,无法同步');
|
||||
->assertJsonPath('msg', '商品不存在或已被删除,无法添加');
|
||||
}
|
||||
|
||||
/** 订单列表按包含的商品名称搜索 */
|
||||
|
||||
Reference in New Issue
Block a user