feat(product-families): add heat-transfer (热转印) craft to matrix vocabularies

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.
This commit is contained in:
yeuimu
2026-09-02 16:33:41 +08:00
parent fb85a56444
commit 8903afdc82
2 changed files with 53 additions and 5 deletions
@@ -1,6 +1,10 @@
import { Test } from '@nestjs/testing'; import { Test } from '@nestjs/testing';
import { Prisma } from '@prisma/client'; 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 { ProductFamiliesService } from './product-families.service';
import { OrganizeService } from './organize.service'; import { OrganizeService } from './organize.service';
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
@@ -441,7 +445,7 @@ describe('derivePriceMatrix 选项组排序', () => {
it('幂等:同输入多跑两遍输出全等(含顺序),与成员遍历顺序无关', () => { it('幂等:同输入多跑两遍输出全等(含顺序),与成员遍历顺序无关', () => {
const members = [ const members = [
mkMember(1, { tags: ['直喷', '双面印花', '不包邮'] }), mkMember(1, { tags: ['直喷', '双面印花', '不包邮'] }),
mkMember(2, { source: 'CUSTOM', craftLabel: '丝印', logisticsLabel: '海运' }), mkMember(2, { source: 'CUSTOM', tags: ['双面印花'], craftLabel: '丝印', logisticsLabel: '海运' }),
mkMember(3, { tags: ['烫画', '单面印花', '包邮'] }), mkMember(3, { tags: ['烫画', '单面印花', '包邮'] }),
]; ];
const first = derivePriceMatrix(members, []); const first = derivePriceMatrix(members, []);
@@ -451,3 +455,47 @@ describe('derivePriceMatrix 选项组排序', () => {
expect(JSON.stringify(reversed)).toBe(JSON.stringify(first)); 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('derivePriceMatrixcrafts 展示序为 烫画→直喷→不打印→热转印(词表内,不再沉底)', () => {
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(['热转印', '丝印']);
});
});
@@ -66,20 +66,20 @@ type OverrideRow = Prisma.FamilyPriceOverrideGetPayload<{}>;
/** 矩阵三个归因维度的合法取值(封闭集合,与派生标签组一致) */ /** 矩阵三个归因维度的合法取值(封闭集合,与派生标签组一致) */
const DIM_VALUES = { const DIM_VALUES = {
printCount: ['单面印花', '双面印花'], printCount: ['单面印花', '双面印花'],
craft: ['烫画', '直喷', '不打印'], craft: ['烫画', '直喷', '不打印', '热转印'],
logistics: ['包邮', '不包邮'], logistics: ['包邮', '不包邮'],
} as const satisfies Record<string, readonly string[]>; } as const satisfies Record<string, readonly string[]>;
type DimKey = keyof typeof DIM_VALUES; type DimKey = keyof typeof DIM_VALUES;
/** /**
* 选项组展示顺序(业务确认):printCount 单面→双面;craft 烫画→直喷→不打印;logistics 不包邮→包邮。 * 选项组展示顺序(业务确认):printCount 单面→双面;craft 烫画→直喷→不打印→热转印logistics 不包邮→包邮。
* 与 DIM_VALUES 的"组合生成序"是两回事,物化前对聚合数组按此稳定排序; * 与 DIM_VALUES 的"组合生成序"是两回事,物化前对聚合数组按此稳定排序;
* 词表外自由文本(CUSTOM 标签,如"海运")沉底,相互间保持首次遇到序。 * 词表外自由文本(CUSTOM 标签,如"海运")沉底,相互间保持首次遇到序。
*/ */
const OPTION_DISPLAY_ORDER = { const OPTION_DISPLAY_ORDER = {
printCount: ['单面印花', '双面印花'], printCount: ['单面印花', '双面印花'],
craft: ['烫画', '直喷', '不打印'], craft: ['烫画', '直喷', '不打印', '热转印'],
logistics: ['不包邮', '包邮'], logistics: ['不包邮', '包邮'],
} as const; } as const;