Files
xin-procurement/tests/Feature/StoreOrderTest.php
T
2026-09-05 19:40:52 +08:00

561 lines
27 KiB
PHP
Raw 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\BillModel;
use App\Models\CustomerLevelModel;
use App\Models\ProductModel;
use App\Models\PurchaseOrderModel;
use App\Models\StoreModel;
use App\Models\StoreOrderModel;
use Modules\SystemTool\Models\SysFileModel;
use Modules\SystemTool\Models\SysSiteConfigGroupModel;
use Modules\SystemTool\Models\SysSiteConfigItemsModel;
use Modules\SystemTool\Services\SysSiteConfigService;
/**
* 小程序下单:等级上浮价快照、服务端重算总价、取消限制、门店数据隔离
* (门店即用户:小程序端登录主体为 StoreModel
*/
class StoreOrderTest extends ProcurementTestCase
{
/**
* @return array{0: StoreModel, 1: ProductModel}
*/
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,
'cost_price' => $price,
]);
return [$store, $product];
}
/** 下单快照等级价,服务端重算单品金额与订单总价 */
public function test_place_order_snapshots_level_price_and_recalculates(): void
{
[$store, $product] = $this->makeStoreWithProduct('5.50');
$this->actingAsMiniStore($store);
$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] = $this->makeStoreWithProduct('5.00');
$this->actingAsMiniStore($store);
$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_place_order_prefills_reference_weight(): void
{
$level = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$byBox = ProductModel::factory()->create([
'status' => ProductModel::STATUS_ON,
'cost_price' => '5.00',
'spec' => '10斤/箱',
'unit' => '箱',
]);
$byJin = ProductModel::factory()->create([
'status' => ProductModel::STATUS_ON,
'cost_price' => '5.00',
'spec' => '散装',
'unit' => '斤',
]);
$this->actingAsMiniStore($store);
$this->postJson('/mini/order', [
'items' => [
['product_id' => $byBox->id, 'quantity' => 3],
['product_id' => $byJin->id, 'quantity' => 5],
],
])->assertOk()->assertJsonPath('success', true);
$order = StoreOrderModel::where('store_id', $store->id)->first();
$boxItem = $order->items->firstWhere('product_id', $byBox->id);
$jinItem = $order->items->firstWhere('product_id', $byJin->id);
$this->assertSame('30.000', (string) $boxItem->weight, '3 箱 × 10斤/箱');
$this->assertSame('5.000', (string) $jinItem->weight, '按斤计价:订货量即重量');
$this->assertSame('35.000', (string) $order->total_weight, '订单总重量 = 明细参考重量合计');
}
/** 门店绑定的客户等级被删除时拒绝下单 */
public function test_store_level_missing_rejected(): void
{
[$store, $product] = $this->makeStoreWithProduct('5.00');
CustomerLevelModel::whereKey($store->level_id)->delete();
$this->actingAsMiniStore($store);
$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] = $this->makeStoreWithProduct();
$this->actingAsMiniStore($store);
$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->refresh()->status);
// 已取消不可重复取消
$this->putJson("/mini/order/{$order->id}/cancel")->assertJsonPath('success', false);
}
/** 已汇总订单不可取消 */
public function test_summarized_order_cannot_be_cancelled(): void
{
[$store, $product] = $this->makeStoreWithProduct();
$this->actingAsMiniStore($store);
$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->refresh()->status);
}
/** 造一笔指定账单日期/状态的门店账单(默认总额 100.00 未支付) */
private function makeStoreBill(StoreModel $store, string $billDate, array $attributes = []): BillModel
{
return BillModel::create(array_merge([
'bill_no' => 'ZD' . random_int(100000000000, 999999999999),
'purchase_id' => PurchaseOrderModel::factory()->create()->id,
'store_id' => $store->id,
'bill_date' => $billDate,
'total_amount' => '100.00',
'status' => BillModel::STATUS_UNPAID,
], $attributes));
}
/** 回款周期校验:存在逾期未回款账单(含审核中)时禁止下单,结清后恢复 */
public function test_place_order_blocked_by_overdue_unpaid_bill(): void
{
$level = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->paymentCycle(7)->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '5.00']);
// 账单日 10 天前(应结算日=3 天前,已逾期)且未支付 → 拒绝
$this->makeStoreBill($store, now()->subDays(10)->toDateString());
$this->actingAsMiniStore($store);
$response = $this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]]);
$response->assertOk()->assertJsonPath('success', false);
$this->assertStringContainsString('已超过回款周期未回款', (string) $response->json('msg'));
$this->assertStringContainsString('¥100.00', (string) $response->json('msg'));
$this->assertSame(0, StoreOrderModel::count(), '拦截时不应生成订单');
// 审核中(已提交付款凭证)同样拦截
$this->makeStoreBill($store, now()->subDays(10)->toDateString(), ['payment_id' => 999]);
$blocked = $this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]]);
$blocked->assertJsonPath('success', false);
$this->assertStringContainsString('2 笔', (string) $blocked->json('msg'));
// 全部结清后恢复下单
BillModel::where('store_id', $store->id)->update(['status' => BillModel::STATUS_PAID]);
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]])
->assertJsonPath('success', true);
}
/** 回款周期校验:周期内账单与到期日当天均不拦截;回款周期 0 天=任何未回款账单(含当天)都拦截 */
public function test_place_order_allowed_within_payment_cycle(): void
{
$level = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->paymentCycle(7)->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '5.00']);
$this->actingAsMiniStore($store);
// 账单日 7 天前 → 应结算日=今天,尚未超过周期,允许下单
$this->makeStoreBill($store, now()->subDays(7)->toDateString());
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]])
->assertJsonPath('success', true);
// 回款周期 0 天:昨天的未回款账单拦截;当天的未回款账单同样拦截(立即结清口径)
$store->update(['payment_cycle_days' => 0]);
$this->makeStoreBill($store, now()->subDays(1)->toDateString());
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]])
->assertJsonPath('success', false);
BillModel::where('store_id', $store->id)->delete();
$this->makeStoreBill($store, now()->toDateString());
$response = $this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]]);
$response->assertJsonPath('success', false);
$this->assertStringContainsString('当日结清', (string) $response->json('msg'));
// 0 天周期下无未回款账单 → 正常下单
BillModel::where('store_id', $store->id)->update(['status' => BillModel::STATUS_PAID]);
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]])
->assertJsonPath('success', true);
}
/** 门店数据隔离:只能查看本店订单 */
public function test_order_data_isolated_between_stores(): void
{
[$storeA, $product] = $this->makeStoreWithProduct();
$this->actingAsMiniStore($storeA);
$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->actingAsMiniStore($storeB);
$this->getJson("/mini/order/{$orderOfA->id}")->assertJsonPath('success', false);
$this->getJson('/mini/order')->assertJsonPath('data.total', 0);
}
/** 列表输出:状态名 / 可取消标记 / 商品预览(前 3 条)与明细种数 */
public function test_order_list_outputs_status_name_and_item_preview(): void
{
$level = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$this->actingAsMiniStore($store);
$items = [];
foreach (['白菜', '土豆', '番茄', '黄瓜'] as $name) {
$product = ProductModel::factory()->create(['name' => $name, 'status' => ProductModel::STATUS_ON, 'cost_price' => '5.00']);
$items[] = ['product_id' => $product->id, 'quantity' => 2];
}
$this->postJson('/mini/order', ['items' => $items])->assertJsonPath('success', true);
$order = StoreOrderModel::where('store_id', $store->id)->first();
$row = collect($this->getJson('/mini/order')->assertOk()->json('data.data'))->firstWhere('id', $order->id);
$this->assertSame('待接单', $row['status_name']);
$this->assertTrue($row['can_cancel']);
$this->assertSame(4, $row['item_count']);
$this->assertCount(3, $row['items'], '预览仅输出前 3 条明细,完整明细走详情接口');
$this->assertSame('白菜', $row['items'][0]['product_name']);
$this->assertSame(2, $row['items'][0]['quantity']);
// 已接单后状态名变化且不可取消
$order->update(['status' => StoreOrderModel::STATUS_SUMMARIZED]);
$row = collect($this->getJson('/mini/order')->json('data.data'))->firstWhere('id', $order->id);
$this->assertSame('已接单', $row['status_name']);
$this->assertFalse($row['can_cancel']);
}
/** 订货日期区间筛选(配合 /order/summary 周期下钻) */
public function test_order_list_filters_by_order_date_range(): void
{
[$store, $product] = $this->makeStoreWithProduct();
$this->actingAsMiniStore($store);
foreach ([1, 1, 1] as $qty) {
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => $qty]]])
->assertJsonPath('success', true);
}
$orders = StoreOrderModel::where('store_id', $store->id)->orderBy('id')->get();
$orders[0]->update(['order_date' => '2026-08-01']);
$orders[1]->update(['order_date' => '2026-08-05']);
$orders[2]->update(['order_date' => '2026-08-10']);
$this->getJson('/mini/order?start_date=2026-08-03')->assertJsonPath('data.total', 2);
$this->getJson('/mini/order?start_date=2026-08-03&end_date=2026-08-06')->assertJsonPath('data.total', 1);
$this->getJson('/mini/order?end_date=2026-08-06')->assertJsonPath('data.total', 2);
}
/** 非法筛选参数被拦截(状态枚举 / 分页上限 / 日期区间倒置) */
public function test_order_list_rejects_invalid_filters(): void
{
[$store] = $this->makeStoreWithProduct();
$this->actingAsMiniStore($store);
$this->getJson('/mini/order?status=7')->assertJsonPath('success', false);
$this->getJson('/mini/order?pageSize=100')->assertJsonPath('success', false);
$this->getJson('/mini/order?start_date=2026-08-10&end_date=2026-08-01')->assertJsonPath('success', false);
}
/** 造一笔指定状态的订单(商品 5.00 × 2 = 10.00 */
private function makeOrderWithStatus(int $status): StoreOrderModel
{
[$store, $product] = $this->makeStoreWithProduct('5.00');
$this->actingAsMiniStore($store);
$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->refresh()->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->actingAsMiniStore(StoreModel::find($order->store_id));
$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->refresh()->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);
}
/** 手动流转:仅保留接单与取消(待接单 → 已接单/已取消) */
public function test_manual_status_flow_only_accept_and_cancel(): void
{
$order = $this->makeOrderWithStatus(StoreOrderModel::STATUS_PENDING);
$this->actingAsSysUser();
$this->putJson("/order/store/{$order->id}/status", ['status' => StoreOrderModel::STATUS_SUMMARIZED])
->assertJsonPath('success', true);
$this->assertSame(StoreOrderModel::STATUS_SUMMARIZED, $order->refresh()->status);
$order2 = $this->makeOrderWithStatus(StoreOrderModel::STATUS_PENDING);
$this->actingAsSysUser();
$this->putJson("/order/store/{$order2->id}/status", ['status' => StoreOrderModel::STATUS_CANCELLED])
->assertJsonPath('success', true);
$this->assertSame(StoreOrderModel::STATUS_CANCELLED, $order2->refresh()->status);
}
/** 手动流转:配送中/已完成等目标状态被拒绝(由采购/账单链路自动推进) */
public function test_manual_status_flow_rejects_delivery_and_completion(): void
{
foreach ([StoreOrderModel::STATUS_DISTRIBUTION, StoreOrderModel::STATUS_COMPLETED] as $target) {
$order = $this->makeOrderWithStatus(StoreOrderModel::STATUS_DELIVERING);
$this->actingAsSysUser();
$this->putJson("/order/store/{$order->id}/status", ['status' => $target])
->assertJsonPath('success', false);
$this->assertSame(StoreOrderModel::STATUS_DELIVERING, $order->refresh()->status);
}
// 已接单不能手动转采购中(须经生成采购单)
$order = $this->makeOrderWithStatus(StoreOrderModel::STATUS_SUMMARIZED);
$this->actingAsSysUser();
$this->putJson("/order/store/{$order->id}/status", ['status' => StoreOrderModel::STATUS_DELIVERING])
->assertJsonPath('success', false);
}
/** 批量流转:仅接单/取消可选,配送/完成目标整批校验拒绝 */
public function test_batch_status_only_accept_and_cancel(): void
{
$pending1 = $this->makeOrderWithStatus(StoreOrderModel::STATUS_PENDING);
$pending2 = $this->makeOrderWithStatus(StoreOrderModel::STATUS_PENDING);
$this->actingAsSysUser();
// 批量接单
$this->putJson('/order/store/batchStatus', [
'ids' => [$pending1->id, $pending2->id],
'status' => StoreOrderModel::STATUS_SUMMARIZED,
])->assertJsonPath('success', true);
$this->assertSame(StoreOrderModel::STATUS_SUMMARIZED, $pending1->refresh()->status);
$this->assertSame(StoreOrderModel::STATUS_SUMMARIZED, $pending2->refresh()->status);
// 批量完成被拒绝(目标状态校验拦截)
$this->putJson('/order/store/batchStatus', [
'ids' => [$pending1->id],
'status' => StoreOrderModel::STATUS_COMPLETED,
])->assertJsonPath('success', false);
// 已接单订单不可批量取消(仅待接单可取消),整批中止
$this->putJson('/order/store/batchStatus', [
'ids' => [$pending1->id],
'status' => StoreOrderModel::STATUS_CANCELLED,
])->assertJsonPath('success', false);
$this->assertSame(StoreOrderModel::STATUS_SUMMARIZED, $pending1->refresh()->status);
}
/** 写入下单时段业务配置并刷新缓存(value 传 null 表示该项不配置) */
private function setOrderTimeWindow(?string $start, ?string $end): void
{
$group = SysSiteConfigGroupModel::create(['title' => '业务配置', 'key' => 'services', 'remark' => '']);
foreach (['order_time_start' => $start, 'order_time_end' => $end] as $key => $value) {
if ($value === null) {
continue;
}
SysSiteConfigItemsModel::create([
'group_id' => $group->id,
'key' => $key,
'title' => $key,
'describe' => '',
'values' => $value,
'type' => 'Input',
'options' => '',
'sort' => 0,
]);
}
SysSiteConfigService::refreshSiteConfig();
}
/** 截单时间:下单时段外禁止下单,时段内正常下单 */
public function test_place_order_blocked_outside_order_time_window(): void
{
[$store, $product] = $this->makeStoreWithProduct('5.00');
$this->setOrderTimeWindow('08:00', '18:00');
$this->actingAsMiniStore($store);
// 20:00 已截单 → 拒绝且不生成订单
$this->travelTo('2026-09-05 20:00:00');
$response = $this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]]);
$response->assertOk()->assertJsonPath('success', false);
$this->assertStringContainsString('不在下单时段内', (string) $response->json('msg'));
$this->assertStringContainsString('08:00 - 18:00', (string) $response->json('msg'));
$this->assertSame(0, StoreOrderModel::count());
// 边界:08:00 开始时间与 18:00 截单时间均可下单
$this->travelTo('2026-09-05 08:00:00');
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]])
->assertJsonPath('success', true);
$this->travelTo('2026-09-05 18:00:00');
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]])
->assertJsonPath('success', true);
$this->assertSame(2, StoreOrderModel::count());
}
/** 截单时间:跨天时段(开始时间晚于截单时间,如 20:00-次日06:00 */
public function test_place_order_cross_midnight_time_window(): void
{
[$store, $product] = $this->makeStoreWithProduct('5.00');
$this->setOrderTimeWindow('20:00', '06:00');
$this->actingAsMiniStore($store);
// 当晚 22:00 与 次日 05:00 均在时段内
$this->travelTo('2026-09-05 22:00:00');
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]])
->assertJsonPath('success', true);
$this->travelTo('2026-09-06 05:00:00');
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]])
->assertJsonPath('success', true);
// 中午 12:00 不在时段内 → 拒绝
$this->travelTo('2026-09-06 12:00:00');
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]])
->assertJsonPath('success', false);
$this->assertSame(2, StoreOrderModel::count());
}
/** 截单时间:只配置截单时间时按单边限制(截止后不能下单);格式非法按未配置处理 */
public function test_place_order_only_cutoff_time_and_invalid_config(): void
{
[$store, $product] = $this->makeStoreWithProduct('5.00');
$this->setOrderTimeWindow(null, '18:00');
$this->actingAsMiniStore($store);
$this->travelTo('2026-09-05 17:59:00');
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]])
->assertJsonPath('success', true);
$this->travelTo('2026-09-05 18:01:00');
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]])
->assertJsonPath('success', false);
// 非法格式不拦截(避免误配置导致全天无法下单)
SysSiteConfigItemsModel::query()->update(['values' => 'abc']);
SysSiteConfigService::refreshSiteConfig();
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 1]]])
->assertJsonPath('success', true);
}
/** 订单列表商品预览:附带首图/规格/单位供小程序端展示 */
public function test_order_list_preview_includes_image_spec_and_unit(): void
{
$file = SysFileModel::create([
'group_id' => 1,
'disk' => 'local',
'channel' => 10,
'file_type' => 10,
'file_name' => '白菜.jpg',
'file_path' => 'product/baicai.jpg',
'file_size' => 1024,
'file_ext' => 'jpg',
'uploader_id' => 1,
]);
$level = CustomerLevelModel::factory()->create();
$store = StoreModel::factory()->create(['level_id' => $level->id]);
$product = ProductModel::factory()->create([
'status' => ProductModel::STATUS_ON,
'spec' => '30斤/筐',
'unit' => '筐',
'image_ids' => (string) $file->id,
'cost_price' => '5.00',
]);
$this->actingAsMiniStore($store);
$this->postJson('/mini/order', ['items' => [['product_id' => $product->id, 'quantity' => 2]]])
->assertJsonPath('success', true);
$rows = $this->getJson('/mini/order')->assertOk()->json('data.data');
$preview = $rows[0]['items'][0];
$this->assertSame($product->name, $preview['product_name']);
$this->assertSame('30斤/筐', $preview['product_spec'], '预览附规格');
$this->assertSame('筐', $preview['unit'], '预览附单位');
$this->assertStringContainsString('baicai.jpg', (string) $preview['image'], '预览附首图URL');
$this->assertArrayNotHasKey('image_ids', $preview, 'image_ids 解析后不输出');
}
}