fix(intake): align non-integrated quick scopes
This commit is contained in:
parent
e0bc1db6bf
commit
614e568763
@ -2576,7 +2576,7 @@ describe('IntakeClient', () => {
|
||||
expect(screen.queryByText('BUY0002')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('filters responsible products by space designer for space visual users', async () => {
|
||||
it('shows responsible products whenever the current user name appears in any owner field', async () => {
|
||||
const actor = userEvent.setup()
|
||||
|
||||
replaceNonIntegratedProducts([
|
||||
@ -2607,7 +2607,7 @@ describe('IntakeClient', () => {
|
||||
await actor.selectOptions(screen.getByLabelText('资料范围'), 'created')
|
||||
|
||||
expect(screen.getByText('SPACE0001')).toBeInTheDocument()
|
||||
expect(screen.queryByText('SPACE0002')).not.toBeInTheDocument()
|
||||
expect(screen.getByText('SPACE0002')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('lets space visual department users open stock products for planning layout editing', async () => {
|
||||
@ -2664,7 +2664,7 @@ describe('IntakeClient', () => {
|
||||
createNonIntegratedProduct({
|
||||
id: 'operation-owner-b',
|
||||
spu: 'OPS0002',
|
||||
designer: 'Operation A',
|
||||
designer: 'Designer B',
|
||||
spuOwner: 'Operation B',
|
||||
operationsOwner: 'Operation B',
|
||||
operator: 'Operation B',
|
||||
@ -2685,7 +2685,7 @@ describe('IntakeClient', () => {
|
||||
expect(screen.queryByText('OPS0002')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows pending products only when the current node belongs to the user', async () => {
|
||||
it('shows pending products only after the purchase branch is actually triggered', async () => {
|
||||
const actor = userEvent.setup()
|
||||
|
||||
replaceNonIntegratedProducts([
|
||||
@ -2723,6 +2723,76 @@ describe('IntakeClient', () => {
|
||||
expect(screen.queryByText('BUY-NOT-YET')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows structure designers pending flows after their branch is triggered even if another branch is currently earlier in the list', async () => {
|
||||
const actor = userEvent.setup()
|
||||
|
||||
replaceNonIntegratedProducts([
|
||||
createNonIntegratedProduct({
|
||||
...createApprovedWorkflowStateBefore('企划布局'),
|
||||
id: 'structure-branch-pending',
|
||||
spu: 'STRUCT-PENDING',
|
||||
designer: 'Appearance A',
|
||||
openingReference: {
|
||||
...createEmptyNonIntegratedOpeningReference(),
|
||||
designer: 'Appearance A',
|
||||
structureDesigner: 'Structure A',
|
||||
},
|
||||
visualPlanning: 'Space A',
|
||||
visualDirector: 'Space A',
|
||||
}),
|
||||
])
|
||||
|
||||
renderSelector({
|
||||
eligibleSkuList: [],
|
||||
userRole: 'USER',
|
||||
userName: 'Structure A',
|
||||
userBusinessRoles: ['DESIGN'],
|
||||
userDepartmentPaths: ['研发中心 > 结构设计部'],
|
||||
})
|
||||
|
||||
await actor.selectOptions(screen.getByLabelText('资料范围'), 'pending')
|
||||
|
||||
expect(screen.getByText('STRUCT-PENDING')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows product planning pending once any SKU has been recorded into the system', async () => {
|
||||
const actor = userEvent.setup()
|
||||
|
||||
replaceNonIntegratedProducts([
|
||||
createNonIntegratedProduct({
|
||||
...createApprovedWorkflowStateBefore('产品核价'),
|
||||
id: 'planning-system-entered-pending',
|
||||
spu: 'PLAN-PENDING',
|
||||
productPlanning: 'Planning A',
|
||||
pricingRows: [
|
||||
{
|
||||
skuId: 'sku-entered',
|
||||
costPrice: '199',
|
||||
productTag: '常规',
|
||||
systemEnteredAt: '2026-06-29T09:00:00.000Z',
|
||||
},
|
||||
{
|
||||
skuId: 'sku-waiting',
|
||||
costPrice: '299',
|
||||
productTag: '常规',
|
||||
},
|
||||
],
|
||||
}),
|
||||
])
|
||||
|
||||
renderSelector({
|
||||
eligibleSkuList: [],
|
||||
userRole: 'USER',
|
||||
userName: 'Planning A',
|
||||
userBusinessRoles: ['PLANNING'],
|
||||
userDepartmentPaths: ['品牌中心 > 商品企划部'],
|
||||
})
|
||||
|
||||
await actor.selectOptions(screen.getByLabelText('资料范围'), 'pending')
|
||||
|
||||
expect(screen.getByText('PLAN-PENDING')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('lets planning users open products for opening-reference editing only', async () => {
|
||||
const actor = userEvent.setup()
|
||||
|
||||
|
||||
@ -22,6 +22,29 @@ import {
|
||||
isIntakeNonIntegratedQualityGroupUser,
|
||||
} from "@/lib/non-integrated-permissions";
|
||||
|
||||
const APPEARANCE_PENDING_CATEGORIES: NonIntegratedDocumentStatusCategory[] = [
|
||||
"开品参考",
|
||||
"设计卖点",
|
||||
"产品模型",
|
||||
"专利申请",
|
||||
];
|
||||
|
||||
const STRUCTURE_PENDING_CATEGORIES: NonIntegratedDocumentStatusCategory[] = [
|
||||
"产品模型",
|
||||
"评审资料",
|
||||
NON_INTEGRATED_BASIC_INFO_WORKSPACE_CATEGORY,
|
||||
"产品实拍图",
|
||||
"安装方式",
|
||||
"包装细节",
|
||||
];
|
||||
|
||||
const PURCHASE_PENDING_CATEGORIES: NonIntegratedDocumentStatusCategory[] = [
|
||||
"产品定厂",
|
||||
"样包测量",
|
||||
"成品寄样",
|
||||
"产品核价",
|
||||
];
|
||||
|
||||
function getUniqueNonEmptyValues(values: Array<string | undefined | null>) {
|
||||
return Array.from(
|
||||
new Set(
|
||||
@ -57,6 +80,42 @@ export function hasProductPricingRowsSystemEnteredForList(
|
||||
);
|
||||
}
|
||||
|
||||
function hasAnyProductPricingRowSystemEnteredForList(
|
||||
product: NonIntegratedProductRecord,
|
||||
) {
|
||||
return (product.pricingRows ?? []).some(
|
||||
(row) => parseProgressDate(row.systemEnteredAt) !== null,
|
||||
);
|
||||
}
|
||||
|
||||
function hasSampleMeasurementContentForPending(
|
||||
product: NonIntegratedProductRecord,
|
||||
) {
|
||||
return (product.sampleMeasurementRows ?? []).some((row) =>
|
||||
[row.lengthCm, row.widthCm, row.heightCm, row.weightKg].some(
|
||||
(value) => value.trim().length > 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function hasIncompleteTriggeredCategories(
|
||||
product: NonIntegratedProductRecord,
|
||||
categories: NonIntegratedDocumentStatusCategory[],
|
||||
documentWorkspaceCountByCategory: Record<
|
||||
NonIntegratedDocumentStatusCategory,
|
||||
number
|
||||
>,
|
||||
) {
|
||||
return categories.some(
|
||||
(category) =>
|
||||
!isDocumentWorkspaceReadyForList(
|
||||
product,
|
||||
category,
|
||||
documentWorkspaceCountByCategory,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function getCurrentNodeOwnerCandidates(
|
||||
product: NonIntegratedProductRecord,
|
||||
category: NonIntegratedDocumentStatusCategory,
|
||||
@ -258,13 +317,95 @@ export function isCurrentUserPendingNonIntegratedProduct(
|
||||
number
|
||||
>,
|
||||
) {
|
||||
const currentCategory = getCurrentNodeCategoryForList(
|
||||
if (isAdminUser(user)) {
|
||||
return getCurrentNodeCategoryForList(product, documentWorkspaceCountByCategory) !== null;
|
||||
}
|
||||
|
||||
const people = getNonIntegratedListPeople(product);
|
||||
const basicInfoReady = isDocumentWorkspaceReadyForList(
|
||||
product,
|
||||
NON_INTEGRATED_BASIC_INFO_WORKSPACE_CATEGORY,
|
||||
documentWorkspaceCountByCategory,
|
||||
);
|
||||
if (!currentCategory) return false;
|
||||
const designSellPointReady = isDocumentWorkspaceReadyForList(
|
||||
product,
|
||||
"设计卖点",
|
||||
documentWorkspaceCountByCategory,
|
||||
);
|
||||
const factoryConfirmed = hasProductFactoryRowsConfirmedForList(product);
|
||||
const pricingGroupTriggered =
|
||||
factoryConfirmed && hasSampleMeasurementContentForPending(product);
|
||||
const systemEnteredTriggered =
|
||||
hasAnyProductPricingRowSystemEnteredForList(product);
|
||||
|
||||
return isCurrentUserResponsibleForCurrentNode(product, user, currentCategory);
|
||||
const appearancePending =
|
||||
matchesCurrentUserName([people.appearanceDesigner], user) &&
|
||||
hasIncompleteTriggeredCategories(
|
||||
product,
|
||||
APPEARANCE_PENDING_CATEGORIES,
|
||||
documentWorkspaceCountByCategory,
|
||||
);
|
||||
if (appearancePending) return true;
|
||||
|
||||
const structurePending =
|
||||
matchesCurrentUserName([people.structureDesigner], user) &&
|
||||
designSellPointReady &&
|
||||
hasIncompleteTriggeredCategories(
|
||||
product,
|
||||
STRUCTURE_PENDING_CATEGORIES,
|
||||
documentWorkspaceCountByCategory,
|
||||
);
|
||||
if (structurePending) return true;
|
||||
|
||||
const spacePending =
|
||||
matchesCurrentUserName([people.spaceDesigner], user) &&
|
||||
basicInfoReady &&
|
||||
!isDocumentWorkspaceReadyForList(
|
||||
product,
|
||||
"企划布局",
|
||||
documentWorkspaceCountByCategory,
|
||||
);
|
||||
if (spacePending) return true;
|
||||
|
||||
const purchasePending =
|
||||
matchesCurrentUserName([people.purchaseOwner], user) &&
|
||||
basicInfoReady &&
|
||||
hasIncompleteTriggeredCategories(
|
||||
product,
|
||||
PURCHASE_PENDING_CATEGORIES,
|
||||
documentWorkspaceCountByCategory,
|
||||
);
|
||||
if (purchasePending) return true;
|
||||
|
||||
const pricingPending =
|
||||
isIntakeNonIntegratedPricingGroupUser(user) &&
|
||||
pricingGroupTriggered &&
|
||||
!isDocumentWorkspaceReadyForList(
|
||||
product,
|
||||
"产品核价",
|
||||
documentWorkspaceCountByCategory,
|
||||
);
|
||||
if (pricingPending) return true;
|
||||
|
||||
const planningPending =
|
||||
matchesCurrentUserName([people.planningOwner], user) &&
|
||||
systemEnteredTriggered &&
|
||||
!isDocumentWorkspaceReadyForList(
|
||||
product,
|
||||
"产品限价",
|
||||
documentWorkspaceCountByCategory,
|
||||
);
|
||||
if (planningPending) return true;
|
||||
|
||||
return (
|
||||
isIntakeNonIntegratedQualityGroupUser(user) &&
|
||||
systemEnteredTriggered &&
|
||||
!isDocumentWorkspaceReadyForList(
|
||||
product,
|
||||
"工厂质检",
|
||||
documentWorkspaceCountByCategory,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function isDocumentWorkspaceApprovedForList(
|
||||
|
||||
@ -83,63 +83,17 @@ export function matchesCurrentUserName(
|
||||
);
|
||||
}
|
||||
|
||||
function hasBusinessRole(user: NonIntegratedListPermissionUser, role: string) {
|
||||
return Boolean(user.businessRoles?.includes(role));
|
||||
}
|
||||
|
||||
export function isAdminUser(user: NonIntegratedListPermissionUser) {
|
||||
return user.role === "SUPER_ADMIN" || user.role === "ADMIN";
|
||||
}
|
||||
|
||||
function getUserDepartmentText(user: NonIntegratedListPermissionUser) {
|
||||
return [
|
||||
user.department?.name,
|
||||
user.department?.fullPath,
|
||||
...(user.departmentPaths ?? []),
|
||||
]
|
||||
.map((value) => value?.trim() ?? "")
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
function isSpaceDesignUser(user: NonIntegratedListPermissionUser) {
|
||||
const departmentText = getUserDepartmentText(user);
|
||||
|
||||
return hasBusinessRole(user, "DESIGN") && /空间|视觉/.test(departmentText);
|
||||
}
|
||||
|
||||
function isOperationsOwnerUser(user: NonIntegratedListPermissionUser) {
|
||||
return /运营/.test(getUserDepartmentText(user));
|
||||
function isExternalIntakeListUser(user: NonIntegratedListPermissionUser) {
|
||||
return Boolean(user.businessRoles?.includes("EXTERNAL_INTAKE"));
|
||||
}
|
||||
|
||||
export function getCurrentUserResponsibleOwnerValues(
|
||||
product: NonIntegratedProductRecord,
|
||||
user: NonIntegratedListPermissionUser,
|
||||
) {
|
||||
const people = getNonIntegratedListPeople(product);
|
||||
const scopedValues: string[] = [];
|
||||
|
||||
if (hasBusinessRole(user, "DESIGN")) {
|
||||
if (isSpaceDesignUser(user)) {
|
||||
scopedValues.push(people.spaceDesigner);
|
||||
} else {
|
||||
scopedValues.push(people.appearanceDesigner, people.structureDesigner);
|
||||
}
|
||||
}
|
||||
if (hasBusinessRole(user, "PLANNING")) {
|
||||
scopedValues.push(people.planningOwner);
|
||||
}
|
||||
if (hasBusinessRole(user, "PURCHASING")) {
|
||||
scopedValues.push(people.purchaseOwner);
|
||||
}
|
||||
if (isOperationsOwnerUser(user)) {
|
||||
scopedValues.push(product.spuOwner, product.operator, product.operationsOwner);
|
||||
}
|
||||
|
||||
if (scopedValues.length > 0) {
|
||||
return scopedValues;
|
||||
}
|
||||
|
||||
return getLegacyNonIntegratedProductOwnerNames(product);
|
||||
}
|
||||
|
||||
@ -151,8 +105,10 @@ export function isCurrentUserResponsibleForNonIntegratedProduct(
|
||||
return true;
|
||||
}
|
||||
|
||||
return matchesCurrentUserName(
|
||||
getCurrentUserResponsibleOwnerValues(product, user),
|
||||
user,
|
||||
);
|
||||
if (isExternalIntakeListUser(user)) {
|
||||
const userId = safeTrim(user.id);
|
||||
return userId.length > 0 && safeTrim(product.createdBy) === userId;
|
||||
}
|
||||
|
||||
return matchesCurrentUserName(getCurrentUserResponsibleOwnerValues(product), user);
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import {
|
||||
NON_INTEGRATED_DEFAULT_BRAND,
|
||||
createDefaultDocumentWorkspaceConfirmations,
|
||||
NON_INTEGRATED_DOCUMENT_WORKSPACE_CATEGORIES,
|
||||
NON_INTEGRATED_WORKFLOW_NODE_CATEGORIES,
|
||||
} from '@/app/intake/non-integrated/nonIntegratedStorage'
|
||||
import { buildIntakeNodePermissionOverrideKey } from '@/lib/intake-node-permission-settings'
|
||||
|
||||
@ -186,6 +187,41 @@ function createRecord(overrides: Record<string, unknown> = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function createApprovedWorkflowStateBefore(targetCategory: string) {
|
||||
const targetIndex = NON_INTEGRATED_WORKFLOW_NODE_CATEGORIES.findIndex(
|
||||
(category) => category === targetCategory,
|
||||
)
|
||||
const hiddenListCategories = new Set(['立项评审', '检测报告'])
|
||||
const readyCategories = NON_INTEGRATED_WORKFLOW_NODE_CATEGORIES.slice(
|
||||
0,
|
||||
targetIndex < 0 ? 0 : targetIndex,
|
||||
).filter((category) => !hiddenListCategories.has(category))
|
||||
const uploadFiles = readyCategories
|
||||
.filter((category) =>
|
||||
NON_INTEGRATED_DOCUMENT_WORKSPACE_CATEGORIES.includes(
|
||||
category as (typeof NON_INTEGRATED_DOCUMENT_WORKSPACE_CATEGORIES)[number],
|
||||
),
|
||||
)
|
||||
.map((category, index) => ({
|
||||
id: `ready-file-${index + 1}`,
|
||||
name: `ready-${index + 1}.pdf`,
|
||||
category,
|
||||
dataUrl: 'data:application/pdf;base64,ready',
|
||||
mimeType: 'application/pdf',
|
||||
}))
|
||||
|
||||
return {
|
||||
uploadFiles,
|
||||
documentWorkspaceConfirmations: createDefaultDocumentWorkspaceConfirmations(),
|
||||
documentWorkspaceApprovedAt: Object.fromEntries(
|
||||
readyCategories.map((category, index) => [
|
||||
category,
|
||||
`2026-05-01T00:${String(index).padStart(2, '0')}:00.000Z`,
|
||||
]),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
function createAssetVariant(overrides: Record<string, unknown> = {}) {
|
||||
const code = typeof overrides.code === 'string' ? overrides.code : 'DBZ-SPJ11'
|
||||
const id = typeof overrides.id === 'string' ? overrides.id : `variant-${code}`
|
||||
@ -407,6 +443,93 @@ describe('intakeNonIntegratedProductService', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('filters internal responsible scope in memory so any listed owner name can match', async () => {
|
||||
mockedRepository.listActive.mockResolvedValue([
|
||||
createRecord({
|
||||
id: 'space-created-visible',
|
||||
spu: 'SPACE-CREATED',
|
||||
designer: 'Space A',
|
||||
payload: createPayload({
|
||||
id: 'space-created-visible',
|
||||
spu: 'SPACE-CREATED',
|
||||
designer: 'Space A',
|
||||
visualPlanning: 'Space B',
|
||||
visualDirector: 'Space B',
|
||||
}),
|
||||
}),
|
||||
createRecord({
|
||||
id: 'space-created-hidden',
|
||||
spu: 'SPACE-HIDDEN',
|
||||
designer: 'Other Designer',
|
||||
payload: createPayload({
|
||||
id: 'space-created-hidden',
|
||||
spu: 'SPACE-HIDDEN',
|
||||
designer: 'Other Designer',
|
||||
visualPlanning: 'Space B',
|
||||
visualDirector: 'Space B',
|
||||
}),
|
||||
}),
|
||||
] as never)
|
||||
|
||||
const result = await intakeNonIntegratedProductService.listPage(
|
||||
{
|
||||
...baseUser,
|
||||
name: 'Space A',
|
||||
businessRoles: ['DESIGN'] as const,
|
||||
departmentPaths: ['品牌中心 > 空间视觉部'],
|
||||
},
|
||||
{ quickScope: 'created' },
|
||||
)
|
||||
|
||||
expect(mockedRepository.listActive).toHaveBeenCalled()
|
||||
expect(mockedRepository.listActivePage).not.toHaveBeenCalled()
|
||||
expect(result.products.map((product) => product.spu)).toEqual(['SPACE-CREATED'])
|
||||
expect(result.pageInfo.total).toBe(1)
|
||||
})
|
||||
|
||||
it('filters internal pending scope in memory instead of reusing responsible ownership', async () => {
|
||||
mockedRepository.listActive.mockResolvedValue([
|
||||
createRecord({
|
||||
id: 'purchase-factory-pending',
|
||||
spu: 'BUY-PENDING',
|
||||
purchaser: 'Purchasing A',
|
||||
payload: createPayload({
|
||||
id: 'purchase-factory-pending',
|
||||
spu: 'BUY-PENDING',
|
||||
purchaser: 'Purchasing A',
|
||||
purchaseOwner: 'Purchasing A',
|
||||
...createApprovedWorkflowStateBefore('产品定厂'),
|
||||
}),
|
||||
}),
|
||||
createRecord({
|
||||
id: 'purchase-owner-but-not-triggered',
|
||||
spu: 'BUY-NOT-YET',
|
||||
purchaser: 'Purchasing A',
|
||||
payload: createPayload({
|
||||
id: 'purchase-owner-but-not-triggered',
|
||||
spu: 'BUY-NOT-YET',
|
||||
purchaser: 'Purchasing A',
|
||||
purchaseOwner: 'Purchasing A',
|
||||
}),
|
||||
}),
|
||||
] as never)
|
||||
|
||||
const result = await intakeNonIntegratedProductService.listPage(
|
||||
{
|
||||
...baseUser,
|
||||
name: 'Purchasing A',
|
||||
businessRoles: ['PURCHASING'] as const,
|
||||
departmentPaths: ['供应链中心 > 采购部'],
|
||||
},
|
||||
{ quickScope: 'pending' },
|
||||
)
|
||||
|
||||
expect(mockedRepository.listActive).toHaveBeenCalled()
|
||||
expect(mockedRepository.listActivePage).not.toHaveBeenCalled()
|
||||
expect(result.products.map((product) => product.spu)).toEqual(['BUY-PENDING'])
|
||||
expect(result.pageInfo.total).toBe(1)
|
||||
})
|
||||
|
||||
it('caps non-integrated list page size at 100 before hitting the repository', async () => {
|
||||
mockedRepository.listActivePage.mockResolvedValue({
|
||||
records: [],
|
||||
|
||||
@ -26,6 +26,9 @@ import {
|
||||
type NonIntegratedSkuItem,
|
||||
type NonIntegratedUploadFile,
|
||||
} from '@/app/intake/non-integrated/nonIntegratedStorage'
|
||||
import { getDocumentWorkspaceCountByCategory } from '@/app/intake/product-development/nonIntegratedDocumentCounts'
|
||||
import { isCurrentUserPendingNonIntegratedProduct } from '@/app/intake/product-development/nonIntegratedCurrentNode'
|
||||
import { isCurrentUserResponsibleForNonIntegratedProduct } from '@/app/intake/product-development/nonIntegratedProductListLogic'
|
||||
import { normalizeNonIntegratedAssetUrl } from '@/app/intake/non-integrated/nonIntegratedAssetUrl'
|
||||
import {
|
||||
buildAssetVariantBarcode,
|
||||
@ -3312,7 +3315,9 @@ function buildQuickScopeWhere(
|
||||
function buildListFilterWhere(
|
||||
params: IntakeNonIntegratedProductListParams,
|
||||
user: AuthUser,
|
||||
options: { includeQuickScope?: boolean } = {},
|
||||
): Prisma.IntakeNonIntegratedProductWhereInput | undefined {
|
||||
const includeQuickScope = options.includeQuickScope ?? true
|
||||
const filters = [
|
||||
intakeNonIntegratedScopeService.isSupplierScopeUser(user)
|
||||
? intakeNonIntegratedScopeService.buildSupplierRecordScopeWhere(user)
|
||||
@ -3321,7 +3326,7 @@ function buildListFilterWhere(
|
||||
buildProductKindWhere(params.productKind),
|
||||
buildSeriesNameSearchWhere(params.seriesName),
|
||||
buildSpuSearchWhere(params.spu ?? ''),
|
||||
buildQuickScopeWhere(params.quickScope, user),
|
||||
includeQuickScope ? buildQuickScopeWhere(params.quickScope, user) : null,
|
||||
...buildStockSourceSearchWhere(params),
|
||||
...buildPeopleSearchWhere(params),
|
||||
].filter((filter): filter is Prisma.IntakeNonIntegratedProductWhereInput => filter !== null)
|
||||
@ -3333,6 +3338,60 @@ function buildListFilterWhere(
|
||||
return { AND: filters }
|
||||
}
|
||||
|
||||
function combineWhereInputs(
|
||||
...filters: Array<Prisma.IntakeNonIntegratedProductWhereInput | undefined | null>
|
||||
) {
|
||||
const normalizedFilters = filters.filter(
|
||||
(filter): filter is Prisma.IntakeNonIntegratedProductWhereInput => filter != null,
|
||||
)
|
||||
|
||||
if (normalizedFilters.length === 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (normalizedFilters.length === 1) {
|
||||
return normalizedFilters[0]
|
||||
}
|
||||
|
||||
return {
|
||||
AND: normalizedFilters,
|
||||
} satisfies Prisma.IntakeNonIntegratedProductWhereInput
|
||||
}
|
||||
|
||||
function shouldFilterListPageQuickScopeInMemory(
|
||||
params: IntakeNonIntegratedProductListParams,
|
||||
user: AuthUser,
|
||||
) {
|
||||
return (
|
||||
(params.quickScope === 'created' || params.quickScope === 'pending') &&
|
||||
!intakeNonIntegratedScopeService.isExternalIntakeScopeUser(user)
|
||||
)
|
||||
}
|
||||
|
||||
function filterProductsByQuickScopeInMemory(
|
||||
products: SharedNonIntegratedProductRecord[],
|
||||
quickScope: IntakeNonIntegratedProductListParams['quickScope'],
|
||||
user: AuthUser,
|
||||
) {
|
||||
if (quickScope === 'created') {
|
||||
return products.filter((product) =>
|
||||
isCurrentUserResponsibleForNonIntegratedProduct(product, user),
|
||||
)
|
||||
}
|
||||
|
||||
if (quickScope === 'pending') {
|
||||
return products.filter((product) =>
|
||||
isCurrentUserPendingNonIntegratedProduct(
|
||||
product,
|
||||
user,
|
||||
getDocumentWorkspaceCountByCategory(product),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return products
|
||||
}
|
||||
|
||||
function normalizeSupplierCodeForScope(value: unknown) {
|
||||
return typeof value === 'string' ? value.trim().toUpperCase() : ''
|
||||
}
|
||||
@ -5235,6 +5294,42 @@ class IntakeNonIntegratedProductService {
|
||||
async listPage(user: AuthUser, params: IntakeNonIntegratedProductListParams = {}) {
|
||||
const page = normalizeListPage(params.page)
|
||||
const pageSize = normalizeListPageSize(params.pageSize)
|
||||
|
||||
if (shouldFilterListPageQuickScopeInMemory(params, user)) {
|
||||
const scopeWhere = intakeNonIntegratedScopeService.buildReadScopeWhere(user)
|
||||
const filterWhere = buildListFilterWhere(params, user, {
|
||||
includeQuickScope: false,
|
||||
})
|
||||
const records = await this.repository.listActive(
|
||||
combineWhereInputs(scopeWhere, filterWhere),
|
||||
)
|
||||
const scopedProducts = filterProductsForSupplierUser(
|
||||
records
|
||||
.map((record) => buildStructuredProductRecordFromRecord(record))
|
||||
.filter((record): record is SharedNonIntegratedProductRecord => record !== null),
|
||||
user,
|
||||
)
|
||||
const filteredProducts = filterProductsByQuickScopeInMemory(
|
||||
scopedProducts,
|
||||
params.quickScope,
|
||||
user,
|
||||
)
|
||||
const total = filteredProducts.length
|
||||
const pageStart = (page - 1) * pageSize
|
||||
const pagedProducts = filteredProducts.slice(pageStart, pageStart + pageSize)
|
||||
const products = await hydrateSeriesProductDisplayNames(pagedProducts)
|
||||
|
||||
return {
|
||||
products,
|
||||
pageInfo: {
|
||||
page,
|
||||
pageSize,
|
||||
total,
|
||||
totalPages: Math.max(1, Math.ceil(total / pageSize)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const { records, total } = await this.repository.listActivePage({
|
||||
page,
|
||||
pageSize,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user