diff --git a/furniture-3d-system/src/app/supply-chain/management/clearance/ClearancePricePushView.test.tsx b/furniture-3d-system/src/app/supply-chain/management/clearance/ClearancePricePushView.test.tsx
index 5f0ab95a..b750a952 100644
--- a/furniture-3d-system/src/app/supply-chain/management/clearance/ClearancePricePushView.test.tsx
+++ b/furniture-3d-system/src/app/supply-chain/management/clearance/ClearancePricePushView.test.tsx
@@ -49,7 +49,7 @@ describe('ClearancePricePushView', () => {
}) as jest.Mock
})
- it('renders the specified headers and labels the clearance-price note', async () => {
+ it('renders the specified headers and shows the clearance-price definition', async () => {
render()
expect(await screen.findByText('SKU-A')).toBeInTheDocument()
@@ -58,8 +58,7 @@ describe('ClearancePricePushView', () => {
name: new RegExp(`^${header}(?:\\s|$)`),
})).toBeInTheDocument()
}
- expect(screen.getByLabelText('即清货价')).toHaveAttribute('title', '即清货价')
- expect(screen.queryByText('即清货价')).not.toBeInTheDocument()
+ expect(screen.getByText('即清货价')).toBeVisible()
})
it('generates and archives the selected month', async () => {
diff --git a/furniture-3d-system/src/app/supply-chain/management/clearance/ClearancePricePushView.tsx b/furniture-3d-system/src/app/supply-chain/management/clearance/ClearancePricePushView.tsx
index ce6094e3..43de81e1 100644
--- a/furniture-3d-system/src/app/supply-chain/management/clearance/ClearancePricePushView.tsx
+++ b/furniture-3d-system/src/app/supply-chain/management/clearance/ClearancePricePushView.tsx
@@ -350,14 +350,8 @@ export function ClearancePricePushView({
成本价 |
开始日期 |
- 历史成本价
-
- 注
-
+ 历史成本价
+ 即清货价
|
店铺编码 |
店铺名称 |
diff --git a/furniture-3d-system/src/app/supply-chain/management/clearance/clearance-price-push.module.css b/furniture-3d-system/src/app/supply-chain/management/clearance/clearance-price-push.module.css
index 88b69181..1f8aa9c1 100644
--- a/furniture-3d-system/src/app/supply-chain/management/clearance/clearance-price-push.module.css
+++ b/furniture-3d-system/src/app/supply-chain/management/clearance/clearance-price-push.module.css
@@ -96,22 +96,20 @@
}
.labelHeader {
- position: relative;
+ min-width: 118px;
}
-.headerNote {
- position: static;
- display: inline-grid;
- place-items: center;
- margin-left: 3px;
- width: 14px;
- height: 14px;
- border-radius: 50%;
- background: #eef2f3;
- color: #667174;
- font-size: 9px;
- font-weight: 800;
- cursor: help;
+.headerTitle,
+.headerDefinition {
+ display: block;
+}
+
+.headerDefinition {
+ margin-top: 2px;
+ color: #7a8588;
+ font-size: 10px;
+ font-weight: 500;
+ line-height: 1.2;
}
@media (max-width: 720px) {
diff --git a/furniture-3d-system/src/server/repositories/__tests__/supply-chain-clearance-price-push.repository.test.ts b/furniture-3d-system/src/server/repositories/__tests__/supply-chain-clearance-price-push.repository.test.ts
index 4d0863e2..b16983c4 100644
--- a/furniture-3d-system/src/server/repositories/__tests__/supply-chain-clearance-price-push.repository.test.ts
+++ b/furniture-3d-system/src/server/repositories/__tests__/supply-chain-clearance-price-push.repository.test.ts
@@ -1,11 +1,21 @@
const mockStoreAccountFindMany = jest.fn()
const mockListShopInfos = jest.fn()
+const mockSnapshotFindUnique = jest.fn()
+const mockItemCount = jest.fn()
+const mockItemFindMany = jest.fn()
jest.mock('@/lib/prisma', () => ({
prisma: {
storeAccount: {
findMany: (...args: unknown[]) => mockStoreAccountFindMany(...args),
},
+ supplyChainClearancePricePushSnapshot: {
+ findUnique: (...args: unknown[]) => mockSnapshotFindUnique(...args),
+ },
+ supplyChainClearancePricePushItem: {
+ count: (...args: unknown[]) => mockItemCount(...args),
+ findMany: (...args: unknown[]) => mockItemFindMany(...args),
+ },
},
}))
@@ -32,6 +42,7 @@ describe('supply chain clearance price push repository', () => {
mockListShopInfos.mockResolvedValue([
{ shopId: '1001', shopName: '店铺一', shopSite: '淘宝天猫' },
{ shopId: '1002', shopName: '店铺二', shopSite: '京东' },
+ { shopId: '21414306', shopName: '抖音-索菲亚精选家具专卖店', shopSite: '头条放心购' },
])
await expect(supplyChainClearancePricePushRepository.listStoresByProfitCenters([
@@ -45,4 +56,35 @@ describe('supply chain clearance price push repository', () => {
}))
expect(mockListShopInfos).toHaveBeenCalled()
})
+
+ it('resolves missing shop ids in archived rows without regenerating the snapshot', async () => {
+ mockStoreAccountFindMany.mockResolvedValue([])
+ mockListShopInfos.mockResolvedValue([
+ { shopId: '21414306', shopName: '抖音-索菲亚精选家具专卖店', shopSite: '头条放心购' },
+ ])
+ mockSnapshotFindUnique.mockResolvedValue({ id: 'snapshot-1' })
+ mockItemCount.mockResolvedValue(1)
+ mockItemFindMany.mockResolvedValue([{
+ id: 'item-1',
+ productCode: 'SKU-A',
+ costPrice: '100.000',
+ startDate: new Date('2026-11-01T00:00:00.000Z'),
+ historicalCostPrice: '60.000',
+ shopId: '',
+ shopName: '抖音-索菲娅精选家具专卖店',
+ }])
+
+ await expect(supplyChainClearancePricePushRepository.listItems({
+ periodKey: '2026-11',
+ page: 1,
+ pageSize: 50,
+ })).resolves.toMatchObject({
+ total: 1,
+ rows: [{
+ id: 'item-1',
+ shopId: '21414306',
+ shopName: '抖音-索菲娅精选家具专卖店',
+ }],
+ })
+ })
})
diff --git a/furniture-3d-system/src/server/repositories/supply-chain-clearance-price-push.repository.ts b/furniture-3d-system/src/server/repositories/supply-chain-clearance-price-push.repository.ts
index d0063f8c..7f2b64fe 100644
--- a/furniture-3d-system/src/server/repositories/supply-chain-clearance-price-push.repository.ts
+++ b/furniture-3d-system/src/server/repositories/supply-chain-clearance-price-push.repository.ts
@@ -31,12 +31,64 @@ export type ClearancePricePushFilters = {
historicalCostPriceMax?: number
}
+const SHOP_ID_MAPPING_TTL_MS = 60_000
+let shopIdMappingCache: {
+ expiresAt: number
+ value: ReadonlyMap
+} | null = null
+
function normalizedCodes(values: readonly string[]) {
return Array.from(new Set(
values.map((value) => value.trim().toUpperCase()).filter(Boolean),
))
}
+function normalizedShopName(value: unknown) {
+ return String(value ?? '')
+ .normalize('NFKC')
+ .trim()
+ .replace(/\s+/g, ' ')
+ .replace(/娅/g, '亚')
+ .toLocaleLowerCase('zh-CN')
+}
+
+async function shopIdByStoreName() {
+ if (shopIdMappingCache && shopIdMappingCache.expiresAt > Date.now()) {
+ return shopIdMappingCache.value
+ }
+ try {
+ const shopInfos = await jstSkumapDistributionRepository.listShopInfos()
+ const mapping = new Map()
+ for (const shop of shopInfos) {
+ const shopName = normalizedShopName(shop.shopName)
+ const shopId = shop.shopId.trim()
+ if (shopName && shopId && !mapping.has(shopName)) {
+ mapping.set(shopName, shopId)
+ }
+ }
+ shopIdMappingCache = {
+ expiresAt: Date.now() + SHOP_ID_MAPPING_TTL_MS,
+ value: mapping,
+ }
+ return mapping
+ } catch (error) {
+ console.warn('[clearance-price-push] jst_shopInfo mapping failed', error)
+ return new Map()
+ }
+}
+
+async function resolveShopIds(rows: readonly T[]) {
+ if (!rows.some((row) => !row.shopId.trim())) return rows
+ const mapping = await shopIdByStoreName()
+ return rows.map((row) => {
+ if (row.shopId.trim()) return row
+ return {
+ ...row,
+ shopId: mapping.get(normalizedShopName(row.shopName)) ?? '',
+ }
+ })
+}
+
function itemWhere(
snapshotId: string,
filters?: ClearancePricePushFilters,
@@ -131,7 +183,7 @@ class SupplyChainClearancePricePushRepository {
},
}),
])
- return { total, rows }
+ return { total, rows: await resolveShopIds(rows) }
}
async countItems(periodKey: string, filters?: ClearancePricePushFilters) {
@@ -176,7 +228,7 @@ class SupplyChainClearancePricePushRepository {
},
})
if (!rows.length) return
- yield rows
+ yield await resolveShopIds(rows)
if (rows.length < batchSize) return
cursor = rows[rows.length - 1].id
}
@@ -199,23 +251,9 @@ class SupplyChainClearancePricePushRepository {
if (!storeName || !profitCenter || !centers.includes(profitCenter)) continue
storeByKey.set(`${profitCenter}\u0000${storeName}`, { storeName, profitCenter })
}
- let shopInfos: Awaited> = []
- try {
- shopInfos = await jstSkumapDistributionRepository.listShopInfos()
- } catch (error) {
- console.warn('[clearance-price-push] jst_shopInfo mapping failed', error)
- }
- const shopIdByStoreName = new Map()
- for (const shop of shopInfos) {
- const storeName = shop.shopName.trim()
- const shopId = shop.shopId.trim()
- const normalizedName = storeName.toLowerCase()
- if (normalizedName && shopId && !shopIdByStoreName.has(normalizedName)) {
- shopIdByStoreName.set(normalizedName, shopId)
- }
- }
+ const mapping = await shopIdByStoreName()
return Array.from(storeByKey.values(), (store) => ({
- shopId: shopIdByStoreName.get(store.storeName.trim().toLowerCase()) ?? '',
+ shopId: mapping.get(normalizedShopName(store.storeName)) ?? '',
shopName: store.storeName,
profitCenter: store.profitCenter,
}))