店铺维度报表销量改为收入+优惠+冲减+退款合计 (#389)
This commit is contained in:
parent
9d8133cc73
commit
a3b36b1fb6
@ -7,7 +7,8 @@ use Modules\EcommerceFinance\Services\StoreReportMirrorSynchronizer;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* 店铺维度报表的「销量」汇总:业绩明细「订单实付/实收金额」按「店铺名称 + 发生日期」预汇总。
|
||||
* 店铺维度报表的「销量」汇总:业绩明细「收入金额 + 优惠金额 + 冲减金额 + 退款金额」
|
||||
* 按「店铺名称 + 发生日期」预汇总。
|
||||
*
|
||||
* 业绩明细同步(store-receipt:order-match:sync)结束后会自动刷新一次;
|
||||
* 这个命令用于单独重跑(例如补数据或按最新代码强制重建)。
|
||||
@ -17,7 +18,7 @@ class SyncStoreReportPaidSales extends Command
|
||||
protected $signature = 'business-mirror:sync-store-report-paid-sales
|
||||
{--force : 忽略来源指纹,强制重建销量汇总}';
|
||||
|
||||
protected $description = '把业绩明细的订单实付/实收金额按店铺和发生日期汇总,供店铺维度报表销量列读取';
|
||||
protected $description = '把业绩明细的收入/优惠/冲减/退款金额按店铺和发生日期汇总,供店铺维度报表销量列读取';
|
||||
|
||||
public function handle(StoreReportMirrorSynchronizer $synchronizer): int
|
||||
{
|
||||
|
||||
@ -180,7 +180,7 @@ return [
|
||||
'store_report_paid_sales_rows' => [
|
||||
'role' => 'aggregate',
|
||||
'target_store' => 'mariadb',
|
||||
'consumer' => '店铺维度报表销量(业绩明细订单实付/实收金额)',
|
||||
'consumer' => '店铺维度报表销量(业绩明细收入+优惠+冲减+退款金额合计)',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
@ -252,7 +252,7 @@ $navigation = [
|
||||
'directory' => [
|
||||
'icon' => 'query_stats',
|
||||
'status' => '共享镜像',
|
||||
'description' => '店铺资料、主体生效区间统一读取团队共享业务镜像;销量取业绩明细的「订单实付/实收金额」,按店铺和发生时间汇总后落到主体期间。',
|
||||
'description' => '店铺资料、主体生效区间统一读取团队共享业务镜像;销量取业绩明细的「收入金额 + 优惠金额 + 冲减金额 + 退款金额」合计,按店铺和发生日期逐日汇总后落到主体期间。',
|
||||
'empty' => '店铺维度报表尚未完成共享数据导入。',
|
||||
'fields' => [
|
||||
['name' => 'erp_store_name', 'label' => 'ERP系统店铺名称'],
|
||||
|
||||
@ -16,8 +16,9 @@ use RuntimeException;
|
||||
* 失效时间为变更日前一天、新主体从变更日当天生效,收入按实际日期归集,不重复统计。
|
||||
*
|
||||
* 两列金额口径不同,页面分别展示:
|
||||
* 销量(period_sales / sales_NN):业绩明细(店铺收款匹配订单明细)的「订单实付/实收金额」,
|
||||
* 按「店铺名称 + 发生时间」预汇总到 store_report_paid_sales_rows,再按发生日期落到主体期间;
|
||||
* 销量(period_sales / sales_NN):业绩明细(店铺收款匹配订单明细)的
|
||||
* 「收入金额 + 优惠金额 + 冲减金额 + 退款金额」合计,按「店铺名称 + 发生日期」
|
||||
* 预汇总到 store_report_paid_sales_rows,再按发生日期落到主体期间;
|
||||
* 收入(rolling_12m_income / income_NN):平台账单日汇总 store_report_daily_rows 的收入,
|
||||
* 供主体维度经营报表按主体汇总使用。
|
||||
*/
|
||||
@ -28,7 +29,7 @@ class StoreReportRepository
|
||||
public const LIFECYCLES_DATASET = 'store-report.store-lifecycles';
|
||||
/** 平台账单日汇总:主体维度经营报表的「收入」列仍按这里取数。 */
|
||||
public const DAILY_DATASET = 'store-report.daily-sales';
|
||||
/** 销量数据集:业绩明细「订单实付/实收金额」按「店铺名称 + 发生日期」预汇总。 */
|
||||
/** 销量数据集:业绩明细四项金额合计按「店铺名称 + 发生日期」预汇总。 */
|
||||
public const PAID_SALES_DATASET = 'store-report.paid-sales';
|
||||
/** 销量汇总表;由业绩明细表汇总而来,页面按店铺名与发生日期关联主体期间。 */
|
||||
public const PAID_SALES_TABLE = 'store_report_paid_sales_rows';
|
||||
@ -36,7 +37,8 @@ class StoreReportRepository
|
||||
public const PAID_SALES_SOURCE_TABLE = 'store_receipt_order_match_rows';
|
||||
|
||||
/**
|
||||
* 销量来源:业绩明细的「订单实付/实收金额」(按店铺 + 发生日期预汇总)。
|
||||
* 销量来源:收入+优惠+冲减+退款四项合计(按店铺 + 发生日期预汇总)。
|
||||
* 列名沿用历史命名 paid_amount,避免共享镜像表结构变更影响线上旧版本。
|
||||
*
|
||||
* @var array{table:string,amount:string}
|
||||
*/
|
||||
|
||||
@ -80,7 +80,7 @@ class StoreReportLogic
|
||||
[
|
||||
'code' => 'R9',
|
||||
'object' => '销量口径',
|
||||
'logic' => '销量=业绩明细(店铺收款匹配订单明细)的「订单实付/实收金额」,按「店铺名称 + 发生时间」汇总;订单实付为空的未匹配行不计入。收入列仍按平台账单日汇总,两个口径分开统计、不互相覆盖。',
|
||||
'logic' => '销量=业绩明细(店铺收款匹配订单明细)的「收入金额 + 优惠金额 + 冲减金额 + 退款金额」合计,按「店铺名称 + 发生日期」逐日汇总;退款、冲减按账单净额(负数)计入,不再使用订单实付/实收金额。收入列仍按平台账单日汇总,两个口径分开统计、不互相覆盖。',
|
||||
'source' => 'store_receipt_order_match_rows → store_report_paid_sales_rows',
|
||||
'priority' => '与业绩明细同口径',
|
||||
],
|
||||
|
||||
@ -447,7 +447,8 @@ class StoreReportMirrorSynchronizer
|
||||
}
|
||||
|
||||
/**
|
||||
* 店铺维度报表的「销量」:把业绩明细的「订单实付/实收金额」按「店铺名称 + 发生日期」预汇总。
|
||||
* 店铺维度报表的「销量」:把业绩明细的
|
||||
* 「收入金额 + 优惠金额 + 冲减金额 + 退款金额」按「店铺名称 + 发生日期」预汇总。
|
||||
*
|
||||
* 业绩明细由 store-receipt:order-match:sync 按「账期 + 平台」整体重写,这里用
|
||||
* 「行数 + 最近写入时间 + 金额合计」的指纹判断是否变化:没变化就复用当前版本,
|
||||
@ -481,7 +482,9 @@ class StoreReportMirrorSynchronizer
|
||||
$this->connection->statement(
|
||||
'INSERT INTO ' . StoreReportRepository::PAID_SALES_TABLE
|
||||
. ' (generation, store_name, occurred_on, paid_amount, order_count, updated_at)'
|
||||
. ' SELECT ?, store_name, occurred_date, SUM(COALESCE(paid_amount, 0)), COUNT(paid_amount), ?'
|
||||
. ' SELECT ?, store_name, occurred_date,'
|
||||
. ' SUM(COALESCE(income_amount, 0) + COALESCE(discount_amount, 0)'
|
||||
. ' + COALESCE(offset_amount, 0) + COALESCE(refund_amount, 0)), COUNT(*), ?'
|
||||
. ' FROM ' . StoreReportRepository::PAID_SALES_SOURCE_TABLE
|
||||
. " WHERE store_name IS NOT NULL AND store_name <> '' AND occurred_date IS NOT NULL"
|
||||
. ' GROUP BY store_name, occurred_date',
|
||||
@ -529,18 +532,20 @@ class StoreReportMirrorSynchronizer
|
||||
}
|
||||
}
|
||||
|
||||
/** 业绩明细是否变化的指纹:行数 + 最近写入时间 + 订单实付合计。 */
|
||||
/** 业绩明细是否变化的指纹:行数 + 最近写入时间 + 销量口径四项金额合计。 */
|
||||
private function paidSalesFingerprint(): string
|
||||
{
|
||||
$row = $this->connection->selectOne(
|
||||
'SELECT COUNT(*) AS rows_total, MAX(imported_at) AS last_imported_at,'
|
||||
. ' COALESCE(SUM(paid_amount), 0) AS paid_total FROM ' . StoreReportRepository::PAID_SALES_SOURCE_TABLE,
|
||||
. ' COALESCE(SUM(COALESCE(income_amount, 0) + COALESCE(discount_amount, 0)'
|
||||
. ' + COALESCE(offset_amount, 0) + COALESCE(refund_amount, 0)), 0) AS sales_total'
|
||||
. ' FROM ' . StoreReportRepository::PAID_SALES_SOURCE_TABLE,
|
||||
);
|
||||
|
||||
return hash('sha256', implode('|', [
|
||||
(string) ($row->rows_total ?? 0),
|
||||
(string) ($row->last_imported_at ?? ''),
|
||||
(string) ($row->paid_total ?? '0'),
|
||||
(string) ($row->sales_total ?? '0'),
|
||||
]));
|
||||
}
|
||||
|
||||
|
||||
@ -228,9 +228,11 @@ class StoreReportMirrorSyncTest extends TestCase
|
||||
$mirror->table('store_receipt_order_match_rows')->insert([
|
||||
$this->perfRow('k1', '2026-08-02', '店铺A', 100.5),
|
||||
$this->perfRow('k2', '2026-08-02', '店铺A', 9.5),
|
||||
// 未匹配行没有订单实付,落到 0,不计入销量金额。
|
||||
// 未匹配行没有订单实付、也没有收入,四项金额合计为 0。
|
||||
$this->perfRow('k3', '2026-08-03', '店铺A', null),
|
||||
$this->perfRow('k4', '2026-08-03', '店铺B', 20),
|
||||
// 销量=收入 + 优惠 + 冲减 + 退款:100 + 5 + (−3) + (−10) = 92。
|
||||
$this->perfRow('k6', '2026-08-04', '店铺C', 100, discount: 5, offset: -3, refund: -10),
|
||||
]);
|
||||
$upstream = $this->createMock(StoreReportUpstreamRepository::class);
|
||||
$upstream->method('lifecycleBatch')->willReturn([]);
|
||||
@ -250,9 +252,13 @@ class StoreReportMirrorSyncTest extends TestCase
|
||||
'generation' => $generation, 'store_name' => '店铺B', 'occurred_on' => '2026-08-03',
|
||||
'paid_amount' => 20, 'order_count' => 1,
|
||||
], 'business_mirror');
|
||||
$this->assertSame(3, $mirror->table('store_report_paid_sales_rows')->count());
|
||||
$this->assertDatabaseHas('store_report_paid_sales_rows', [
|
||||
'generation' => $generation, 'store_name' => '店铺C', 'occurred_on' => '2026-08-04',
|
||||
'paid_amount' => 92, 'order_count' => 1,
|
||||
], 'business_mirror');
|
||||
$this->assertSame(4, $mirror->table('store_report_paid_sales_rows')->count());
|
||||
$this->assertDatabaseHas('store_report_import_states', [
|
||||
'dataset_key' => StoreReportRepository::PAID_SALES_DATASET, 'status' => 'succeeded', 'row_count' => 3,
|
||||
'dataset_key' => StoreReportRepository::PAID_SALES_DATASET, 'status' => 'succeeded', 'row_count' => 4,
|
||||
], 'business_mirror');
|
||||
|
||||
// 业绩明细没有变化:沿用同一版本,不重复全量重建。
|
||||
@ -262,22 +268,36 @@ class StoreReportMirrorSyncTest extends TestCase
|
||||
$this->assertSame($generation, $mirror->table('store_report_paid_sales_rows')->value('generation'));
|
||||
|
||||
// 账期重写后指纹变化:重建并只保留新版本,页面不会读到两个版本。
|
||||
$mirror->table('store_receipt_order_match_rows')->insert($this->perfRow('k5', '2026-08-04', '店铺B', 5, '2026-10-16 12:00:00'));
|
||||
$mirror->table('store_receipt_order_match_rows')->insert($this->perfRow('k5', '2026-08-05', '店铺B', 5, '2026-10-16 12:00:00'));
|
||||
$third = $synchronizer->sync(100, 2);
|
||||
$newGeneration = $mirror->table('store_report_paid_sales_rows')->where('occurred_on', '2026-08-04')->value('generation');
|
||||
$newGeneration = $mirror->table('store_report_paid_sales_rows')->where('occurred_on', '2026-08-05')->value('generation');
|
||||
$this->assertTrue($third['paid_sales']['changed']);
|
||||
$this->assertNotSame($generation, $newGeneration);
|
||||
$this->assertSame(4, $mirror->table('store_report_paid_sales_rows')->count());
|
||||
$this->assertSame(5, $mirror->table('store_report_paid_sales_rows')->count());
|
||||
$this->assertSame(0, $mirror->table('store_report_paid_sales_rows')->where('generation', $generation)->count());
|
||||
}
|
||||
|
||||
/** @return array<string,mixed> */
|
||||
private function perfRow(string $key, string $occurredOn, string $store, ?float $paid, string $importedAt = '2026-10-16 10:00:00'): array
|
||||
private function perfRow(
|
||||
string $key,
|
||||
string $occurredOn,
|
||||
string $store,
|
||||
?float $income,
|
||||
string $importedAt = '2026-10-16 10:00:00',
|
||||
float $discount = 0,
|
||||
float $offset = 0,
|
||||
float $refund = 0,
|
||||
): array
|
||||
{
|
||||
return [
|
||||
'report_month' => '2026-08-01', 'platform' => '淘系支付宝', 'row_key' => $key,
|
||||
'occurred_date' => $occurredOn, 'store_name' => $store,
|
||||
'paid_amount' => $paid, 'imported_at' => $importedAt,
|
||||
'income_amount' => $income,
|
||||
'discount_amount' => $discount,
|
||||
'offset_amount' => $offset,
|
||||
'refund_amount' => $refund,
|
||||
'paid_amount' => $income,
|
||||
'imported_at' => $importedAt,
|
||||
];
|
||||
}
|
||||
|
||||
@ -371,6 +391,10 @@ class StoreReportMirrorSyncTest extends TestCase
|
||||
$table->string('row_key');
|
||||
$table->date('occurred_date')->nullable();
|
||||
$table->string('store_name')->nullable();
|
||||
$table->decimal('income_amount', 20, 4)->nullable();
|
||||
$table->decimal('discount_amount', 20, 4)->nullable();
|
||||
$table->decimal('offset_amount', 20, 4)->nullable();
|
||||
$table->decimal('refund_amount', 20, 4)->nullable();
|
||||
$table->decimal('paid_amount', 20, 4)->nullable();
|
||||
$table->dateTime('imported_at');
|
||||
$table->primary(['report_month', 'row_key']);
|
||||
|
||||
@ -57,7 +57,7 @@ class StoreReportRepositoryTest extends TestCase
|
||||
$table->decimal('income', 20, 4)->default(0);
|
||||
$table->decimal('expense', 20, 4)->default(0);
|
||||
});
|
||||
// 销量来源:业绩明细「订单实付/实收金额」按店铺 + 发生日期的预汇总。
|
||||
// 销量来源:业绩明细「收入+优惠+冲减+退款金额」按店铺 + 发生日期的预汇总。
|
||||
$schema->create('store_report_paid_sales_rows', function (Blueprint $table): void {
|
||||
$table->increments('id');
|
||||
$table->string('generation');
|
||||
@ -224,13 +224,13 @@ class StoreReportRepositoryTest extends TestCase
|
||||
$this->repository->rows([['erp_store_name' => '店铺A']], 2026);
|
||||
}
|
||||
|
||||
public function test_sales_use_performance_details_paid_amount_and_income_keeps_bill_summary(): void
|
||||
public function test_sales_use_performance_details_amount_components_and_income_keeps_bill_summary(): void
|
||||
{
|
||||
Carbon::setTestNow('2026-09-15 10:00:00');
|
||||
// 同一店铺同一天:账单收入 100,业绩明细订单实付 88 —— 销量取实付、收入取账单。
|
||||
// 同一店铺同一天:账单收入 100,业绩明细四项金额合计 88 —— 销量取合计、收入取账单。
|
||||
$this->insertIncome('店铺A', '2026-07-05', 100);
|
||||
$this->insertPaidSales('店铺A', '2026-07-05', 88);
|
||||
$this->insertPaidSales('店铺A', '2026-08-03', 5);
|
||||
$this->insertSales('店铺A', '2026-07-05', 88);
|
||||
$this->insertSales('店铺A', '2026-08-03', 5);
|
||||
|
||||
$rows = $this->repository->rows([
|
||||
['erp_store_name' => '店铺A', 'company_name' => '主体甲', 'store_status' => '0'],
|
||||
@ -351,8 +351,8 @@ class StoreReportRepositoryTest extends TestCase
|
||||
private function insertBill(string $store, string $occurredOn, float $income): void
|
||||
{
|
||||
$this->insertIncome($store, $occurredOn, $income);
|
||||
// 销量与收入同源不同表:默认按同额写入业绩明细的订单实付,需要区分口径的用例单独调用。
|
||||
$this->insertPaidSales($store, $occurredOn, $income);
|
||||
// 销量与收入同源不同表:默认按同额写入业绩明细的四项金额合计,需要区分口径的用例单独调用。
|
||||
$this->insertSales($store, $occurredOn, $income);
|
||||
}
|
||||
|
||||
/** 平台账单日汇总的收入行(主体维度经营报表的「收入」列)。 */
|
||||
@ -367,14 +367,14 @@ class StoreReportRepositoryTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/** 业绩明细「订单实付/实收金额」按店铺 + 发生日期的预汇总行。 */
|
||||
private function insertPaidSales(string $store, string $occurredOn, float $paid): void
|
||||
/** 业绩明细「收入+优惠+冲减+退款金额」按店铺 + 发生日期的预汇总行。 */
|
||||
private function insertSales(string $store, string $occurredOn, float $amount): void
|
||||
{
|
||||
DB::connection('business_mirror')->table('store_report_paid_sales_rows')->insert([
|
||||
'generation' => $this->paidGeneration,
|
||||
'store_name' => $store,
|
||||
'occurred_on' => $occurredOn,
|
||||
'paid_amount' => $paid,
|
||||
'paid_amount' => $amount,
|
||||
'order_count' => 1,
|
||||
]);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user