巴西/西语市场上游尺码表带一个名为「尺码」的列,值是本地尺码码 P/M/G/GG(=S/M/L/XL),与行首尺码重复且非测量值;解析时整列丢弃。 其他非数字参考列(身高/体重/建议体重)保留。存量 2 条详情已修复 并重算受影响族。
124 lines
5.3 KiB
TypeScript
124 lines
5.3 KiB
TypeScript
import {
|
||
normalizeProductDetail,
|
||
parsePackageSpecs,
|
||
parseSizeChart,
|
||
} from './sds-product-detail.mapper';
|
||
|
||
const sizeTable = JSON.stringify([
|
||
['尺码', '衣长(cm/in)', '胸围(cm/in)', '肩宽(cm/in)', '袖长(cm/in)'].map((content) => ({ content, remark: '' })),
|
||
['S', '71', '92', '43', '22'].map((content) => ({ content, remark: '' })),
|
||
['M', '74', '102', '45', '22'].map((content) => ({ content, remark: '' })),
|
||
['L', '76', '112', '48', '23'].map((content) => ({ content, remark: '' })),
|
||
['XL', '79', '122', '51', '23'].map((content) => ({ content, remark: '' })),
|
||
['2XL', '82', '132', '53', '25'].map((content) => ({ content, remark: '' })),
|
||
['3XL', '84', '142', '56', '25'].map((content) => ({ content, remark: '' })),
|
||
]);
|
||
|
||
const packageTable = JSON.stringify([
|
||
['尺码', '包装尺寸(cm)', '包装尺寸(in)', '包装体积(cm³)', '包装体积(in³)', '含包装重量(g)', '含包装重量(lb)'].map((content) => ({ content })),
|
||
['S', '36.0*26.0*1.0\t', '14.17*10.24*0.39\t', '936.00', '57.12', '208.00', '0.46'].map((content) => ({ content })),
|
||
['M', '36.0*26.0*1.0', '14.17*10.24*0.39', '936.00', '57.12', '218.00', '0.48'].map((content) => ({ content })),
|
||
]);
|
||
|
||
describe('SDS product detail mapper', () => {
|
||
it('parses the product_detail.txt size table into structured rows', () => {
|
||
const chart = parseSizeChart(sizeTable) as any;
|
||
expect(chart.columns.map((column: any) => column.key)).toEqual([
|
||
'bodyLength',
|
||
'chest',
|
||
'shoulder',
|
||
'sleeveLength',
|
||
]);
|
||
expect(chart.rows).toHaveLength(6);
|
||
expect(chart.rows[0].measurements[0]).toEqual({ key: 'bodyLength', cm: '71', in: '27.95' });
|
||
});
|
||
|
||
it('drops the local size-code column (P/M/G/GG) named 尺码 when all values are non-numeric', () => {
|
||
// 巴西/西语市场的 SDS 上游在表头第二列再放一个「尺码」列,值是本地尺码码
|
||
// P/M/G/GG(=S/M/L/XL),与行首尺码重复且非测量值,应整列丢弃
|
||
const raw = JSON.stringify([
|
||
['尺码', '尺码(cm/in)', '肩宽(cm/in)', '胸围(cm/in)'].map((content) => ({ content, remark: '' })),
|
||
['S', 'P', '52', '106'].map((content) => ({ content, remark: '' })),
|
||
['M', 'M', '55', '112'].map((content) => ({ content, remark: '' })),
|
||
['L', 'G', '58', '118'].map((content) => ({ content, remark: '' })),
|
||
['XL', 'GG', '61', '124'].map((content) => ({ content, remark: '' })),
|
||
]);
|
||
const chart = parseSizeChart(raw) as any;
|
||
expect(chart.columns.map((c: any) => c.name)).toEqual(['肩宽', '胸围']);
|
||
expect(chart.rows[0].measurements.map((m: any) => m.key)).toEqual(['shoulder', 'chest']);
|
||
expect(chart.rows[0].measurements[0]).toEqual({ key: 'shoulder', cm: '52', in: '20.47' });
|
||
});
|
||
|
||
it('keeps a 尺码-named column when its values are numeric', () => {
|
||
const raw = JSON.stringify([
|
||
['尺码', '尺码(cm/in)', '胸围(cm/in)'].map((content) => ({ content, remark: '' })),
|
||
['S', '1', '106'].map((content) => ({ content, remark: '' })),
|
||
['M', '2', '112'].map((content) => ({ content, remark: '' })),
|
||
]);
|
||
const chart = parseSizeChart(raw) as any;
|
||
expect(chart.columns).toHaveLength(2);
|
||
expect(chart.rows[0].measurements[0].cm).toBe('1');
|
||
});
|
||
|
||
it('keeps non-numeric reference columns like 身高/体重', () => {
|
||
const raw = JSON.stringify([
|
||
['尺码', '身高(cm/in)', '体重(cm/in)'].map((content) => ({ content, remark: '' })),
|
||
['S', '50-60', '3-4kg'].map((content) => ({ content, remark: '' })),
|
||
['M', '60-70', '4-5kg'].map((content) => ({ content, remark: '' })),
|
||
]);
|
||
const chart = parseSizeChart(raw) as any;
|
||
expect(chart.columns.map((c: any) => c.name)).toEqual(['身高', '体重']);
|
||
});
|
||
|
||
it('parses packaging dimensions and weights', () => {
|
||
const specs = parsePackageSpecs(packageTable) as any;
|
||
expect(specs.rows).toHaveLength(2);
|
||
expect(specs.rows[0].dimensionsCm).toEqual({ length: '36.0', width: '26.0', height: '1.0' });
|
||
expect(specs.rows[0].grossWeightG).toBe('208.00');
|
||
});
|
||
|
||
it('normalizes detail text, options and variants', () => {
|
||
const normalized = normalizeProductDetail({
|
||
id: 168746,
|
||
sku: 'OZ10827003',
|
||
english_name: 't-shirt',
|
||
productionCycle: 24,
|
||
minWeight: 250,
|
||
product_details: {
|
||
production_process: '白墨烫画',
|
||
material_description: '100%纯棉',
|
||
product_size: sizeTable,
|
||
packaging_specification: packageTable,
|
||
},
|
||
subproducts: {
|
||
attributers: [{
|
||
size: 'S',
|
||
sizeId: 1922304,
|
||
colors: [{ colorId: 1139383, color: '#000300', color_name: 'black', colorSort: 1 }],
|
||
}],
|
||
items: [{
|
||
id: 168747,
|
||
sku: 'OZ10827003001',
|
||
size: 'S',
|
||
sizeId: 1922304,
|
||
colorId: 1139383,
|
||
color: { colorId: 1139383, color: '#000300', color_name: 'black' },
|
||
currentPrice: 38,
|
||
originalPrice: 38,
|
||
weight: 250,
|
||
box_length: 30,
|
||
box_width: 20,
|
||
box_height: 5,
|
||
status: 1,
|
||
delFlag: '0',
|
||
}],
|
||
},
|
||
});
|
||
|
||
expect(normalized.productCode).toBe('OZ10827003');
|
||
expect(normalized.productionProcess).toBe('白墨烫画');
|
||
expect(normalized.variants[0].sku).toBe('OZ10827003001');
|
||
expect(normalized.variants[0].price?.toString()).toBe('38');
|
||
});
|
||
});
|