feat(cleaner): support selectedManagers filtering for admin cleaner execution

Admin can now pass selectedManagers to getCleanerData so material codes
are queried from MaterialsToBeDeleted by ManagerName IN (selectedManagers).
When no managers are selected, fallback to DiscreteMaterialPlanData by
orderNumbers. User behavior is unchanged. Includes updated tests and
role-based flow documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-04-06 19:20:22 +08:00
parent 91f29a1167
commit 3be7959067
7 changed files with 455 additions and 21 deletions

View File

@@ -194,7 +194,8 @@ export function registerValidationHandlers(): void {
ipcMain.handle(
IPC_CHANNELS.VALIDATION_GET_CLEANER_DATA,
async (
event
event,
params?: { selectedManagers?: string[] }
): Promise<{
success: boolean
orderNumbers?: string[]
@@ -213,7 +214,11 @@ export function registerValidationHandlers(): void {
}
}
return validationApplicationService.getCleanerData(userInfo, event.sender.id)
return validationApplicationService.getCleanerData(
userInfo,
event.sender.id,
params?.selectedManagers ?? []
)
}
)
}

View File

@@ -231,7 +231,8 @@ export class ValidationApplicationService {
async getCleanerData(
userInfo: UserInfo,
senderId: number
senderId: number,
selectedManagers: string[] = []
): Promise<{
success: boolean
orderNumbers?: string[]
@@ -250,6 +251,7 @@ export class ValidationApplicationService {
userId: userInfo.id,
username,
isAdmin,
selectedManagers,
requestId
})
@@ -270,7 +272,13 @@ export class ValidationApplicationService {
})
}
const materialCodes = await this.loadMaterialCodesForCleaner(dbService, username, isAdmin)
const materialCodes = await this.loadMaterialCodesForCleaner(
dbService,
username,
isAdmin,
selectedManagers,
orderNumbers
)
log.info('Cleaner data retrieved', {
userId: userInfo.id,
orderCount: orderNumbers.length,
@@ -489,27 +497,54 @@ export class ValidationApplicationService {
private async loadMaterialCodesForCleaner(
dbService: ValidationDatabaseService,
username: string,
isAdmin: boolean
isAdmin: boolean,
selectedManagers: string[],
orderNumbers: string[]
): Promise<string[]> {
const markedTableName = getValidationTableName('dbo_MaterialsToBeDeleted')
if (isAdmin) {
const result = await dbService.query(
`
SELECT MaterialCode
FROM ${markedTableName}
WHERE MaterialCode IS NOT NULL
`
// Admin with selected managers: filter MaterialsToBeDeleted by ManagerName IN (selectedManagers)
if (isAdmin && selectedManagers && selectedManagers.length > 0) {
const materialCodes = await this.queryMaterialCodesByManagers(
dbService,
markedTableName,
selectedManagers
)
const materialCodes = result.rows.map((row) => row.MaterialCode as string).filter(Boolean)
log.info(`Admin user: got ${materialCodes.length} materials`, {
log.info(`Admin with selected managers: got ${materialCodes.length} materials`, {
userId: username,
isAdmin: true,
selectedManagers,
materialCount: materialCodes.length
})
return materialCodes
}
// Admin without selected managers: query all materials from DiscreteMaterialPlanData by orderNumbers
if (isAdmin) {
if (orderNumbers.length === 0) {
log.warn('Admin without selected managers but no orderNumbers available', {
userId: username
})
return []
}
const materialDao = new DiscreteMaterialPlanDAO()
const records = await materialDao.queryBySourceNumbersDistinct(orderNumbers)
const materialCodes = [
...new Set(records.map((r) => r.MaterialCode as string).filter(Boolean))
]
log.info(
`Admin without selected managers: got ${materialCodes.length} materials from DiscreteMaterialPlanData`,
{
userId: username,
isAdmin: true,
orderCount: orderNumbers.length,
materialCount: materialCodes.length
}
)
return materialCodes
}
// Regular user: filter MaterialsToBeDeleted by ManagerName = username
if (dbService.type === 'sqlserver') {
const sql = await import('mssql')
const result = await (dbService as SqlServerService).queryWithParams(
@@ -568,6 +603,57 @@ export class ValidationApplicationService {
return materialCodes
}
private async queryMaterialCodesByManagers(
dbService: ValidationDatabaseService,
tableName: string,
managers: string[]
): Promise<string[]> {
if (dbService.type === 'sqlserver') {
const sql = await import('mssql')
const params: Record<string, { value: string; type: any }> = {}
const paramNames = managers.map((m, i) => {
const name = `@manager${i}`
params[`manager${i}`] = { value: m, type: sql.default.NVarChar }
return name
})
const result = await (dbService as SqlServerService).queryWithParams(
`
SELECT MaterialCode
FROM ${tableName}
WHERE ManagerName IN (${paramNames.join(', ')}) AND MaterialCode IS NOT NULL
`,
params
)
return result.rows
.map((row: Record<string, unknown>) => row.MaterialCode as string)
.filter(Boolean)
}
if (dbService.type === 'postgresql') {
const placeholders = managers.map((_, i) => `$${i + 1}`).join(', ')
const result = await dbService.query(
`
SELECT "MaterialCode"
FROM ${tableName}
WHERE "ManagerName" IN (${placeholders}) AND "MaterialCode" IS NOT NULL
`,
managers
)
return result.rows.map((row) => row.MaterialCode as string).filter(Boolean)
}
const placeholders = managers.map(() => '?').join(', ')
const result = await dbService.query(
`
SELECT MaterialCode
FROM ${tableName}
WHERE ManagerName IN (${placeholders}) AND MaterialCode IS NOT NULL
`,
managers
)
return result.rows.map((row) => row.MaterialCode as string).filter(Boolean)
}
private async disconnectQuietly(dbService: ValidationDatabaseService): Promise<void> {
try {
await dbService.disconnect()

View File

@@ -8,5 +8,6 @@ export const validationApi = {
invokeIpc(IPC_CHANNELS.VALIDATION_SET_SHARED_PRODUCTION_IDS, productionIds),
getSharedProductionIds: () => invokeIpc(IPC_CHANNELS.VALIDATION_GET_SHARED_PRODUCTION_IDS),
clearSharedProductionIds: () => invokeIpc(IPC_CHANNELS.VALIDATION_CLEAR_SHARED_PRODUCTION_IDS),
getCleanerData: () => invokeIpc(IPC_CHANNELS.VALIDATION_GET_CLEANER_DATA)
getCleanerData: (params?: { selectedManagers?: string[] }) =>
invokeIpc(IPC_CHANNELS.VALIDATION_GET_CLEANER_DATA, params ?? { selectedManagers: [] })
} as const

View File

@@ -118,8 +118,11 @@ export async function runCleanerExecution(params: {
headless: boolean
queryBatchSize: number
processConcurrency: number
selectedManagers: string[]
}): Promise<CleanerReportData> {
const cleanerDataResult = await window.electron.validation.getCleanerData()
const cleanerDataResult = await window.electron.validation.getCleanerData({
selectedManagers: params.selectedManagers
})
const cleanerData = cleanerDataResult.success
? (cleanerDataResult.data as CleanerDataPayload | null)
: null

View File

@@ -376,7 +376,8 @@ export function useCleaner() {
dryRun,
headless,
queryBatchSize,
processConcurrency
processConcurrency,
selectedManagers: Array.from(selectedManagers)
})
setReportData(result)
} catch (err) {