From 8903afdc822ea3d350eb328dfa4036e4ca184a3e Mon Sep 17 00:00:00 2001 From: yeuimu <2197651308@qq.com> Date: Wed, 2 Sep 2026 16:33:41 +0800 Subject: [PATCH] =?UTF-8?q?feat(product-families):=20add=20heat-transfer?= =?UTF-8?q?=20(=E7=83=AD=E8=BD=AC=E5=8D=B0)=20craft=20to=20matrix=20vocabu?= =?UTF-8?q?laries?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Append 热转印 to DIM_VALUES.craft so links manually tagged with the new tag enter the price matrix (previously the closed vocabulary filtered it out and the link's prices would silently disappear), and to OPTION_DISPLAY_ORDER.craft so the button group renders it after 不打印. No derivation rules changed: existing links are unaffected until an operator explicitly tags them. --- .../family-recompute.service.spec.ts | 52 ++++++++++++++++++- .../family-recompute.service.ts | 6 +-- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/apps/api/src/product-families/family-recompute.service.spec.ts b/apps/api/src/product-families/family-recompute.service.spec.ts index b238c99..efd7cbb 100644 --- a/apps/api/src/product-families/family-recompute.service.spec.ts +++ b/apps/api/src/product-families/family-recompute.service.spec.ts @@ -1,6 +1,10 @@ import { Test } from '@nestjs/testing'; import { Prisma } from '@prisma/client'; -import { FamilyRecomputeService, derivePriceMatrix } from './family-recompute.service'; +import { + FamilyRecomputeService, + derivePriceMatrix, + memberMatrixCombos, +} from './family-recompute.service'; import { ProductFamiliesService } from './product-families.service'; import { OrganizeService } from './organize.service'; import { PrismaService } from '../prisma/prisma.service'; @@ -441,7 +445,7 @@ describe('derivePriceMatrix 选项组排序', () => { it('幂等:同输入多跑两遍输出全等(含顺序),与成员遍历顺序无关', () => { const members = [ mkMember(1, { tags: ['直喷', '双面印花', '不包邮'] }), - mkMember(2, { source: 'CUSTOM', craftLabel: '丝印', logisticsLabel: '海运' }), + mkMember(2, { source: 'CUSTOM', tags: ['双面印花'], craftLabel: '丝印', logisticsLabel: '海运' }), mkMember(3, { tags: ['烫画', '单面印花', '包邮'] }), ]; const first = derivePriceMatrix(members, []); @@ -451,3 +455,47 @@ describe('derivePriceMatrix 选项组排序', () => { expect(JSON.stringify(reversed)).toBe(JSON.stringify(first)); }); }); + +/** + * 印刷工艺新增"热转印":矩阵维度词表放开准入 + 展示序追加末尾。 + * 自动派生规则不改 —— SDS 链接只能人工打标;不打标的链接/族零影响。 + */ +describe('热转印工艺', () => { + const mkMember = ( + id: number, + over: { tags?: string[]; source?: 'SDS' | 'CUSTOM'; craftLabel?: string | null; logisticsLabel?: string | null }, + ) => + ({ + id: BigInt(id), + source: over.source ?? 'SDS', + originGoodTags: (over.tags ?? []).map((t) => ({ tag: { tagName: t } })), + variants: [], + craftLabel: over.craftLabel ?? null, + logisticsLabel: over.logisticsLabel ?? null, + }) as any; + + it('memberMatrixCombos:打标热转印的链接产出矩阵组合(此前被词表过滤为空)', () => { + const combos = memberMatrixCombos({ tagNames: ['单面印花', '热转印', '包邮'] }); + expect(combos).toEqual([{ printCount: '单面印花', craft: '热转印', logistics: '包邮' }]); + }); + + it('derivePriceMatrix:crafts 展示序为 烫画→直喷→不打印→热转印(词表内,不再沉底)', () => { + const members = [ + mkMember(1, { tags: ['热转印', '单面印花', '包邮'] }), + mkMember(2, { tags: ['烫画', '单面印花', '包邮'] }), + mkMember(3, { tags: ['直喷', '单面印花', '不包邮'] }), + mkMember(4, { tags: ['不打印', '单面印花', '包邮'] }), + ]; + const m = derivePriceMatrix(members, []); + expect(m.crafts).toEqual(['烫画', '直喷', '不打印', '热转印']); + }); + + it('词表外自由文本(丝印/水洗)仍然沉底,与热转印区分', () => { + const members = [ + mkMember(1, { tags: ['热转印', '单面印花', '包邮'] }), + mkMember(2, { source: 'CUSTOM', tags: ['单面印花'], craftLabel: '丝印', logisticsLabel: '包邮' }), + ]; + const m = derivePriceMatrix(members, []); + expect(m.crafts).toEqual(['热转印', '丝印']); + }); +}); diff --git a/apps/api/src/product-families/family-recompute.service.ts b/apps/api/src/product-families/family-recompute.service.ts index daa3d26..c3b3ad0 100644 --- a/apps/api/src/product-families/family-recompute.service.ts +++ b/apps/api/src/product-families/family-recompute.service.ts @@ -66,20 +66,20 @@ type OverrideRow = Prisma.FamilyPriceOverrideGetPayload<{}>; /** 矩阵三个归因维度的合法取值(封闭集合,与派生标签组一致) */ const DIM_VALUES = { printCount: ['单面印花', '双面印花'], - craft: ['烫画', '直喷', '不打印'], + craft: ['烫画', '直喷', '不打印', '热转印'], logistics: ['包邮', '不包邮'], } as const satisfies Record; type DimKey = keyof typeof DIM_VALUES; /** - * 选项组展示顺序(业务确认):printCount 单面→双面;craft 烫画→直喷→不打印;logistics 不包邮→包邮。 + * 选项组展示顺序(业务确认):printCount 单面→双面;craft 烫画→直喷→不打印→热转印;logistics 不包邮→包邮。 * 与 DIM_VALUES 的"组合生成序"是两回事,物化前对聚合数组按此稳定排序; * 词表外自由文本(CUSTOM 标签,如"海运")沉底,相互间保持首次遇到序。 */ const OPTION_DISPLAY_ORDER = { printCount: ['单面印花', '双面印花'], - craft: ['烫画', '直喷', '不打印'], + craft: ['烫画', '直喷', '不打印', '热转印'], logistics: ['不包邮', '包邮'], } as const;