Files
2026-08-21 12:05:31 +08:00

258 lines
11 KiB
PHP
Raw Permalink 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\StoreModel;
use App\Models\StoreOrderItemModel;
use App\Models\StoreOrderModel;
use Carbon\Carbon;
/**
* 小程序采购报表:周期区间推导(本周/上周/本月/上月/自定义)、采购总金额与单品累计金额/占比、
* 已取消与已删除订单剔除、门店数据隔离、参数校验
*/
class MiniReportTest extends ProcurementTestCase
{
/** 造一张指定日期/状态的订货单 */
private function makeOrder(StoreModel $store, string $date, int $status = StoreOrderModel::STATUS_COMPLETED): StoreOrderModel
{
return StoreOrderModel::factory()->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);
}
}