199 lines
8.5 KiB
PHP
199 lines
8.5 KiB
PHP
<?php
|
||
|
||
namespace Tests\Feature;
|
||
|
||
use App\Models\CustomerLevelModel;
|
||
use App\Models\ProductModel;
|
||
use App\Models\ProductPriceModel;
|
||
use App\Models\StoreModel;
|
||
use App\Models\StoreOrderModel;
|
||
use App\Models\UserModel;
|
||
|
||
/**
|
||
* 小程序下单:等级价快照、服务端重算总价、取消限制、门店数据隔离
|
||
*/
|
||
class StoreOrderTest extends ProcurementTestCase
|
||
{
|
||
/**
|
||
* @return array{0: StoreModel, 1: ProductModel, 2: UserModel}
|
||
*/
|
||
private function makeStoreWithProduct(string $price = '5.00'): array
|
||
{
|
||
$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' => $price,
|
||
]);
|
||
|
||
return [$store, $product, UserModel::factory()->forStore($store->id)->create()];
|
||
}
|
||
|
||
/** 下单快照等级价,服务端重算单品金额与订单总价 */
|
||
public function test_place_order_snapshots_level_price_and_recalculates(): void
|
||
{
|
||
[$store, $product, $user] = $this->makeStoreWithProduct('5.50');
|
||
$this->actingAsMiniUser($user);
|
||
|
||
$this->postJson('/mini/order', [
|
||
'items' => [['product_id' => $product->id, 'quantity' => 3]],
|
||
])->assertOk()->assertJsonPath('success', true);
|
||
|
||
$order = StoreOrderModel::where('store_id', $store->id)->first();
|
||
$this->assertNotNull($order);
|
||
$this->assertSame(StoreOrderModel::STATUS_PENDING, $order->status);
|
||
$this->assertStringStartsWith('SO', $order->order_no);
|
||
$this->assertSame('16.50', (string) $order->total_amount, '5.50 × 3');
|
||
|
||
$item = $order->items->first();
|
||
$this->assertSame('5.50', (string) $item->price, '明细快照等级价');
|
||
$this->assertSame('16.50', (string) $item->amount);
|
||
$this->assertSame($product->name, $item->product_name, '明细快照商品名');
|
||
}
|
||
|
||
/** 前端传入的金额字段一律被忽略 */
|
||
public function test_client_supplied_amount_is_ignored(): void
|
||
{
|
||
[$store, $product, $user] = $this->makeStoreWithProduct('5.00');
|
||
$this->actingAsMiniUser($user);
|
||
|
||
$this->postJson('/mini/order', [
|
||
'items' => [[
|
||
'product_id' => $product->id,
|
||
'quantity' => 2,
|
||
'price' => 0.01,
|
||
'amount' => 0.02,
|
||
]],
|
||
])->assertOk()->assertJsonPath('success', true);
|
||
|
||
$order = StoreOrderModel::where('store_id', $store->id)->first();
|
||
$this->assertSame('10.00', (string) $order->total_amount, '应按等级价 5.00×2 计算,忽略前端金额');
|
||
}
|
||
|
||
/** 商品未设置门店等级价格时拒绝下单 */
|
||
public function test_product_without_level_price_rejected(): void
|
||
{
|
||
[, $product, $user] = $this->makeStoreWithProduct('5.00');
|
||
ProductPriceModel::where('product_id', $product->id)->delete();
|
||
$this->actingAsMiniUser($user);
|
||
|
||
$this->postJson('/mini/order', [
|
||
'items' => [['product_id' => $product->id, 'quantity' => 1]],
|
||
])->assertOk()->assertJsonPath('success', false);
|
||
|
||
$this->assertSame(0, StoreOrderModel::count());
|
||
}
|
||
|
||
/** 取消:仅待汇总订单可取消 */
|
||
public function test_cancel_only_pending_orders(): void
|
||
{
|
||
[$store, $product, $user] = $this->makeStoreWithProduct();
|
||
$this->actingAsMiniUser($user);
|
||
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]]);
|
||
$order = StoreOrderModel::where('store_id', $store->id)->first();
|
||
|
||
$this->putJson("/mini/order/{$order->id}/cancel")->assertJsonPath('success', true);
|
||
$this->assertSame(StoreOrderModel::STATUS_CANCELLED, $order->fresh()->status);
|
||
|
||
// 已取消不可重复取消
|
||
$this->putJson("/mini/order/{$order->id}/cancel")->assertJsonPath('success', false);
|
||
}
|
||
|
||
/** 已汇总订单不可取消 */
|
||
public function test_summarized_order_cannot_be_cancelled(): void
|
||
{
|
||
[$store, $product, $user] = $this->makeStoreWithProduct();
|
||
$this->actingAsMiniUser($user);
|
||
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]]);
|
||
$order = StoreOrderModel::where('store_id', $store->id)->first();
|
||
$order->update(['status' => StoreOrderModel::STATUS_SUMMARIZED]);
|
||
|
||
$this->putJson("/mini/order/{$order->id}/cancel")->assertJsonPath('success', false);
|
||
$this->assertSame(StoreOrderModel::STATUS_SUMMARIZED, $order->fresh()->status);
|
||
}
|
||
|
||
/** 门店数据隔离:只能查看本店订单 */
|
||
public function test_order_data_isolated_between_stores(): void
|
||
{
|
||
[$storeA, $product, $userA] = $this->makeStoreWithProduct();
|
||
$this->actingAsMiniUser($userA);
|
||
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]]);
|
||
$orderOfA = StoreOrderModel::where('store_id', $storeA->id)->first();
|
||
|
||
// 门店 B 用户访问门店 A 的订单
|
||
$storeB = StoreModel::factory()->create(['level_id' => $storeA->level_id]);
|
||
$this->actingAsMiniUser(UserModel::factory()->forStore($storeB->id)->create());
|
||
|
||
$this->getJson("/mini/order/{$orderOfA->id}")->assertJsonPath('success', false);
|
||
$this->getJson('/mini/order')->assertJsonPath('data.total', 0);
|
||
}
|
||
|
||
/** 造一笔指定状态的订单(商品 5.00 × 2 = 10.00) */
|
||
private function makeOrderWithStatus(int $status): StoreOrderModel
|
||
{
|
||
[$store, $product, $user] = $this->makeStoreWithProduct('5.00');
|
||
$this->actingAsMiniUser($user);
|
||
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 2]]])
|
||
->assertJsonPath('success', true);
|
||
$order = StoreOrderModel::where('store_id', $store->id)->first();
|
||
$order->update(['status' => $status]);
|
||
|
||
return $order;
|
||
}
|
||
|
||
/** 软删除:仅已取消订单可由后台删除,删除后后台/小程序端均不可见 */
|
||
public function test_soft_delete_only_cancelled_orders(): void
|
||
{
|
||
$order = $this->makeOrderWithStatus(StoreOrderModel::STATUS_CANCELLED);
|
||
|
||
// 删除前小程序端可见
|
||
$this->getJson('/mini/order')->assertOk()->assertJsonPath('data.total', 1);
|
||
|
||
$this->actingAsSysUser();
|
||
$this->deleteJson("/order/store/{$order->id}")->assertOk()->assertJsonPath('success', true);
|
||
|
||
$this->assertNotNull($order->fresh()->deleted_at, '软删除应写入 deleted_at');
|
||
$this->assertNull(StoreOrderModel::find($order->id), '默认查询不可见软删除订单');
|
||
$this->assertNotNull(StoreOrderModel::withTrashed()->find($order->id), '数据仍保留在库中');
|
||
|
||
// 后台列表/详情不可见
|
||
$this->getJson('/order/store')->assertOk()->assertJsonPath('data.total', 0);
|
||
$this->getJson("/order/store/{$order->id}")->assertJsonPath('success', false);
|
||
|
||
// 小程序端历史订单同样不可见
|
||
$this->actingAsMiniUser(UserModel::where('store_id', $order->store_id)->first());
|
||
$this->getJson('/mini/order')->assertOk()->assertJsonPath('data.total', 0);
|
||
$this->getJson("/mini/order/{$order->id}")->assertJsonPath('success', false);
|
||
}
|
||
|
||
/** 软删除:非已取消订单拒绝删除 */
|
||
public function test_soft_delete_rejected_for_non_cancelled_orders(): void
|
||
{
|
||
foreach ([
|
||
StoreOrderModel::STATUS_PENDING,
|
||
StoreOrderModel::STATUS_SUMMARIZED,
|
||
StoreOrderModel::STATUS_DELIVERING,
|
||
StoreOrderModel::STATUS_DISTRIBUTION,
|
||
StoreOrderModel::STATUS_COMPLETED,
|
||
] as $status) {
|
||
$order = $this->makeOrderWithStatus($status);
|
||
$this->actingAsSysUser();
|
||
$this->deleteJson("/order/store/{$order->id}")
|
||
->assertOk()->assertJsonPath('success', false);
|
||
$this->assertNull($order->fresh()->deleted_at, '非已取消订单不得删除');
|
||
}
|
||
}
|
||
|
||
/** 软删除:重复删除返回订单不存在 */
|
||
public function test_soft_delete_twice_rejected(): void
|
||
{
|
||
$order = $this->makeOrderWithStatus(StoreOrderModel::STATUS_CANCELLED);
|
||
$this->actingAsSysUser();
|
||
|
||
$this->deleteJson("/order/store/{$order->id}")->assertOk()->assertJsonPath('success', true);
|
||
$this->deleteJson("/order/store/{$order->id}")->assertJsonPath('success', false);
|
||
}
|
||
}
|