[ '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_shop_candidates_scan_only_changed_rows_and_reconcile_deleted_names_at_night(): void { $db = DB::connection('business_mirror'); foreach (['ops_jst_orders', 'ops_jst_qmcust_orders'] as $table) { $db->getSchemaBuilder()->table($table, function (Blueprint $t): void { $t->dateTime('_mirror_synced_at')->nullable()->index(); }); } $this->insertOrder($db, 1, 'SO-A', '历史店铺'); $db->table('ops_jst_orders')->update(['_mirror_synced_at' => '2026-10-01 00:00:00']); $repository = $this->repository($db); $this->assertSame(['历史店铺'], $repository->refreshShopNamesCache()); $this->insertOrder($db, 2, 'SO-B', '新增店铺'); $db->table('ops_jst_orders')->where('o_id', 2)->update(['_mirror_synced_at' => '2026-10-19 12:00:00']); $db->enableQueryLog(); $this->assertSame(['历史店铺', '新增店铺'], $repository->refreshShopNamesCache()); $scans = array_values(array_filter($db->getQueryLog(), static fn ($q) => str_contains($q['query'], 'select distinct "shop_name" from "ops_jst_orders"'))); $this->assertCount(1, $scans); $this->assertStringContainsString('"_mirror_synced_at" >= ?', $scans[0]['query']); $this->assertSame('2026-10-01 00:00:00', $scans[0]['bindings'][1]); // A second write with the exact same timestamp must also be discovered. $this->insertOrder($db, 3, 'SO-C', '同秒新增'); $db->table('ops_jst_orders')->where('o_id', 3)->update(['_mirror_synced_at' => '2026-10-19 12:00:00']); $this->assertContains('同秒新增', $repository->refreshShopNamesCache()); $db->table('ops_jst_orders')->where('o_id', 1)->delete(); $this->assertNotContains('历史店铺', $repository->refreshShopNamesCache(true)); } public function test_upgrade_keeps_legacy_complete_candidates_and_reads_a_day_of_changes(): void { $db = DB::connection('business_mirror'); foreach (['ops_jst_orders', 'ops_jst_qmcust_orders'] as $table) { $db->getSchemaBuilder()->table($table, function (Blueprint $t): void { $t->dateTime('_mirror_synced_at')->nullable(); }); } Cache::put(OrderDataMirrorRepository::SHOP_NAMES_CACHE_KEY, ['历史店铺']); $this->insertOrder($db, 1, 'SO-A', '新增店铺'); $db->table('ops_jst_orders')->update(['_mirror_synced_at' => '2026-10-19 12:00:00']); $this->assertSame(['历史店铺', '新增店铺'], $this->repository($db)->refreshShopNamesCache()); } 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> $rows @return list */ 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> $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(); }); } }