feat: 普通商品展示完整价格变更记录

This commit is contained in:
limeng 2026-09-14 12:29:03 +08:00
parent edf51109fe
commit d0e7bfbcf7
4 changed files with 586 additions and 22 deletions

View File

@ -177,7 +177,7 @@ class ProductCatalogMirrorRepository
$this->attachPurchasePrices($rows, $purchasePriceSyncStatus['initialized']);
$this->attachPhysicalFields($rows);
$this->attachPriceChanges($rows, $priceChangeSyncStatus['initialized']);
$this->attachPriceChanges($rows, $priceChangeSyncStatus['initialized'], false);
$this->attachWorkPlatformPrices($rows, $workPlatformPriceSyncStatus['initialized']);
foreach ($rows as $row) {
yield $row;
@ -307,11 +307,11 @@ class ProductCatalogMirrorRepository
->orderByDesc('effectTime')
->orderByDesc('Id')
->get(['skuCode', 'purchasePrice', 'effectTime'])
->unique('skuCode')
->keyBy('skuCode');
->unique(fn (object $price): string => $this->normalizeSku($price->skuCode ?? null))
->keyBy(fn (object $price): string => $this->normalizeSku($price->skuCode ?? null));
foreach ($rows as $row) {
$price = $candidates->get(trim((string) ($row->sku_id ?? '')));
$price = $candidates->get($this->normalizeSku($row->sku_id ?? null));
if ($price === null) {
continue;
}
@ -325,17 +325,20 @@ class ProductCatalogMirrorRepository
/**
* 变价记录与采购单价一样是补充来源:即使镜像尚未就绪,普通商品行仍必须
* 完整显示,四个变价字段保持为空,由页面统一说明同步状态。
* 完整显示,变价字段保持为空,由页面统一说明同步状态。分页页面附带该
* SKU 的全部定价/变价历史;大批量导出只附带最近一次变价,避免放大内存。
*
* @param list<object> $rows
*/
private function attachPriceChanges(array $rows, bool $initialized): void
private function attachPriceChanges(array $rows, bool $initialized, bool $includeHistory = true): void
{
foreach ($rows as $row) {
$row->price_change_before_price = null;
$row->price_change_after_price = null;
$row->price_change_diff_amount = null;
$row->price_change_effect_time = null;
$row->price_change_history = [];
$row->price_change_history_count = 0;
}
if ($rows === [] || ! $initialized) {
@ -353,23 +356,53 @@ class ProductCatalogMirrorRepository
return;
}
$candidates = $this->connection->table($this->priceChangeTable())
$query = $this->connection->table($this->priceChangeTable())
->whereIn('skucode', $skuIds)
->where('priceType', '1')
->whereIn('priceType', $includeHistory ? ['0', '1'] : ['1'])
->orderByDesc('effectStartTime')
->orderByDesc('Id')
->get([
'skucode',
'originalPrice',
'afterPrice',
'diffPrice',
'effectStartTime',
])
->unique('skucode')
->keyBy('skucode');
->orderByDesc('Id');
$candidates = $query->get([
'Id',
'skucode',
'priceType',
'originalPrice',
'afterPrice',
'diffPrice',
'effectStartTime',
'effectEndTime',
'createTime',
])
->groupBy(fn (object $change): string => $this->normalizeSku($change->skucode ?? null));
foreach ($rows as $row) {
$change = $candidates->get(trim((string) ($row->sku_id ?? '')));
$history = $candidates->get($this->normalizeSku($row->sku_id ?? null), collect())->values();
if ($includeHistory) {
$row->price_change_history = $history->map(static function (object $change): array {
$type = trim((string) ($change->priceType ?? ''));
return [
'id' => $change->Id ?? null,
'record_type' => $type,
'record_type_label' => match ($type) {
'0' => '定价',
'1' => '变价',
default => $type === '' ? '-' : $type,
},
'before_price' => $change->originalPrice ?? null,
'after_price' => $change->afterPrice ?? null,
'diff_amount' => $change->diffPrice ?? null,
'effect_start_time' => $change->effectStartTime ?? null,
'effect_end_time' => $change->effectEndTime ?? null,
'created_at' => $change->createTime ?? null,
];
})->all();
$row->price_change_history_count = count($row->price_change_history);
}
$change = $history->first(
static fn (object $candidate): bool => trim((string) ($candidate->priceType ?? '')) === '1',
);
if ($change === null) {
continue;
}
@ -469,6 +502,11 @@ class ProductCatalogMirrorRepository
);
}
private function normalizeSku(mixed $value): string
{
return mb_strtolower(trim((string) $value), 'UTF-8');
}
/** @param array<string,mixed> $filters */
private function filteredQuery(array $filters): Builder
{

View File

@ -230,7 +230,7 @@
.product-catalog-table {
width: 100%;
min-width: 3180px;
min-width: 3280px;
border-collapse: collapse;
font-size: 0.78125rem;
}
@ -288,6 +288,49 @@
white-space: nowrap;
}
.product-catalog-history-action {
display: flex;
align-items: center;
gap: 7px;
white-space: nowrap;
}
.product-catalog-icon-button {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
padding: 0;
border: 1px solid var(--vip-content-accent-dark, #856632);
border-radius: 50%;
background: var(--vip-content-accent-dark, #856632);
color: #ffffff;
cursor: pointer;
}
.product-catalog-icon-button:hover {
background: #6a5129;
}
.product-catalog-icon-button:focus-visible {
box-shadow: 0 0 0 3px rgba(133, 102, 50, 0.20);
outline: none;
}
.product-catalog-icon-button:disabled {
border-color: var(--vip-border, #d8d2c7);
background: #efede8;
color: var(--catalog-muted);
cursor: not-allowed;
opacity: 0.75;
}
.product-catalog-history-count {
color: var(--catalog-muted);
font-variant-numeric: tabular-nums;
}
.product-catalog-table .is-clamped {
overflow: hidden;
text-overflow: ellipsis;
@ -365,6 +408,161 @@
pointer-events: none;
}
body.product-price-history-open {
overflow: hidden;
}
.product-price-history-modal[hidden] {
display: none;
}
.product-price-history-modal {
position: fixed;
z-index: 1100;
inset: 0;
display: flex;
align-items: flex-end;
justify-content: center;
padding: 16px;
}
.product-price-history-modal__backdrop {
position: absolute;
inset: 0;
background: rgba(5, 5, 6, 0.54);
}
.product-price-history-modal__panel {
position: relative;
display: flex;
width: min(1240px, calc(100vw - 32px));
max-height: min(760px, calc(100vh - 48px));
flex-direction: column;
overflow: hidden;
border: 1px solid var(--vip-border, #d8d2c7);
border-radius: 8px;
background: #ffffff;
box-shadow: 0 4px 8px rgba(23, 23, 23, 0.10);
}
.product-price-history-modal__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 14px 16px;
border-bottom: 1px solid var(--vip-border, #d8d2c7);
}
.product-price-history-modal__heading {
min-width: 0;
}
.product-price-history-modal__title {
margin: 0;
color: var(--catalog-text);
font-size: 0.875rem;
font-weight: 600;
line-height: 1.25;
}
.product-price-history-modal__subtitle {
margin: 4px 0 0;
overflow: hidden;
color: var(--catalog-muted);
font-size: 0.75rem;
text-overflow: ellipsis;
white-space: nowrap;
}
.product-price-history-modal__actions {
display: flex;
align-items: center;
gap: 8px;
flex: 0 0 auto;
}
.product-price-history-modal__summary {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0;
border-bottom: 1px solid var(--vip-border, #d8d2c7);
background: var(--vip-content-accent-soft, #f7f0de);
}
.product-price-history-modal__metric {
min-width: 0;
padding: 10px 16px;
border-right: 1px solid var(--vip-border, #d8d2c7);
}
.product-price-history-modal__metric:last-child {
border-right: 0;
}
.product-price-history-modal__metric-label {
display: block;
margin-bottom: 3px;
color: var(--catalog-muted);
font-size: 0.75rem;
}
.product-price-history-modal__metric-value {
display: block;
overflow: hidden;
color: var(--catalog-text);
font-size: 0.8125rem;
font-variant-numeric: tabular-nums;
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
}
.product-price-history-modal__table-wrap {
flex: 1 1 auto;
min-height: 0;
overflow: auto;
}
.product-price-history-table {
width: 100%;
min-width: 980px;
border-collapse: collapse;
font-size: 0.78125rem;
}
.product-price-history-table th,
.product-price-history-table td {
padding: 9px 10px;
border-bottom: 1px solid #eeeae3;
text-align: left;
vertical-align: middle;
white-space: nowrap;
}
.product-price-history-table th {
position: sticky;
z-index: 1;
top: 0;
background: #f5f3ee;
color: #4f4a43;
font-size: 0.75rem;
font-weight: 600;
}
.product-price-history-table td.is-numeric {
text-align: right;
font-variant-numeric: tabular-nums;
}
.product-price-history-table td.is-positive {
color: var(--vip-warning, #935f07);
}
.product-price-history-table td.is-negative {
color: var(--vip-success, #53793e);
}
@media (max-width: 1500px) {
.product-catalog-filters {
grid-template-columns: repeat(4, minmax(140px, 1fr));
@ -398,6 +596,36 @@
.product-catalog-actions {
justify-content: flex-start;
}
.product-price-history-modal {
padding: 8px;
}
.product-price-history-modal__panel {
width: calc(100vw - 16px);
max-height: calc(100vh - 16px);
}
.product-price-history-modal__header {
align-items: flex-start;
}
.product-price-history-modal__actions .product-catalog-button span:last-child {
display: none;
}
.product-price-history-modal__summary {
grid-template-columns: 1fr;
}
.product-price-history-modal__metric {
border-right: 0;
border-bottom: 1px solid var(--vip-border, #d8d2c7);
}
.product-price-history-modal__metric:last-child {
border-bottom: 0;
}
}
</style>
@ -568,6 +796,7 @@
<th class="is-numeric">变价后价格</th>
<th class="is-numeric">差额</th>
<th>变价时间</th>
<th>变价记录</th>
<th>数据来源</th>
<th class="is-numeric">市场价</th>
<th class="is-numeric">重量(kg)</th>
@ -621,6 +850,32 @@
@endforeach
<td class="is-numeric">{{ isset($row->price_change_diff_amount) && $row->price_change_diff_amount !== null ? number_format((float) $row->price_change_diff_amount, 2) : '-' }}</td>
<td class="is-code">{{ !empty($row->price_change_effect_time) ? \Illuminate\Support\Carbon::parse($row->price_change_effect_time)->format('Y-m-d') : '-' }}</td>
@php
$priceChangeHistory = $row->price_change_history ?? [];
$priceChangeHistoryCount = (int) ($row->price_change_history_count ?? count($priceChangeHistory));
$priceChangeHistoryId = 'product-price-history-' . (int) ($row->autoid ?? 0);
@endphp
<td>
<div class="product-catalog-history-action">
<button
type="button"
class="product-catalog-icon-button"
data-price-history-trigger
data-history-target="{{ $priceChangeHistoryId }}"
data-sku-code="{{ $row->sku_id ?? '' }}"
data-product-name="{{ $row->name ?? '' }}"
data-cost-price="{{ $row->cost_price ?? '' }}"
data-work-platform-price="{{ $row->work_platform_price ?? '' }}"
aria-label="查看 {{ $row->sku_id ?? '当前商品' }} 的价格变更明细"
title="{{ $priceChangeHistoryCount > 0 ? '查看价格变更明细' : '当前商品暂无价格变更记录' }}"
@disabled($priceChangeHistoryCount === 0)
>
<span class="material-icons-outlined text-base" aria-hidden="true">history</span>
</button>
<span class="product-catalog-history-count">{{ $priceChangeHistoryCount }} 条</span>
<script id="{{ $priceChangeHistoryId }}" type="application/json">@json($priceChangeHistory, JSON_UNESCAPED_UNICODE | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT)</script>
</div>
</td>
<td class="product-catalog-source-list">
@forelse ($row->data_sources ?? [] as $source)
<span class="product-catalog-source-item">{{ $source }}</span>
@ -650,7 +905,7 @@
</tr>
@empty
<tr>
<td colspan="33" class="product-catalog-empty">
<td colspan="34" class="product-catalog-empty">
<span class="material-icons-outlined text-3xl block mb-2" aria-hidden="true">inventory_2</span>
@if ($productCatalogError)
数据暂时不可用。
@ -690,4 +945,213 @@
</div>
@endif
</div>
<div class="product-price-history-modal" data-price-history-modal hidden>
<div class="product-price-history-modal__backdrop" data-price-history-close aria-hidden="true"></div>
<section
class="product-price-history-modal__panel"
role="dialog"
aria-modal="true"
aria-labelledby="product-price-history-title"
>
<header class="product-price-history-modal__header">
<div class="product-price-history-modal__heading">
<h2 id="product-price-history-title" class="product-price-history-modal__title">价格变更明细</h2>
<p class="product-price-history-modal__subtitle" data-price-history-subtitle></p>
</div>
<div class="product-price-history-modal__actions">
<button type="button" class="product-catalog-button" data-price-history-export>
<span class="material-icons-outlined text-base" aria-hidden="true">download</span>
<span>导出变价记录</span>
</button>
<button
type="button"
class="product-catalog-icon-button"
data-price-history-close
aria-label="关闭价格变更明细"
title="关闭"
>
<span class="material-icons-outlined text-base" aria-hidden="true">close</span>
</button>
</div>
</header>
<div class="product-price-history-modal__summary">
<div class="product-price-history-modal__metric">
<span class="product-price-history-modal__metric-label">商品编码</span>
<span class="product-price-history-modal__metric-value" data-price-history-sku>-</span>
</div>
<div class="product-price-history-modal__metric">
<span class="product-price-history-modal__metric-label">当前成本价</span>
<span class="product-price-history-modal__metric-value" data-price-history-cost>-</span>
</div>
<div class="product-price-history-modal__metric">
<span class="product-price-history-modal__metric-label">当前工作平台价格</span>
<span class="product-price-history-modal__metric-value" data-price-history-platform>-</span>
</div>
</div>
<div class="product-price-history-modal__table-wrap">
<table class="product-price-history-table" aria-label="当前商品价格变更明细">
<thead>
<tr>
<th>价格类型</th>
<th class="is-numeric">变价前价格</th>
<th class="is-numeric">变价后价格</th>
<th class="is-numeric">差价</th>
<th>生效开始日期</th>
<th>生效结束日期</th>
<th>记录时间</th>
</tr>
</thead>
<tbody data-price-history-body></tbody>
</table>
</div>
</section>
</div>
</div>
<script>
(() => {
const page = document.querySelector('[data-product-catalog-page]');
if (!page || page.dataset.priceHistoryBound === 'true') {
return;
}
page.dataset.priceHistoryBound = 'true';
const modal = page.querySelector('[data-price-history-modal]');
const tableBody = page.querySelector('[data-price-history-body]');
const exportButton = page.querySelector('[data-price-history-export]');
const closeButton = modal?.querySelector('button[data-price-history-close]');
const state = { history: [], sku: '', productName: '', trigger: null };
if (!modal || !tableBody || !exportButton || !closeButton) {
return;
}
const displayValue = (value) => value === null || value === undefined || value === '' ? '-' : String(value);
const formatMoney = (value) => {
if (value === null || value === undefined || value === '') {
return '-';
}
const numeric = Number(value);
return Number.isFinite(numeric)
? numeric.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
: '-';
};
const formatDate = (value, withTime = false) => {
if (!value) {
return '-';
}
const normalized = String(value).replace('T', ' ');
return normalized.slice(0, withTime ? 19 : 10);
};
const appendCell = (row, value, className = '') => {
const cell = document.createElement('td');
cell.textContent = displayValue(value);
if (className) {
cell.className = className;
}
row.appendChild(cell);
};
const renderHistory = () => {
tableBody.replaceChildren();
state.history.forEach((item) => {
const row = document.createElement('tr');
appendCell(row, item.record_type_label);
appendCell(row, formatMoney(item.before_price), 'is-numeric');
appendCell(row, formatMoney(item.after_price), 'is-numeric');
const difference = Number(item.diff_amount);
const differenceClass = Number.isFinite(difference) && difference !== 0
? `is-numeric ${difference > 0 ? 'is-positive' : 'is-negative'}`
: 'is-numeric';
appendCell(row, formatMoney(item.diff_amount), differenceClass);
appendCell(row, formatDate(item.effect_start_time));
appendCell(row, formatDate(item.effect_end_time));
appendCell(row, formatDate(item.created_at, true));
tableBody.appendChild(row);
});
};
const openModal = (trigger) => {
const source = document.getElementById(trigger.dataset.historyTarget || '');
if (!source) {
return;
}
try {
state.history = JSON.parse(source.textContent || '[]');
} catch (error) {
state.history = [];
}
if (state.history.length === 0) {
return;
}
state.sku = trigger.dataset.skuCode || '';
state.productName = trigger.dataset.productName || '';
state.trigger = trigger;
page.querySelector('[data-price-history-subtitle]').textContent = `${state.productName || '未命名商品'} · ${state.history.length} 条记录`;
page.querySelector('[data-price-history-sku]').textContent = displayValue(state.sku);
page.querySelector('[data-price-history-cost]').textContent = formatMoney(trigger.dataset.costPrice);
page.querySelector('[data-price-history-platform]').textContent = formatMoney(trigger.dataset.workPlatformPrice);
renderHistory();
modal.hidden = false;
document.body.classList.add('product-price-history-open');
closeButton.focus();
};
const closeModal = () => {
modal.hidden = true;
document.body.classList.remove('product-price-history-open');
state.trigger?.focus();
};
const csvCell = (value) => `"${displayValue(value).replaceAll('"', '""')}"`;
const exportHistory = () => {
const headers = ['价格类型', '变价前价格', '变价后价格', '差价', '生效开始日期', '生效结束日期', '记录时间'];
const lines = [headers.map(csvCell).join(',')];
state.history.forEach((item) => {
lines.push([
item.record_type_label,
item.before_price,
item.after_price,
item.diff_amount,
formatDate(item.effect_start_time),
formatDate(item.effect_end_time),
formatDate(item.created_at, true),
].map(csvCell).join(','));
});
const blob = new Blob(['\uFEFF', lines.join('\r\n')], { type: 'text/csv;charset=utf-8' });
const link = document.createElement('a');
const safeSku = (state.sku || '商品').replace(/[\\/:*?"<>|]/g, '_');
link.href = URL.createObjectURL(blob);
link.download = `${safeSku}_价格变更明细.csv`;
document.body.appendChild(link);
link.click();
link.remove();
URL.revokeObjectURL(link.href);
};
page.addEventListener('click', (event) => {
const trigger = event.target.closest('[data-price-history-trigger]');
if (trigger && !trigger.disabled) {
openModal(trigger);
return;
}
if (event.target.closest('[data-price-history-close]')) {
closeModal();
}
});
exportButton.addEventListener('click', exportHistory);
modal.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
closeModal();
}
});
})();
</script>

View File

@ -65,6 +65,31 @@ class ProductCatalogPageTest extends FeatureTestCase
'price_change_after_price' => 475,
'price_change_diff_amount' => -15,
'price_change_effect_time' => '2026-08-11 00:00:00',
'price_change_history' => [
[
'id' => 12,
'record_type' => '1',
'record_type_label' => '变价',
'before_price' => 490,
'after_price' => 475,
'diff_amount' => -15,
'effect_start_time' => '2026-08-11 00:00:00',
'effect_end_time' => null,
'created_at' => '2026-08-10 09:30:00',
],
[
'id' => 3,
'record_type' => '0',
'record_type_label' => '定价',
'before_price' => null,
'after_price' => 490,
'diff_amount' => null,
'effect_start_time' => '2026-01-01 00:00:00',
'effect_end_time' => '2026-08-10 00:00:00',
'created_at' => '2025-12-31 08:00:00',
],
],
'price_change_history_count' => 2,
'purchase_order_unit_price' => 65,
'purchase_order_finished_at' => '2026-08-20 11:03:05',
'purchase_order_id' => 827380,
@ -140,6 +165,17 @@ class ProductCatalogPageTest extends FeatureTestCase
->assertSeeText('变价后价格')
->assertSeeText('差额')
->assertSeeText('-15.00')
->assertSeeText('变价记录')
->assertSeeText('2 条')
->assertSee('data-price-history-trigger', false)
->assertSeeText('价格变更明细')
->assertSeeText('当前成本价')
->assertSeeText('当前工作平台价格')
->assertSeeText('导出变价记录')
->assertSeeText('生效结束日期')
->assertSeeText('记录时间')
->assertSee('"record_type_label":"变价"', false)
->assertSee('"record_type_label":"定价"', false)
->assertSeeText('数据来源')
->assertSeeText('商品及库存管理(普通商品资料)')
->assertSeeText('1688代发单价')

View File

@ -263,17 +263,29 @@ class ProductCatalogMirrorRepositoryTest extends TestCase
$this->assertSame('pending', $result['work_platform_price_sync_status']['code']);
}
public function test_it_attaches_the_latest_price_type_one_change_details(): void
public function test_it_attaches_all_price_history_and_keeps_the_latest_change_on_the_main_row(): void
{
$this->markReady(1);
$this->insertProduct(1, ['sku_id' => 'SKU-CHANGE']);
$this->createPriceChangeTables(true);
$this->insertPriceChange(9, [
'skucode' => 'SKU-CHANGE',
'priceType' => 0,
'originalPrice' => null,
'afterPrice' => 500,
'diffPrice' => null,
'effectStartTime' => '2026-08-01 00:00:00',
'effectEndTime' => '2026-08-17 00:00:00',
'createTime' => '2026-07-31 09:00:00',
]);
$this->insertPriceChange(10, [
'skucode' => 'SKU-CHANGE',
'originalPrice' => 500,
'afterPrice' => 480,
'diffPrice' => -20,
'effectStartTime' => '2026-08-18 00:00:00',
'effectEndTime' => '2026-08-19 00:00:00',
'createTime' => '2026-08-17 09:00:00',
]);
$this->insertPriceChange(11, [
'skucode' => 'SKU-CHANGE',
@ -281,6 +293,7 @@ class ProductCatalogMirrorRepositoryTest extends TestCase
'afterPrice' => 475,
'diffPrice' => -5,
'effectStartTime' => '2026-08-20 00:00:00',
'createTime' => '2026-08-19 09:00:00',
]);
$this->insertPriceChange(12, [
'skucode' => 'SKU-CHANGE',
@ -298,6 +311,13 @@ class ProductCatalogMirrorRepositoryTest extends TestCase
$this->assertSame('475', (string) $row->price_change_after_price);
$this->assertSame('-5', (string) $row->price_change_diff_amount);
$this->assertStringStartsWith('2026-08-20', (string) $row->price_change_effect_time);
$this->assertSame(3, $row->price_change_history_count);
$this->assertSame(['变价', '变价', '定价'], array_column($row->price_change_history, 'record_type_label'));
$this->assertSame('480', (string) $row->price_change_history[0]['before_price']);
$this->assertSame('475', (string) $row->price_change_history[0]['after_price']);
$this->assertSame('-5', (string) $row->price_change_history[0]['diff_amount']);
$this->assertStringStartsWith('2026-08-19', (string) $row->price_change_history[0]['created_at']);
$this->assertStringStartsWith('2026-08-19', (string) $row->price_change_history[1]['effect_end_time']);
$this->assertTrue($result['price_change_sync_status']['initialized']);
}
@ -314,6 +334,8 @@ class ProductCatalogMirrorRepositoryTest extends TestCase
$this->assertNull($row->price_change_after_price);
$this->assertNull($row->price_change_diff_amount);
$this->assertNull($row->price_change_effect_time);
$this->assertSame([], $row->price_change_history);
$this->assertSame(0, $row->price_change_history_count);
$this->assertFalse($result['price_change_sync_status']['initialized']);
$this->assertSame('pending', $result['price_change_sync_status']['code']);
}
@ -559,6 +581,8 @@ class ProductCatalogMirrorRepositoryTest extends TestCase
$table->decimal('afterPrice', 18, 2)->nullable();
$table->decimal('diffPrice', 18, 2)->nullable();
$table->dateTime('effectStartTime')->nullable();
$table->dateTime('effectEndTime')->nullable();
$table->dateTime('createTime')->nullable();
});
DB::connection('business_mirror')->table('business_mirror_table_states')->insert([
'dataset_key' => ProductCatalogMirrorRepository::PRICE_CHANGE_DATASET_KEY,
@ -579,6 +603,8 @@ class ProductCatalogMirrorRepositoryTest extends TestCase
'afterPrice' => 50,
'diffPrice' => -5,
'effectStartTime' => '2026-08-01 00:00:00',
'effectEndTime' => null,
'createTime' => '2026-07-31 09:00:00',
], $overrides));
}