fix(intake): 按节点校验开品保存权限
This commit is contained in:
parent
603b114345
commit
4f7c7cdf45
@ -2510,6 +2510,9 @@ describe('IntakeClient', () => {
|
||||
expect(pendingRow).not.toBeNull()
|
||||
expect(within(checkedRow as HTMLElement).getByText('设计卖点')).toBeInTheDocument()
|
||||
expect(within(pendingRow as HTMLElement).getByText('开品参考')).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByRole('columnheader', { name: '开品参考 外观设计师' }),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByRole('columnheader', { name: '产品实拍 结构设计师' }),
|
||||
).toBeInTheDocument()
|
||||
|
||||
@ -489,7 +489,7 @@ const NON_INTEGRATED_FLOW_COMPLETE_CATEGORY =
|
||||
const NON_INTEGRATED_NODE_OWNER_LABELS: Partial<
|
||||
Record<NonIntegratedDocumentStatusCategory, string>
|
||||
> = {
|
||||
开品参考: "商品企划",
|
||||
开品参考: "外观设计师",
|
||||
设计卖点: "外观设计师",
|
||||
产品模型: "外观/结构",
|
||||
评审资料: "设计/采购",
|
||||
|
||||
@ -261,6 +261,9 @@ describe('intakeNonIntegratedProductService', () => {
|
||||
mockedRepository.findById.mockResolvedValue(
|
||||
createRecord({
|
||||
createdBy: 'creator-2',
|
||||
payload: createPayload({
|
||||
designer: 'Teammate',
|
||||
}),
|
||||
}) as never,
|
||||
)
|
||||
mockedRepository.update.mockResolvedValue(createRecord() as never)
|
||||
@ -286,6 +289,150 @@ describe('intakeNonIntegratedProductService', () => {
|
||||
expect(mockedRepository.update).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('rejects purchasers from confirming product photo through the shared save API', async () => {
|
||||
const existingConfirmations = {
|
||||
...createDefaultDocumentWorkspaceConfirmations(),
|
||||
产品实拍图: false,
|
||||
}
|
||||
const existingPayload = createPayload({
|
||||
designer: '设计A',
|
||||
purchaser: '采购A',
|
||||
purchaseOwner: '采购A',
|
||||
uploadFiles: [
|
||||
{
|
||||
id: 'photo-1',
|
||||
name: '正视图.png',
|
||||
category: '产品实拍图',
|
||||
dataUrl: 'https://s3.example.com/photo-1.png',
|
||||
mimeType: 'image/png',
|
||||
},
|
||||
],
|
||||
documentWorkspaceConfirmations: existingConfirmations,
|
||||
})
|
||||
const incomingPayload = createPayload({
|
||||
...existingPayload,
|
||||
documentWorkspaceConfirmations: {
|
||||
...existingConfirmations,
|
||||
产品实拍图: true,
|
||||
},
|
||||
documentWorkspaceApprovedAt: {
|
||||
产品实拍图: '2026-05-21T01:00:00.000Z',
|
||||
},
|
||||
})
|
||||
|
||||
mockedRepository.findBySpu.mockResolvedValue(null as never)
|
||||
mockedRepository.findById.mockResolvedValue(
|
||||
createRecord({
|
||||
payload: existingPayload,
|
||||
}) as never,
|
||||
)
|
||||
|
||||
await expect(
|
||||
intakeNonIntegratedProductService.save(incomingPayload, {
|
||||
...baseUser,
|
||||
id: 'purchase-1',
|
||||
email: 'purchase@example.com',
|
||||
name: '采购A',
|
||||
businessRoles: [] as const,
|
||||
departmentPaths: [
|
||||
'江苏简尚品牌管理有限公司 > 供应链中心 > 基本盘 采购部 > 采购一部',
|
||||
],
|
||||
}),
|
||||
).rejects.toThrow('没有权限编辑这条非集成产品记录')
|
||||
|
||||
expect(mockedRepository.update).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('allows purchase owners to confirm product factory through the shared save API', async () => {
|
||||
const existingConfirmations = {
|
||||
...createDefaultDocumentWorkspaceConfirmations(),
|
||||
产品定厂: false,
|
||||
}
|
||||
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: '设计A',
|
||||
purchaser: '采购A',
|
||||
purchaseOwner: '采购A',
|
||||
factoryRows: existingFactoryRows,
|
||||
documentWorkspaceConfirmations: existingConfirmations,
|
||||
})
|
||||
const incomingPayload = createPayload({
|
||||
...existingPayload,
|
||||
factoryRows: existingFactoryRows.map((row) => ({
|
||||
...row,
|
||||
confirmed: true,
|
||||
})),
|
||||
documentWorkspaceConfirmations: {
|
||||
...existingConfirmations,
|
||||
产品定厂: true,
|
||||
},
|
||||
documentWorkspaceApprovedAt: {
|
||||
产品定厂: '2026-05-21T02:00:00.000Z',
|
||||
},
|
||||
})
|
||||
|
||||
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 expect(
|
||||
intakeNonIntegratedProductService.save(incomingPayload, {
|
||||
...baseUser,
|
||||
id: 'purchase-1',
|
||||
email: 'purchase@example.com',
|
||||
name: '采购A',
|
||||
businessRoles: [] as const,
|
||||
departmentPaths: [
|
||||
'江苏简尚品牌管理有限公司 > 供应链中心 > 基本盘 采购部 > 采购一部',
|
||||
],
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
documentWorkspaceConfirmations: expect.objectContaining({
|
||||
产品定厂: true,
|
||||
}),
|
||||
})
|
||||
|
||||
expect(mockedRepository.update).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('keeps existing BOM details when an autosave submits an empty BOM payload', async () => {
|
||||
const existingBomDetails = [
|
||||
{
|
||||
@ -399,7 +546,11 @@ describe('intakeNonIntegratedProductService', () => {
|
||||
})
|
||||
|
||||
mockedRepository.findBySpu.mockResolvedValue(null as never)
|
||||
mockedRepository.findById.mockResolvedValue(createRecord() as never)
|
||||
mockedRepository.findById.mockResolvedValue(
|
||||
createRecord({
|
||||
payload,
|
||||
}) as never,
|
||||
)
|
||||
mockedRepository.update.mockResolvedValue(createRecord() as never)
|
||||
mockedRepository.findActiveById.mockResolvedValue(
|
||||
createRecord({
|
||||
@ -433,6 +584,7 @@ describe('intakeNonIntegratedProductService', () => {
|
||||
id: 'viewer-1',
|
||||
email: 'viewer@example.com',
|
||||
name: 'Viewer',
|
||||
role: 'ADMIN',
|
||||
}),
|
||||
).resolves.toBeUndefined()
|
||||
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { randomUUID } from 'crypto'
|
||||
import { Prisma } from '@prisma/client'
|
||||
import {
|
||||
NON_INTEGRATED_BASIC_INFO_WORKSPACE_CATEGORY,
|
||||
NON_INTEGRATED_DUPLICATE_SPU_MESSAGE,
|
||||
NON_INTEGRATED_WORKFLOW_NODE_CATEGORIES,
|
||||
createDefaultDocumentWorkspaceConfirmations as createSharedDocumentWorkspaceConfirmations,
|
||||
normalizeNonIntegratedProductRecord,
|
||||
resolveNonIntegratedPersistedSpu,
|
||||
@ -90,6 +92,8 @@ type IntakeNonIntegratedRecord = NonNullable<
|
||||
Awaited<ReturnType<IntakeNonIntegratedProductRepositoryClass['findActiveById']>>
|
||||
>
|
||||
|
||||
type NonIntegratedWorkspaceCategoryKey = keyof NonIntegratedDocumentWorkspaceConfirmationMap
|
||||
|
||||
export type IntakeNonIntegratedProductListParams = {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
@ -114,6 +118,76 @@ export type IntakeNonIntegratedProductListParams = {
|
||||
const DEFAULT_LIST_PAGE_SIZE = 50
|
||||
const MAX_LIST_PAGE_SIZE = 100
|
||||
|
||||
const OPENING_REFERENCE_WORKSPACE_CATEGORY = '开品参考'
|
||||
const PROJECT_REVIEW_WORKSPACE_CATEGORY = '立项评审'
|
||||
const PRODUCT_DATA_WORKSPACE_CATEGORY = '产品模型'
|
||||
const REVIEW_WORKSPACE_CATEGORY = '评审资料'
|
||||
const PATENT_APPLICATION_WORKSPACE_CATEGORY = '专利申请'
|
||||
const INSTALLATION_WORKSPACE_CATEGORY = '安装方式'
|
||||
const PACKAGING_WORKSPACE_CATEGORY = '包装细节'
|
||||
const PRODUCT_FACTORY_WORKSPACE_CATEGORY = '产品定厂'
|
||||
const SAMPLE_MEASUREMENT_WORKSPACE_CATEGORY = '样包测量'
|
||||
const FINISHED_SAMPLE_SHIPMENT_WORKSPACE_CATEGORY = '成品寄样'
|
||||
const PRODUCT_PRICING_WORKSPACE_CATEGORY = '产品核价'
|
||||
const PRODUCT_LIMIT_WORKSPACE_CATEGORY = '产品限价'
|
||||
const PLANNING_LAYOUT_WORKSPACE_CATEGORY = '企划布局'
|
||||
const FACTORY_QUALITY_WORKSPACE_CATEGORY = '工厂质检'
|
||||
|
||||
const BASIC_INFO_FIELD_KEYS = [
|
||||
'productDevelopmentKind',
|
||||
'spu',
|
||||
'year',
|
||||
'demandFlowNo',
|
||||
'processTemplate',
|
||||
'productCategory',
|
||||
'salesCategory',
|
||||
'productPlanning',
|
||||
'demandDepartment',
|
||||
'isSelfDevelopedPatentProduct',
|
||||
'patentOwner',
|
||||
'spuOwner',
|
||||
'needsClearanceHandling',
|
||||
'operationsOwner',
|
||||
'profitCenter',
|
||||
'salesChannel',
|
||||
'salesPlatform',
|
||||
'purchaseOwner',
|
||||
'goodsType',
|
||||
'materialType',
|
||||
'productionType',
|
||||
'manualProducer',
|
||||
'shippingMode',
|
||||
'visualDirector',
|
||||
'itemId',
|
||||
'priceBand',
|
||||
'outputRequirementType',
|
||||
'visualPlanning',
|
||||
'visualPlanningType',
|
||||
'photographer',
|
||||
'graphicDesigner',
|
||||
'designer3d',
|
||||
'sampleRequired',
|
||||
'flatOutputPath',
|
||||
'output3dPath',
|
||||
'brand',
|
||||
'adaptableBrands',
|
||||
'designer',
|
||||
'operator',
|
||||
'purchaser',
|
||||
'previewImage',
|
||||
'previewImageName',
|
||||
'productCode',
|
||||
'size',
|
||||
'color',
|
||||
'tagMaterial',
|
||||
'formalSpuAssigned',
|
||||
'skuItems',
|
||||
'skuBatchCount',
|
||||
'skuBatchFlowNos',
|
||||
'skuBatchChangeTypes',
|
||||
'skuBatchConfirmedAt',
|
||||
] as const
|
||||
|
||||
type ResolveUploadFileForPreviewResult = {
|
||||
record: IntakeNonIntegratedRecord
|
||||
product: SharedNonIntegratedProductRecord
|
||||
@ -196,6 +270,352 @@ function normalizeDocumentWorkspaceNotes(value: unknown) {
|
||||
return Object.keys(nextNotes).length > 0 ? nextNotes : undefined
|
||||
}
|
||||
|
||||
function normalizeComparableValue(value: unknown): unknown {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(normalizeComparableValue)
|
||||
}
|
||||
|
||||
if (!isRecord(value)) {
|
||||
return value ?? null
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.keys(value)
|
||||
.filter((key) => value[key] !== undefined)
|
||||
.sort()
|
||||
.map((key) => [key, normalizeComparableValue(value[key])]),
|
||||
)
|
||||
}
|
||||
|
||||
function stableStringify(value: unknown) {
|
||||
return JSON.stringify(normalizeComparableValue(value))
|
||||
}
|
||||
|
||||
function comparableEquals(left: unknown, right: unknown) {
|
||||
return stableStringify(left) === stableStringify(right)
|
||||
}
|
||||
|
||||
function pickProductFields(
|
||||
product: SharedNonIntegratedProductRecord,
|
||||
keys: readonly string[],
|
||||
) {
|
||||
return Object.fromEntries(keys.map((key) => [key, product[key]]))
|
||||
}
|
||||
|
||||
function styleDetailsByStyleKey(product: SharedNonIntegratedProductRecord) {
|
||||
return Object.fromEntries(
|
||||
(product.styleDetails ?? []).map((detail) => [detail.styleKey, detail]),
|
||||
)
|
||||
}
|
||||
|
||||
function buildInstallationStyleSignature(product: SharedNonIntegratedProductRecord) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(styleDetailsByStyleKey(product)).map(([styleKey, detail]) => [
|
||||
styleKey,
|
||||
{
|
||||
installMethod: detail.installMethod,
|
||||
installSteps: detail.installSteps,
|
||||
},
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
function buildPackagingStyleSignature(product: SharedNonIntegratedProductRecord) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(styleDetailsByStyleKey(product)).map(([styleKey, detail]) => [
|
||||
styleKey,
|
||||
{
|
||||
packageDetail: detail.packageDetail,
|
||||
craftDetail: detail.craftDetail,
|
||||
outerPackagingType: detail.outerPackagingType,
|
||||
innerPackagingItems: detail.innerPackagingItems,
|
||||
packagingModel3dFileName: detail.packagingModel3dFileName,
|
||||
packagingModel3dDataUrl: detail.packagingModel3dDataUrl,
|
||||
packagingModel3dNodeOffsets: detail.packagingModel3dNodeOffsets,
|
||||
packagingModel3dNodeRotations: detail.packagingModel3dNodeRotations,
|
||||
},
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
function buildProductDataStyleSignature(product: SharedNonIntegratedProductRecord) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(styleDetailsByStyleKey(product)).map(([styleKey, detail]) => [
|
||||
styleKey,
|
||||
{
|
||||
model3dFileName: detail.model3dFileName,
|
||||
model3dDataUrl: detail.model3dDataUrl,
|
||||
model3dNodeOffsets: detail.model3dNodeOffsets,
|
||||
model3dNodeRotations: detail.model3dNodeRotations,
|
||||
},
|
||||
]),
|
||||
)
|
||||
}
|
||||
|
||||
function addWorkspaceCategory(
|
||||
categories: Set<NonIntegratedWorkspaceCategoryKey>,
|
||||
category: string | undefined | null,
|
||||
) {
|
||||
const resolvedCategory = toDocumentWorkspaceCategory(category ?? undefined)
|
||||
if (resolvedCategory) {
|
||||
categories.add(resolvedCategory)
|
||||
}
|
||||
}
|
||||
|
||||
function addChangedWorkspaceCategory(
|
||||
categories: Set<NonIntegratedWorkspaceCategoryKey>,
|
||||
category: string,
|
||||
existingValue: unknown,
|
||||
nextValue: unknown,
|
||||
) {
|
||||
if (!comparableEquals(existingValue, nextValue)) {
|
||||
addWorkspaceCategory(categories, category)
|
||||
}
|
||||
}
|
||||
|
||||
function getUploadFileCategories(product: SharedNonIntegratedProductRecord) {
|
||||
return new Set(
|
||||
product.uploadFiles
|
||||
.map((file) => toDocumentWorkspaceCategory(file.category))
|
||||
.filter((category): category is NonIntegratedWorkspaceCategoryKey => Boolean(category)),
|
||||
)
|
||||
}
|
||||
|
||||
function uploadFilesForCategory(
|
||||
product: SharedNonIntegratedProductRecord,
|
||||
category: NonIntegratedWorkspaceCategoryKey,
|
||||
) {
|
||||
return product.uploadFiles.filter(
|
||||
(file) => toDocumentWorkspaceCategory(file.category) === category,
|
||||
)
|
||||
}
|
||||
|
||||
function uncategorizedUploadFiles(product: SharedNonIntegratedProductRecord) {
|
||||
return product.uploadFiles.filter(
|
||||
(file) => !toDocumentWorkspaceCategory(file.category),
|
||||
)
|
||||
}
|
||||
|
||||
function addChangedUploadWorkspaceCategories(
|
||||
categories: Set<NonIntegratedWorkspaceCategoryKey>,
|
||||
existingProduct: SharedNonIntegratedProductRecord,
|
||||
nextProduct: SharedNonIntegratedProductRecord,
|
||||
) {
|
||||
const uploadCategories = new Set([
|
||||
...getUploadFileCategories(existingProduct),
|
||||
...getUploadFileCategories(nextProduct),
|
||||
])
|
||||
|
||||
uploadCategories.forEach((category) => {
|
||||
if (
|
||||
!comparableEquals(
|
||||
uploadFilesForCategory(existingProduct, category),
|
||||
uploadFilesForCategory(nextProduct, category),
|
||||
)
|
||||
) {
|
||||
categories.add(category)
|
||||
}
|
||||
})
|
||||
|
||||
if (
|
||||
!comparableEquals(
|
||||
uncategorizedUploadFiles(existingProduct),
|
||||
uncategorizedUploadFiles(nextProduct),
|
||||
)
|
||||
) {
|
||||
categories.add(PRODUCT_DATA_WORKSPACE_CATEGORY)
|
||||
}
|
||||
}
|
||||
|
||||
function addChangedDocumentWorkspaceMapCategories(
|
||||
categories: Set<NonIntegratedWorkspaceCategoryKey>,
|
||||
existingProduct: SharedNonIntegratedProductRecord,
|
||||
nextProduct: SharedNonIntegratedProductRecord,
|
||||
) {
|
||||
NON_INTEGRATED_WORKFLOW_NODE_CATEGORIES.forEach((category) => {
|
||||
const existingConfirmed = existingProduct.documentWorkspaceConfirmations[category]
|
||||
const nextConfirmed = nextProduct.documentWorkspaceConfirmations[category]
|
||||
const existingApprovedAt = existingProduct.documentWorkspaceApprovedAt?.[category] ?? ''
|
||||
const nextApprovedAt = nextProduct.documentWorkspaceApprovedAt?.[category] ?? ''
|
||||
|
||||
if (existingConfirmed !== nextConfirmed || existingApprovedAt !== nextApprovedAt) {
|
||||
addWorkspaceCategory(categories, category)
|
||||
}
|
||||
})
|
||||
|
||||
const noteCategories = new Set([
|
||||
...Object.keys(existingProduct.documentWorkspaceNotes ?? {}),
|
||||
...Object.keys(nextProduct.documentWorkspaceNotes ?? {}),
|
||||
])
|
||||
|
||||
noteCategories.forEach((category) => {
|
||||
const resolvedCategory = toDocumentWorkspaceCategory(category)
|
||||
if (
|
||||
resolvedCategory &&
|
||||
(existingProduct.documentWorkspaceNotes?.[category] ?? '') !==
|
||||
(nextProduct.documentWorkspaceNotes?.[category] ?? '')
|
||||
) {
|
||||
categories.add(resolvedCategory)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getChangedWorkspaceCategories(
|
||||
existingProduct: SharedNonIntegratedProductRecord,
|
||||
nextProduct: SharedNonIntegratedProductRecord,
|
||||
) {
|
||||
const categories = new Set<NonIntegratedWorkspaceCategoryKey>()
|
||||
|
||||
addChangedDocumentWorkspaceMapCategories(categories, existingProduct, nextProduct)
|
||||
addChangedUploadWorkspaceCategories(categories, existingProduct, nextProduct)
|
||||
|
||||
addChangedWorkspaceCategory(
|
||||
categories,
|
||||
NON_INTEGRATED_BASIC_INFO_WORKSPACE_CATEGORY,
|
||||
pickProductFields(existingProduct, BASIC_INFO_FIELD_KEYS),
|
||||
pickProductFields(nextProduct, BASIC_INFO_FIELD_KEYS),
|
||||
)
|
||||
addChangedWorkspaceCategory(
|
||||
categories,
|
||||
OPENING_REFERENCE_WORKSPACE_CATEGORY,
|
||||
existingProduct.openingReference,
|
||||
nextProduct.openingReference,
|
||||
)
|
||||
addChangedWorkspaceCategory(
|
||||
categories,
|
||||
PROJECT_REVIEW_WORKSPACE_CATEGORY,
|
||||
{
|
||||
projectReview: existingProduct.projectReview,
|
||||
projectStatus: existingProduct.projectStatus,
|
||||
},
|
||||
{
|
||||
projectReview: nextProduct.projectReview,
|
||||
projectStatus: nextProduct.projectStatus,
|
||||
},
|
||||
)
|
||||
addChangedWorkspaceCategory(
|
||||
categories,
|
||||
REVIEW_WORKSPACE_CATEGORY,
|
||||
{
|
||||
reviewSheet: existingProduct.reviewSheet,
|
||||
reviewSheets: existingProduct.reviewSheets,
|
||||
},
|
||||
{
|
||||
reviewSheet: nextProduct.reviewSheet,
|
||||
reviewSheets: nextProduct.reviewSheets,
|
||||
},
|
||||
)
|
||||
addChangedWorkspaceCategory(
|
||||
categories,
|
||||
PATENT_APPLICATION_WORKSPACE_CATEGORY,
|
||||
existingProduct.patentApplication,
|
||||
nextProduct.patentApplication,
|
||||
)
|
||||
addChangedWorkspaceCategory(
|
||||
categories,
|
||||
PRODUCT_DATA_WORKSPACE_CATEGORY,
|
||||
buildProductDataStyleSignature(existingProduct),
|
||||
buildProductDataStyleSignature(nextProduct),
|
||||
)
|
||||
addChangedWorkspaceCategory(
|
||||
categories,
|
||||
INSTALLATION_WORKSPACE_CATEGORY,
|
||||
buildInstallationStyleSignature(existingProduct),
|
||||
buildInstallationStyleSignature(nextProduct),
|
||||
)
|
||||
addChangedWorkspaceCategory(
|
||||
categories,
|
||||
PACKAGING_WORKSPACE_CATEGORY,
|
||||
buildPackagingStyleSignature(existingProduct),
|
||||
buildPackagingStyleSignature(nextProduct),
|
||||
)
|
||||
addChangedWorkspaceCategory(
|
||||
categories,
|
||||
PRODUCT_FACTORY_WORKSPACE_CATEGORY,
|
||||
existingProduct.factoryRows,
|
||||
nextProduct.factoryRows,
|
||||
)
|
||||
addChangedWorkspaceCategory(
|
||||
categories,
|
||||
SAMPLE_MEASUREMENT_WORKSPACE_CATEGORY,
|
||||
existingProduct.sampleMeasurementRows,
|
||||
nextProduct.sampleMeasurementRows,
|
||||
)
|
||||
addChangedWorkspaceCategory(
|
||||
categories,
|
||||
FINISHED_SAMPLE_SHIPMENT_WORKSPACE_CATEGORY,
|
||||
existingProduct.finishedSampleShipmentRows,
|
||||
nextProduct.finishedSampleShipmentRows,
|
||||
)
|
||||
addChangedWorkspaceCategory(
|
||||
categories,
|
||||
PRODUCT_PRICING_WORKSPACE_CATEGORY,
|
||||
{
|
||||
pricingRows: existingProduct.pricingRows,
|
||||
bomDetails: existingProduct.bomDetails,
|
||||
},
|
||||
{
|
||||
pricingRows: nextProduct.pricingRows,
|
||||
bomDetails: nextProduct.bomDetails,
|
||||
},
|
||||
)
|
||||
addChangedWorkspaceCategory(
|
||||
categories,
|
||||
PRODUCT_LIMIT_WORKSPACE_CATEGORY,
|
||||
existingProduct.productLimitRows,
|
||||
nextProduct.productLimitRows,
|
||||
)
|
||||
addChangedWorkspaceCategory(
|
||||
categories,
|
||||
PLANNING_LAYOUT_WORKSPACE_CATEGORY,
|
||||
existingProduct.planningLayoutRows,
|
||||
nextProduct.planningLayoutRows,
|
||||
)
|
||||
addChangedWorkspaceCategory(
|
||||
categories,
|
||||
FACTORY_QUALITY_WORKSPACE_CATEGORY,
|
||||
{
|
||||
qualityInspectionSheet: existingProduct.qualityInspectionSheet,
|
||||
firstQualityRows: existingProduct.firstQualityRows,
|
||||
},
|
||||
{
|
||||
qualityInspectionSheet: nextProduct.qualityInspectionSheet,
|
||||
firstQualityRows: nextProduct.firstQualityRows,
|
||||
},
|
||||
)
|
||||
|
||||
return categories
|
||||
}
|
||||
|
||||
function assertCanWriteProductChanges(
|
||||
record: IntakeNonIntegratedRecord,
|
||||
user: AuthUser,
|
||||
nextProduct: SharedNonIntegratedProductRecord,
|
||||
) {
|
||||
const existingProduct = buildHydratedProductRecord(record.payload, {
|
||||
createdAt: record.createdAt,
|
||||
updatedAt: record.updatedAt,
|
||||
createdBy: record.createdBy,
|
||||
updatedBy: record.updatedBy,
|
||||
})
|
||||
|
||||
if (!existingProduct) {
|
||||
intakeNonIntegratedScopeService.assertCanWriteRecord(record, user)
|
||||
return
|
||||
}
|
||||
|
||||
const changedCategories = getChangedWorkspaceCategories(existingProduct, nextProduct)
|
||||
|
||||
if (changedCategories.size === 0) {
|
||||
intakeNonIntegratedScopeService.assertCanWriteRecord(record, user)
|
||||
return
|
||||
}
|
||||
|
||||
changedCategories.forEach((category) => {
|
||||
intakeNonIntegratedScopeService.assertCanWriteRecord(record, user, category)
|
||||
})
|
||||
}
|
||||
|
||||
function normalizePresentationPreview(
|
||||
value: unknown,
|
||||
): NonIntegratedUploadPresentationPreview | undefined {
|
||||
@ -1069,7 +1489,7 @@ class IntakeNonIntegratedProductService {
|
||||
|
||||
try {
|
||||
if (existingRecord) {
|
||||
intakeNonIntegratedScopeService.assertCanWriteRecord(existingRecord, user)
|
||||
assertCanWriteProductChanges(existingRecord, user, persistedProduct)
|
||||
|
||||
await this.repository.update(existingRecord.id, {
|
||||
spu: normalizedSpu,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user