diff --git a/src/renderer/src/components/ExecutionReportDialog.tsx b/src/renderer/src/components/ExecutionReportDialog.tsx index 2bfd3ef..3798597 100644 --- a/src/renderer/src/components/ExecutionReportDialog.tsx +++ b/src/renderer/src/components/ExecutionReportDialog.tsx @@ -10,17 +10,7 @@ import React from 'react' import { CheckCircle, XCircle, SkipForward, Package, Loader2 } from 'lucide-react' import { Modal } from './ui/Modal' - -interface CleanerProgress { - message: string - progress: number - currentOrderIndex: number - totalOrders: number - currentMaterialIndex: number - totalMaterialsInOrder: number - currentOrderNumber?: string - phase: 'login' | 'processing' | 'complete' -} +import type { CleanerProgress } from '../hooks/cleaner/types' interface ExecutionReportDialogProps { isOpen: boolean diff --git a/src/renderer/src/hooks/cleaner/api.ts b/src/renderer/src/hooks/cleaner/api.ts new file mode 100644 index 0000000..77f4e02 --- /dev/null +++ b/src/renderer/src/hooks/cleaner/api.ts @@ -0,0 +1,159 @@ +import type { + CleanerConfigResult, + CleanerInitializationResult, + CleanerReportData, + ValidationRequest, + ValidationResponsePayload +} from './types' +import type { CleanerExportItem, MaterialBatchChange } from './helpers' + +export async function initializeCleanerPage(): Promise { + const adminResult = await window.electron.auth.isAdmin() + const userResult = await window.electron.auth.getCurrentUser() + const isAdmin = adminResult.success && Boolean(adminResult.data) + const user = userResult.success ? userResult.data : undefined + const currentUsername = user?.userInfo?.username ?? '' + + let managers: string[] = [] + if (isAdmin) { + const response = await window.electron.materials.getManagers() + const payload = response.success + ? (response.data as { managers?: string[] } | undefined) + : undefined + managers = payload?.managers ?? [] + } + + const sharedIdsResult = await window.electron.validation.getSharedProductionIds() + const sharedIdsPayload = sharedIdsResult.success + ? (sharedIdsResult.data as { productionIds?: string[] } | undefined) + : undefined + + return { + isAdmin, + currentUsername, + managers, + sharedProductionIdsCount: sharedIdsPayload?.productionIds?.length ?? 0 + } +} + +export async function loadCleanerConfig(): Promise { + const result = await window.electron.config.getCleaner() + if (!result.success || !result.data) { + return null + } + + return { + queryBatchSize: result.data.queryBatchSize, + processConcurrency: result.data.processConcurrency + } +} + +export async function runValidationRequest( + request: ValidationRequest +): Promise { + const response = await window.electron.validation.validate(request) + if (!response.success || !response.data) { + return response.success ? null : { success: false, error: response.error } + } + + return response.data as ValidationResponsePayload +} + +export async function saveDeletionPlan(params: { + materialsToUpsert: MaterialBatchChange[] + materialsToDelete: string[] +}): Promise<{ upserted: number; deleted: number }> { + const { materialsToUpsert, materialsToDelete } = params + let upserted = 0 + let deleted = 0 + + if (materialsToUpsert.length > 0) { + const response = await window.electron.materials.upsertBatch(materialsToUpsert) + const payload = response.success + ? (response.data as { stats?: { success?: number } } | undefined) + : undefined + if (!response.success) { + throw new Error(response.error || '写入物料失败') + } + upserted = payload?.stats?.success || 0 + } + + if (materialsToDelete.length > 0) { + const response = await window.electron.materials.delete(materialsToDelete) + const payload = response.success ? (response.data as { count?: number } | undefined) : undefined + if (!response.success) { + throw new Error(response.error || '删除物料失败') + } + deleted = payload?.count || 0 + } + + return { upserted, deleted } +} + +export async function reloadManagers(): Promise { + const response = await window.electron.materials.getManagers() + const payload = response.success + ? (response.data as { managers?: string[] } | undefined) + : undefined + return payload?.managers ?? [] +} + +export async function runCleanerExecution(params: { + dryRun: boolean + headless: boolean + queryBatchSize: number + processConcurrency: number +}): Promise { + const cleanerDataResult = await window.electron.validation.getCleanerData() + const cleanerData = cleanerDataResult.success ? (cleanerDataResult.data as any) : null + + if (!cleanerDataResult.success || cleanerData?.success === false) { + throw new Error(cleanerDataResult.error || '获取清理数据失败') + } + + const orderNumberList = cleanerData?.orderNumbers || [] + const materialCodeList = cleanerData?.materialCodes || [] + + if (orderNumberList.length === 0) { + throw new Error('没有订单号数据。请先到数据提取页面输入 Production ID。') + } + if (materialCodeList.length === 0) { + throw new Error('没有物料代码数据。请确认已在物料清理界面确认要删除的物料。') + } + + const response = await window.electron.cleaner.runCleaner({ + orderNumbers: orderNumberList, + materialCodes: materialCodeList, + dryRun: params.dryRun, + headless: params.headless, + queryBatchSize: params.queryBatchSize, + processConcurrency: params.processConcurrency + }) + + const cleanerRunData = response.success ? (response.data as any) : null + if (!response.success || !cleanerRunData) { + throw new Error(response.error || '清理失败') + } + + return { + ordersProcessed: cleanerRunData.ordersProcessed, + materialsDeleted: cleanerRunData.materialsDeleted, + materialsSkipped: cleanerRunData.materialsSkipped, + errors: cleanerRunData.errors, + retriedOrders: cleanerRunData.retriedOrders, + successfulRetries: cleanerRunData.successfulRetries + } +} + +export async function exportCleanerResults(items: CleanerExportItem[]): Promise { + const response = await window.electron.cleaner.exportResults(items) + const exportData = response.success + ? (response.data as { success?: boolean; filePath?: string; error?: string } | null) + : null + + if (!response.success || exportData?.success === false) { + throw new Error(response.error || exportData?.error || '导出失败') + } + + return exportData?.filePath ?? '' +} diff --git a/src/renderer/src/hooks/cleaner/helpers.ts b/src/renderer/src/hooks/cleaner/helpers.ts new file mode 100644 index 0000000..c6236c4 --- /dev/null +++ b/src/renderer/src/hooks/cleaner/helpers.ts @@ -0,0 +1,107 @@ +import type { ValidationResult } from './types' + +export interface MaterialBatchChange { + materialCode: string + managerName: string +} + +export interface DeletionPlan { + materialsToUpsert: MaterialBatchChange[] + materialsToDelete: string[] + missingManager: string[] +} + +export interface CleanerExportItem { + materialName: string + materialCode: string + specification: string + model: string + managerName: string + isMarkedForDeletion: boolean + isSelected: boolean +} + +export function getStoredBoolean(key: string, fallback: boolean): boolean { + const saved = sessionStorage.getItem(key) + return saved ? saved === 'true' : fallback +} + +export function getStoredValidationMode(): 'full' | 'filtered' { + const saved = sessionStorage.getItem('cleaner_validationMode') + return saved === 'full' ? 'full' : 'filtered' +} + +export function filterValidationResults(params: { + validationResults: ValidationResult[] + isAdmin: boolean + currentUsername: string + managers: string[] + selectedManagers: Set + hiddenItems: Set +}): ValidationResult[] { + const { validationResults, isAdmin, currentUsername, managers, selectedManagers, hiddenItems } = + params + + let results = validationResults + if (!isAdmin && currentUsername) { + results = results.filter((result) => { + return result.managerName === currentUsername || !result.managerName + }) + } else if (managers.length > 0 && selectedManagers.size > 0) { + results = results.filter((result) => { + return selectedManagers.has(result.managerName) || !result.managerName + }) + } + + return results.filter((result) => !hiddenItems.has(result.materialCode)) +} + +export function buildDeletionPlan( + resultsToProcess: ValidationResult[], + selectedItems: Set +): DeletionPlan { + const materialsToUpsert: MaterialBatchChange[] = [] + const materialsToDelete: string[] = [] + const missingManager: string[] = [] + + for (const result of resultsToProcess) { + if (!result.materialCode?.trim()) { + continue + } + + const code = result.materialCode.trim() + if (selectedItems.has(code)) { + if (!result.managerName?.trim()) { + missingManager.push(code) + } else { + materialsToUpsert.push({ + materialCode: code, + managerName: result.managerName.trim() + }) + } + } else { + materialsToDelete.push(code) + } + } + + return { + materialsToUpsert, + materialsToDelete, + missingManager + } +} + +export function buildExportItems( + filteredResults: ValidationResult[], + selectedItems: Set +): CleanerExportItem[] { + return filteredResults.map((result) => ({ + materialName: result.materialName, + materialCode: result.materialCode, + specification: result.specification || '', + model: result.model || '', + managerName: result.managerName || '', + isMarkedForDeletion: result.isMarkedForDeletion, + isSelected: selectedItems.has(result.materialCode) + })) +} diff --git a/src/renderer/src/hooks/cleaner/types.ts b/src/renderer/src/hooks/cleaner/types.ts new file mode 100644 index 0000000..6cc8691 --- /dev/null +++ b/src/renderer/src/hooks/cleaner/types.ts @@ -0,0 +1,60 @@ +export interface ValidationRequest { + mode: 'database_full' | 'database_filtered' + productionIdFile?: string + useSharedProductionIds?: boolean +} + +export interface ValidationResult { + materialName: string + materialCode: string + specification: string + model: string + managerName: string + isMarkedForDeletion: boolean + matchedTypeKeyword?: string +} + +export interface ValidationStats { + totalRecords: number + matchedCount: number + markedCount: number +} + +export interface ValidationResponsePayload { + success: boolean + results?: ValidationResult[] + stats?: ValidationStats + error?: string +} + +export interface CleanerProgress { + message: string + progress: number + currentOrderIndex: number + totalOrders: number + currentMaterialIndex: number + totalMaterialsInOrder: number + currentOrderNumber?: string + phase: 'login' | 'processing' | 'complete' +} + +export interface CleanerReportData { + ordersProcessed: number + materialsDeleted: number + materialsSkipped: number + errors: string[] + retriedOrders?: number + successfulRetries?: number +} + +export interface CleanerInitializationResult { + isAdmin: boolean + currentUsername: string + managers: string[] + sharedProductionIdsCount: number +} + +export interface CleanerConfigResult { + queryBatchSize: number + processConcurrency: number +} diff --git a/src/renderer/src/hooks/useCleaner.ts b/src/renderer/src/hooks/useCleaner.ts index b9c0e10..e6dda23 100644 --- a/src/renderer/src/hooks/useCleaner.ts +++ b/src/renderer/src/hooks/useCleaner.ts @@ -1,27 +1,23 @@ import { useState, useEffect, useMemo, useRef, useCallback } from 'react' import { showSuccess, showError, showWarning, formatListMessage } from '../stores/useAppStore' import { ConfirmDialogProps } from '../components/ui/ConfirmDialog' - -export interface ValidationResult { - materialName: string - materialCode: string - specification: string - model: string - managerName: string - isMarkedForDeletion: boolean - matchedTypeKeyword?: string -} - -export interface CleanerProgress { - message: string - progress: number - currentOrderIndex: number - totalOrders: number - currentMaterialIndex: number - totalMaterialsInOrder: number - currentOrderNumber?: string - phase: 'login' | 'processing' | 'complete' -} +import { + buildDeletionPlan, + buildExportItems, + filterValidationResults, + getStoredBoolean, + getStoredValidationMode +} from './cleaner/helpers' +import { + exportCleanerResults, + initializeCleanerPage, + loadCleanerConfig as fetchCleanerConfig, + reloadManagers, + runCleanerExecution, + runValidationRequest, + saveDeletionPlan +} from './cleaner/api' +import type { CleanerProgress, CleanerReportData, ValidationResult } from './cleaner/types' export function useCleaner() { // Authentication & permissions @@ -29,15 +25,8 @@ export function useCleaner() { const [currentUsername, setCurrentUsername] = useState('') // State with sessionStorage persistence - const [dryRun, setDryRun] = useState(() => { - const saved = sessionStorage.getItem('cleaner_dryRun') - return saved ? saved === 'true' : false - }) - - const [valMode, setValMode] = useState<'full' | 'filtered'>(() => { - const saved = sessionStorage.getItem('cleaner_validationMode') - return saved === 'full' ? 'full' : 'filtered' - }) + const [dryRun, setDryRun] = useState(() => getStoredBoolean('cleaner_dryRun', false)) + const [valMode, setValMode] = useState<'full' | 'filtered'>(() => getStoredValidationMode()) // Validation state const [validationResults, setValidationResults] = useState([]) @@ -60,24 +49,14 @@ export function useCleaner() { // Execution report dialog state const [isReportDialogOpen, setIsReportDialogOpen] = useState(false) - const [reportData, setReportData] = useState<{ - ordersProcessed: number - materialsDeleted: number - materialsSkipped: number - errors: string[] - retriedOrders?: number - successfulRetries?: number - } | null>(null) + const [reportData, setReportData] = useState(null) // Progress state const [progress, setProgress] = useState(null) const [startTime, setStartTime] = useState(null) // Execution settings state - const [headless, setHeadless] = useState(() => { - const saved = sessionStorage.getItem('cleaner_headless') - return saved ? saved === 'true' : true - }) + const [headless, setHeadless] = useState(() => getStoredBoolean('cleaner_headless', true)) const [queryBatchSize, setQueryBatchSize] = useState(100) const [processConcurrency, setProcessConcurrency] = useState(1) const [showSettingsMenu, setShowSettingsMenu] = useState(false) @@ -117,35 +96,17 @@ export function useCleaner() { useEffect(() => { const initializePage = async () => { try { - const adminResult = await window.electron.auth.isAdmin() - const userResult = await window.electron.auth.getCurrentUser() - const admin = adminResult.success && Boolean(adminResult.data) - const user = userResult.success ? userResult.data : undefined - setIsAdmin(admin) - if (user && user.userInfo) { - setCurrentUsername(user.userInfo.username) - if (!admin) { - setSelectedManagers(new Set([user.userInfo.username])) - } - } + const result = await initializeCleanerPage() + setIsAdmin(result.isAdmin) + setCurrentUsername(result.currentUsername) + setManagers(result.managers) + setSharedProductionIdsCount(result.sharedProductionIdsCount) - // Load managers - if (admin) { - const resp = await window.electron.materials.getManagers() - const managersPayload = resp.success - ? (resp.data as { managers: string[] } | undefined) - : undefined - const managerList = managersPayload?.managers ?? [] - setManagers(managerList) - setSelectedManagers(new Set(managerList)) + if (result.isAdmin) { + setSelectedManagers(new Set(result.managers)) + } else if (result.currentUsername) { + setSelectedManagers(new Set([result.currentUsername])) } - - // Get shared Production IDs - const result = await window.electron.validation.getSharedProductionIds() - const idsPayload = result.success - ? (result.data as { productionIds?: string[] } | undefined) - : undefined - setSharedProductionIdsCount(idsPayload?.productionIds?.length ?? 0) } catch (err) { console.error('Initialization failed:', err) } @@ -168,10 +129,10 @@ export function useCleaner() { useEffect(() => { const loadCleanerConfig = async () => { try { - const result = await window.electron.config.getCleaner() - if (result.success && result.data) { - setQueryBatchSize(result.data.queryBatchSize) - setProcessConcurrency(result.data.processConcurrency) + const result = await fetchCleanerConfig() + if (result) { + setQueryBatchSize(result.queryBatchSize) + setProcessConcurrency(result.processConcurrency) } } catch (err) { console.error('Failed to load cleaner config:', err) @@ -204,14 +165,14 @@ export function useCleaner() { // Filter validation results by selected managers and hidden items const filteredResults = useMemo(() => { - let results = validationResults - if (!isAdmin && currentUsername) { - results = results.filter((r) => r.managerName === currentUsername || !r.managerName) - } else if (managers.length > 0 && selectedManagers.size > 0) { - results = results.filter((r) => selectedManagers.has(r.managerName) || !r.managerName) - } - results = results.filter((r) => !hiddenItems.has(r.materialCode)) - return results + return filterValidationResults({ + validationResults, + isAdmin, + currentUsername, + managers, + selectedManagers, + hiddenItems + }) }, [validationResults, isAdmin, currentUsername, managers, selectedManagers, hiddenItems]) // Handlers @@ -222,13 +183,12 @@ export function useCleaner() { setHiddenItems(new Set()) try { - const response = await window.electron.validation.validate({ + const validationData = await runValidationRequest({ mode: valMode === 'full' ? 'database_full' : 'database_filtered', useSharedProductionIds: valMode === 'filtered' }) - const validationData = response.success ? (response.data as any) : null - if (response.success && validationData?.success && validationData.results) { + if (validationData?.success && validationData.results) { setValidationResults(validationData.results) const markedCodes = new Set( validationData.results @@ -248,7 +208,7 @@ export function useCleaner() { } showSuccess('校验完成') } else { - showError(response.error || validationData?.error || '校验失败') + showError(validationData?.error || '校验失败') } } catch (err) { showError(err instanceof Error ? err.message : '校验过程中发生未知错误') @@ -324,21 +284,10 @@ export function useCleaner() { return } - const materialsToUpsert: { materialCode: string; managerName: string }[] = [] - const materialsToDelete: string[] = [] - const missingManager: string[] = [] - - for (const result of resultsToProcess) { - if (!result.materialCode?.trim()) continue - - const code = result.materialCode.trim() - if (selectedItems.has(code)) { - if (!result.managerName?.trim()) missingManager.push(code) - else materialsToUpsert.push({ materialCode: code, managerName: result.managerName.trim() }) - } else { - materialsToDelete.push(code) - } - } + const { materialsToUpsert, materialsToDelete, missingManager } = buildDeletionPlan( + resultsToProcess, + selectedItems + ) if (missingManager.length > 0) { showError( @@ -368,31 +317,15 @@ export function useCleaner() { setIsRunning(true) const msgParts: string[] = [] - if (materialsToUpsert.length > 0) { - const res = await window.electron.materials.upsertBatch(materialsToUpsert) - const payload = res.success - ? (res.data as { stats?: { success?: number } } | undefined) - : undefined - if (!res.success) throw new Error(res.error || '写入物料失败') - msgParts.push(`写入/更新成功:${payload?.stats?.success || 0} 条`) - } - - if (materialsToDelete.length > 0) { - const res = await window.electron.materials.delete(materialsToDelete) - const payload = res.success ? (res.data as { count?: number } | undefined) : undefined - if (!res.success) throw new Error(res.error || '删除物料失败') - msgParts.push(`删除成功:${payload?.count || 0} 条`) - } + const result = await saveDeletionPlan({ materialsToUpsert, materialsToDelete }) + if (materialsToUpsert.length > 0) msgParts.push(`写入/更新成功:${result.upserted} 条`) + if (materialsToDelete.length > 0) msgParts.push(`删除成功:${result.deleted} 条`) showSuccess(`操作完成!\n\n${msgParts.join('\n')}`) // Reload managers if admin if (isAdmin) { - const resp = await window.electron.materials.getManagers() - const payload = resp.success - ? (resp.data as { managers?: string[] } | undefined) - : undefined - setManagers(payload?.managers ?? []) + setManagers(await reloadManagers()) } } catch (err) { showError(err instanceof Error ? err.message : '操作失败') @@ -429,42 +362,13 @@ export function useCleaner() { setIsReportDialogOpen(true) try { - const cleanerDataResult = await window.electron.validation.getCleanerData() - const cleanerData = cleanerDataResult.success ? (cleanerDataResult.data as any) : null - if (!cleanerDataResult.success || cleanerData?.success === false) { - throw new Error(cleanerDataResult.error || '获取清理数据失败') - } - - const orderNumberList = cleanerData?.orderNumbers || [] - const materialCodeList = cleanerData?.materialCodes || [] - - if (orderNumberList.length === 0) - throw new Error('没有订单号数据。请先到数据提取页面输入 Production ID。') - if (materialCodeList.length === 0) - throw new Error('没有物料代码数据。请确认已在物料清理界面确认要删除的物料。') - - const response = await window.electron.cleaner.runCleaner({ - orderNumbers: orderNumberList, - materialCodes: materialCodeList, + const result = await runCleanerExecution({ dryRun, headless, queryBatchSize, processConcurrency }) - const cleanerRunData = response.success ? (response.data as any) : null - - if (response.success && cleanerRunData) { - setReportData({ - ordersProcessed: cleanerRunData.ordersProcessed, - materialsDeleted: cleanerRunData.materialsDeleted, - materialsSkipped: cleanerRunData.materialsSkipped, - errors: cleanerRunData.errors, - retriedOrders: cleanerRunData.retriedOrders, - successfulRetries: cleanerRunData.successfulRetries - }) - } else { - throw new Error(response.error || '清理失败') - } + setReportData(result) } catch (err) { showError(err instanceof Error ? err.message : '发生未知错误') } finally { @@ -488,25 +392,8 @@ export function useCleaner() { setIsExporting(true) try { - // Prepare export data from filtered results - const exportItems = filteredResults.map((result) => ({ - materialName: result.materialName, - materialCode: result.materialCode, - specification: result.specification || '', - model: result.model || '', - managerName: result.managerName || '', - isMarkedForDeletion: result.isMarkedForDeletion, - isSelected: selectedItems.has(result.materialCode) - })) - - const response = await window.electron.cleaner.exportResults(exportItems) - const exportData = response.success ? (response.data as any) : null - - if (response.success && exportData?.success !== false) { - showSuccess(`导出成功!\n文件已保存到:${exportData?.filePath ?? ''}`) - } else { - throw new Error(response.error || exportData?.error || '导出失败') - } + const filePath = await exportCleanerResults(buildExportItems(filteredResults, selectedItems)) + showSuccess(`导出成功!\n文件已保存到:${filePath}`) } catch (err) { showError(err instanceof Error ? err.message : '导出过程中发生错误') } finally { diff --git a/src/renderer/src/hooks/useValidation.ts b/src/renderer/src/hooks/useValidation.ts index 6820347..28ac259 100644 --- a/src/renderer/src/hooks/useValidation.ts +++ b/src/renderer/src/hooks/useValidation.ts @@ -1,33 +1,10 @@ import { useState, useCallback } from 'react' - -interface ValidationRequest { - mode: 'database_full' | 'database_filtered' - productionIdFile?: string - useSharedProductionIds?: boolean -} - -interface ValidationResult { - materialName: string - materialCode: string - specification: string - model: string - managerName: string - isMarkedForDeletion: boolean - matchedTypeKeyword?: string -} - -interface ValidationStats { - totalRecords: number - matchedCount: number - markedCount: number -} - -interface ValidationResponse { - success: boolean - results?: ValidationResult[] - stats?: ValidationStats - error?: string -} +import type { + ValidationRequest, + ValidationResponsePayload as ValidationResponse, + ValidationResult, + ValidationStats +} from './cleaner/types' interface UseValidationState { loading: boolean diff --git a/tests/unit/cleaner-helpers.test.ts b/tests/unit/cleaner-helpers.test.ts new file mode 100644 index 0000000..811e61b --- /dev/null +++ b/tests/unit/cleaner-helpers.test.ts @@ -0,0 +1,73 @@ +import { describe, expect, it } from 'vitest' +import { + buildDeletionPlan, + buildExportItems, + filterValidationResults +} from '../../src/renderer/src/hooks/cleaner/helpers' +import type { ValidationResult } from '../../src/renderer/src/hooks/cleaner/types' + +const sampleResults: ValidationResult[] = [ + { + materialName: 'A', + materialCode: 'M1', + specification: '', + model: '', + managerName: 'alice', + isMarkedForDeletion: true + }, + { + materialName: 'B', + materialCode: 'M2', + specification: '', + model: '', + managerName: '', + isMarkedForDeletion: false + }, + { + materialName: 'C', + materialCode: 'M3', + specification: '', + model: '', + managerName: 'bob', + isMarkedForDeletion: false + } +] + +describe('cleaner helpers', () => { + it('filters results for non-admin users and hidden items', () => { + const filtered = filterValidationResults({ + validationResults: sampleResults, + isAdmin: false, + currentUsername: 'alice', + managers: [], + selectedManagers: new Set(), + hiddenItems: new Set(['M2']) + }) + + expect(filtered.map((item) => item.materialCode)).toEqual(['M1']) + }) + + it('builds deletion plan with missing managers', () => { + const plan = buildDeletionPlan(sampleResults, new Set(['M1', 'M2'])) + + expect(plan.materialsToUpsert).toEqual([{ materialCode: 'M1', managerName: 'alice' }]) + expect(plan.materialsToDelete).toEqual(['M3']) + expect(plan.missingManager).toEqual(['M2']) + }) + + it('builds export items with selection state', () => { + const items = buildExportItems(sampleResults.slice(0, 1), new Set(['M1'])) + + expect(items).toEqual([ + { + materialName: 'A', + materialCode: 'M1', + specification: '', + model: '', + managerName: 'alice', + isMarkedForDeletion: true, + isSelected: true + } + ]) + }) +})