feat(cleaner): add configurable row number protection toggle
This commit is contained in:
@@ -66,6 +66,7 @@ cleaner:
|
|||||||
queryBatchSize: 100
|
queryBatchSize: 100
|
||||||
processConcurrency: 1
|
processConcurrency: 1
|
||||||
sessionRefreshOrderThreshold: 160 # 会在 batch 边界检查;达到或超过阈值后,在当前 batch 完成后重建浏览器会话
|
sessionRefreshOrderThreshold: 160 # 会在 batch 边界检查;达到或超过阈值后,在当前 batch 完成后重建浏览器会话
|
||||||
|
enableRowProtection: true # 行号保护:禁止删除行号 2000-7999 范围内的物料,关闭后不再检查行号范围
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
level: info
|
level: info
|
||||||
|
|||||||
@@ -100,7 +100,8 @@ const DEFAULT_CONFIG: FullConfig = {
|
|||||||
cleaner: {
|
cleaner: {
|
||||||
queryBatchSize: 100,
|
queryBatchSize: 100,
|
||||||
processConcurrency: 1,
|
processConcurrency: 1,
|
||||||
sessionRefreshOrderThreshold: 160
|
sessionRefreshOrderThreshold: 160,
|
||||||
|
enableRowProtection: true
|
||||||
},
|
},
|
||||||
orderResolution: {
|
orderResolution: {
|
||||||
tableName: '',
|
tableName: '',
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ export interface ShouldDeleteParams {
|
|||||||
pendingQty: string
|
pendingQty: string
|
||||||
materialCode: string
|
materialCode: string
|
||||||
deleteSet: Set<string>
|
deleteSet: Set<string>
|
||||||
|
enableRowProtection: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
function clampNumber(
|
function clampNumber(
|
||||||
@@ -244,13 +245,13 @@ export class CleanerService {
|
|||||||
* Determine if a material should be deleted
|
* Determine if a material should be deleted
|
||||||
*/
|
*/
|
||||||
shouldDeleteMaterial(params: ShouldDeleteParams): boolean {
|
shouldDeleteMaterial(params: ShouldDeleteParams): boolean {
|
||||||
const { rowNumber, pendingQty, materialCode, deleteSet } = params
|
const { rowNumber, pendingQty, materialCode, deleteSet, enableRowProtection } = params
|
||||||
|
|
||||||
if (!deleteSet.has(materialCode)) {
|
if (!deleteSet.has(materialCode)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rowNumber >= 2000 && rowNumber < 8000) {
|
if (enableRowProtection && rowNumber >= 2000 && rowNumber < 8000) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,12 +263,12 @@ export class CleanerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getSkipReason(params: ShouldDeleteParams): string {
|
getSkipReason(params: ShouldDeleteParams): string {
|
||||||
const { rowNumber, pendingQty, materialCode, deleteSet } = params
|
const { rowNumber, pendingQty, materialCode, deleteSet, enableRowProtection } = params
|
||||||
|
|
||||||
if (!deleteSet.has(materialCode)) {
|
if (!deleteSet.has(materialCode)) {
|
||||||
return '物料不在删除清单中'
|
return '物料不在删除清单中'
|
||||||
}
|
}
|
||||||
if (rowNumber >= 2000 && rowNumber < 8000) {
|
if (enableRowProtection && rowNumber >= 2000 && rowNumber < 8000) {
|
||||||
return '行号在 2000-7999 范围内(受保护)'
|
return '行号在 2000-7999 范围内(受保护)'
|
||||||
}
|
}
|
||||||
if (pendingQty && pendingQty.trim() !== '') {
|
if (pendingQty && pendingQty.trim() !== '') {
|
||||||
@@ -317,6 +318,7 @@ export class CleanerService {
|
|||||||
input.sessionRefreshOrderThreshold && input.sessionRefreshOrderThreshold > 0
|
input.sessionRefreshOrderThreshold && input.sessionRefreshOrderThreshold > 0
|
||||||
? Math.trunc(input.sessionRefreshOrderThreshold)
|
? Math.trunc(input.sessionRefreshOrderThreshold)
|
||||||
: DEFAULT_SESSION_REFRESH_ORDER_THRESHOLD
|
: DEFAULT_SESSION_REFRESH_ORDER_THRESHOLD
|
||||||
|
const enableRowProtection = input.enableRowProtection ?? true
|
||||||
|
|
||||||
log.info('Starting cleaner', {
|
log.info('Starting cleaner', {
|
||||||
totalOrders,
|
totalOrders,
|
||||||
@@ -446,6 +448,7 @@ export class CleanerService {
|
|||||||
detailPage: openedDetailPage,
|
detailPage: openedDetailPage,
|
||||||
deleteSet,
|
deleteSet,
|
||||||
dryRun,
|
dryRun,
|
||||||
|
enableRowProtection,
|
||||||
expectedOrderNumber: orderNumber,
|
expectedOrderNumber: orderNumber,
|
||||||
progressState,
|
progressState,
|
||||||
onProgress: input.onProgress
|
onProgress: input.onProgress
|
||||||
@@ -567,6 +570,7 @@ export class CleanerService {
|
|||||||
),
|
),
|
||||||
deleteSet,
|
deleteSet,
|
||||||
dryRun,
|
dryRun,
|
||||||
|
enableRowProtection,
|
||||||
onProgress: input.onProgress
|
onProgress: input.onProgress
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1149,6 +1153,7 @@ export class CleanerService {
|
|||||||
detailPage: Page
|
detailPage: Page
|
||||||
deleteSet: Set<string>
|
deleteSet: Set<string>
|
||||||
dryRun: boolean
|
dryRun: boolean
|
||||||
|
enableRowProtection: boolean
|
||||||
progressState: ProgressState
|
progressState: ProgressState
|
||||||
expectedOrderNumber?: string
|
expectedOrderNumber?: string
|
||||||
onProgress?: (
|
onProgress?: (
|
||||||
@@ -1157,7 +1162,7 @@ export class CleanerService {
|
|||||||
extra?: Partial<import('../../types/cleaner.types').CleanerProgress>
|
extra?: Partial<import('../../types/cleaner.types').CleanerProgress>
|
||||||
) => void
|
) => void
|
||||||
}): Promise<OrderCleanDetail> {
|
}): Promise<OrderCleanDetail> {
|
||||||
const { detailPage, deleteSet, dryRun, progressState, expectedOrderNumber, onProgress } = params
|
const { detailPage, deleteSet, dryRun, enableRowProtection, progressState, expectedOrderNumber, onProgress } = params
|
||||||
const processStartTime = Date.now()
|
const processStartTime = Date.now()
|
||||||
|
|
||||||
log.info('[ORDER_START] 开始处理订单', {
|
log.info('[ORDER_START] 开始处理订单', {
|
||||||
@@ -1482,7 +1487,8 @@ export class CleanerService {
|
|||||||
rowNumber: rowNumInt,
|
rowNumber: rowNumInt,
|
||||||
pendingQty,
|
pendingQty,
|
||||||
materialCode,
|
materialCode,
|
||||||
deleteSet
|
deleteSet,
|
||||||
|
enableRowProtection
|
||||||
})
|
})
|
||||||
|
|
||||||
if (shouldDelete && !dryRun) {
|
if (shouldDelete && !dryRun) {
|
||||||
@@ -1559,7 +1565,8 @@ export class CleanerService {
|
|||||||
rowNumber: rowNumInt,
|
rowNumber: rowNumInt,
|
||||||
pendingQty,
|
pendingQty,
|
||||||
materialCode,
|
materialCode,
|
||||||
deleteSet
|
deleteSet,
|
||||||
|
enableRowProtection
|
||||||
})
|
})
|
||||||
log.debug('[物料跳过] 物料不满足删除条件', {
|
log.debug('[物料跳过] 物料不满足删除条件', {
|
||||||
orderNumber,
|
orderNumber,
|
||||||
@@ -2097,13 +2104,14 @@ export class CleanerService {
|
|||||||
failedDetails: OrderCleanDetail[]
|
failedDetails: OrderCleanDetail[]
|
||||||
deleteSet: Set<string>
|
deleteSet: Set<string>
|
||||||
dryRun: boolean
|
dryRun: boolean
|
||||||
|
enableRowProtection: boolean
|
||||||
onProgress?: (
|
onProgress?: (
|
||||||
message: string,
|
message: string,
|
||||||
progress?: number,
|
progress?: number,
|
||||||
extra?: Partial<import('../../types/cleaner.types').CleanerProgress>
|
extra?: Partial<import('../../types/cleaner.types').CleanerProgress>
|
||||||
) => void
|
) => void
|
||||||
}): Promise<RetryResult> {
|
}): Promise<RetryResult> {
|
||||||
const { workFrame, popupPage, failedDetails, deleteSet, dryRun, onProgress } = params
|
const { workFrame, popupPage, failedDetails, deleteSet, dryRun, enableRowProtection, onProgress } = params
|
||||||
|
|
||||||
const result: RetryResult = {
|
const result: RetryResult = {
|
||||||
retriedOrders: 0,
|
retriedOrders: 0,
|
||||||
@@ -2204,6 +2212,7 @@ export class CleanerService {
|
|||||||
detailPage,
|
detailPage,
|
||||||
deleteSet,
|
deleteSet,
|
||||||
dryRun,
|
dryRun,
|
||||||
|
enableRowProtection,
|
||||||
expectedOrderNumber: orderNumber,
|
expectedOrderNumber: orderNumber,
|
||||||
progressState: {
|
progressState: {
|
||||||
ordersStarted: detailIndex,
|
ordersStarted: detailIndex,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export interface CleanerInput {
|
|||||||
queryBatchSize?: number
|
queryBatchSize?: number
|
||||||
processConcurrency?: number
|
processConcurrency?: number
|
||||||
sessionRefreshOrderThreshold?: number
|
sessionRefreshOrderThreshold?: number
|
||||||
|
enableRowProtection?: boolean
|
||||||
onProgress?: (message: string, progress?: number, extra?: Partial<CleanerProgress>) => void
|
onProgress?: (message: string, progress?: number, extra?: Partial<CleanerProgress>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -118,7 +118,8 @@ export const validationConfigSchema = z.object({
|
|||||||
export const cleanerConfigSchema = z.object({
|
export const cleanerConfigSchema = z.object({
|
||||||
queryBatchSize: z.number().int().min(1).max(100).default(100),
|
queryBatchSize: z.number().int().min(1).max(100).default(100),
|
||||||
processConcurrency: z.number().int().min(1).max(20).default(1),
|
processConcurrency: z.number().int().min(1).max(20).default(1),
|
||||||
sessionRefreshOrderThreshold: z.number().int().positive().default(160)
|
sessionRefreshOrderThreshold: z.number().int().positive().default(160),
|
||||||
|
enableRowProtection: z.boolean().default(true)
|
||||||
})
|
})
|
||||||
export type CleanerConfig = z.infer<typeof cleanerConfigSchema>
|
export type CleanerConfig = z.infer<typeof cleanerConfigSchema>
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ interface CleanerExecutionBarProps {
|
|||||||
setDryRun: (value: boolean) => void
|
setDryRun: (value: boolean) => void
|
||||||
headless: boolean
|
headless: boolean
|
||||||
setHeadless: (value: boolean) => void
|
setHeadless: (value: boolean) => void
|
||||||
|
enableRowProtection: boolean
|
||||||
|
setEnableRowProtection: (value: boolean) => void
|
||||||
processConcurrency: number
|
processConcurrency: number
|
||||||
updateProcessConcurrency: (value: number) => void
|
updateProcessConcurrency: (value: number) => void
|
||||||
showSettingsMenu: boolean
|
showSettingsMenu: boolean
|
||||||
@@ -30,7 +32,9 @@ export function CleanerExecutionBar({
|
|||||||
setShowSettingsMenu,
|
setShowSettingsMenu,
|
||||||
handleExecuteDeletion,
|
handleExecuteDeletion,
|
||||||
isRunning,
|
isRunning,
|
||||||
executeButtonRef
|
executeButtonRef,
|
||||||
|
enableRowProtection,
|
||||||
|
setEnableRowProtection
|
||||||
}: CleanerExecutionBarProps): React.JSX.Element {
|
}: CleanerExecutionBarProps): React.JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className="bg-white border-t border-slate-200 p-4 flex justify-between items-center shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.05)] z-10 flex-shrink-0">
|
<div className="bg-white border-t border-slate-200 p-4 flex justify-between items-center shadow-[0_-4px_6px_-1px_rgba(0,0,0,0.05)] z-10 flex-shrink-0">
|
||||||
@@ -82,6 +86,22 @@ export function CleanerExecutionBar({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="border-t border-slate-100 pt-3">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<div className="text-sm font-medium text-slate-800">行号保护</div>
|
||||||
|
<div className="text-xs text-slate-500 mt-0.5">
|
||||||
|
保护 2000-7999 行号范围的物料不被删除
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => setEnableRowProtection(!enableRowProtection)}
|
||||||
|
className={`transition-colors flex-shrink-0 ml-4 ${enableRowProtection ? 'text-green-500' : 'text-slate-300'}`}
|
||||||
|
>
|
||||||
|
{enableRowProtection ? <ToggleRight size={32} /> : <ToggleLeft size={32} />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className="border-t border-slate-100 pt-3 space-y-3">
|
<div className="border-t border-slate-100 pt-3 space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-medium text-slate-800">并行处理数量</div>
|
<div className="text-sm font-medium text-slate-800">并行处理数量</div>
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ export async function runCleanerExecution(params: {
|
|||||||
queryBatchSize: number
|
queryBatchSize: number
|
||||||
processConcurrency: number
|
processConcurrency: number
|
||||||
sessionRefreshOrderThreshold: number
|
sessionRefreshOrderThreshold: number
|
||||||
|
enableRowProtection: boolean
|
||||||
selectedManagers: string[]
|
selectedManagers: string[]
|
||||||
}): Promise<CleanerReportData> {
|
}): Promise<CleanerReportData> {
|
||||||
const cleanerDataResult = await window.electron.validation.getCleanerData({
|
const cleanerDataResult = await window.electron.validation.getCleanerData({
|
||||||
@@ -154,7 +155,8 @@ export async function runCleanerExecution(params: {
|
|||||||
headless: params.headless,
|
headless: params.headless,
|
||||||
queryBatchSize: params.queryBatchSize,
|
queryBatchSize: params.queryBatchSize,
|
||||||
processConcurrency: params.processConcurrency,
|
processConcurrency: params.processConcurrency,
|
||||||
sessionRefreshOrderThreshold: params.sessionRefreshOrderThreshold
|
sessionRefreshOrderThreshold: params.sessionRefreshOrderThreshold,
|
||||||
|
enableRowProtection: params.enableRowProtection
|
||||||
})
|
})
|
||||||
|
|
||||||
const cleanerRunData = response.success ? (response.data as CleanerRunPayload | null) : null
|
const cleanerRunData = response.success ? (response.data as CleanerRunPayload | null) : null
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ export function useCleaner() {
|
|||||||
const [queryBatchSize, setQueryBatchSize] = useState(100)
|
const [queryBatchSize, setQueryBatchSize] = useState(100)
|
||||||
const [processConcurrency, setProcessConcurrency] = useState(1)
|
const [processConcurrency, setProcessConcurrency] = useState(1)
|
||||||
const [sessionRefreshOrderThreshold, setSessionRefreshOrderThreshold] = useState(160)
|
const [sessionRefreshOrderThreshold, setSessionRefreshOrderThreshold] = useState(160)
|
||||||
|
const [enableRowProtection, setEnableRowProtection] = useState(() =>
|
||||||
|
getStoredBoolean('cleaner_enableRowProtection', true)
|
||||||
|
)
|
||||||
const [showSettingsMenu, setShowSettingsMenu] = useState(false)
|
const [showSettingsMenu, setShowSettingsMenu] = useState(false)
|
||||||
|
|
||||||
// Inline editing state for manager field (Admin only)
|
// Inline editing state for manager field (Admin only)
|
||||||
@@ -158,6 +161,10 @@ export function useCleaner() {
|
|||||||
sessionStorage.setItem('cleaner_headless', headless.toString())
|
sessionStorage.setItem('cleaner_headless', headless.toString())
|
||||||
}, [headless])
|
}, [headless])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
sessionStorage.setItem('cleaner_enableRowProtection', enableRowProtection.toString())
|
||||||
|
}, [enableRowProtection])
|
||||||
|
|
||||||
const updateProcessConcurrency = async (value: number) => {
|
const updateProcessConcurrency = async (value: number) => {
|
||||||
const clamped = Math.max(1, Math.min(20, value))
|
const clamped = Math.max(1, Math.min(20, value))
|
||||||
setProcessConcurrency(clamped)
|
setProcessConcurrency(clamped)
|
||||||
@@ -380,6 +387,7 @@ export function useCleaner() {
|
|||||||
queryBatchSize,
|
queryBatchSize,
|
||||||
processConcurrency,
|
processConcurrency,
|
||||||
sessionRefreshOrderThreshold,
|
sessionRefreshOrderThreshold,
|
||||||
|
enableRowProtection,
|
||||||
selectedManagers: Array.from(selectedManagers)
|
selectedManagers: Array.from(selectedManagers)
|
||||||
})
|
})
|
||||||
setReportData(result)
|
setReportData(result)
|
||||||
@@ -445,6 +453,8 @@ export function useCleaner() {
|
|||||||
setProcessConcurrency,
|
setProcessConcurrency,
|
||||||
sessionRefreshOrderThreshold,
|
sessionRefreshOrderThreshold,
|
||||||
setSessionRefreshOrderThreshold,
|
setSessionRefreshOrderThreshold,
|
||||||
|
enableRowProtection,
|
||||||
|
setEnableRowProtection,
|
||||||
updateProcessConcurrency,
|
updateProcessConcurrency,
|
||||||
showSettingsMenu,
|
showSettingsMenu,
|
||||||
setShowSettingsMenu,
|
setShowSettingsMenu,
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ const CleanerPage: React.FC = () => {
|
|||||||
setHeadless,
|
setHeadless,
|
||||||
processConcurrency,
|
processConcurrency,
|
||||||
updateProcessConcurrency,
|
updateProcessConcurrency,
|
||||||
|
enableRowProtection,
|
||||||
|
setEnableRowProtection,
|
||||||
showSettingsMenu,
|
showSettingsMenu,
|
||||||
setShowSettingsMenu,
|
setShowSettingsMenu,
|
||||||
filteredResults,
|
filteredResults,
|
||||||
@@ -124,6 +126,8 @@ const CleanerPage: React.FC = () => {
|
|||||||
setDryRun={setDryRun}
|
setDryRun={setDryRun}
|
||||||
headless={headless}
|
headless={headless}
|
||||||
setHeadless={setHeadless}
|
setHeadless={setHeadless}
|
||||||
|
enableRowProtection={enableRowProtection}
|
||||||
|
setEnableRowProtection={setEnableRowProtection}
|
||||||
processConcurrency={processConcurrency}
|
processConcurrency={processConcurrency}
|
||||||
updateProcessConcurrency={updateProcessConcurrency}
|
updateProcessConcurrency={updateProcessConcurrency}
|
||||||
showSettingsMenu={showSettingsMenu}
|
showSettingsMenu={showSettingsMenu}
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ describe('CleanerService - Helper Methods', () => {
|
|||||||
rowNumber: 100,
|
rowNumber: 100,
|
||||||
pendingQty: '',
|
pendingQty: '',
|
||||||
materialCode: 'MAT001',
|
materialCode: 'MAT001',
|
||||||
deleteSet
|
deleteSet,
|
||||||
|
enableRowProtection: true
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toBe(true)
|
expect(result).toBe(true)
|
||||||
@@ -34,7 +35,8 @@ describe('CleanerService - Helper Methods', () => {
|
|||||||
rowNumber: 100,
|
rowNumber: 100,
|
||||||
pendingQty: '',
|
pendingQty: '',
|
||||||
materialCode: 'MAT999',
|
materialCode: 'MAT999',
|
||||||
deleteSet
|
deleteSet,
|
||||||
|
enableRowProtection: true
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toBe(false)
|
expect(result).toBe(false)
|
||||||
@@ -45,7 +47,8 @@ describe('CleanerService - Helper Methods', () => {
|
|||||||
rowNumber: 5000,
|
rowNumber: 5000,
|
||||||
pendingQty: '',
|
pendingQty: '',
|
||||||
materialCode: 'MAT001',
|
materialCode: 'MAT001',
|
||||||
deleteSet
|
deleteSet,
|
||||||
|
enableRowProtection: true
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toBe(false)
|
expect(result).toBe(false)
|
||||||
@@ -56,7 +59,8 @@ describe('CleanerService - Helper Methods', () => {
|
|||||||
rowNumber: 100,
|
rowNumber: 100,
|
||||||
pendingQty: '5',
|
pendingQty: '5',
|
||||||
materialCode: 'MAT001',
|
materialCode: 'MAT001',
|
||||||
deleteSet
|
deleteSet,
|
||||||
|
enableRowProtection: true
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toBe(false)
|
expect(result).toBe(false)
|
||||||
@@ -67,7 +71,8 @@ describe('CleanerService - Helper Methods', () => {
|
|||||||
rowNumber: 100,
|
rowNumber: 100,
|
||||||
pendingQty: ' ',
|
pendingQty: ' ',
|
||||||
materialCode: 'MAT001',
|
materialCode: 'MAT001',
|
||||||
deleteSet
|
deleteSet,
|
||||||
|
enableRowProtection: true
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toBe(true) // whitespace-only is treated as empty after trim
|
expect(result).toBe(true) // whitespace-only is treated as empty after trim
|
||||||
@@ -80,7 +85,8 @@ describe('CleanerService - Helper Methods', () => {
|
|||||||
rowNumber: 1999,
|
rowNumber: 1999,
|
||||||
pendingQty: '',
|
pendingQty: '',
|
||||||
materialCode: 'MAT001',
|
materialCode: 'MAT001',
|
||||||
deleteSet
|
deleteSet,
|
||||||
|
enableRowProtection: true
|
||||||
})
|
})
|
||||||
).toBe(true)
|
).toBe(true)
|
||||||
|
|
||||||
@@ -90,7 +96,8 @@ describe('CleanerService - Helper Methods', () => {
|
|||||||
rowNumber: 2000,
|
rowNumber: 2000,
|
||||||
pendingQty: '',
|
pendingQty: '',
|
||||||
materialCode: 'MAT001',
|
materialCode: 'MAT001',
|
||||||
deleteSet
|
deleteSet,
|
||||||
|
enableRowProtection: true
|
||||||
})
|
})
|
||||||
).toBe(false)
|
).toBe(false)
|
||||||
|
|
||||||
@@ -100,7 +107,8 @@ describe('CleanerService - Helper Methods', () => {
|
|||||||
rowNumber: 7999,
|
rowNumber: 7999,
|
||||||
pendingQty: '',
|
pendingQty: '',
|
||||||
materialCode: 'MAT001',
|
materialCode: 'MAT001',
|
||||||
deleteSet
|
deleteSet,
|
||||||
|
enableRowProtection: true
|
||||||
})
|
})
|
||||||
).toBe(false)
|
).toBe(false)
|
||||||
|
|
||||||
@@ -110,10 +118,47 @@ describe('CleanerService - Helper Methods', () => {
|
|||||||
rowNumber: 8000,
|
rowNumber: 8000,
|
||||||
pendingQty: '',
|
pendingQty: '',
|
||||||
materialCode: 'MAT001',
|
materialCode: 'MAT001',
|
||||||
deleteSet
|
deleteSet,
|
||||||
|
enableRowProtection: true
|
||||||
})
|
})
|
||||||
).toBe(true)
|
).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should allow deletion when row protection is disabled (row in 2000-7999)', () => {
|
||||||
|
const result = cleaner.shouldDeleteMaterial({
|
||||||
|
rowNumber: 5000,
|
||||||
|
pendingQty: '',
|
||||||
|
materialCode: 'MAT001',
|
||||||
|
deleteSet,
|
||||||
|
enableRowProtection: false
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should still block deletion when row protection is disabled but pendingQty is not empty', () => {
|
||||||
|
const result = cleaner.shouldDeleteMaterial({
|
||||||
|
rowNumber: 5000,
|
||||||
|
pendingQty: '10',
|
||||||
|
materialCode: 'MAT001',
|
||||||
|
deleteSet,
|
||||||
|
enableRowProtection: false
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should still block deletion when row protection is disabled but material not in delete set', () => {
|
||||||
|
const result = cleaner.shouldDeleteMaterial({
|
||||||
|
rowNumber: 5000,
|
||||||
|
pendingQty: '',
|
||||||
|
materialCode: 'MAT999',
|
||||||
|
deleteSet,
|
||||||
|
enableRowProtection: false
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toBe(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getSkipReason()', () => {
|
describe('getSkipReason()', () => {
|
||||||
@@ -125,7 +170,8 @@ describe('CleanerService - Helper Methods', () => {
|
|||||||
rowNumber: 3000,
|
rowNumber: 3000,
|
||||||
pendingQty: '',
|
pendingQty: '',
|
||||||
materialCode: 'MAT001',
|
materialCode: 'MAT001',
|
||||||
deleteSet
|
deleteSet,
|
||||||
|
enableRowProtection: true
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(reason).toBe('行号在 2000-7999 范围内(受保护)')
|
expect(reason).toBe('行号在 2000-7999 范围内(受保护)')
|
||||||
@@ -136,13 +182,15 @@ describe('CleanerService - Helper Methods', () => {
|
|||||||
rowNumber: 100,
|
rowNumber: 100,
|
||||||
pendingQty: '',
|
pendingQty: '',
|
||||||
materialCode: 'MAT001',
|
materialCode: 'MAT001',
|
||||||
deleteSet
|
deleteSet,
|
||||||
|
enableRowProtection: true
|
||||||
})
|
})
|
||||||
const reason = cleaner.getSkipReason({
|
const reason = cleaner.getSkipReason({
|
||||||
rowNumber: 100,
|
rowNumber: 100,
|
||||||
pendingQty: '',
|
pendingQty: '',
|
||||||
materialCode: 'MAT001',
|
materialCode: 'MAT001',
|
||||||
deleteSet
|
deleteSet,
|
||||||
|
enableRowProtection: true
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(result).toBe(true)
|
expect(result).toBe(true)
|
||||||
@@ -154,7 +202,8 @@ describe('CleanerService - Helper Methods', () => {
|
|||||||
rowNumber: 100,
|
rowNumber: 100,
|
||||||
pendingQty: '',
|
pendingQty: '',
|
||||||
materialCode: 'MAT999',
|
materialCode: 'MAT999',
|
||||||
deleteSet
|
deleteSet,
|
||||||
|
enableRowProtection: true
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(reason).toBe('物料不在删除清单中')
|
expect(reason).toBe('物料不在删除清单中')
|
||||||
@@ -165,7 +214,8 @@ describe('CleanerService - Helper Methods', () => {
|
|||||||
rowNumber: 100,
|
rowNumber: 100,
|
||||||
pendingQty: '10',
|
pendingQty: '10',
|
||||||
materialCode: 'MAT001',
|
materialCode: 'MAT001',
|
||||||
deleteSet
|
deleteSet,
|
||||||
|
enableRowProtection: true
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(reason).toBe('累计待发数量不为空')
|
expect(reason).toBe('累计待发数量不为空')
|
||||||
|
|||||||
Reference in New Issue
Block a user