CaiWuXiTong/tests/Unit/EcommerceFinance/OrderDataMirrorRepositoryTest.php
renshen c4a976751d 修复订单售后页面一直转圈:店铺候选改成定时预热
上一次把店铺候选的完整计算放在 app()->terminating() 里,但服务器是 mod_php,
响应要等整个请求(含终止回调)结束才算完成,浏览器就一直转圈、侧边栏停在骨架屏。
线上实测:发布后第一个打开订单售后费用明细的人,页面会卡两分钟以上。

改成:页面只返回毫秒级的店铺目录兜底,完整候选(目录 ∪ 订单镜像店铺)由新增的
order-data:warm-shop-candidates 每小时预热一次,并在发布脚本的 cron 里注册。
2026-10-07 14:21:36 +08:00

461 lines
24 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\Unit\EcommerceFinance;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Mockery;
use Modules\EcommerceFinance\Repositories\GenericBusinessMirrorRepository;
use Modules\EcommerceFinance\Repositories\OrderDataMirrorRepository;
use Modules\EcommerceFinance\Services\BusinessMirrorDatasetRegistry;
use Tests\TestCase;
class OrderDataMirrorRepositoryTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
config(['database.connections.business_mirror' => [
'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '',
]]);
DB::purge('business_mirror');
$this->createTables();
}
public function test_after_sales_reads_complete_rows_from_shared_mirror(): void
{
$db = DB::connection('business_mirror');
$db->table('ops_jst_orders')->insert([
'o_id' => 1, 'so_id' => 'SO-1', 'pay_date' => '2026-08-01 08:00:00',
'end_time' => '2026-08-02 12:00:00', 'send_date' => '2026-08-02 10:00:00',
'order_date' => '2026-08-01 09:00:00', 'shop_name' => '京东店铺',
'shop_id' => 'SHOP-1', 'shop_site' => 'jd', 'type' => '普通订单',
'labels' => '正常', 'wms_co_id' => 'WH-1', 'l_id' => 'EXP-1',
'logistics_company' => '物流', 'receiver_state' => '江苏', 'receiver_city' => '常州',
'creator_name' => '销售员', 'free_amount' => 2, 'freight' => 5,
]);
$db->table('ops_jst_order_item')->insert([
'oi_id' => 11, 'o_id' => 1, 'outer_oi_id' => 'SUB-1', 'sku_id' => 'SKU-1',
'i_id' => 'STYLE-1', 'shop_sku_id' => 'SHOP-SKU-1', 'shop_i_id' => '1001',
'properties_value' => '黑色', 'name' => '线上商品', 'qty' => 2,
'amount' => 100, 'buyer_paid_amount' => 95, 'seller_income_amount' => 98,
'is_gift' => 0, 'src_combine_sku_id' => null, 'send_warehouse' => 'WH-1',
]);
$db->table('ops_jst_sku')->insert([
'autoid' => 1, 'sku_type' => 'normal', 'sku_id' => 'SKU-1', 'brand' => '品牌A', 'category' => '家具', 'vc_name' => '桌子',
'cost_price' => 20, 'name' => '镜像商品', 'supplier_id' => 'SUP-1', 'supplier_name' => '供应商A',
]);
$db->table('ops_jst_warehouse_info')->insert([
'wms_co_id' => 'WH-1', 'name' => '主仓', 'remark1' => '运营云仓',
]);
$db->table('ops_jst_return_refund_info')->insert([
'as_id' => 21, 'outer_as_id' => 'AF-1', 'o_id' => 1,
'as_date' => '2026-08-02 12:00:00', 'type' => '退货退款',
'question_type' => '质量问题', 'labels' => '售后', 'refund' => 50, 'free_amount' => 3,
]);
$db->table('ops_jst_return_refund_item')->insert([
'as_id' => 21, 'outer_oi_id' => 'SUB-1', 'sku_id' => 'SKU-1', 'type' => '退货',
'qty' => 1, 'amount' => 50, 'r_qty' => 1, 'shop_amount' => 50, 'defective_qty' => 1,
]);
$mirror = Mockery::mock(GenericBusinessMirrorRepository::class);
$mirror->shouldReceive('connection')->andReturn($db);
$repository = new OrderDataMirrorRepository(
Mockery::mock(BusinessMirrorDatasetRegistry::class), $mirror,
);
$result = $repository->afterSales([
'from' => '2026-08-01', 'to' => '2026-08-02',
'order_no' => '', 'shop_name' => '', 'sku_id' => '',
], 1, 25);
$this->assertCount(1, $result['rows']);
$row = $result['rows'][0];
$this->assertSame('SO-1', $row['online_order_no']);
$this->assertSame('AF-1', $row['after_sales_no']);
$this->assertSame(40.0, $row['product_cost']);
$this->assertSame(1.0, $row['post_return_quantity']);
$this->assertSame('https://item.jd.com/1001.html', $row['product_link']);
$this->assertNull($row['account_500112']);
$this->assertNull($row['account_8001201']);
}
public function test_promotion_expenses_use_the_bill_item_without_scanning_the_large_sku_map(): void
{
$db = DB::connection('business_mirror');
$db->table('ops_item_sales_cost_ratio')->insert([
'stat_date' => '2026-08-09 00:00:00', 'shop_name' => '京东店铺',
'item_id' => 'ITEM-1', 'push_fee' => 9.05,
]);
$mirror = Mockery::mock(GenericBusinessMirrorRepository::class);
$mirror->shouldReceive('connection')->andReturn($db);
$repository = new OrderDataMirrorRepository(
Mockery::mock(BusinessMirrorDatasetRegistry::class), $mirror,
);
$result = $repository->promotionExpenses([
'date' => '2026-08-09', 'shop_name' => '', 'sku_id' => '',
], 1, 25);
$this->assertSame('2026-08-09', $result['date']);
$this->assertNull($result['rows'][0]['product_code']);
$this->assertSame('ITEM-1', $result['rows'][0]['shop_product_code']);
$this->assertSame(9.05, $result['rows'][0]['promotion_fee']);
}
public function test_promotion_readiness_does_not_wait_for_the_unused_sku_map(): void
{
$datasets = app(BusinessMirrorDatasetRegistry::class);
$mirror = Mockery::mock(GenericBusinessMirrorRepository::class);
$mirror->shouldReceive('connection')->andReturn(DB::connection('business_mirror'));
$mirror->shouldReceive('ready')->once()->andReturnTrue();
$this->assertTrue((new OrderDataMirrorRepository($datasets, $mirror))->promotionReady());
}
public function test_after_sales_filters_by_multiple_shop_names(): void
{
$db = DB::connection('business_mirror');
$this->insertOrder($db, 1, 'SO-A', '拼多多-甲店');
$this->insertOrder($db, 2, 'SO-B', '京东-乙店');
$this->insertOrder($db, 3, 'SO-C', '抖音-丙店');
$repository = $this->repository($db);
$base = ['from' => '2026-08-01', 'to' => '2026-08-02', 'order_no' => '', 'shop_name' => '', 'sku_id' => ''];
$both = $repository->afterSales(array_replace($base, ['shop_names' => ['甲店', '乙店']]), 1, 25);
$this->assertSame(['京东-乙店', '拼多多-甲店'], $this->sortedShops($both['rows']));
$single = $repository->afterSales(array_replace($base, ['shop_names' => ['甲店']]), 1, 25);
$this->assertSame(['拼多多-甲店'], $this->sortedShops($single['rows']));
// 兼容旧的单值 shop_name 过滤。
$legacy = $repository->afterSales(array_replace($base, ['shop_name' => '丙店']), 1, 25);
$this->assertSame(['抖音-丙店'], $this->sortedShops($legacy['rows']));
$none = $repository->afterSales(array_replace($base, ['shop_names' => ['没有这个店铺']]), 1, 25);
$this->assertSame([], $none['rows']);
}
public function test_shop_names_lists_distinct_shops_from_the_order_mirror(): void
{
$db = DB::connection('business_mirror');
$this->insertOrder($db, 1, 'SO-A', '拼多多-甲店');
$this->insertOrder($db, 2, 'SO-B', '拼多多-甲店');
$this->insertOrder($db, 3, 'SO-C', '京东-乙店');
$names = $this->repository($db)->shopNames();
sort($names);
// 冷缓存只返回毫秒级的店铺目录,不能为了下拉候选去扫订单宽表。
$db->table('ops_jst_shop_info')->insert(['shop_id' => 1, 'shop_name' => '抖音-丙店']);
$directory = $this->repository($db)->shopNames();
sort($directory);
$this->assertSame(['抖音-丙店'], $directory);
// 完整候选由定时预热写入:店铺目录 ∪ 订单镜像里出现过的店铺。
$names = $this->repository($db)->refreshShopNamesCache();
sort($names);
$this->assertSame(['京东-乙店', '抖音-丙店', '拼多多-甲店'], $names);
}
public function test_after_sales_includes_taobao_orders_and_their_refunds(): void
{
$db = DB::connection('business_mirror');
$this->insertOrder($db, 1, 'SO-A', '拼多多-甲店');
// 淘系(天猫)订单与售后走另一套表,必须一起出现在报表里。
$db->table('ops_jst_qmcust_orders')->insert([
'Id' => 9001, 'o_id' => 9325277, 'so_id' => '5127742981080004116',
'order_date' => '2026-09-11 13:22:54', 'pay_date' => '2026-09-11 13:23:08',
'send_date' => '2026-09-15 10:00:00',
'shop_name' => '天猫-卓禾家具旗舰店', 'shop_id' => '18525257', 'shop_site' => '淘宝天猫',
'type' => '普通订单', 'wms_co_id' => 'WH-1',
]);
$db->table('ops_jst_qmcust_order_items')->insert([
'Id' => 8001, 'oi_id' => 9380102, 'o_id' => 9325277,
'outer_oi_id' => '5127742981080004116', 'sku_id' => 'SKU-TB-1',
'i_id' => 'STYLE-TB-1', 'name' => '天猫商品', 'qty' => 1, 'amount' => 88.0,
]);
$db->table('ops_jst_qmcust_refunds')->insert([
'as_id' => 7001, 'o_id' => 9325277, 'outer_as_id' => 'AF-TB-1',
'as_date' => '2026-09-20 10:00:00', 'type' => '退货退款',
'question_type' => '质量问题', 'labels' => '售后', 'refund' => 30,
]);
$db->table('ops_jst_qmcust_refund_items')->insert([
'asi_id' => 6001, 'as_id' => 7001, 'outer_oi_id' => '5127742981080004116',
'sku_id' => 'SKU-TB-1', 'type' => '退货', 'qty' => 1, 'amount' => 30, 'r_qty' => 1,
]);
$result = $this->repository($db)->afterSales([
'from' => '2026-09-01', 'to' => '2026-09-30',
'order_no' => '', 'shop_name' => '', 'sku_id' => '',
], 1, 25);
$rows = collect($result['rows'])->keyBy('shop_name');
$this->assertTrue($rows->has('天猫-卓禾家具旗舰店'));
$taobao = $rows->get('天猫-卓禾家具旗舰店');
$this->assertSame('5127742981080004116', $taobao['online_order_no']);
$this->assertSame('AF-TB-1', $taobao['after_sales_no']);
$this->assertSame('质量问题', $taobao['question_type']);
$this->assertSame(1.0, $taobao['post_return_quantity']);
$this->assertArrayNotHasKey('source_branch', $taobao);
// 店铺候选也要包含淘系店铺(完整候选由定时预热写入)。
$names = $this->repository($db)->refreshShopNamesCache();
$this->assertContains('天猫-卓禾家具旗舰店', $names);
}
public function test_after_sales_accepts_batch_order_numbers_and_sku_codes(): void
{
$db = DB::connection('business_mirror');
$this->insertOrder($db, 1, 'SO-2026-0001', '拼多多-甲店');
$this->insertOrder($db, 2, 'SO-2026-0002', '拼多多-乙店');
$this->insertOrder($db, 3, 'SO-2026-0003', '拼多多-丙店');
$repository = $this->repository($db);
$base = ['from' => '', 'to' => '', 'shop_name' => '', 'sku_id' => ''];
// 批量订单号:换行、逗号、空格分隔都能识别。
$batch = $repository->afterSales(
array_replace($base, ['order_no' => "SO-2026-0001\nSO-2026-0003"]),
1,
25,
);
$this->assertSame(['SO-2026-0001', 'SO-2026-0003'], $this->sortedOnlineOrders($batch['rows']));
$comma = $repository->afterSales(
array_replace($base, ['order_no' => 'SO-2026-0002, SO-2026-0003']),
1,
25,
);
$this->assertSame(['SO-2026-0002', 'SO-2026-0003'], $this->sortedOnlineOrders($comma['rows']));
// 批量商品编码:每个编码在 商品/款式/店铺编码 三个字段里匹配,多组取并集。
$skus = $repository->afterSales(
array_replace($base, ['order_no' => '', 'sku_id' => 'SKU-1, SKU-3']),
1,
25,
);
$this->assertSame(['SO-2026-0001', 'SO-2026-0003'], $this->sortedOnlineOrders($skus['rows']));
}
public function test_batch_order_numbers_keep_their_exact_text_form(): void
{
$db = DB::connection('business_mirror');
$this->insertOrder($db, 1, '0001234', '拼多多-甲店');
$this->insertOrder($db, 2, '1234', '拼多多-乙店');
$result = $this->repository($db)->afterSales([
'from' => '', 'to' => '', 'order_no' => "0001234\n9999999", 'shop_name' => '', 'sku_id' => '',
], 1, 25);
// 纯数字单号不能被 PHP 当数组键转成 int,否则前导零的单号会串到别的订单。
$this->assertSame(['0001234'], $this->sortedOnlineOrders($result['rows']));
}
public function test_promotion_expenses_filters_by_multiple_shops(): void
{
$db = DB::connection('business_mirror');
foreach ([['天猫_甲店', 'ITEM-1', 5.5], ['京东_乙店', 'ITEM-2', 3.0], ['拼多多_丙店', 'ITEM-3', 1.0]] as [$shop, $item, $fee]) {
$db->table('ops_item_sales_cost_ratio')->insert([
'stat_date' => '2026-08-13 00:00:00', 'shop_name' => $shop, 'item_id' => $item, 'push_fee' => $fee,
]);
}
$repository = $this->repository($db);
$all = $repository->promotionExpenses(['date' => '2026-08-13', 'shop_names' => []], 1, 25);
$this->assertSame(['天猫_甲店', '京东_乙店', '拼多多_丙店'], array_column($all['rows'], 'shop_name'));
$multi = $repository->promotionExpenses(
['date' => '2026-08-13', 'shop_names' => ['天猫_甲店', '拼多多_丙店']],
1,
25,
);
$this->assertSame(['天猫_甲店', '拼多多_丙店'], array_column($multi['rows'], 'shop_name'));
// 兼容原来的单值 shop_name。
$legacy = $repository->promotionExpenses(['date' => '2026-08-13', 'shop_name' => '京东_乙店'], 1, 25);
$this->assertSame(['京东_乙店'], array_column($legacy['rows'], 'shop_name'));
// 商品编码同样支持批量。
$batch = $repository->promotionExpenses(
['date' => '2026-08-13', 'shop_names' => [], 'sku_id' => "ITEM-1\nITEM-3"],
1,
25,
);
$this->assertSame(['天猫_甲店', '拼多多_丙店'], array_column($batch['rows'], 'shop_name'));
}
public function test_promotion_shop_names_lists_the_shops_of_the_selected_date(): void
{
$db = DB::connection('business_mirror');
foreach ([['天猫_甲店', '2026-08-13'], ['京东_乙店', '2026-08-13'], ['天猫_甲店', '2026-08-12']] as [$shop, $date]) {
$db->table('ops_item_sales_cost_ratio')->insert([
'stat_date' => $date . ' 00:00:00', 'shop_name' => $shop, 'item_id' => 'ITEM-1', 'push_fee' => 1.0,
]);
}
$repository = $this->repository($db);
$this->assertSame(['京东_乙店', '天猫_甲店'], $repository->promotionShopNames('2026-08-13'));
$this->assertSame([], $repository->promotionShopNames(''));
}
public function test_after_sales_order_number_is_resolved_to_candidates_before_filtering(): void
{
$db = DB::connection('business_mirror');
$this->insertOrder($db, 1, 'SO-2026-0001', '拼多多-甲店');
$this->insertOrder($db, 2, 'SO-2026-0002', '拼多多-乙店');
$repository = $this->repository($db);
$base = ['from' => '', 'to' => '', 'shop_name' => '', 'sku_id' => ''];
// 完整线上订单号:只返回这一单。
$exact = $repository->afterSales(array_replace($base, ['order_no' => 'SO-2026-0001']), 1, 25);
$this->assertSame(['SO-2026-0001'], $this->sortedOnlineOrders($exact['rows']));
// 子订单号(明细表的 outer_oi_id)同样能定位到订单。
$sub = $repository->afterSales(array_replace($base, ['order_no' => 'SUB-2']), 1, 25);
$this->assertSame(['SO-2026-0002'], $this->sortedOnlineOrders($sub['rows']));
// 查不到的单号直接返回空,不再对整张宽表做包含匹配。
$missing = $repository->afterSales(array_replace($base, ['order_no' => 'SO-9999-0000']), 1, 25);
$this->assertSame([], $missing['rows']);
}
public function test_after_sales_short_order_fragment_keeps_contains_match(): void
{
$db = DB::connection('business_mirror');
$this->insertOrder($db, 1, 'SO-2026-0001', '拼多多-甲店');
$this->insertOrder($db, 2, 'SO-2026-0002', '拼多多-乙店');
$result = $this->repository($db)->afterSales([
'from' => '', 'to' => '', 'order_no' => 'SO-', 'shop_name' => '', 'sku_id' => '',
], 1, 25);
$this->assertSame(['SO-2026-0001', 'SO-2026-0002'], $this->sortedOnlineOrders($result['rows']));
}
/** @param array<int,array<string,mixed>> $rows @return list<string> */
private function sortedOnlineOrders(array $rows): array
{
$numbers = array_map(static fn (array $row): string => (string) ($row['online_order_no'] ?? ''), $rows);
sort($numbers);
return array_values($numbers);
}
private function repository(\Illuminate\Database\ConnectionInterface $db): OrderDataMirrorRepository
{
$mirror = Mockery::mock(GenericBusinessMirrorRepository::class);
$mirror->shouldReceive('connection')->andReturn($db);
return new OrderDataMirrorRepository(Mockery::mock(BusinessMirrorDatasetRegistry::class), $mirror);
}
private function insertOrder(\Illuminate\Database\ConnectionInterface $db, int $id, string $soId, string $shopName): void
{
$db->table('ops_jst_orders')->insert([
'o_id' => $id, 'so_id' => $soId, 'order_date' => '2026-08-01 09:00:00',
'shop_name' => $shopName, 'shop_id' => 'SHOP-' . $id,
]);
$db->table('ops_jst_order_item')->insert([
'oi_id' => 100 + $id, 'o_id' => $id, 'outer_oi_id' => 'SUB-' . $id,
'sku_id' => 'SKU-' . $id, 'qty' => 1, 'amount' => 10,
]);
}
/** @param array<int,array<string,mixed>> $rows */
private function sortedShops(array $rows): array
{
$names = array_map(static fn (array $row): string => (string) ($row['shop_name'] ?? ''), $rows);
sort($names);
return $names;
}
private function createTables(): void
{
$schema = Schema::connection('business_mirror');
$schema->create('ops_jst_orders', function (Blueprint $t): void {
$t->integer('o_id'); $t->string('so_id')->nullable(); $t->dateTime('pay_date')->nullable();
$t->dateTime('end_time')->nullable(); $t->dateTime('send_date')->nullable(); $t->dateTime('order_date');
$t->string('shop_name'); $t->string('shop_id')->nullable(); $t->string('shop_site')->nullable();
$t->string('type')->nullable(); $t->string('labels')->nullable(); $t->string('wms_co_id')->nullable();
$t->string('l_id')->nullable(); $t->string('logistics_company')->nullable();
$t->string('receiver_state')->nullable(); $t->string('receiver_city')->nullable();
$t->string('creator_name')->nullable(); $t->decimal('free_amount')->nullable(); $t->decimal('freight')->nullable();
});
$schema->create('ops_jst_order_item', function (Blueprint $t): void {
$t->integer('oi_id'); $t->integer('o_id'); $t->string('outer_oi_id')->nullable();
$t->string('sku_id'); $t->string('i_id')->nullable(); $t->string('shop_sku_id')->nullable();
$t->string('shop_i_id')->nullable(); $t->string('properties_value')->nullable(); $t->string('name')->nullable();
$t->decimal('qty')->nullable(); $t->decimal('amount')->nullable(); $t->decimal('buyer_paid_amount')->nullable();
$t->decimal('seller_income_amount')->nullable(); $t->integer('is_gift')->nullable();
$t->string('src_combine_sku_id')->nullable(); $t->string('send_warehouse')->nullable();
});
$schema->create('ops_jst_sku', function (Blueprint $t): void {
$t->integer('autoid'); $t->string('sku_type'); $t->string('sku_id'); $t->string('brand')->nullable(); $t->string('category')->nullable();
$t->string('vc_name')->nullable(); $t->decimal('cost_price')->nullable(); $t->string('name')->nullable();
$t->string('supplier_id')->nullable(); $t->string('supplier_name')->nullable();
});
$schema->create('ops_jst_warehouse_info', function (Blueprint $t): void {
$t->string('wms_co_id'); $t->string('name')->nullable(); $t->string('remark1')->nullable();
});
$schema->create('ops_jst_return_refund_info', function (Blueprint $t): void {
$t->integer('as_id'); $t->string('outer_as_id')->nullable(); $t->integer('o_id');
$t->dateTime('as_date'); $t->string('type')->nullable(); $t->string('question_type')->nullable();
$t->string('labels')->nullable(); $t->decimal('refund')->nullable(); $t->decimal('free_amount')->nullable();
});
$schema->create('ops_jst_return_refund_item', function (Blueprint $t): void {
$t->integer('as_id'); $t->string('outer_oi_id')->nullable(); $t->string('sku_id')->nullable();
$t->string('type')->nullable(); $t->decimal('qty')->nullable(); $t->decimal('amount')->nullable();
$t->decimal('r_qty')->nullable(); $t->decimal('shop_amount')->nullable(); $t->decimal('defective_qty')->nullable();
});
$schema->create('ops_item_sales_cost_ratio', function (Blueprint $t): void {
$t->dateTime('stat_date'); $t->string('shop_name'); $t->string('item_id'); $t->decimal('push_fee');
});
$schema->create('ops_jst_sku_map', function (Blueprint $t): void {
$t->string('shop_i_id'); $t->integer('shop_id'); $t->string('sku_id')->nullable();
$t->string('i_id')->nullable(); $t->string('shop_sku_id')->nullable();
$t->string('properties_value')->nullable(); $t->dateTime('modified')->nullable();
});
$schema->create('ops_jst_shop_info', function (Blueprint $t): void {
$t->integer('shop_id'); $t->string('shop_name');
});
// 淘系(天猫/淘宝)订单与售后:与 jst_Orders 系列并列的第二套来源。
$schema->create('ops_jst_qmcust_orders', function (Blueprint $t): void {
$t->integer('Id'); $t->integer('o_id'); $t->string('so_id')->nullable(); $t->dateTime('order_date');
$t->dateTime('pay_date')->nullable(); $t->dateTime('end_time')->nullable(); $t->dateTime('send_date')->nullable();
$t->string('shop_name'); $t->string('shop_id')->nullable(); $t->string('shop_site')->nullable();
$t->string('type')->nullable(); $t->string('labels')->nullable(); $t->string('wms_co_id')->nullable();
$t->string('l_id')->nullable(); $t->string('logistics_company')->nullable();
$t->string('receiver_state')->nullable(); $t->string('receiver_city')->nullable();
$t->string('creator_name')->nullable(); $t->decimal('free_amount')->nullable(); $t->decimal('freight')->nullable();
});
$schema->create('ops_jst_qmcust_order_items', function (Blueprint $t): void {
$t->integer('Id'); $t->integer('oi_id'); $t->integer('o_id'); $t->string('outer_oi_id')->nullable();
$t->string('sku_id'); $t->string('i_id')->nullable(); $t->string('shop_sku_id')->nullable();
$t->string('shop_i_id')->nullable(); $t->string('properties_value')->nullable(); $t->string('name')->nullable();
$t->decimal('qty')->nullable(); $t->decimal('amount')->nullable(); $t->decimal('buyer_paid_amount')->nullable();
$t->decimal('seller_income_amount')->nullable(); $t->integer('is_gift')->nullable();
$t->string('src_combine_sku_id')->nullable();
});
$schema->create('ops_jst_qmcust_refunds', function (Blueprint $t): void {
$t->integer('as_id'); $t->integer('o_id'); $t->string('outer_as_id')->nullable();
$t->dateTime('as_date'); $t->string('type')->nullable(); $t->string('question_type')->nullable();
$t->string('labels')->nullable(); $t->decimal('refund')->nullable(); $t->decimal('free_amount')->nullable();
});
$schema->create('ops_jst_qmcust_refund_items', function (Blueprint $t): void {
$t->integer('asi_id'); $t->integer('as_id'); $t->string('outer_oi_id')->nullable();
$t->string('sku_id')->nullable(); $t->string('type')->nullable(); $t->decimal('qty')->nullable();
$t->decimal('amount')->nullable(); $t->decimal('r_qty')->nullable(); $t->decimal('shop_amount')->nullable();
$t->decimal('defective_qty')->nullable();
});
}
}