create([ 'store_id' => $store->id, 'order_date' => $date, 'status' => $status, ]); } /** 造一条明细(金额默认按 price × quantity 计算) */ private function makeItem(StoreOrderModel $order, array $attributes = []): StoreOrderItemModel { return StoreOrderItemModel::factory()->create(array_merge([ 'order_id' => $order->id, 'store_id' => $order->store_id, ], $attributes)); } /** 汇总与占比:总金额=全部明细金额之和,单品按金额降序,占比 1 位小数四舍五入 */ public function test_report_aggregates_total_and_item_percent(): void { $store = StoreModel::factory()->create(); $this->actingAsMiniStore($store); // 总额恰好 100000.00:土豆 8764.00 → 8.8%,洋葱 3664.00 → 3.7%,白菜 87572.00 → 87.6% $orderA = $this->makeOrder($store, '2026-07-03'); $this->makeItem($orderA, ['product_id' => 11, 'product_name' => '土豆', 'quantity' => 500, 'price' => '17.53', 'amount' => '8764.00']); $this->makeItem($orderA, ['product_id' => 12, 'product_name' => '洋葱', 'quantity' => 200, 'price' => '18.32', 'amount' => '3664.00']); $orderB = $this->makeOrder($store, '2026-07-20'); $this->makeItem($orderB, ['product_id' => 13, 'product_name' => '白菜', 'quantity' => 1000, 'price' => '87.57', 'amount' => '87572.00']); $data = $this->getJson('/mini/report/purchase?start_date=2026-07-01&end_date=2026-07-31') ->assertOk() ->json('data'); $this->assertSame('custom', $data['preset']); $this->assertSame('2026-07-01', $data['start_date']); $this->assertSame('2026-07-31', $data['end_date']); $this->assertSame('100000.00', $data['total_amount']); $this->assertSame(1700, $data['total_quantity']); $this->assertSame(2, $data['order_count']); $this->assertSame(3, $data['item_count']); // 按金额降序 $this->assertSame(['白菜', '土豆', '洋葱'], array_column($data['items'], 'product_name')); $potato = $data['items'][1]; $this->assertSame(11, $potato['product_id']); $this->assertSame('8764.00', $potato['amount']); $this->assertSame(500, $potato['quantity']); $this->assertSame(8.8, $potato['percent']); $onion = $data['items'][2]; $this->assertSame('3664.00', $onion['amount']); $this->assertSame(3.7, $onion['percent']); $cabbage = $data['items'][0]; $this->assertSame(87.6, $cabbage['percent']); } /** 同一单品跨多订单累计:数量/金额求和,品名取快照 */ public function test_report_accumulates_same_product_across_orders(): void { $store = StoreModel::factory()->create(); $this->actingAsMiniStore($store); $orderA = $this->makeOrder($store, '2026-08-01'); $this->makeItem($orderA, ['product_id' => 11, 'product_name' => '土豆', 'quantity' => 100, 'price' => '10.00', 'amount' => '1000.00']); $orderB = $this->makeOrder($store, '2026-08-05'); $this->makeItem($orderB, ['product_id' => 11, 'product_name' => '土豆', 'quantity' => 300, 'price' => '10.00', 'amount' => '3000.00']); $data = $this->getJson('/mini/report/purchase?start_date=2026-08-01&end_date=2026-08-31') ->assertOk() ->json('data'); $this->assertSame('4000.00', $data['total_amount']); $this->assertSame(1, $data['item_count']); $this->assertSame(400, $data['items'][0]['quantity']); $this->assertSame('4000.00', $data['items'][0]['amount']); $this->assertEquals(100.0, $data['items'][0]['percent'], '单一单品占比 100%(整数百分比 JSON 编码为 100)'); } /** 自定义区间:区间外订单不计入 */ public function test_report_filters_by_custom_date_range(): void { $store = StoreModel::factory()->create(); $this->actingAsMiniStore($store); $in = $this->makeOrder($store, '2026-08-10'); $this->makeItem($in, ['product_id' => 11, 'amount' => '100.00']); $out = $this->makeOrder($store, '2026-08-20'); $this->makeItem($out, ['product_id' => 11, 'amount' => '900.00']); $data = $this->getJson('/mini/report/purchase?start_date=2026-08-01&end_date=2026-08-15') ->assertOk() ->json('data'); $this->assertSame('100.00', $data['total_amount']); $this->assertSame(1, $data['order_count']); } /** 已取消与后台已删除订单不计入 */ public function test_report_excludes_cancelled_and_deleted_orders(): void { $store = StoreModel::factory()->create(); $this->actingAsMiniStore($store); $normal = $this->makeOrder($store, '2026-08-10'); $this->makeItem($normal, ['product_id' => 11, 'amount' => '100.00']); $cancelled = $this->makeOrder($store, '2026-08-10', StoreOrderModel::STATUS_CANCELLED); $this->makeItem($cancelled, ['product_id' => 12, 'amount' => '500.00']); $deleted = $this->makeOrder($store, '2026-08-10', StoreOrderModel::STATUS_CANCELLED); $this->makeItem($deleted, ['product_id' => 13, 'amount' => '700.00']); $deleted->delete(); $data = $this->getJson('/mini/report/purchase?start_date=2026-08-01&end_date=2026-08-31') ->assertOk() ->json('data'); $this->assertSame('100.00', $data['total_amount']); $this->assertSame(1, $data['order_count']); $this->assertSame(1, $data['item_count']); $this->assertSame(11, $data['items'][0]['product_id']); } /** preset 推导:本月(结束日期封顶到今天)与上月 */ public function test_report_preset_month_and_last_month(): void { Carbon::setTestNow('2026-08-21 12:00:00'); $store = StoreModel::factory()->create(); $this->actingAsMiniStore($store); $july = $this->makeOrder($store, '2026-07-15'); $this->makeItem($july, ['product_id' => 11, 'amount' => '200.00']); $august = $this->makeOrder($store, '2026-08-15'); $this->makeItem($august, ['product_id' => 11, 'amount' => '300.00']); $month = $this->getJson('/mini/report/purchase?preset=month')->assertOk()->json('data'); $this->assertSame('2026-08-01', $month['start_date']); $this->assertSame('2026-08-21', $month['end_date'], '进行中的本月封顶到今天'); $this->assertSame('300.00', $month['total_amount']); $lastMonth = $this->getJson('/mini/report/purchase?preset=last_month')->assertOk()->json('data'); $this->assertSame('2026-07-01', $lastMonth['start_date']); $this->assertSame('2026-07-31', $lastMonth['end_date']); $this->assertSame('200.00', $lastMonth['total_amount']); } /** preset 推导:本周(周一起算)与上周 */ public function test_report_preset_week_and_last_week(): void { Carbon::setTestNow('2026-08-21 12:00:00'); // 周五,本周一 = 2026-08-17 $store = StoreModel::factory()->create(); $this->actingAsMiniStore($store); $thisWeek = $this->makeOrder($store, '2026-08-18'); $this->makeItem($thisWeek, ['product_id' => 11, 'amount' => '100.00']); $lastWeek = $this->makeOrder($store, '2026-08-12'); $this->makeItem($lastWeek, ['product_id' => 11, 'amount' => '400.00']); $week = $this->getJson('/mini/report/purchase?preset=week')->assertOk()->json('data'); $this->assertSame('2026-08-17', $week['start_date']); $this->assertSame('2026-08-21', $week['end_date']); $this->assertSame('100.00', $week['total_amount']); $last = $this->getJson('/mini/report/purchase?preset=last_week')->assertOk()->json('data'); $this->assertSame('2026-08-10', $last['start_date']); $this->assertSame('2026-08-16', $last['end_date']); $this->assertSame('400.00', $last['total_amount']); } /** 不传参数默认本月 */ public function test_report_defaults_to_current_month(): void { Carbon::setTestNow('2026-08-21 12:00:00'); $store = StoreModel::factory()->create(); $this->actingAsMiniStore($store); $order = $this->makeOrder($store, '2026-08-02'); $this->makeItem($order, ['product_id' => 11, 'amount' => '66.00']); $data = $this->getJson('/mini/report/purchase')->assertOk()->json('data'); $this->assertSame('month', $data['preset']); $this->assertSame('2026-08-01', $data['start_date']); $this->assertSame('66.00', $data['total_amount']); } /** 门店数据隔离:仅统计本店订单 */ public function test_report_data_isolated_between_stores(): void { $storeA = StoreModel::factory()->create(); $orderA = $this->makeOrder($storeA, '2026-08-10'); $this->makeItem($orderA, ['product_id' => 11, 'amount' => '999.00']); $storeB = StoreModel::factory()->create(); $this->actingAsMiniStore($storeB); $data = $this->getJson('/mini/report/purchase?start_date=2026-08-01&end_date=2026-08-31') ->assertOk() ->json('data'); $this->assertSame('0.00', $data['total_amount']); $this->assertSame(0, $data['order_count']); $this->assertSame([], $data['items']); } /** 空周期:总额为 0、明细为空,不出现除零 */ public function test_report_empty_period_returns_zero(): void { $store = StoreModel::factory()->create(); $this->actingAsMiniStore($store); $data = $this->getJson('/mini/report/purchase?start_date=2026-01-01&end_date=2026-01-31') ->assertOk() ->json('data'); $this->assertSame('0.00', $data['total_amount']); $this->assertSame(0, $data['total_quantity']); $this->assertSame(0, $data['order_count']); $this->assertSame(0, $data['item_count']); $this->assertSame([], $data['items']); } /** 参数校验:非法 preset / 日期格式 / 结束早于开始 / 区间缺一侧 */ public function test_report_validates_params(): void { $store = StoreModel::factory()->create(); $this->actingAsMiniStore($store); $this->getJson('/mini/report/purchase?preset=year')->assertJsonPath('success', false); $this->getJson('/mini/report/purchase?start_date=2026/08/01&end_date=2026-08-31')->assertJsonPath('success', false); $this->getJson('/mini/report/purchase?start_date=2026-08-31&end_date=2026-08-01')->assertJsonPath('success', false); $this->getJson('/mini/report/purchase?start_date=2026-08-01')->assertJsonPath('success', false); $this->getJson('/mini/report/purchase?end_date=2026-08-31')->assertJsonPath('success', false); } }