258 lines
11 KiB
PHP
258 lines
11 KiB
PHP
<?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);
|
||
}
|
||
}
|