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); } }