Merge remote-tracking branch 'origin/codex/xuxiake-mainline' into codex/xuxiake-mainline
This commit is contained in:
commit
6cc339cdb5
@ -922,12 +922,12 @@ describe('intakeNonIntegratedProductService', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('分配正式 SPU 前必须补齐 SKU 主数据', async () => {
|
||||
it('分配正式 SPU 时允许 SKU 主数据在后续节点补齐', async () => {
|
||||
const payload = createPayload({
|
||||
id: 'draft-incomplete-before-formal-spu',
|
||||
spu: '',
|
||||
productCategory: '餐桌',
|
||||
productDevelopmentKind: '',
|
||||
productDevelopmentKind: 'product',
|
||||
openingReference: {
|
||||
productCategory: '餐桌',
|
||||
},
|
||||
@ -949,6 +949,60 @@ describe('intakeNonIntegratedProductService', () => {
|
||||
})
|
||||
mockedRepository.findDeletedBySpuInTransaction.mockResolvedValue(null as never)
|
||||
mockedRepository.findBySpuInTransaction.mockResolvedValue(null as never)
|
||||
mockedRepository.createInTransaction.mockResolvedValue(
|
||||
createRecord({ id: 'CZ0011-1770000000000' }) as never,
|
||||
)
|
||||
mockedRepository.findActiveById.mockResolvedValue(
|
||||
createRecord({
|
||||
id: 'CZ0011-1770000000000',
|
||||
spu: 'CZ0011',
|
||||
spuBase: 'CZ0011',
|
||||
payload: createPayload({ id: 'CZ0011-1770000000000', spu: 'CZ0011' }),
|
||||
}) as never,
|
||||
)
|
||||
|
||||
await intakeNonIntegratedProductService.save(payload, baseUser, {
|
||||
formalSpuAssignmentProductCategory: '餐桌',
|
||||
})
|
||||
|
||||
const createArg = mockedRepository.createInTransaction.mock.calls[0]?.[1] as {
|
||||
skuItems?: { createMany?: { data?: Array<Record<string, unknown>> } }
|
||||
}
|
||||
expect(createArg.skuItems?.createMany?.data?.[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
skuCode: 'CZ001101',
|
||||
barcode: 'CZ001101N01V01',
|
||||
model: '',
|
||||
specName: '',
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('分配正式 SPU 前仍必须补齐产品主数据', async () => {
|
||||
const payload = createPayload({
|
||||
id: 'draft-incomplete-product-before-formal-spu',
|
||||
spu: '',
|
||||
productCategory: '',
|
||||
productDevelopmentKind: 'product',
|
||||
openingReference: {
|
||||
productCategory: '',
|
||||
},
|
||||
skuItems: [
|
||||
createSkuItem({
|
||||
skuSequence: 1,
|
||||
skuCode: '',
|
||||
barcode: '',
|
||||
}),
|
||||
],
|
||||
})
|
||||
mockedSpuNumberReservationService.reserveInTransaction.mockResolvedValue({
|
||||
productCategory: '餐桌',
|
||||
prefix: 'CZ',
|
||||
index: 12,
|
||||
spu: 'CZ0012',
|
||||
})
|
||||
mockedRepository.findDeletedBySpuInTransaction.mockResolvedValue(null as never)
|
||||
mockedRepository.findBySpuInTransaction.mockResolvedValue(null as never)
|
||||
|
||||
const promise = intakeNonIntegratedProductService.save(payload, baseUser, {
|
||||
formalSpuAssignmentProductCategory: '餐桌',
|
||||
@ -957,7 +1011,7 @@ describe('intakeNonIntegratedProductService', () => {
|
||||
await expect(promise).rejects.toMatchObject({
|
||||
code: ErrorCode.VALIDATION_REQUIRED_FIELD,
|
||||
})
|
||||
await expect(promise).rejects.toThrow('正式 SPU 分配存在未补齐的 SKU 主数据')
|
||||
await expect(promise).rejects.toThrow('正式 SPU 分配存在未补齐的产品主数据')
|
||||
|
||||
expect(mockedRepository.createInTransaction).not.toHaveBeenCalled()
|
||||
expect(mockedRepository.updateInTransaction).not.toHaveBeenCalled()
|
||||
@ -1872,6 +1926,148 @@ describe('intakeNonIntegratedProductService', () => {
|
||||
expect(mockedRepository.update).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps downstream factory rows when a designer batch save carries derived factory changes', async () => {
|
||||
const existingFactoryRows = [
|
||||
{
|
||||
skuId: 'sku-1',
|
||||
confirmed: false,
|
||||
model: 'K01',
|
||||
skuCode: 'DNZ000101',
|
||||
setCount: '1',
|
||||
barcode: 'DNZ000101N1280V01',
|
||||
specName: '款式一',
|
||||
size: '',
|
||||
color: '',
|
||||
material: '',
|
||||
installMethod: '',
|
||||
supplierName: '供应商A',
|
||||
supplierCode: 'N1280',
|
||||
warehouse: '',
|
||||
minimumOrderQuantity: '',
|
||||
leadTime: '',
|
||||
assemblyMode: '',
|
||||
productionType: '',
|
||||
installationService: '',
|
||||
supplierCategory: '',
|
||||
purchaseOrderMethod: '',
|
||||
executeStandard: '',
|
||||
},
|
||||
]
|
||||
const existingPayload = createPayload({
|
||||
designer: 'Creator',
|
||||
openingReference: {
|
||||
designer: 'Creator',
|
||||
structureDesigner: 'Creator',
|
||||
},
|
||||
skuItems: [createSkuItem({ id: 'sku-1', skuCode: 'DNZ000101' })],
|
||||
skuBatchCount: 1,
|
||||
skuBatchFlowNos: {},
|
||||
skuBatchChangeTypes: {},
|
||||
factoryRows: existingFactoryRows,
|
||||
})
|
||||
const incomingPayload = createPayload({
|
||||
...existingPayload,
|
||||
skuBatchCount: 2,
|
||||
skuBatchFlowNos: { '2': 'FLOW-002' },
|
||||
skuBatchChangeTypes: { '2': '新增' },
|
||||
factoryRows: existingFactoryRows.map((row) => ({
|
||||
...row,
|
||||
supplierName: '供应商B',
|
||||
})),
|
||||
})
|
||||
|
||||
mockedRepository.findBySpu.mockResolvedValue(null as never)
|
||||
mockedRepository.findById.mockResolvedValue(
|
||||
createRecord({ payload: existingPayload }) as never,
|
||||
)
|
||||
mockedRepository.update.mockResolvedValue(
|
||||
createRecord({ payload: incomingPayload }) as never,
|
||||
)
|
||||
mockedRepository.findActiveById.mockResolvedValue(
|
||||
createRecord({ payload: incomingPayload }) as never,
|
||||
)
|
||||
|
||||
await intakeNonIntegratedProductService.save(
|
||||
incomingPayload,
|
||||
baseUser,
|
||||
{ workspaceCategory: '产品定厂' },
|
||||
)
|
||||
|
||||
const updatePayload = mockedRepository.update.mock.calls[0]?.[1]?.payload as
|
||||
| Record<string, unknown>
|
||||
| undefined
|
||||
expect(updatePayload).toEqual(
|
||||
expect.objectContaining({
|
||||
skuBatchCount: 2,
|
||||
skuBatchFlowNos: { '2': 'FLOW-002' },
|
||||
skuBatchChangeTypes: { '2': '新增' },
|
||||
factoryRows: existingFactoryRows,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it('treats writable workspace autosaves with only downstream derived changes as no-ops', async () => {
|
||||
const existingFactoryRows = [
|
||||
{
|
||||
skuId: 'sku-1',
|
||||
confirmed: false,
|
||||
model: 'K01',
|
||||
skuCode: 'DNZ000101',
|
||||
setCount: '1',
|
||||
barcode: 'DNZ000101N1280V01',
|
||||
specName: '款式一',
|
||||
size: '',
|
||||
color: '',
|
||||
material: '',
|
||||
installMethod: '',
|
||||
supplierName: '供应商A',
|
||||
supplierCode: 'N1280',
|
||||
warehouse: '',
|
||||
minimumOrderQuantity: '',
|
||||
leadTime: '',
|
||||
assemblyMode: '',
|
||||
productionType: '',
|
||||
installationService: '',
|
||||
supplierCategory: '',
|
||||
purchaseOrderMethod: '',
|
||||
executeStandard: '',
|
||||
},
|
||||
]
|
||||
const existingPayload = createPayload({
|
||||
designer: 'Creator',
|
||||
openingReference: {
|
||||
designer: 'Creator',
|
||||
structureDesigner: 'Creator',
|
||||
},
|
||||
factoryRows: existingFactoryRows,
|
||||
})
|
||||
const incomingPayload = createPayload({
|
||||
...existingPayload,
|
||||
factoryRows: existingFactoryRows.map((row) => ({
|
||||
...row,
|
||||
supplierName: '供应商B',
|
||||
})),
|
||||
})
|
||||
|
||||
mockedRepository.findBySpu.mockResolvedValue(null as never)
|
||||
mockedRepository.findById.mockResolvedValue(
|
||||
createRecord({ payload: existingPayload }) as never,
|
||||
)
|
||||
mockedRepository.findActiveById.mockResolvedValue(
|
||||
createRecord({ payload: existingPayload }) as never,
|
||||
)
|
||||
|
||||
await expect(
|
||||
intakeNonIntegratedProductService.save(
|
||||
incomingPayload,
|
||||
baseUser,
|
||||
{ workspaceCategory: '基础信息' },
|
||||
),
|
||||
).resolves.toEqual(expect.objectContaining({ id: 'non-integrated-1' }))
|
||||
|
||||
expect(mockedRepository.update).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('allows space visual department users to save planning layout through the shared save API', async () => {
|
||||
const skuItems = [
|
||||
{
|
||||
|
||||
@ -40,7 +40,7 @@ import {
|
||||
preSkuRepository,
|
||||
} from '../repositories/draft-group.repository'
|
||||
import {
|
||||
assertNonIntegratedProductMasterDataReadyForFormalUse,
|
||||
assertNonIntegratedProductProfileReadyForFormalUse,
|
||||
resolveNonIntegratedSkuQuality,
|
||||
} from './non-integrated-sku-quality'
|
||||
import {
|
||||
@ -1455,6 +1455,25 @@ function getRequestedWritableCategories(
|
||||
return category ? new Set<NonIntegratedWorkspaceCategoryKey>([category]) : fallbackCategories
|
||||
}
|
||||
|
||||
function getWritableChangedCategories(
|
||||
changedCategories: Set<NonIntegratedWorkspaceCategoryKey>,
|
||||
writableCategories: Set<NonIntegratedWorkspaceCategoryKey>,
|
||||
) {
|
||||
return new Set(
|
||||
Array.from(changedCategories).filter((category) =>
|
||||
writableCategories.has(category),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
function canTreatRequestedWorkspaceSaveAsNoop(
|
||||
requestedCategory: string | null | undefined,
|
||||
writableCategories: Set<NonIntegratedWorkspaceCategoryKey>,
|
||||
) {
|
||||
const category = toDocumentWorkspaceCategory(requestedCategory ?? undefined)
|
||||
return Boolean(category && writableCategories.has(category))
|
||||
}
|
||||
|
||||
function isExplicitPlanningLayoutWriteRequest(
|
||||
requestedCategory: string | null | undefined,
|
||||
) {
|
||||
@ -1618,6 +1637,10 @@ async function prepareProductForPermittedWrite(
|
||||
|
||||
deniedChangedCategories.push(category)
|
||||
}
|
||||
const effectivePermittedCategories =
|
||||
permittedCategories.size > 0
|
||||
? permittedCategories
|
||||
: getWritableChangedCategories(changedCategories, writableCategories)
|
||||
|
||||
const preserveOptions = {
|
||||
allowProductFactorySkuSync:
|
||||
@ -1629,7 +1652,7 @@ async function prepareProductForPermittedWrite(
|
||||
existingProduct,
|
||||
nextProduct,
|
||||
changedCategories,
|
||||
permittedCategories,
|
||||
effectivePermittedCategories,
|
||||
preserveOptions,
|
||||
)
|
||||
) {
|
||||
@ -1638,6 +1661,11 @@ async function prepareProductForPermittedWrite(
|
||||
options.workspaceCategory,
|
||||
writableCategories,
|
||||
) ||
|
||||
(effectivePermittedCategories.size === 0 &&
|
||||
canTreatRequestedWorkspaceSaveAsNoop(
|
||||
options.workspaceCategory,
|
||||
writableCategories,
|
||||
)) ||
|
||||
canTreatStockReadonlyCompatibilitySaveAsNoop(
|
||||
isStockProduct,
|
||||
changedCategories,
|
||||
@ -1664,10 +1692,7 @@ async function prepareProductForPermittedWrite(
|
||||
}
|
||||
|
||||
const categoriesToPreserve = TRACKED_WORKSPACE_CATEGORIES.filter(
|
||||
(category) =>
|
||||
!writableCategories.has(category) ||
|
||||
!requestedWritableCategories.has(category) ||
|
||||
!changedCategories.has(category),
|
||||
(category) => !effectivePermittedCategories.has(category),
|
||||
)
|
||||
const scopedNextProduct = categoriesToPreserve.reduce(
|
||||
(currentProduct, category) =>
|
||||
@ -4149,7 +4174,7 @@ class IntakeNonIntegratedProductService {
|
||||
if (!normalizedProduct) {
|
||||
throw new AppError(ErrorCode.VALIDATION_FAILED, '非集成产品数据不完整,无法保存')
|
||||
}
|
||||
assertNonIntegratedProductMasterDataReadyForFormalUse(normalizedProduct, {
|
||||
assertNonIntegratedProductProfileReadyForFormalUse(normalizedProduct, {
|
||||
usageLabel: '正式 SPU 分配',
|
||||
})
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user