fix(intake): allow stock planning layout save noops

This commit is contained in:
WuYongJie 2026-05-28 11:02:33 +08:00
parent d8c08bd625
commit 6d6e4b299d
2 changed files with 116 additions and 0 deletions

View File

@ -955,6 +955,93 @@ describe('intakeNonIntegratedProductService', () => {
expect(mockedRepository.update).not.toHaveBeenCalled()
})
it('treats stock planning layout manual saves with only readonly legacy differences as successful no-ops', async () => {
const existingPayload = createPayload({
id: 'stock-planning-layout-manual-save',
spu: 'GBPM-AD0302',
productCode: 'GBPM-AD0302',
processTemplate: '存量商品',
brand: '基本盘',
adaptableBrands: ['基本盘'],
openingReference: {
brand: '基本盘',
adaptableBrands: ['基本盘'],
currentStage: '存量商品',
},
patentApplication: {
patentName: '',
},
planningLayoutRows: [
{
skuId: 'stock-sku-1',
styles: ['中古风'],
styleTones: [],
audiences: [],
spaces: ['餐厅'],
categories: [],
brands: ['自有品牌-朴美'],
model: '',
priceBand: '',
shop: '',
},
],
})
const incomingPayload = createPayload({
...existingPayload,
brand: '其他品牌-普通大货',
adaptableBrands: ['其他品牌-普通大货'],
openingReference: {
...(existingPayload.openingReference as Record<string, unknown>),
brand: '其他品牌-普通大货',
adaptableBrands: ['其他品牌-普通大货'],
},
patentApplication: {
...(existingPayload.patentApplication as Record<string, unknown>),
patentName: '不应写入的只读差异',
},
})
mockedRepository.findBySpu.mockResolvedValue(null as never)
mockedRepository.findById.mockResolvedValue(
createRecord({
id: 'stock-planning-layout-manual-save',
spu: 'GBPM-AD0302',
payload: existingPayload,
}) as never,
)
mockedRepository.findActiveById.mockResolvedValue(
createRecord({
id: 'stock-planning-layout-manual-save',
spu: 'GBPM-AD0302',
payload: existingPayload,
}) as never,
)
await expect(
intakeNonIntegratedProductService.save(
incomingPayload,
{
...baseUser,
id: 'space-visual-1',
email: 'space@example.com',
name: '希文',
businessRoles: ['PLANNING'] as const,
department: {
id: 'space-dept',
name: '空间视觉部',
fullPath: '江苏简尚品牌管理有限公司 > 品牌中心 > 空间视觉部',
},
departmentPaths: [
'江苏简尚品牌管理有限公司 > 品牌中心 > 空间视觉部',
],
},
{ workspaceCategory: '企划布局' },
),
).resolves.toEqual(expect.objectContaining({ id: 'stock-planning-layout-manual-save' }))
expect(mockedRepository.update).not.toHaveBeenCalled()
})
it('keeps existing BOM details when an autosave submits an empty BOM payload', async () => {
const existingBomDetails = [
{

View File

@ -1324,6 +1324,13 @@ function getRequestedWritableCategories(
return category ? new Set<NonIntegratedWorkspaceCategoryKey>([category]) : fallbackCategories
}
function isExplicitPlanningLayoutWriteRequest(
requestedCategory: string | null | undefined,
) {
return toDocumentWorkspaceCategory(requestedCategory ?? undefined) ===
PLANNING_LAYOUT_WORKSPACE_CATEGORY
}
function throwNonIntegratedWriteDenied(): never {
throw new AppError(ErrorCode.AUTH_PERMISSION_DENIED, '没有权限编辑这条非集成产品记录')
}
@ -1436,6 +1443,17 @@ async function prepareProductForPermittedWrite(
)
) {
if (isStockProduct) {
if (
isExplicitPlanningLayoutWriteRequest(options.workspaceCategory) &&
writableCategories.has(PLANNING_LAYOUT_WORKSPACE_CATEGORY)
) {
return {
product: existingProduct,
preserveRecordColumns: true,
skipUpdate: true,
}
}
throwNonIntegratedWriteDenied()
}
@ -1470,6 +1488,17 @@ async function prepareProductForPermittedWrite(
if (scopedChangedCategories.size === 0) {
if (isStockProduct) {
if (
isExplicitPlanningLayoutWriteRequest(options.workspaceCategory) &&
writableCategories.has(PLANNING_LAYOUT_WORKSPACE_CATEGORY)
) {
return {
product: existingProduct,
preserveRecordColumns: true,
skipUpdate: true,
}
}
throwNonIntegratedWriteDenied()
}