CaiWuXiTong/tests/Unit/EcommerceFinance/OrderDataMirrorRepositoryTest.php

519 lines
28 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\Cache;
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']);
\Illuminate\Support\Facades\Cache::put(OrderDataMirrorRepository::SHOP_NAMES_CACHE_KEY, ['拼多多-甲店', '京东-乙店', '抖音-丙店'], 300);
$db->enableQueryLog();
$selected = $repository->afterSales(array_replace($base, ['shop_names' => ['拼多多-甲店'], 'shop_names_exact' => true]), 1, 25);
$this->assertSame(['拼多多-甲店'], $this->sortedShops($selected['rows']));
$this->assertDoesNotMatchRegularExpression('/shop_name\s+LIKE/i', collect($db->getQueryLog())->pluck('query')->implode("\n"));
Cache::forget(OrderDataMirrorRepository::SHOP_NAMES_CACHE_KEY);
$db->table('ops_jst_shop_info')->insert(['shop_id' => 1, 'shop_name' => '拼多多-甲店']);
$db->flushQueryLog();
$selected = $repository->afterSales(array_replace($base, ['shop_names' => ['拼多多-甲店'], 'shop_names_exact' => true]), 1, 25);
$this->assertSame(['拼多多-甲店'], $this->sortedShops($selected['rows']));
$this->assertDoesNotMatchRegularExpression('/shop_name\s+LIKE/i', collect($db->getQueryLog())->pluck('query')->implode("\n"));
// Array inputs containing partial names still retain the original contains semantics.
$partial = $repository->afterSales(array_replace($base, ['shop_names' => ['甲店'], 'shop_names_exact' => true]), 1, 25);
$this->assertSame(['拼多多-甲店'], $this->sortedShops($partial['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<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();
});
}
}