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:
@@ -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 ?? []
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -376,7 +376,8 @@ export function useCleaner() {
|
||||
dryRun,
|
||||
headless,
|
||||
queryBatchSize,
|
||||
processConcurrency
|
||||
processConcurrency,
|
||||
selectedManagers: Array.from(selectedManagers)
|
||||
})
|
||||
setReportData(result)
|
||||
} catch (err) {
|
||||
|
||||
Reference in New Issue
Block a user