diff --git a/furniture-3d-system/src/app/supply-chain/database/product-archive/columns.ts b/furniture-3d-system/src/app/supply-chain/database/product-archive/columns.ts index 8837a802..63250daf 100644 --- a/furniture-3d-system/src/app/supply-chain/database/product-archive/columns.ts +++ b/furniture-3d-system/src/app/supply-chain/database/product-archive/columns.ts @@ -95,6 +95,7 @@ export const ARCHIVE_COLUMNS: ArchiveColumnGroup[] = [ { prop: 'costPriceSecondary', label: '成本价', width: 90 }, { prop: 'shipAmount', label: '发货金额', width: 100 }, { prop: 'returnAmount', label: '退货金额', width: 100 }, + { prop: 'salesAmount', label: '销售金额', width: 100 }, { prop: 'realPerformance', label: '真实业绩', width: 100 }, { prop: 'shipQty', label: '发货件数', width: 90 }, { prop: 'grossProfit', label: '毛利额', width: 90 }, diff --git a/furniture-3d-system/src/app/supply-chain/database/product-archive/useColumnSettings.test.ts b/furniture-3d-system/src/app/supply-chain/database/product-archive/useColumnSettings.test.ts index dd591df6..28562826 100644 --- a/furniture-3d-system/src/app/supply-chain/database/product-archive/useColumnSettings.test.ts +++ b/furniture-3d-system/src/app/supply-chain/database/product-archive/useColumnSettings.test.ts @@ -1,4 +1,6 @@ import { + migrateSalesAmountColumnOrder, + migrateSalesAmountColumnOrderOnce, migrateWarehouseColumnOrder, migrateWarehouseColumnOrderOnce, type ColumnSetting, @@ -92,3 +94,110 @@ describe('product archive warehouse column order migration', () => { expect(storage.setItem).not.toHaveBeenCalled() }) }) + +describe('product archive sales amount column order migration', () => { + it('moves the appended 销售金额 back in front of 真实业绩', () => { + const settings: ColumnSetting[] = [{ + group: '数据信息', + columns: [ + { prop: 'purchasePrice', label: '采购价', width: 90, visible: true }, + { prop: 'shipAmount', label: '发货金额', width: 100, visible: true }, + { prop: 'returnAmount', label: '退货金额', width: 100, visible: true }, + { prop: 'realPerformance', label: '真实业绩', width: 100, visible: true }, + { prop: 'salesAmount', label: '销售金额', width: 100, visible: true }, + { prop: 'shipQty', label: '发货件数', width: 90, visible: true }, + ], + }] + + const migrated = migrateSalesAmountColumnOrder(settings) + + expect(migrated[0]?.columns.map((column) => column.prop)).toEqual([ + 'purchasePrice', + 'shipAmount', + 'returnAmount', + 'salesAmount', + 'realPerformance', + 'shipQty', + ]) + }) + + it('keeps hidden state and width of the migrated column', () => { + const settings: ColumnSetting[] = [{ + group: '数据信息', + columns: [ + { prop: 'realPerformance', label: '真实业绩', width: 140, visible: true }, + { prop: 'salesAmount', label: '销售金额', width: 210, visible: false }, + ], + }] + + const migrated = migrateSalesAmountColumnOrder(settings) + + expect(migrated[0]?.columns.find((column) => column.prop === 'salesAmount')).toEqual( + expect.objectContaining({ width: 210, visible: false }), + ) + }) + + it('does not change other groups or settings without the column', () => { + const otherGroup: ColumnSetting = { + group: '仓库信息', + columns: [ + { prop: 'warehouseHead', label: '仓库负责人', width: 110, visible: true }, + { prop: 'salesAmount', label: '销售金额', width: 100, visible: true }, + ], + } + const dataGroup: ColumnSetting = { + group: '数据信息', + columns: [ + { prop: 'shipAmount', label: '发货金额', width: 100, visible: true }, + { prop: 'realPerformance', label: '真实业绩', width: 100, visible: true }, + ], + } + + const migrated = migrateSalesAmountColumnOrder([otherGroup, dataGroup]) + + expect(migrated[0]).toBe(otherGroup) + expect(migrated[1]).toBe(dataGroup) + }) + + it('persists the migrated order before marking the migration complete', () => { + const settings: ColumnSetting[] = [{ + group: '数据信息', + columns: [ + { prop: 'realPerformance', label: '真实业绩', width: 100, visible: true }, + { prop: 'salesAmount', label: '销售金额', width: 100, visible: true }, + ], + }] + const storage = { + getItem: jest.fn(() => null), + setItem: jest.fn(), + } + + migrateSalesAmountColumnOrderOnce(settings, storage) + + expect(storage.setItem.mock.calls.map(([key]) => key)).toEqual([ + 'caigou_archive_columns', + 'caigou_archive_columns_sales_amount_v1', + ]) + expect(JSON.parse(storage.setItem.mock.calls[0]?.[1] ?? '[]')[0].columns.map( + (column: { prop: string }) => column.prop, + )).toEqual(['salesAmount', 'realPerformance']) + }) + + it('preserves later manual sorting after the one-time migration', () => { + const settings: ColumnSetting[] = [{ + group: '数据信息', + columns: [ + { prop: 'realPerformance', label: '真实业绩', width: 100, visible: true }, + ], + }] + const storage = { + getItem: jest.fn(() => '1'), + setItem: jest.fn(), + } + + const migrated = migrateSalesAmountColumnOrderOnce(settings, storage) + + expect(migrated).toBe(settings) + expect(storage.setItem).not.toHaveBeenCalled() + }) +}) diff --git a/furniture-3d-system/src/app/supply-chain/database/product-archive/useColumnSettings.ts b/furniture-3d-system/src/app/supply-chain/database/product-archive/useColumnSettings.ts index 3aa58b83..5316ec38 100644 --- a/furniture-3d-system/src/app/supply-chain/database/product-archive/useColumnSettings.ts +++ b/furniture-3d-system/src/app/supply-chain/database/product-archive/useColumnSettings.ts @@ -68,6 +68,47 @@ export function migrateWarehouseColumnOrderOnce( return migrated } +const SALES_AMOUNT_ORDER_MIGRATION_KEY = 'caigou_archive_columns_sales_amount_v1' +const SALES_AMOUNT_COLUMN_PROP = 'salesAmount' +const SALES_AMOUNT_ANCHOR_PROP = 'realPerformance' +const DATA_INFO_GROUP_NAME = '数据信息' + +/** + * 新增「销售金额」后,已保存过列设置的浏览器会把新字段追加到分组末尾。 + * 这里把它挪回「真实业绩」前面,和默认表头顺序一致。 + */ +export function migrateSalesAmountColumnOrder(settings: ColumnSetting[]): ColumnSetting[] { + return settings.map((group) => { + if (group.group !== DATA_INFO_GROUP_NAME) return group + + const column = group.columns.find((entry) => entry.prop === SALES_AMOUNT_COLUMN_PROP) + if (!column) return group + + const rest = group.columns.filter((entry) => entry.prop !== SALES_AMOUNT_COLUMN_PROP) + const anchor = rest.findIndex((entry) => entry.prop === SALES_AMOUNT_ANCHOR_PROP) + if (anchor < 0) return group + + const columns = [...rest] + columns.splice(anchor, 0, column) + const unchanged = columns.every((entry, index) => entry === group.columns[index]) + return unchanged ? group : { ...group, columns } + }) +} + +export function migrateSalesAmountColumnOrderOnce( + settings: ColumnSetting[], + storage: Pick, +): ColumnSetting[] { + if (storage.getItem(SALES_AMOUNT_ORDER_MIGRATION_KEY) === '1') { + return settings + } + + const migrated = migrateSalesAmountColumnOrder(settings) + storage.setItem(STORAGE_KEY, JSON.stringify(migrated)) + storage.setItem(SALES_AMOUNT_ORDER_MIGRATION_KEY, '1') + return migrated +} + export function persistColumnSettings(settings: ColumnSetting[]) { if (typeof window === 'undefined') return localStorage.setItem(STORAGE_KEY, JSON.stringify(settings)) @@ -144,7 +185,10 @@ function loadSettings(): ColumnSetting[] { columns: [...orderedCols, ...customColumns], } }) - return migrateWarehouseColumnOrderOnce(merged, localStorage) + return migrateSalesAmountColumnOrderOnce( + migrateWarehouseColumnOrderOnce(merged, localStorage), + localStorage, + ) } catch { return defaults } diff --git a/furniture-3d-system/src/lib/supply-chain-product-archive.ts b/furniture-3d-system/src/lib/supply-chain-product-archive.ts index 39f0552c..cddf177b 100644 --- a/furniture-3d-system/src/lib/supply-chain-product-archive.ts +++ b/furniture-3d-system/src/lib/supply-chain-product-archive.ts @@ -61,6 +61,7 @@ export type SupplyChainProductArchiveRow = { costPriceSecondary: string shipAmount: string returnAmount: string + salesAmount: string realPerformance: string shipQty: string grossProfit: string @@ -213,6 +214,7 @@ export type ProductArchiveFilters = { returnQty?: string[] shipAmount?: string[] returnAmount?: string[] + salesAmount?: string[] realPerformance?: string[] shipQty?: string[] grossProfit?: string[] @@ -244,7 +246,7 @@ export const PRODUCT_ARCHIVE_FILTER_OPTION_KEYS = [ 'afterSalesRate', 'afterSalesCost', 'afterSalesCostRate', 'returnQty', 'returnRate', 'partsResendCount', 'partsRate', 'installDocId', 'hasDrawing', 'hasVideo', 'distributionIds', 'opsPriceLimit', 'createDays', 'shipAmount', 'returnAmount', - 'realPerformance', 'shipQty', 'grossProfit', 'grossMargin', 'feeRatio', + 'salesAmount', 'realPerformance', 'shipQty', 'grossProfit', 'grossMargin', 'feeRatio', 'operatingProfit', ] as const diff --git a/furniture-3d-system/src/server/repositories/product-archive-sales-performance-source.repository.ts b/furniture-3d-system/src/server/repositories/product-archive-sales-performance-source.repository.ts index 27908c30..dc0f1f5e 100644 --- a/furniture-3d-system/src/server/repositories/product-archive-sales-performance-source.repository.ts +++ b/furniture-3d-system/src/server/repositories/product-archive-sales-performance-source.repository.ts @@ -2,6 +2,7 @@ import sql from 'mssql' export type ProductArchiveSalesPerformanceAggregate = { barcode: string + salesAmount: string shipAmount: string returnAmount: string realPerformance: string @@ -24,7 +25,6 @@ export type ProductArchiveSalesPerformanceDailyAggregate = ProductArchiveSalesPerformanceAggregate & { statDate: string sourceRowCount: number - salesAmount: string // Operator and unit-cost enrichment is sourced for DF barcodes only. skuOperator: string unitCost: string | null @@ -138,6 +138,7 @@ class ProductArchiveSalesPerformanceSourceRepositoryClass .query(` SELECT UPPER(LTRIM(RTRIM(CAST(skuoid AS nvarchar(100))))) AS barcode, + CONVERT(varchar(64), SUM(CAST(COALESCE(sales_amount, 0) AS decimal(38, 4)))) AS salesAmount, CONVERT(varchar(64), SUM(CAST(COALESCE(clear_goods_cost, 0) AS decimal(38, 4)))) AS shipAmount, CONVERT(varchar(64), SUM(CAST(COALESCE(refund_amount, 0) AS decimal(38, 4)))) AS returnAmount, CONVERT(varchar(64), SUM(CAST(COALESCE(real_performance, 0) AS decimal(38, 4)))) AS realPerformance, diff --git a/furniture-3d-system/src/server/repositories/supply-chain-product-archive-sales-period.repository.test.ts b/furniture-3d-system/src/server/repositories/supply-chain-product-archive-sales-period.repository.test.ts index 9af05c70..97d81451 100644 --- a/furniture-3d-system/src/server/repositories/supply-chain-product-archive-sales-period.repository.test.ts +++ b/furniture-3d-system/src/server/repositories/supply-chain-product-archive-sales-period.repository.test.ts @@ -4,7 +4,8 @@ import { Prisma } from '@prisma/client' import { matchesPerformanceFilters, performanceText, type PeriodPerformance } from './supply-chain-product-archive-sales-period.repository' import { PRODUCT_ARCHIVE_EMPTY_FILTER_VALUE as empty } from '@/lib/supply-chain-product-archive' const value: PeriodPerformance = { - shipAmount: new Prisma.Decimal('8.8400'), returnAmount: new Prisma.Decimal(0), realPerformance: new Prisma.Decimal(20), + shipAmount: new Prisma.Decimal('8.8400'), returnAmount: new Prisma.Decimal(0), salesAmount: new Prisma.Decimal('10.5'), + realPerformance: new Prisma.Decimal(20), shipQty: 1n, returnQty: 0n, grossProfit: new Prisma.Decimal(4), grossMargin: new Prisma.Decimal('.12345678'), feeRatio: null, operatingProfit: new Prisma.Decimal(-1), } @@ -13,6 +14,14 @@ it('matches decimal amounts by value and exposes rates as the table shows them', expect(performanceText(value, 'grossMargin')).toBe('12.35%') expect(matchesPerformanceFilters(value, { grossMargin: ['0.12345678'] })).toBe(true) }) + +it('filters 销售金额 with the same decimal rule as other period amounts', () => { + expect(performanceText(value, 'salesAmount')).toBe('10.5') + expect(matchesPerformanceFilters(value, { salesAmount: ['10.50'] })).toBe(true) + expect(matchesPerformanceFilters(value, { salesAmount: ['10.5'] })).toBe(true) + expect(matchesPerformanceFilters(value, { salesAmount: ['11'] })).toBe(false) + expect(matchesPerformanceFilters(null, { salesAmount: [empty] })).toBe(true) +}) it('treats missing rows and undefined ratios as empty, including when other filters apply', () => { expect(matchesPerformanceFilters(null, { shipAmount: [empty] })).toBe(true) expect(matchesPerformanceFilters(value, { feeRatio: [empty] })).toBe(true) diff --git a/furniture-3d-system/src/server/repositories/supply-chain-product-archive-sales-period.repository.ts b/furniture-3d-system/src/server/repositories/supply-chain-product-archive-sales-period.repository.ts index ac0f00ff..65ce781c 100644 --- a/furniture-3d-system/src/server/repositories/supply-chain-product-archive-sales-period.repository.ts +++ b/furniture-3d-system/src/server/repositories/supply-chain-product-archive-sales-period.repository.ts @@ -8,11 +8,19 @@ import { buildProductArchiveWhereWithTextRules } from './supply-chain-product-ar import type { ProductArchiveRecord } from './supply-chain-product-archive-select' export const PERFORMANCE_KEYS = [ - 'shipAmount', 'returnAmount', 'realPerformance', 'shipQty', 'grossProfit', + 'shipAmount', 'returnAmount', 'salesAmount', 'realPerformance', 'shipQty', 'grossProfit', 'grossMargin', 'feeRatio', 'operatingProfit', 'returnQty', ] as const export type PerformanceKey = typeof PERFORMANCE_KEYS[number] -export type PeriodPerformance = NonNullable +/** + * 周期业绩:按页面选中的 日/月/年 从每日快照表聚合出来的值。 + * + * 基础字段与 productArchiveSelect.salesPerformance 对齐;salesAmount 只有每日快照表有, + * 旧的全周期汇总表没有这一列,所以在这里扩展类型,避免为旧表加列和做迁移。 + */ +export type PeriodPerformance = NonNullable & { + salesAmount: Prisma.Decimal +} export async function readSalesCoverage(): Promise { const [row] = await prisma.$queryRaw(Prisma.sql` @@ -58,7 +66,7 @@ export async function listPeriodPerformance(filters: ProductArchiveFilters = {}) const period = resolveSalesPeriod(filters, coverage) const performanceByBarcode = new Map() if (!period.hasCoverage) return { period, performanceByBarcode } - const money = ['shipAmount', 'returnAmount', 'realPerformance', 'clearSalesCost', 'sendFreight', + const money = ['shipAmount', 'returnAmount', 'salesAmount', 'realPerformance', 'clearSalesCost', 'sendFreight', 'platformCommission', 'xyCost', 'afterSaleAmount', 'pushFeeTotal', 'marketingFeeTotal', 'advertisementFeeTotal', 'sharedWarehousingSum', 'otherFee', 'operatingFee'] as const const aggregates = await prisma.$queryRaw(Prisma.sql` @@ -73,6 +81,7 @@ export async function listPeriodPerformance(filters: ProductArchiveFilters = {}) const value = calculateProductArchiveSalesPerformance(aggregate) performanceByBarcode.set(value.barcode, { shipAmount: new Prisma.Decimal(value.shipAmount), returnAmount: new Prisma.Decimal(value.returnAmount), + salesAmount: new Prisma.Decimal(value.salesAmount), realPerformance: new Prisma.Decimal(value.realPerformance), shipQty: value.shipQty, returnQty: value.returnQty, grossProfit: new Prisma.Decimal(value.grossProfit), grossMargin: value.grossMargin === null ? null : new Prisma.Decimal(value.grossMargin), diff --git a/furniture-3d-system/src/server/services/__tests__/product-archive-sales-performance.test.ts b/furniture-3d-system/src/server/services/__tests__/product-archive-sales-performance.test.ts index 5b8c283e..4923b1c2 100644 --- a/furniture-3d-system/src/server/services/__tests__/product-archive-sales-performance.test.ts +++ b/furniture-3d-system/src/server/services/__tests__/product-archive-sales-performance.test.ts @@ -4,6 +4,7 @@ describe('calculateProductArchiveSalesPerformance', () => { it('calculates gross profit and gross margin from the confirmed components', () => { expect(calculateProductArchiveSalesPerformance({ barcode: ' sku-001 ', + salesAmount: '80', shipAmount: '70', returnAmount: '5.5', realPerformance: '100', @@ -22,6 +23,7 @@ describe('calculateProductArchiveSalesPerformance', () => { operatingFee: '2', })).toEqual({ barcode: 'SKU-001', + salesAmount: '80.0000', shipAmount: '70.0000', returnAmount: '5.5000', realPerformance: '100.0000', @@ -37,6 +39,7 @@ describe('calculateProductArchiveSalesPerformance', () => { it('leaves gross margin empty when real performance is zero', () => { const result = calculateProductArchiveSalesPerformance({ barcode: 'SKU-002', + salesAmount: '0', shipAmount: '0', returnAmount: '0', realPerformance: '0', diff --git a/furniture-3d-system/src/server/services/__tests__/supply-chain-product-archive-export.service.test.ts b/furniture-3d-system/src/server/services/__tests__/supply-chain-product-archive-export.service.test.ts index 584219ff..5e6d3261 100644 --- a/furniture-3d-system/src/server/services/__tests__/supply-chain-product-archive-export.service.test.ts +++ b/furniture-3d-system/src/server/services/__tests__/supply-chain-product-archive-export.service.test.ts @@ -77,8 +77,8 @@ describe('product archive full export', () => { expect(repository.openRowSource).toHaveBeenCalledWith({ productName: ['测试桌'] }) expect(result.total).toBe(1) - expect(result.fieldCount).toBe(89) - expect(headers).toHaveLength(89) + expect(result.fieldCount).toBe(90) + expect(headers).toHaveLength(90) expect(headers).toContain('商品名称') expect(headers).toContain('配件率') expect(worksheet.getCell(2, headers.indexOf('商品名称') + 1).value).toBe('测试桌') @@ -98,7 +98,7 @@ describe('product archive full export', () => { expect(worksheet.views).toEqual([ expect.objectContaining({ state: 'frozen', ySplit: 1 }), ]) - expect(ARCHIVE_COLUMNS.flatMap((group) => group.columns)).toHaveLength(89) + expect(ARCHIVE_COLUMNS.flatMap((group) => group.columns)).toHaveLength(90) }) it('streams rows in batches instead of materialising them all', async () => { diff --git a/furniture-3d-system/src/server/services/__tests__/supply-chain-product-archive-sales-performance-sync.service.test.ts b/furniture-3d-system/src/server/services/__tests__/supply-chain-product-archive-sales-performance-sync.service.test.ts index c5f03c88..ecc3eee4 100644 --- a/furniture-3d-system/src/server/services/__tests__/supply-chain-product-archive-sales-performance-sync.service.test.ts +++ b/furniture-3d-system/src/server/services/__tests__/supply-chain-product-archive-sales-performance-sync.service.test.ts @@ -20,6 +20,7 @@ describe('SupplyChainProductArchiveSalesPerformanceSyncService', () => { listAggregates: jest.fn().mockResolvedValue([ { barcode: ' sku-001 ', + salesAmount: '80', shipAmount: '70', returnAmount: '5', realPerformance: '100', @@ -39,6 +40,7 @@ describe('SupplyChainProductArchiveSalesPerformanceSyncService', () => { }, { barcode: 'NOT-IN-ARCHIVE', + salesAmount: '20', shipAmount: '20', returnAmount: '0', realPerformance: '20', diff --git a/furniture-3d-system/src/server/services/product-archive-sales-performance.ts b/furniture-3d-system/src/server/services/product-archive-sales-performance.ts index 778850a6..2001ad02 100644 --- a/furniture-3d-system/src/server/services/product-archive-sales-performance.ts +++ b/furniture-3d-system/src/server/services/product-archive-sales-performance.ts @@ -3,6 +3,7 @@ import type { ProductArchiveSalesPerformanceAggregate } from '@/server/repositor export type ProductArchiveSalesPerformanceValues = { barcode: string + salesAmount: string shipAmount: string returnAmount: string realPerformance: string @@ -40,6 +41,7 @@ export function calculateProductArchiveSalesPerformance( const barcode = normalizeSalesPerformanceBarcode(aggregate.barcode) if (!barcode) throw new Error('Sales performance barcode is required') + const salesAmount = decimal(aggregate.salesAmount, 'salesAmount') const shipAmount = decimal(aggregate.shipAmount, 'shipAmount') const returnAmount = decimal(aggregate.returnAmount, 'returnAmount') const realPerformance = decimal(aggregate.realPerformance, 'realPerformance') @@ -69,6 +71,7 @@ export function calculateProductArchiveSalesPerformance( return { barcode, + salesAmount: salesAmount.toFixed(4), shipAmount: shipAmount.toFixed(4), returnAmount: returnAmount.toFixed(4), realPerformance: realPerformance.toFixed(4), diff --git a/furniture-3d-system/src/server/services/supply-chain-product-archive-export.service.ts b/furniture-3d-system/src/server/services/supply-chain-product-archive-export.service.ts index 44af6790..8a9fac6a 100644 --- a/furniture-3d-system/src/server/services/supply-chain-product-archive-export.service.ts +++ b/furniture-3d-system/src/server/services/supply-chain-product-archive-export.service.ts @@ -170,7 +170,7 @@ export class SupplyChainProductArchiveExportService { * 返回时只做完了「确定要导哪些行」这步(查 id 快照、装载仓库配置),xlsx 字节 * 还没开始产;调用方拿到 stream 立刻响应,边生成边下发,堆占用与总行数无关。 * - * 这条路径取代了原来的 `createWorkbook`(整包 writeBuffer)——旧写法在 89 列下 + * 这条路径取代了原来的 `createWorkbook`(整包 writeBuffer)——旧写法在 90 列下 * 约 60MB / 千行,生产 768MB 堆实际只能撑约 5000 行,99434 行量级的全量导出必崩。 */ async openWorkbook(params: { filters?: ProductArchiveFilters }): Promise { diff --git a/furniture-3d-system/src/server/services/supply-chain-product-archive-query.ts b/furniture-3d-system/src/server/services/supply-chain-product-archive-query.ts index 9c3c82a5..53a6e2df 100644 --- a/furniture-3d-system/src/server/services/supply-chain-product-archive-query.ts +++ b/furniture-3d-system/src/server/services/supply-chain-product-archive-query.ts @@ -107,6 +107,7 @@ const productArchiveFiltersSchema = z.object({ createDays: archiveValueArraySchema.optional(), shipAmount: archiveValueArraySchema.optional(), returnAmount: archiveValueArraySchema.optional(), + salesAmount: archiveValueArraySchema.optional(), realPerformance: archiveValueArraySchema.optional(), shipQty: archiveValueArraySchema.optional(), grossProfit: archiveValueArraySchema.optional(), diff --git a/furniture-3d-system/src/server/services/supply-chain-product-archive.mapper.ts b/furniture-3d-system/src/server/services/supply-chain-product-archive.mapper.ts index 21db1779..18b0db18 100644 --- a/furniture-3d-system/src/server/services/supply-chain-product-archive.mapper.ts +++ b/furniture-3d-system/src/server/services/supply-chain-product-archive.mapper.ts @@ -1,7 +1,21 @@ +import type { Prisma } from '@prisma/client' import type { SupplyChainProductArchiveRow } from '@/lib/supply-chain-product-archive' import { storageTypeLabel } from '@/lib/supply-chain-product-archive-values' import type { ProductArchiveRecord } from '@/server/repositories/supply-chain-product-archive.repository' +/** + * 页面和导出拿到的是「已叠加周期业绩」的档案行:salesPerformance 会被换成按 日/月/年 + * 从每日快照表聚合出来的对象,比 productArchiveSelect 的结果多一个 salesAmount + * (每日快照表有这一列,旧的全周期汇总表没有)。这里放宽入参类型,两种形状都接受。 + */ +type MappableArchiveRecord = Omit & { + salesPerformance?: + | (NonNullable & { + salesAmount?: Prisma.Decimal | null + }) + | null +} + const DAY_IN_MS = 24 * 60 * 60 * 1000 function calculateCreateDays(createdAt: Date, now: Date) { @@ -34,7 +48,7 @@ export function mapProductArchiveRow({ record, now, }: { - record: ProductArchiveRecord + record: MappableArchiveRecord now: Date }) { return { @@ -102,6 +116,7 @@ export function mapProductArchiveRow({ costPriceSecondary: record.costPriceSecondary, shipAmount: decimalText(record.salesPerformance?.shipAmount ?? null), returnAmount: decimalText(record.salesPerformance?.returnAmount ?? null), + salesAmount: decimalText(record.salesPerformance?.salesAmount ?? null), realPerformance: decimalText(record.salesPerformance?.realPerformance ?? null), shipQty: record.salesPerformance?.shipQty.toString() ?? '', grossProfit: decimalText(record.salesPerformance?.grossProfit ?? null),