fix(intake): prevent single sku master data gaps

This commit is contained in:
WuYongJie 2026-06-05 18:29:01 +08:00
parent 1892a5d579
commit f3c2b9bbee
2 changed files with 86 additions and 2 deletions

View File

@ -927,6 +927,72 @@ describe('intakeNonIntegratedProductService', () => {
)
})
it('分配正式 SPU 时用产品名和 SPU 补齐单 SKU 主数据', async () => {
const payload = createPayload({
id: 'draft-single-sku-master-data-autofill',
spu: '',
productCategory: '角几/边几',
productDevelopmentKind: 'product',
openingReference: {
productName: '禾沐边几(A款)',
productCategory: '角几/边几',
},
skuItems: [
createSkuItem({
skuSequence: 1,
specName: '',
model: '',
skuCode: '',
barcode: '',
}),
],
})
mockedSpuNumberReservationService.reserveInTransaction.mockResolvedValue({
productCategory: '角几/边几',
prefix: 'BJ',
index: 214,
spu: 'BJ0214',
})
mockedRepository.findDeletedBySpuInTransaction.mockResolvedValue(null as never)
mockedRepository.findBySpuInTransaction.mockResolvedValue(null as never)
mockedRepository.createInTransaction.mockResolvedValue(
createRecord({ id: 'BJ0214-1770000000000' }) as never,
)
mockedRepository.findActiveById.mockResolvedValue(
createRecord({
id: 'BJ0214-1770000000000',
spu: 'BJ0214',
spuBase: 'BJ0214',
payload: createPayload({ id: 'BJ0214-1770000000000', spu: 'BJ0214' }),
}) as never,
)
await intakeNonIntegratedProductService.save(payload, baseUser, {
formalSpuAssignmentProductCategory: '角几/边几',
})
const createArg = mockedRepository.createInTransaction.mock.calls[0]?.[1] as {
payload?: { skuItems?: Array<Record<string, unknown>> }
skuItems?: { createMany?: { data?: Array<Record<string, unknown>> } }
}
expect(createArg.payload?.skuItems?.[0]).toEqual(
expect.objectContaining({
specName: '禾沐边几(A款)',
model: 'BJ0214',
skuCode: 'BJ021401',
barcode: 'BJ021401N01V01',
}),
)
expect(createArg.skuItems?.createMany?.data?.[0]).toEqual(
expect.objectContaining({
specName: '禾沐边几(A款)',
model: 'BJ0214',
dataQualityStatus: 'READY',
missingRequiredFields: [],
}),
)
})
it('分配正式 SPU 时阻断未补齐的 SKU 主数据,避免生成新的地基阻塞', async () => {
const payload = createPayload({
id: 'draft-incomplete-before-formal-spu',

View File

@ -345,7 +345,13 @@ function shouldPreserveFormalSkuCode(skuCode: string, formalSpu: string) {
return Boolean(normalizedSkuCode && normalizedFormalSpu && normalizedSkuCode.startsWith(normalizedFormalSpu))
}
function assignFormalSpuToSkuItems(value: unknown, formalSpu: string) {
function assignFormalSpuToSkuItems(
value: unknown,
formalSpu: string,
options: {
fallbackSingleSkuSpecName?: string
} = {},
) {
if (!Array.isArray(value)) return value
return value.map((item, index) => {
@ -360,9 +366,19 @@ function assignFormalSpuToSkuItems(value: unknown, formalSpu: string) {
const barcode = existingBarcode && existingBarcode.startsWith(skuCode)
? existingBarcode
: buildAutoBarcode(skuCode, item.manufacturerCode, versionNo)
const shouldAutofillSingleSkuMasterData =
value.length === 1 && Boolean(readText(options.fallbackSingleSkuSpecName))
return {
...item,
specName:
readText(item.specName) ||
(shouldAutofillSingleSkuMasterData
? readText(options.fallbackSingleSkuSpecName)
: item.specName),
model:
readText(item.model) ||
(shouldAutofillSingleSkuMasterData ? formalSpu.trim().toUpperCase() : item.model),
skuCode,
barcode,
versionNo,
@ -4094,7 +4110,9 @@ class IntakeNonIntegratedProductService {
: assignedSpu,
brand: normalizedBrand,
formalSpuAssigned: true,
skuItems: assignFormalSpuToSkuItems(productInput.skuItems, assignedSpu),
skuItems: assignFormalSpuToSkuItems(productInput.skuItems, assignedSpu, {
fallbackSingleSkuSpecName: readText(openingReference.productName),
}),
openingReference: {
...openingReference,
brand: readText(openingReference.brand) || normalizedBrand,