furniture-3d-system-Wu/furniture-3d-system/src/server/services/intake-non-integrated-scope.service.ts

550 lines
16 KiB
TypeScript

import { Prisma, type IntakeNonIntegratedProduct, type IntakeNonIntegratedProductProfile } from '@prisma/client'
import { AppError, ErrorCode } from '@/server/core/errors'
import type { AuthUser } from '@/server/core/types'
import {
canUploadNonIntegratedDetachedAsset,
canCreateNonIntegratedProductFromIntake,
canDeleteNonIntegratedProductByIntakePermission,
canEditAnyNonIntegratedWorkspace,
canEditNonIntegratedWorkspace,
canDownloadNonIntegratedWorkspace,
canPushNonIntegratedProductPricingToJushuitan,
isIntakeNonIntegratedFullEditor,
type IntakeNonIntegratedPermissionProduct,
} from '@/lib/non-integrated-permissions'
import {
isExternalIntakeRecordOwner,
isExternalIntakeUser,
} from '@/lib/non-integrated-external-entry-permissions'
import {
getIntakeNodePermissionTargetKeyByWorkspaceCategory,
resolveIntakeNodePermissionAccess,
resolveIntakeNodePermissionSubjectKeys,
} from '@/lib/intake-node-permission-settings'
import { intakeNodePermissionSettingsService } from './intake-node-permission-settings.service'
type IntakeNonIntegratedPermissionTarget = Pick<
IntakeNonIntegratedProduct,
| 'id'
| 'createdBy'
| 'designer'
| 'purchaser'
| 'visibility'
| 'departmentId'
| 'teamId'
> & {
operator?: string | null
payload?: Prisma.JsonValue | null
profile?: IntakeNonIntegratedProductProfile | null
skuItems?: Array<{
skuItemId?: string | null
purchaseOwner?: string | null
manufacturerCode?: string | null
}>
}
const ADMIN_ROLES = new Set<AuthUser['role']>(['SUPER_ADMIN', 'ADMIN'])
const PRODUCT_FACTORY_WORKSPACE_CATEGORY = '\u4ea7\u54c1\u5b9a\u5382'
export const DEFAULT_NON_INTEGRATED_VISIBILITY = 'DEPARTMENT'
export const NON_INTEGRATED_SCOPE_ENABLED = false
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
function readText(value: unknown) {
return typeof value === 'string' ? value.trim() : ''
}
function readPayloadField(payload: unknown, key: string) {
return isRecord(payload) ? readText(payload[key]) : ''
}
function readOpeningReferenceField(payload: unknown, key: string) {
if (!isRecord(payload)) return ''
const openingReference = payload.openingReference
return isRecord(openingReference) ? readText(openingReference[key]) : ''
}
function normalizeSupplierCode(value: unknown) {
return readText(value).toUpperCase()
}
function buildSupplierRecordScopeWhere(
supplierCode: string,
): Prisma.IntakeNonIntegratedProductWhereInput {
return {
OR: [
{
skuItems: {
some: {
manufacturerCode: supplierCode,
},
},
},
{
payload: {
path: ['factoryRows'],
array_contains: [{ supplierCode }],
},
},
],
}
}
function readPayloadFactoryRows(payload: unknown) {
if (!isRecord(payload)) return []
const rows = payload.factoryRows
return Array.isArray(rows)
? rows.filter((row): row is Record<string, unknown> => isRecord(row))
: []
}
class IntakeNonIntegratedScopeService {
isScopeEnabled(): boolean {
return NON_INTEGRATED_SCOPE_ENABLED
}
isAdmin(user: AuthUser): boolean {
return ADMIN_ROLES.has(user.role)
}
isExternalIntakeScopeUser(user: AuthUser): boolean {
return !this.isAdmin(user) && isExternalIntakeUser(user)
}
isSupplierScopeUser(user: AuthUser): boolean {
return !this.isAdmin(user) && Boolean(user.businessRoles?.includes('SUPPLIER'))
}
getSupplierCode(user: AuthUser): string {
return normalizeSupplierCode(user.supplierCode)
}
buildSupplierRecordScopeWhere(user: AuthUser): Prisma.IntakeNonIntegratedProductWhereInput {
const supplierCode = this.getSupplierCode(user)
if (!supplierCode) {
return { id: '__unbound_supplier__' }
}
return buildSupplierRecordScopeWhere(supplierCode)
}
getScopedDepartmentId(user: AuthUser): string | undefined {
return user.managedDepartmentId ?? user.departmentId
}
getScopedTeamId(user: AuthUser): string | undefined {
return user.managedTeamId ?? user.teamId
}
buildReadScopeWhere(user: AuthUser): Prisma.IntakeNonIntegratedProductWhereInput | undefined {
if (this.isSupplierScopeUser(user)) {
return this.buildSupplierRecordScopeWhere(user)
}
if (this.isExternalIntakeScopeUser(user)) {
return { createdBy: user.id }
}
if (!this.isScopeEnabled()) {
return undefined
}
if (this.isAdmin(user)) {
return undefined
}
const orConditions: Prisma.IntakeNonIntegratedProductWhereInput[] = [
{ createdBy: user.id },
{ visibility: 'PUBLIC' },
]
const scopedDepartmentId = this.getScopedDepartmentId(user)
if (scopedDepartmentId) {
orConditions.push({
visibility: 'DEPARTMENT',
departmentId: scopedDepartmentId,
})
}
const scopedTeamId = this.getScopedTeamId(user)
if (scopedTeamId) {
orConditions.push({
visibility: 'TEAM',
teamId: scopedTeamId,
})
}
return {
OR: orConditions,
}
}
canReadRecord(record: IntakeNonIntegratedPermissionTarget, user: AuthUser): boolean {
if (this.isSupplierScopeUser(user)) {
const supplierCode = this.getSupplierCode(user)
if (!supplierCode) return false
if (
record.skuItems?.some(
(item) => normalizeSupplierCode(item.manufacturerCode) === supplierCode,
)
) {
return true
}
return readPayloadFactoryRows(record.payload).some(
(row) => normalizeSupplierCode(row.supplierCode) === supplierCode,
)
}
if (this.isExternalIntakeScopeUser(user)) {
return isExternalIntakeRecordOwner(record, user)
}
if (!this.isScopeEnabled()) return true
if (this.isAdmin(user)) return true
if (record.createdBy === user.id) return true
if (record.visibility === 'PUBLIC') return true
const scopedDepartmentId = this.getScopedDepartmentId(user)
if (
record.visibility === 'DEPARTMENT' &&
scopedDepartmentId &&
record.departmentId === scopedDepartmentId
) {
return true
}
const scopedTeamId = this.getScopedTeamId(user)
if (record.visibility === 'TEAM' && scopedTeamId && record.teamId === scopedTeamId) {
return true
}
return false
}
canCreateRecord(user: AuthUser): boolean {
return canCreateNonIntegratedProductFromIntake(user)
}
private buildPermissionProduct(
record: IntakeNonIntegratedPermissionTarget,
skuItemIds?: readonly string[],
): IntakeNonIntegratedPermissionProduct {
const profile = record.profile ?? null
const readProfile = (key: keyof IntakeNonIntegratedProductProfile) => {
const value = profile?.[key]
return readText(value)
}
const payloadDesigner = readPayloadField(record.payload, 'designer')
const openingDesigner = readOpeningReferenceField(record.payload, 'designer')
const persistedDesigner = readText(record.designer)
const productPlanning = readProfile('productPlanning')
const targetSkuItemIds = new Set(
(skuItemIds ?? []).map(readText).filter(Boolean),
)
const fallbackPurchaseOwner =
readProfile('purchaseOwner') || readText(record.purchaser)
const skuPurchaseOwners = (record.skuItems ?? [])
.filter(
(item) =>
targetSkuItemIds.size === 0 ||
targetSkuItemIds.has(readText(item.skuItemId)),
)
.map((item) => {
const skuPurchaseOwner = readText(item.purchaseOwner)
return skuPurchaseOwner ||
(targetSkuItemIds.size === 0 ? fallbackPurchaseOwner : '')
})
.filter(Boolean)
const nextOpeningReference = {
productName: readProfile('productName'),
productImage: readProfile('productImageUrl'),
productCategory: readProfile('productCategory'),
salesCategory: readProfile('salesCategory'),
currentStage: readProfile('productStatus'),
year: readProfile('productYear'),
designer: openingDesigner || payloadDesigner || persistedDesigner,
structureDesigner:
readProfile('structureDesigner') ||
readOpeningReferenceField(record.payload, 'structureDesigner'),
planningOwner: productPlanning,
}
return {
createdBy: record.createdBy,
designer: persistedDesigner || payloadDesigner || openingDesigner,
purchaser: readText(record.purchaser),
purchaseOwner: readProfile('purchaseOwner'),
brandPlanning: readProfile('brandPlanning'),
spuOwner: readProfile('spuOwner'),
operator: readText(record.operator),
operationsOwner: readProfile('operationsOwner'),
productPlanning,
visualPlanning: readProfile('visualPlanning'),
visualDirector: readProfile('visualDirector'),
skuPurchaseOwners,
openingReference: nextOpeningReference,
}
}
canWriteRecord(record: IntakeNonIntegratedPermissionTarget, user: AuthUser): boolean {
return canEditAnyNonIntegratedWorkspace(this.buildPermissionProduct(record), user)
}
canUploadDetachedAsset(record: IntakeNonIntegratedPermissionTarget, user: AuthUser): boolean {
return canUploadNonIntegratedDetachedAsset(this.buildPermissionProduct(record), user)
}
canPushProductPricing(record: IntakeNonIntegratedPermissionTarget, user: AuthUser): boolean {
return canPushNonIntegratedProductPricingToJushuitan(
this.buildPermissionProduct(record),
user,
)
}
canWriteWorkspaceRecord(
record: IntakeNonIntegratedPermissionTarget,
user: AuthUser,
category?: string | null,
targetSkuItemIds?: readonly string[],
): boolean {
if (!category) {
return this.canWriteRecord(record, user)
}
const normalizedTargetSkuItemIds = Array.from(
new Set((targetSkuItemIds ?? []).map(readText).filter(Boolean)),
)
if (
category === PRODUCT_FACTORY_WORKSPACE_CATEGORY &&
normalizedTargetSkuItemIds.length > 0
) {
const skuItemsById = new Map(
(record.skuItems ?? []).map((item) => [readText(item.skuItemId), item]),
)
if (
normalizedTargetSkuItemIds.some((skuItemId) => !skuItemsById.has(skuItemId))
) {
return false
}
return normalizedTargetSkuItemIds.every((skuItemId) =>
canEditNonIntegratedWorkspace(
this.buildPermissionProduct(record, [skuItemId]),
user,
category,
),
)
}
if (category === PRODUCT_FACTORY_WORKSPACE_CATEGORY) {
const fallbackPurchaseOwner =
readText(record.profile?.purchaseOwner) || readText(record.purchaser)
const skuPurchaseOwners = new Set(
(record.skuItems ?? [])
.map((item) => readText(item.purchaseOwner) || fallbackPurchaseOwner)
.filter(Boolean),
)
if (skuPurchaseOwners.size > 1) return false
}
return canEditNonIntegratedWorkspace(this.buildPermissionProduct(record), user, category)
}
canDownloadWorkspaceRecord(
record: IntakeNonIntegratedPermissionTarget,
user: AuthUser,
category?: string | null,
): boolean {
if (!category) {
return false
}
return canDownloadNonIntegratedWorkspace(
this.buildPermissionProduct(record),
user,
category,
)
}
async canWriteWorkspaceRecordWithSettings(
record: IntakeNonIntegratedPermissionTarget,
user: AuthUser,
category?: string | null,
targetSkuItemIds?: readonly string[],
): Promise<boolean> {
if (isIntakeNonIntegratedFullEditor(user)) {
return true
}
const targetKey = getIntakeNodePermissionTargetKeyByWorkspaceCategory(category)
if (!targetKey) {
return false
}
const { overrides } = await intakeNodePermissionSettingsService.getSettings()
const subjectKeys = resolveIntakeNodePermissionSubjectKeys(user)
const nodePermissionAccess = resolveIntakeNodePermissionAccess(
overrides,
targetKey,
subjectKeys,
)
if (!nodePermissionAccess.edit) {
return false
}
if (nodePermissionAccess.override.edit) {
return true
}
return this.canWriteWorkspaceRecord(record, user, category, targetSkuItemIds)
}
async canDownloadWorkspaceRecordWithSettings(
record: IntakeNonIntegratedPermissionTarget,
user: AuthUser,
category?: string | null,
): Promise<boolean> {
if (isIntakeNonIntegratedFullEditor(user)) {
return true
}
const targetKey = getIntakeNodePermissionTargetKeyByWorkspaceCategory(category)
if (!targetKey) {
return false
}
const { overrides } = await intakeNodePermissionSettingsService.getSettings()
const subjectKeys = resolveIntakeNodePermissionSubjectKeys(user)
const nodePermissionAccess = resolveIntakeNodePermissionAccess(
overrides,
targetKey,
subjectKeys,
)
if (!nodePermissionAccess.download) {
return false
}
if (nodePermissionAccess.override.download) {
return true
}
return this.canDownloadWorkspaceRecord(record, user, category)
}
async canPreviewWorkspaceRecordWithSettings(
record: IntakeNonIntegratedPermissionTarget,
user: AuthUser,
category?: string | null,
): Promise<boolean> {
if (isIntakeNonIntegratedFullEditor(user)) {
return true
}
const targetKey = getIntakeNodePermissionTargetKeyByWorkspaceCategory(category)
if (!targetKey) {
return false
}
const { overrides } = await intakeNodePermissionSettingsService.getSettings()
const subjectKeys = resolveIntakeNodePermissionSubjectKeys(user)
const nodePermissionAccess = resolveIntakeNodePermissionAccess(
overrides,
targetKey,
subjectKeys,
)
if (!nodePermissionAccess.view) {
return false
}
if (nodePermissionAccess.override.view) {
return true
}
return this.canReadRecord(record, user)
}
async assertCanWriteRecord(
record: IntakeNonIntegratedPermissionTarget,
user: AuthUser,
category?: string | null,
targetSkuItemIds?: readonly string[],
): Promise<void> {
if (
await this.canWriteWorkspaceRecordWithSettings(
record,
user,
category,
targetSkuItemIds,
)
) {
return
}
throw new AppError(ErrorCode.AUTH_PERMISSION_DENIED, '没有权限编辑这条非集成产品记录')
}
assertCanUploadDetachedAsset(
record: IntakeNonIntegratedPermissionTarget,
user: AuthUser,
): void {
if (this.canUploadDetachedAsset(record, user)) {
return
}
throw new AppError(ErrorCode.AUTH_PERMISSION_DENIED, '没有权限编辑这条非集成产品记录')
}
async assertCanDownloadWorkspaceRecord(
record: IntakeNonIntegratedPermissionTarget,
user: AuthUser,
category?: string | null,
): Promise<void> {
if (await this.canDownloadWorkspaceRecordWithSettings(record, user, category)) {
return
}
throw new AppError(ErrorCode.AUTH_PERMISSION_DENIED, '没有权限下载这条非集成产品资料')
}
async assertCanPreviewWorkspaceRecord(
record: IntakeNonIntegratedPermissionTarget,
user: AuthUser,
category?: string | null,
): Promise<void> {
if (await this.canPreviewWorkspaceRecordWithSettings(record, user, category)) {
return
}
throw new AppError(ErrorCode.AUTH_PERMISSION_DENIED, '没有权限查看这条非集成产品资料')
}
assertCanPushProductPricing(
record: IntakeNonIntegratedPermissionTarget,
user: AuthUser,
): void {
if (this.canPushProductPricing(record, user)) {
return
}
throw new AppError(ErrorCode.AUTH_PERMISSION_DENIED, '没有权限推送这条非集成产品核价')
}
assertCanDeleteRecord(record: IntakeNonIntegratedPermissionTarget, user: AuthUser): void {
if (canDeleteNonIntegratedProductByIntakePermission(user)) {
return
}
throw new AppError(ErrorCode.AUTH_PERMISSION_DENIED, '没有权限删除这条非集成产品记录')
}
}
export const intakeNonIntegratedScopeService = new IntakeNonIntegratedScopeService()
export { IntakeNonIntegratedScopeService }