From 55c27e77bf0ab4a3c30bef35a2590186bb81864c Mon Sep 17 00:00:00 2001 From: mairuiming Date: Thu, 9 Apr 2026 10:13:24 +0800 Subject: [PATCH] 3 3 --- src/App.tsx | 490 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 346 insertions(+), 144 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index bffe37c..b21f474 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -224,6 +224,7 @@ export default function App() { const [batchEndRow, setBatchEndRow] = useState(1); const [batchTotalRows, setBatchTotalRows] = useState(0); const [batchLoading, setBatchLoading] = useState(false); + const [batchMode, setBatchMode] = useState<'generate' | 'adjust'>('generate'); const [batchProcessedCount, setBatchProcessedCount] = useState(0); const [batchProcessingTotal, setBatchProcessingTotal] = useState(0); const [batchSuccessCount, setBatchSuccessCount] = useState(0); @@ -404,6 +405,9 @@ export default function App() { const BATCH_LABEL11_FIELD_ID = 'fld4BZHtBV'; const BATCH_LABEL12_FIELD_ID = 'fldnRlMeaD'; const BATCH_BUFFER_FIELD_ID = 'fldLBXEAo0'; + const BATCH_SOURCE_DELIVERY_RECORD_ID_FIELD_ID = 'fld5pcGWeh'; + const BATCH_RESULT_DELIVERY_RECORD_ID_FIELD_ID = 'fld9as1Y1c'; + const BATCH_CUSTOMER_EXPECTED_DATE_FIELD_ID = 'fldqi7nUix'; const activateTableForPaging = async (table: any) => { try { @@ -468,6 +472,115 @@ export default function App() { return Number.isFinite(n) ? n : null; }; + const parseBatchDateValue = (raw: any): Date | null => { + if (raw == null) return null; + if (raw instanceof Date && !isNaN(raw.getTime())) return raw; + if (typeof raw === 'number' && Number.isFinite(raw)) { + const d = new Date(raw); + return isNaN(d.getTime()) ? null : d; + } + if (Array.isArray(raw) && raw.length > 0) { + for (const item of raw) { + const parsed = parseBatchDateValue(item); + if (parsed) return parsed; + } + return null; + } + if (typeof raw === 'string') { + const t = raw.trim(); + if (!t) return null; + const asNum = Number(t); + if (Number.isFinite(asNum) && /^\d{10,13}$/.test(t)) { + const d = new Date(asNum); + if (!isNaN(d.getTime())) return d; + } + const parsed = parseDate(t); + if (parsed && !isNaN(parsed.getTime())) return parsed; + const d = new Date(t); + return isNaN(d.getTime()) ? null : d; + } + if (typeof raw === 'object') { + const v = (raw as any).value ?? (raw as any).text ?? (raw as any).name; + return parseBatchDateValue(v); + } + return null; + }; + + const normalizeActualCompletionDatesMap = (raw: any): { [key: number]: Date | null } => { + const out: { [key: number]: Date | null } = {}; + if (!raw || typeof raw !== 'object') return out; + for (const [k, v] of Object.entries(raw)) { + const idx = Number(k); + if (!Number.isFinite(idx)) continue; + const parsed = parseBatchDateValue(v); + out[idx] = parsed; + } + return out; + }; + + const getBatchSimulationStateFromSnapshot = async (sourceDeliveryRecordId: string) => { + const deliveryTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID); + const deliveryRecord: any = await deliveryTable.getRecordById(sourceDeliveryRecordId); + const fields = deliveryRecord?.fields || {}; + const extractAllText = (val: any): string => { + if (val === null || val === undefined) return ''; + if (typeof val === 'string') return val; + if (typeof val === 'number') return String(val); + if (Array.isArray(val)) return val.map((el: any) => extractAllText(el)).join(''); + if (typeof val === 'object') return (val as any).text || (val as any).name || (val as any).value?.toString?.() || ''; + return ''; + }; + const snapshotPart1 = extractAllText(fields?.[DELIVERY_SNAPSHOT_JSON_FIELD_ID]); + const snapshotPart2 = extractAllText(fields?.[DELIVERY_SNAPSHOT_JSON_2_FIELD_ID]); + const snapshotStr = `${snapshotPart1 || ''}${snapshotPart2 || ''}`.trim(); + if (!snapshotStr) { + throw new Error('源货期记录缺少快照'); + } + const snapshot = JSON.parse(snapshotStr); + if (!Array.isArray(snapshot?.timelineResults) || snapshot.timelineResults.length === 0) { + throw new Error('源快照没有可用的timelineResults'); + } + const timelineResultsFromSnapshot = snapshot.timelineResults as any[]; + const labelsFromSnapshot = normalizeSelectedLabelsForRestore(snapshot?.selectedLabels || {}); + const expectedFromSnapshot = + parseBatchDateValue(snapshot?.expectedDateTimestamp) || + parseBatchDateValue(snapshot?.expectedDateString) || + parseBatchDateValue(fields?.[DELIVERY_CUSTOMER_EXPECTED_DATE_FIELD_ID]); + const startFromSnapshot = + parseBatchDateValue(snapshot?.startTimestamp) || + parseBatchDateValue(snapshot?.startString) || + parseBatchDateValue(fields?.[DELIVERY_START_TIME_FIELD_ID]); + const snapshotAdjustments = snapshot?.timelineAdjustments + ? normalizeTimelineAdjustmentsFromSnapshot(snapshot.timelineAdjustments, timelineResultsFromSnapshot) + : deriveTimelineAdjustmentsFromResults(timelineResultsFromSnapshot); + const excludedDatesByNodeFromResults = buildExcludedDatesByNodeFromTimeline(timelineResultsFromSnapshot); + const excludedDatesByNodeFromSnapshotMap = normalizeExcludedDatesByNodeMap( + snapshot?.excludedDatesByNodeOverride || + snapshot?.timelineCalculationState?.excludedDatesByNodeOverride || + {} + ); + const sourceRecordIdsText = extractText(fields?.[DELIVERY_RECORD_IDS_FIELD_ID]).trim(); + return { + snapshot, + timelineResults: timelineResultsFromSnapshot, + selectedLabels: labelsFromSnapshot, + expectedDate: expectedFromSnapshot, + startTime: startFromSnapshot, + timelineAdjustments: snapshotAdjustments, + excludedDatesOverride: normalizeExcludedDatesOverride(snapshot?.excludedDatesOverride || []), + excludedDatesByNodeOverride: Object.keys(excludedDatesByNodeFromResults).length > 0 + ? excludedDatesByNodeFromResults + : excludedDatesByNodeFromSnapshotMap, + foreignId: snapshot?.foreignId || '', + styleText: snapshot?.styleText || '', + colorText: snapshot?.colorText || '', + text2: snapshot?.text2 || '', + versionNumber: Number.isFinite(Number(snapshot?.version)) ? Number(snapshot.version) : null, + actualCompletionDates: normalizeActualCompletionDatesMap(snapshot?.actualCompletionDates), + sourceRecordIdsText + }; + }; + // 已移除:调整模式不再加载货期记录列表 // 入口选择处理 @@ -648,6 +761,21 @@ export default function App() { return '#unknown'; }; + const getExcludedDatesNodeKeyCandidates = (node: any, indexFallback?: number): string[] => { + const nodeName = node?.nodeName; + const processOrder = node?.processOrder; + const instanceKey = node?.processGroupInstanceId || node?.processGroupInstanceName; + const candidates = [ + buildExcludedDatesNodeKey(nodeName, processOrder, indexFallback, instanceKey), + buildExcludedDatesNodeKey(nodeName, processOrder, indexFallback), + buildExcludedDatesNodeKey(nodeName, undefined, indexFallback, instanceKey), + buildExcludedDatesNodeKey(nodeName, undefined, indexFallback), + ] + .map(k => (k || '').trim()) + .filter(k => k && k !== '#unknown'); + return Array.from(new Set(candidates)); + }; + const buildTimelineAdjustmentKey = (node: any, indexFallback?: number) => { return buildExcludedDatesNodeKey( node?.nodeName, @@ -832,16 +960,35 @@ export default function App() { const buildExcludedDatesByNodeFromTimeline = (results: any[]): Record => { const map: Record = {}; + const assignExcludedDates = (key: string, val: string[]) => { + if (!key) return; + const existing = Array.isArray(map[key]) ? map[key] : []; + if (existing.length > 0 && val.length === 0) return; + map[key] = val; + }; for (let i = 0; i < results.length; i++) { const r = results[i]; - const key = buildExcludedDatesNodeKey( + const keyWithInstance = buildExcludedDatesNodeKey( r?.nodeName, r?.processOrder, i, r?.processGroupInstanceId || r?.processGroupInstanceName ); + const keyWithoutInstance = buildExcludedDatesNodeKey( + r?.nodeName, + r?.processOrder, + i + ); + const keyByName = buildExcludedDatesNodeKey( + r?.nodeName, + undefined, + i + ); const arr = Array.isArray(r?.excludedDates) ? r.excludedDates : []; - map[key] = normalizeExcludedDatesOverride(arr); + const normalized = normalizeExcludedDatesOverride(arr); + assignExcludedDates(keyWithInstance, normalized); + assignExcludedDates(keyWithoutInstance, normalized); + assignExcludedDates(keyByName, normalized); } return map; }; @@ -861,6 +1008,18 @@ export default function App() { }; const restoreExcludedDatesByNodeOverrideFromSnapshot = (snapshot: any, timelineResultsCandidate?: any[]) => { + const results = Array.isArray(timelineResultsCandidate) + ? timelineResultsCandidate + : (Array.isArray(snapshot?.timelineResults) ? snapshot.timelineResults : []); + if (results.length > 0) { + const derived = buildExcludedDatesByNodeFromTimeline(results); + if (Object.keys(derived).length > 0) { + excludedDatesByNodeOverrideRef.current = derived; + setExcludedDatesByNodeOverride(derived); + return; + } + } + const candidates: any[] = [ snapshot?.excludedDatesByNodeOverride, snapshot?.excludedDatesByNode, @@ -877,16 +1036,6 @@ export default function App() { } } - const results = Array.isArray(timelineResultsCandidate) - ? timelineResultsCandidate - : (Array.isArray(snapshot?.timelineResults) ? snapshot.timelineResults : []); - if (results.length > 0) { - const derived = buildExcludedDatesByNodeFromTimeline(results); - excludedDatesByNodeOverrideRef.current = derived; - setExcludedDatesByNodeOverride(derived); - return; - } - excludedDatesByNodeOverrideRef.current = {}; setExcludedDatesByNodeOverride({}); }; @@ -2775,7 +2924,8 @@ export default function App() { selectedLabels?: {[key: string]: string | string[]}, expectedDate?: Date | null, startTime?: Date | null, - excludedDates?: string[] + excludedDates?: string[], + prevResultsForAdjustmentRemap?: any[] }, showUI: boolean = true // 新增参数控制是否显示UI ) => { @@ -2785,8 +2935,6 @@ export default function App() { const currentSelectedLabels = overrideData?.selectedLabels || selectedLabels; const currentExpectedDate = overrideData?.expectedDate || expectedDate; const currentStartTime = overrideData?.startTime || startTime; - const currentExcludedDates = Array.isArray(overrideData?.excludedDates) ? overrideData!.excludedDates : []; - const globalExcludedDates = normalizeExcludedDatesOverride(currentExcludedDates); const excludedByNode = excludedDatesByNodeOverrideRef.current || {}; const isBackward = timelineDirection === 'backward'; @@ -3159,7 +3307,6 @@ export default function App() { selectedLabels: currentSelectedLabels, expectedDate: currentExpectedDate, startTime: currentStartTime, - excludedDates: currentExcludedDates, }, showUI, }; @@ -3269,20 +3416,20 @@ export default function App() { for (let i = 0; i < nodesToProcess.length; i++) { const processNode = nodesToProcess[i]; - const nodeKey = buildExcludedDatesNodeKey( - processNode?.nodeName, - processNode?.processOrder, - i, - processNode?.processGroupInstanceId || processNode?.processGroupInstanceName - ); - const overrideList = Object.prototype.hasOwnProperty.call(excludedByNode, nodeKey) - ? excludedByNode[nodeKey] - : undefined; + const keyCandidates = getExcludedDatesNodeKeyCandidates(processNode, i); + let overrideList: string[] | undefined = undefined; + for (const candidateKey of keyCandidates) { + if (Object.prototype.hasOwnProperty.call(excludedByNode, candidateKey)) { + overrideList = excludedByNode[candidateKey]; + break; + } + } const baseList = Array.isArray(processNode?.excludedDates) ? processNode.excludedDates : []; - const selectedList = Array.isArray(overrideList) ? overrideList : baseList; + const oldExcludedDates = normalizeExcludedDatesOverride(Array.isArray(overrideList) ? overrideList : []); + const newRuleExcludedDates = normalizeExcludedDatesOverride(baseList); const nodeExcludedDates = Array.from(new Set([ - ...normalizeExcludedDatesOverride(selectedList), - ...globalExcludedDates, + ...oldExcludedDates, + ...newRuleExcludedDates, ])).filter(Boolean); let timelineValue = null; let matchedTimelineRecord = null; @@ -3718,7 +3865,10 @@ export default function App() { } } - const nextAdjustments = remapTimelineAdjustmentsToNewResults(timelineResults, results); + const prevResultsForRemap = Array.isArray(overrideData?.prevResultsForAdjustmentRemap) + ? overrideData!.prevResultsForAdjustmentRemap! + : timelineResults; + const nextAdjustments = remapTimelineAdjustmentsToNewResults(prevResultsForRemap, results); setTimelineAdjustments(nextAdjustments); pendingRecalculateAfterCalculateAdjustmentsRef.current = nextAdjustments; pendingRecalculateAfterCalculateRef.current = true; @@ -3797,10 +3947,17 @@ export default function App() { // 获取重新计算后的时间线结果(不更新状态,逻辑对齐页面的重算口径) const getRecalculatedTimeline = ( adjustments: Record, - opts?: { ignoreActualCompletionDates?: boolean; actualCompletionDatesOverride?: { [key: number]: Date | null } } + opts?: { + ignoreActualCompletionDates?: boolean; + actualCompletionDatesOverride?: { [key: number]: Date | null }; + baseResultsOverride?: any[]; + startTimeOverride?: Date | null; + } ) => { - const updatedResults = [...timelineResults]; - let cumulativeStartTime = startTime ? new Date(startTime) : new Date(); // 从起始时间开始 + const baseResults = Array.isArray(opts?.baseResultsOverride) ? opts!.baseResultsOverride! : timelineResults; + const updatedResults = [...baseResults]; + const baseStartTime = opts?.startTimeOverride ?? startTime; + let cumulativeStartTime = baseStartTime ? new Date(baseStartTime) : new Date(); // 从起始时间开始 for (let i = 0; i < updatedResults.length; i++) { const result = updatedResults[i]; @@ -4530,7 +4687,7 @@ export default function App() { timelineResults: any[], processRecordIds: string[], timelineAdjustments: Record = {}, - overrides?: { foreignId?: string; style?: string; color?: string; expectedDate?: Date | null; startTime?: Date | null; selectedLabels?: {[key: string]: string | string[]}; baseBufferDays?: number } + overrides?: { foreignId?: string; style?: string; color?: string; expectedDate?: Date | null; startTime?: Date | null; selectedLabels?: {[key: string]: string | string[]}; baseBufferDays?: number; recordIdsTextOverride?: string; baseVersionNumber?: number | null; forceMode?: 'generate' | 'adjust' | null } ) => { let recordCells: any[] | undefined; try { @@ -4722,10 +4879,15 @@ export default function App() { // 创建当前时间戳 const currentTime = new Date().getTime(); + const effectiveMode = overrides?.forceMode ?? mode; // 计算版本号(数字)并格式化货期调整信息 let versionNumber = 1; - if (mode === 'adjust' && currentVersionNumber !== null) { + const overrideBaseVersion = Number(overrides?.baseVersionNumber); + const hasOverrideBaseVersion = Number.isFinite(overrideBaseVersion); + if (hasOverrideBaseVersion) { + versionNumber = overrideBaseVersion + 1; + } else if (effectiveMode === 'adjust' && currentVersionNumber !== null) { versionNumber = currentVersionNumber + 1; } @@ -4779,7 +4941,7 @@ export default function App() { styleText, colorText, text2, - mode, + mode: effectiveMode, timelineDirection, excludedDatesOverride, excludedDatesByNodeOverride: excludedDatesByNodeOverrideRef.current || excludedDatesByNodeOverride, @@ -4912,9 +5074,11 @@ export default function App() { // 使用createCell方法创建各个字段的Cell const startTimestamp = currentStartTime ? currentStartTime.getTime() : currentTime; - const recordIdsText = (restoredRecordIdsText && restoredRecordIdsText.trim() !== '') - ? restoredRecordIdsText.trim() - : ''; + const recordIdsText = (overrides?.recordIdsTextOverride && overrides.recordIdsTextOverride.trim() !== '') + ? overrides.recordIdsTextOverride.trim() + : ((restoredRecordIdsText && restoredRecordIdsText.trim() !== '') + ? restoredRecordIdsText.trim() + : ''); const [ foreignIdCell, @@ -5470,30 +5634,13 @@ export default function App() { const styleText = getText('styleText'); const colorText = getText('colorText'); const rawStart = f[nameToId.get('startTimestamp') || '']; - const rawExpected = f[nameToId.get('expectedDateTimestamp') || '']; + const rawExpected = + f[BATCH_CUSTOMER_EXPECTED_DATE_FIELD_ID] ?? + f[nameToId.get('customerExpectedDate') || ''] ?? + f[nameToId.get('expectedDateTimestamp') || '']; const rawBufferDays = f[nameToId.get('缓冲期') || BATCH_BUFFER_FIELD_ID || '']; - let startDate: Date | null = null; - let expectedDateObj: Date | null = null; - if (typeof rawStart === 'number') startDate = new Date(rawStart); - else if (Array.isArray(rawStart) && rawStart.length > 0) { - const item = rawStart[0]; - if (typeof item === 'number') startDate = new Date(item); - else startDate = parseDate(extractText(rawStart)); - } else if (typeof rawStart === 'string') startDate = parseDate(rawStart); - else if (rawStart && typeof rawStart === 'object') { - const v = (rawStart as any).value || (rawStart as any).text || (rawStart as any).name || ''; - if (typeof v === 'number') startDate = new Date(v); else startDate = parseDate(v); - } - if (typeof rawExpected === 'number') expectedDateObj = new Date(rawExpected); - else if (Array.isArray(rawExpected) && rawExpected.length > 0) { - const item = rawExpected[0]; - if (typeof item === 'number') expectedDateObj = new Date(item); - else expectedDateObj = parseDate(extractText(rawExpected)); - } else if (typeof rawExpected === 'string') expectedDateObj = parseDate(rawExpected); - else if (rawExpected && typeof rawExpected === 'object') { - const v = (rawExpected as any).value || (rawExpected as any).text || (rawExpected as any).name || ''; - if (typeof v === 'number') expectedDateObj = new Date(v); else expectedDateObj = parseDate(v); - } + let startDate: Date | null = parseBatchDateValue(rawStart); + let expectedDateObj: Date | null = parseBatchDateValue(rawExpected); const parseBufferDays = (raw: any): number | null => { if (raw == null) return null; @@ -5516,45 +5663,6 @@ export default function App() { } return null; }; - - const normalizeExcludedDates = (raw: any): string[] => { - const out: string[] = []; - const pushToken = (token: any) => { - if (token == null) return; - if (typeof token === 'number' && Number.isFinite(token)) { - const d = new Date(token); - if (!isNaN(d.getTime())) out.push(format(d, 'yyyy-MM-dd')); - return; - } - const text = (typeof token === 'string') ? token : extractText(token); - if (!text) return; - const parts = String(text).split(/[,,;;、\n\r\t ]+/).map(s => s.trim()).filter(Boolean); - for (const p of parts) { - const head = p.slice(0, 10); - if (/^\d{4}-\d{2}-\d{2}$/.test(head)) { - out.push(head); - continue; - } - const parsed = parseDate(p); - if (parsed && !isNaN(parsed.getTime())) out.push(format(parsed, 'yyyy-MM-dd')); - } - }; - const walk = (v: any) => { - if (Array.isArray(v)) { - v.forEach(walk); - return; - } - if (v && typeof v === 'object' && Array.isArray((v as any).value)) { - walk((v as any).value); - return; - } - pushToken(v); - }; - walk(raw); - return Array.from(new Set(out)).filter(Boolean); - }; - - const batchExcludedDates = normalizeExcludedDates(f['fldQNxtHnd']); const splitVals = (s: string) => (s || '').split(/[,,、]+/).map(v => v.trim()).filter(Boolean); const normalizeToStringList = (raw: any): string[] => { if (!raw) return []; @@ -5580,32 +5688,96 @@ export default function App() { else labels[key] = list.join(','); } } - { - const requiredLabelKeys = Array.from({ length: 12 }, (_, k) => `标签${k + 1}`); - const missing = requiredLabelKeys.filter(k => { - const val = (labels as any)[k]; - if (Array.isArray(val)) return val.length === 0; - return !(typeof val === 'string' && val.trim().length > 0); - }); - if (missing.length > 0) { - setBatchProcessedCount(p => p + 1); - setBatchFailureCount(fCount => fCount + 1); - setBatchProgressList(list => [...list, { index: displayIndex, foreignId: foreignId || '', status: 'failed', message: `标签不完整:${missing.join('、')}` }]); - continue; - } - } + const sourceDeliveryRecordId = extractText( + f[BATCH_SOURCE_DELIVERY_RECORD_ID_FIELD_ID] ?? + f[nameToId.get('sourceDeliveryRecordId') || ''] + ).trim(); setBatchCurrentRowInfo({ index: displayIndex, foreignId: foreignId || '', style: styleText || '', color: colorText || '' }); - setCurrentForeignId(foreignId || ''); - setCurrentStyleText(styleText || ''); - setCurrentColorText(colorText || ''); - setExpectedDate(expectedDateObj || null); - setStartTime(startDate || null); - setSelectedLabels(labels); try { - const results = await handleCalculateTimeline(true, { selectedLabels: labels, expectedDate: expectedDateObj || null, startTime: startDate || null, excludedDates: batchExcludedDates }, false); + let labelsToUse: { [key: string]: string | string[] } = labels; + let expectedDateToUse: Date | null = expectedDateObj || null; + let startTimeToUse: Date | null = startDate || null; + let foreignIdToUse = foreignId || ''; + let styleToUse = styleText || ''; + let colorToUse = colorText || ''; + let adjustmentsToUse: Record = {}; + let prevResultsForRemap: any[] | undefined = undefined; + let sourceRecordIdsText = ''; + let actualCompletionDatesForWrite: { [key: number]: Date | null } = {}; + let baseVersionNumberToUse: number | null = null; + + if (batchMode === 'adjust') { + if (!sourceDeliveryRecordId) { + throw new Error('缺少sourceDeliveryRecordId'); + } + const simulation = await getBatchSimulationStateFromSnapshot(sourceDeliveryRecordId); + setMode('adjust'); + setTimelineResults(simulation.timelineResults); + setTimelineAdjustments(simulation.timelineAdjustments || {}); + setSelectedLabels(simulation.selectedLabels || {}); + setExpectedDate(simulation.expectedDate || null); + setStartTime(simulation.startTime || null); + setCurrentForeignId(simulation.foreignId || ''); + setCurrentStyleText(simulation.styleText || ''); + setCurrentColorText(simulation.colorText || ''); + setCurrentText2(simulation.text2 || ''); + setCurrentVersionNumber(simulation.versionNumber); + setActualCompletionDates(simulation.actualCompletionDates || {}); + setExcludedDatesOverride(simulation.excludedDatesOverride || []); + excludedDatesByNodeOverrideRef.current = simulation.excludedDatesByNodeOverride || {}; + setExcludedDatesByNodeOverride(simulation.excludedDatesByNodeOverride || {}); + pendingRecalculateAfterCalculateRef.current = false; + pendingRecalculateAfterCalculateAdjustmentsRef.current = null; + + labelsToUse = { ...(simulation.selectedLabels || {}) }; + for (const [k, v] of Object.entries(labels || {})) { + if (Array.isArray(v) && v.length > 0) labelsToUse[k] = v; + if (typeof v === 'string' && v.trim() !== '') labelsToUse[k] = v.trim(); + } + expectedDateToUse = expectedDateObj || simulation.expectedDate || null; + startTimeToUse = simulation.startTime || null; + foreignIdToUse = simulation.foreignId || foreignIdToUse; + styleToUse = simulation.styleText || styleToUse; + colorToUse = simulation.colorText || colorToUse; + adjustmentsToUse = simulation.timelineAdjustments || {}; + prevResultsForRemap = simulation.timelineResults || []; + sourceRecordIdsText = simulation.sourceRecordIdsText || ''; + actualCompletionDatesForWrite = simulation.actualCompletionDates || {}; + baseVersionNumberToUse = Number.isFinite(Number(simulation.versionNumber)) + ? Number(simulation.versionNumber) + : null; + } else { + const requiredLabelKeys = Array.from({ length: 12 }, (_, k) => `标签${k + 1}`); + const missing = requiredLabelKeys.filter(k => { + const val = (labelsToUse as any)[k]; + if (Array.isArray(val)) return val.length === 0; + return !(typeof val === 'string' && val.trim().length > 0); + }); + if (missing.length > 0) { + throw new Error(`标签不完整:${missing.join('、')}`); + } + setCurrentForeignId(foreignIdToUse); + setCurrentStyleText(styleToUse); + setCurrentColorText(colorToUse); + setExpectedDate(expectedDateToUse); + setStartTime(startTimeToUse); + setSelectedLabels(labelsToUse); + } + + const results = await handleCalculateTimeline( + true, + { + selectedLabels: labelsToUse, + expectedDate: expectedDateToUse, + startTime: startTimeToUse, + prevResultsForAdjustmentRemap: prevResultsForRemap + }, + false + ); if (results && results.length > 0) { - let effectiveExpectedDate = expectedDateObj || null; - if (!effectiveExpectedDate) { + let resultsToWrite = results; + let effectiveExpectedDate = expectedDateToUse; + if (!effectiveExpectedDate && batchMode === 'generate') { const bufferDays = parseBufferDays(rawBufferDays); const fallbackDays = Number.isFinite(bufferDays as number) ? (bufferDays as number) : 14; const autoExpected = computeExpectedDateByBufferDays(results, fallbackDays, completionDateAdjustment); @@ -5614,25 +5786,43 @@ export default function App() { setExpectedDate(autoExpected); } } - const processRecordIds = await writeToProcessDataTable(results, { foreignId, style: styleText, color: colorText }); + if (batchMode === 'adjust' && Array.isArray(prevResultsForRemap) && prevResultsForRemap.length > 0) { + adjustmentsToUse = remapTimelineAdjustmentsToNewResults(prevResultsForRemap, results); + resultsToWrite = getRecalculatedTimeline(adjustmentsToUse, { + actualCompletionDatesOverride: actualCompletionDatesForWrite, + baseResultsOverride: results, + startTimeOverride: startTimeToUse + }); + } else { + adjustmentsToUse = {}; + } + const processRecordIds = await writeToProcessDataTable(resultsToWrite, { foreignId: foreignIdToUse, style: styleToUse, color: colorToUse }); const deliveryRecordId = await writeToDeliveryRecordTable( - results, + resultsToWrite, processRecordIds, - {}, + adjustmentsToUse, { - foreignId, - style: styleText, - color: colorText, + foreignId: foreignIdToUse, + style: styleToUse, + color: colorToUse, expectedDate: effectiveExpectedDate, - startTime: startDate || null, - selectedLabels: labels + startTime: startTimeToUse, + selectedLabels: labelsToUse, + recordIdsTextOverride: sourceRecordIdsText, + baseVersionNumber: baseVersionNumberToUse, + forceMode: batchMode === 'adjust' ? 'adjust' : 'generate' } ); try { - const candidateNames = ['状态','record_id','记录ID','货期记录ID','deliveryRecordId']; - let statusFieldId = ''; - for (const nm of candidateNames) { const id = nameToId.get(nm) || ''; if (id) { statusFieldId = id; break; } } - if (!statusFieldId) statusFieldId = 'fldKTpPL9s'; + const candidateNames = ['resultDeliveryRecordId', '状态', 'record_id', '记录ID', '货期记录ID', 'deliveryRecordId']; + let statusFieldId = BATCH_RESULT_DELIVERY_RECORD_ID_FIELD_ID; + for (const nm of candidateNames) { + const id = nameToId.get(nm) || ''; + if (id) { + statusFieldId = id; + break; + } + } const rowRecordId = (row.id || (row as any).recordId || (row as any)._id || (row as any).record_id); const deliveryRecordIdStr = typeof deliveryRecordId === 'string' ? deliveryRecordId @@ -5656,15 +5846,15 @@ export default function App() { } } catch (e2) { throw e2; + } } - } - setBatchProgressList(list => [...list, { index: displayIndex, foreignId: foreignId || '', status: 'success', message: `记录ID: ${deliveryRecordIdStr}` }]); + setBatchProgressList(list => [...list, { index: displayIndex, foreignId: foreignIdToUse || '', status: 'success', message: `记录ID: ${deliveryRecordIdStr}` }]); } else { - setBatchProgressList(list => [...list, { index: displayIndex, foreignId: foreignId || '', status: 'failed', message: '未找到状态字段或记录ID为空' }]); + setBatchProgressList(list => [...list, { index: displayIndex, foreignId: foreignIdToUse || '', status: 'failed', message: '未找到结果字段或记录ID为空' }]); } } catch (statusErr: any) { console.warn('回写批量状态字段失败', statusErr); - setBatchProgressList(list => [...list, { index: displayIndex, foreignId: foreignId || '', status: 'failed', message: `状态写入失败: ${statusErr?.message || '未知错误'}` }]); + setBatchProgressList(list => [...list, { index: displayIndex, foreignId: foreignIdToUse || '', status: 'failed', message: `结果回写失败: ${statusErr?.message || '未知错误'}` }]); } processed++; setBatchProcessedCount(p => p + 1); @@ -5682,7 +5872,8 @@ export default function App() { } if (bitable.ui.showToast) { const aborted = batchAbortRef.current; - await bitable.ui.showToast({ toastType: ToastType.success, message: aborted ? `批量已中止,已处理 ${processed} 条记录` : `批量生成完成,共处理 ${processed} 条记录` }); + const finishedText = batchMode === 'adjust' ? '批量调整完成' : '批量生成完成'; + await bitable.ui.showToast({ toastType: ToastType.success, message: aborted ? `批量已中止,已处理 ${processed} 条记录` : `${finishedText},共处理 ${processed} 条记录` }); } } catch (error) { console.error('批量处理失败:', error); @@ -6329,8 +6520,8 @@ const omsVersionColumns = [ background: 'linear-gradient(180deg, #fff, #f9fbff)' }} > - 批量生成 - 从批量生成数据表读取并写入记录 + 批量生成/调整 + 按批量表执行新建记录或基于快照批量调整
@@ -6339,7 +6530,7 @@ const omsVersionColumns = [ { batchAbortRef.current = true; setBatchModalVisible(false); }} footer={null} @@ -6348,13 +6539,24 @@ const omsVersionColumns = [
+ 模式 +