## 任务背景 订单数据页面已完成附件表头,本任务由 renshen 接入聚水潭上游真实只读数据。 ## 任务目标 - 订单售后费用明细:按附件字段展示订单、商品、SKU 成本及售后关联数据。 - 商品推广费用明细:按商品/款式汇总推广费用。 - 不硬编码附件演示值;缺失字段保持空值或零。 ## 预计修改路径 - modules/EcommerceFinance/Repositories/OrderDataRepository.php - modules/EcommerceFinance/View/Composers/OrderDataPageComposer.php - modules/EcommerceFinance/Providers/Main.php - resources/views/business/pages/order-data/*.blade.php - tests/Unit/EcommerceFinance/OrderDataRepositoryTest.php - tests/Feature/Business/OrderDataPagesUpstreamTest.php ## 页面和路由 复用 order-data/after-sales-expenses、order-data/promotion-expenses 和 business.pages.show;不新增或修改 routes。 ## Controller / Repository 新增 OrderDataRepository 和专用 View Composer;不修改 app/Http/Controllers/Business/Pages.php。 ## 数据源、表和字段 source_key=operations_replica,数据库 JSReportReplica,schema=rpt。固定参数化只读查询使用 jst_Orders、jst_OrderItem、jst_sku、jst_skumap、jst_returnAndRefundInfo、jst_returnAndRefundItem、jst_warehouseInfo、ads_itemid_sales_cost_ratio_v2_di。字段限于订单/商品标识、时间、店铺、SKU 元数据、金额、售后数量金额及 push_fee。 ## scope - order-data.after-sales.list - order-data.promotion.list ## 权限、菜单和公共组件 复用现有页面权限、菜单、UpstreamReadOnlyQueryFactory 和 UpstreamQueryCacheFactory;不修改权限和导航。 ## 明确不修改 不修改 routes、Pages Controller、数据库迁移、上游连接配置、权限、菜单、京东钱包功能、共享数据库结构或任何上游数据。 ## 范围补充:后台快照刷新 - modules/EcommerceFinance/Services/OperationsReplicaSnapshotRefresher.php - tests/Unit/EcommerceFinance/OperationsReplicaSnapshotRefresherTest.php 刷新器仅为 order-data.after-sales.list 和 order-data.promotion.list 分派同一 OrderDataRepository 固定查询;其他 scope 继续复用现有 UpstreamBusinessInformation。 Reviewed-on: http://git.1080p.hk/WuYongJie/CaiWuXiTong/pulls/27
174 lines
7.1 KiB
PHP
174 lines
7.1 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\EcommerceFinance;
|
|
|
|
use Modules\EcommerceFinance\Repositories\OrderDataRepository;
|
|
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQuery;
|
|
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQueryFactory;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
class OrderDataRepositoryTest extends TestCase
|
|
{
|
|
public function test_after_sales_uses_fixed_joins_parameterized_filters_and_overfetch_pagination(): void
|
|
{
|
|
$query = $this->createMock(UpstreamReadOnlyQuery::class);
|
|
$call = 0;
|
|
$query->expects($this->exactly(2))->method('select')->willReturnCallback(
|
|
function (string $sql, array $bindings) use (&$call): array {
|
|
$call++;
|
|
UpstreamReadOnlyQuery::assertReadOnlySql($sql);
|
|
|
|
if ($call === 2) {
|
|
$this->assertStringContainsString('[rpt].[jst_returnAndRefundInfo]', $sql);
|
|
$this->assertSame(['1'], $bindings);
|
|
|
|
return [[
|
|
'internal_order_no' => '1',
|
|
'after_sales_id' => 'AF-1',
|
|
'after_sales_time' => '2026-08-02 12:00:00',
|
|
'sub_order_no' => 'SUB-1',
|
|
'product_code' => 'SKU-1',
|
|
'application_quantity' => '1',
|
|
'application_amount' => '6.00',
|
|
'return_quantity' => '1',
|
|
'return_amount' => '6.00',
|
|
]];
|
|
}
|
|
|
|
$this->assertStringContainsString('[rpt].[jst_Orders]', $sql);
|
|
$this->assertStringContainsString('[rpt].[jst_OrderItem]', $sql);
|
|
$this->assertStringContainsString('OFFSET 2 ROWS FETCH NEXT 3 ROWS ONLY', $sql);
|
|
$this->assertStringNotContainsString('COUNT(*) OVER', $sql);
|
|
$this->assertSame([
|
|
'2026-08-01',
|
|
'2026-08-02',
|
|
'%SO\%\_%',
|
|
'%SO\%\_%',
|
|
'%SO\%\_%',
|
|
'%JD%',
|
|
'%SKU%',
|
|
'%SKU%',
|
|
'%SKU%',
|
|
], $bindings);
|
|
|
|
return [[
|
|
'internal_order_no' => '1',
|
|
'sub_order_no' => 'SUB-1',
|
|
'product_code' => 'SKU-1',
|
|
'shop_site' => 'jd',
|
|
'shop_style_code' => '1001',
|
|
'send_time' => '2026-08-02 10:00:00',
|
|
'unit_cost' => '3.00',
|
|
'product_amount' => '12.34',
|
|
'product_quantity' => '2',
|
|
'actual_shipped_amount' => '12.34',
|
|
'actual_shipped_quantity' => '2',
|
|
'actual_shipped_cost' => '6.00',
|
|
'pre_application_quantity' => '0',
|
|
'pre_application_amount' => '0',
|
|
'pre_application_cost' => '0',
|
|
'pre_return_quantity' => '0',
|
|
'pre_return_amount' => '0',
|
|
'pre_return_cost' => '0',
|
|
'pre_refund_amount' => '0',
|
|
'pre_refund_cost' => '0',
|
|
'pre_platform_subsidy' => '0',
|
|
'post_application_quantity' => '0',
|
|
'post_application_amount' => '0',
|
|
'post_application_cost' => '0',
|
|
'post_return_quantity' => '0',
|
|
'post_return_amount' => '0',
|
|
'post_return_cost' => '0',
|
|
'post_refund_amount' => '0',
|
|
'post_refund_cost' => '0',
|
|
'post_platform_subsidy' => '0',
|
|
'defective_quantity' => '0',
|
|
]];
|
|
},
|
|
);
|
|
|
|
$result = (new OrderDataRepository($this->factory($query)))->afterSales([
|
|
'from' => '2026-08-01',
|
|
'to' => '2026-08-02',
|
|
'order_no' => 'SO%_',
|
|
'shop_name' => 'JD',
|
|
'sku_id' => 'SKU',
|
|
], 2, 2);
|
|
|
|
$this->assertCount(1, $result['rows']);
|
|
$this->assertSame(12.34, $result['rows'][0]['product_amount']);
|
|
$this->assertSame(2.0, $result['rows'][0]['product_quantity']);
|
|
$this->assertSame('https://item.jd.com/1001.html', $result['rows'][0]['product_link']);
|
|
$this->assertSame('AF-1', $result['rows'][0]['after_sales_no']);
|
|
$this->assertSame(1.0, $result['rows'][0]['post_return_quantity']);
|
|
$this->assertNull($result['rows'][0]['account_8001201']);
|
|
}
|
|
|
|
public function test_promotion_expenses_use_one_statistics_date_and_map_only_the_page_rows(): void
|
|
{
|
|
$query = $this->createMock(UpstreamReadOnlyQuery::class);
|
|
$call = 0;
|
|
$query->expects($this->exactly(2))->method('select')->willReturnCallback(
|
|
function (string $sql, array $bindings) use (&$call): array {
|
|
$call++;
|
|
UpstreamReadOnlyQuery::assertReadOnlySql($sql);
|
|
|
|
if ($call === 2) {
|
|
$this->assertStringContainsString('[rpt].[jst_skumap]', $sql);
|
|
$this->assertSame(['ITEM-1'], $bindings);
|
|
|
|
return [[
|
|
'shop_style_code' => 'ITEM-1',
|
|
'shop_name' => 'JD',
|
|
'product_code' => 'SKU-1',
|
|
'style_code' => 'STYLE-1',
|
|
'shop_product_code' => 'SHOP-SKU-1',
|
|
'online_color_spec' => '黑色',
|
|
]];
|
|
}
|
|
|
|
$this->assertStringContainsString('[rpt].[ads_itemid_sales_cost_ratio_v2_di]', $sql);
|
|
$this->assertStringContainsString('OFFSET 3 ROWS FETCH NEXT 4 ROWS ONLY', $sql);
|
|
$this->assertStringContainsString('FROM paged p', $sql);
|
|
$this->assertStringNotContainsString('SUM(c.[push_fee])', $sql);
|
|
$this->assertStringNotContainsString('COUNT(*) OVER', $sql);
|
|
$this->assertSame([
|
|
'2026-08-09',
|
|
'%JD%',
|
|
'%SKU\_%',
|
|
'%SKU\_%',
|
|
'%SKU\_%',
|
|
'%SKU\_%',
|
|
], $bindings);
|
|
|
|
return [[
|
|
'shop_name' => 'JD',
|
|
'shop_style_code' => 'ITEM-1',
|
|
'promotion_fee' => '9.05',
|
|
]];
|
|
},
|
|
);
|
|
|
|
$result = (new OrderDataRepository($this->factory($query)))->promotionExpenses([
|
|
'date' => '2026-08-09',
|
|
'shop_name' => 'JD',
|
|
'sku_id' => 'SKU_',
|
|
], 2, 3);
|
|
|
|
$this->assertSame('2026-08-09', $result['date']);
|
|
$this->assertSame(9.05, $result['rows'][0]['promotion_fee']);
|
|
$this->assertSame('SKU-1', $result['rows'][0]['product_code']);
|
|
}
|
|
|
|
private function factory(UpstreamReadOnlyQuery $query): UpstreamReadOnlyQueryFactory
|
|
{
|
|
$factory = $this->createMock(UpstreamReadOnlyQueryFactory::class);
|
|
$factory->expects($this->once())
|
|
->method('forSource')
|
|
->with('operations_replica')
|
|
->willReturn($query);
|
|
|
|
return $factory;
|
|
}
|
|
}
|