diff --git a/src/App.tsx b/src/App.tsx index 4ded152..36c52cc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,6 +18,19 @@ const DATE_FORMATS = { // 统一的星期显示 const WEEKDAYS = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'] as const; +// 统一的字段提取函数 +const extractText = (val: any) => { + if (Array.isArray(val) && val.length > 0) { + const item = val[0]; + return typeof item === 'string' ? item : (item?.text || item?.name || ''); + } else if (typeof val === 'string') { + return val; + } else if (val && typeof val === 'object') { + return val.text || val.name || ''; + } + return ''; +}; + export default function App() { const [selectedRecords, setSelectedRecords] = useState([]); const [recordDetails, setRecordDetails] = useState([]); @@ -46,6 +59,14 @@ export default function App() { const [timelineLoading, setTimelineLoading] = useState(false); const [timelineResults, setTimelineResults] = useState([]); const [timelineAdjustments, setTimelineAdjustments] = useState<{[key: number]: number}>({}); + // 交期余量扣减状态:记录从交期余量中扣减的天数 + const [deliveryMarginDeductions, setDeliveryMarginDeductions] = useState(0); + // 最后流程完成日期调整状态:记录最后流程完成日期增加的天数 + const [completionDateAdjustment, setCompletionDateAdjustment] = useState(0); + // 实际完成日期状态:记录每个节点的实际完成日期 + const [actualCompletionDates, setActualCompletionDates] = useState<{[key: number]: Date | null}>({}); + // 基础缓冲期天数(可配置),用于计算动态缓冲期,默认14天 + const [baseBufferDays, setBaseBufferDays] = useState(14); // 快照回填来源(foreign_id、款式、颜色、文本2) const [currentForeignId, setCurrentForeignId] = useState(null); const [currentStyleText, setCurrentStyleText] = useState(''); @@ -64,6 +85,15 @@ export default function App() { const [batchProgress, setBatchProgress] = useState({ current: 0, total: 0 }); const [batchRowCount, setBatchRowCount] = useState(10); // 默认处理10行 + // 批量处理当前记录信息(用于保存时传递正确的数据) + const [currentBatchRecord, setCurrentBatchRecord] = useState<{ + selectedRecords: string[], + recordDetails: any[], + labels: Record, + expectedDate: any, + startTime: any + } | null>(null); + // 批量处理表和视图选择状态 const [selectedBatchTableId, setSelectedBatchTableId] = useState(''); const [selectedBatchViewId, setSelectedBatchViewId] = useState(''); @@ -71,6 +101,47 @@ export default function App() { const [availableViews, setAvailableViews] = useState>([]); const [tablesLoading, setTablesLoading] = useState(false); const [viewsLoading, setViewsLoading] = useState(false); + + // 全局变量重置:在切换功能或切换版单/批量数据时,清空页面与计算相关状态 + const resetGlobalState = (opts?: { resetMode?: boolean }) => { + // 运行时加载状态 + setLoading(false); + setQueryLoading(false); + setSecondaryProcessLoading(false); + setPricingDetailsLoading(false); + setLabelLoading(false); + setAdjustLoading(false); + setTimelineLoading(false); + setBatchProcessing(false); + setBatchProgress({ current: 0, total: 0 }); + + // 页面与计算数据 + setSelectedRecords([]); + setRecordDetails([]); + setSelectedLabels({}); + setExpectedDate(null); + setStartTime(null); + setTimelineVisible(false); + setTimelineResults([]); + setTimelineAdjustments({}); + setIsRestoringSnapshot(false); + + // 当前记录与批量信息 + setCurrentBatchRecord(null); + + // 当前回填状态 + setCurrentForeignId(null); + setCurrentStyleText(''); + setCurrentColorText(''); + setCurrentText2(''); + setCurrentVersionNumber(null); + + // 可选:重置模式 + if (opts?.resetMode) { + setMode(null); + setModeSelectionVisible(true); + } + }; // 指定的数据表ID和视图ID const TABLE_ID = 'tblPIJ7unndydSMu'; @@ -102,6 +173,8 @@ export default function App() { // 入口选择处理 const chooseMode = (m: 'generate' | 'adjust') => { + // 切换功能时重置全局变量,但保留新的mode + resetGlobalState({ resetMode: false }); setMode(m); setModeSelectionVisible(false); }; @@ -120,6 +193,117 @@ export default function App() { const deliveryRecord = await deliveryTable.getRecordById(deliveryRecordId); const nodeDetailsVal = deliveryRecord?.fields?.[DELIVERY_NODE_DETAILS_FIELD_ID]; + // 优先使用货期记录表中的快照字段进行一键还原(新方案) + try { + const deliverySnapVal = deliveryRecord?.fields?.[DELIVERY_SNAPSHOT_JSON_FIELD_ID]; + let deliverySnapStr: string | null = null; + if (typeof deliverySnapVal === 'string') { + deliverySnapStr = deliverySnapVal; + } else if (Array.isArray(deliverySnapVal)) { + const texts = deliverySnapVal + .filter((el: any) => el && el.type === 'text' && typeof el.text === 'string') + .map((el: any) => el.text); + deliverySnapStr = texts.length > 0 ? texts.join('') : null; + } else if (deliverySnapVal && typeof deliverySnapVal === 'object') { + if ((deliverySnapVal as any).text && typeof (deliverySnapVal as any).text === 'string') { + deliverySnapStr = (deliverySnapVal as any).text; + } else if ((deliverySnapVal as any).type === 'text' && typeof (deliverySnapVal as any).text === 'string') { + deliverySnapStr = (deliverySnapVal as any).text; + } + } + + if (deliverySnapStr && deliverySnapStr.trim() !== '') { + setIsRestoringSnapshot(true); + const snapshot = JSON.parse(deliverySnapStr); + + // 恢复页面与全局状态 + if (snapshot.selectedLabels) setSelectedLabels(snapshot.selectedLabels); + if (!mode && snapshot.mode) setMode(snapshot.mode); + if (snapshot.foreignId) setCurrentForeignId(snapshot.foreignId); + if (snapshot.styleText) setCurrentStyleText(snapshot.styleText); + if (snapshot.colorText) setCurrentColorText(snapshot.colorText); + if (snapshot.text2) setCurrentText2(snapshot.text2); + + if (snapshot.generationModeState) { + const genState = snapshot.generationModeState; + if (genState.currentForeignId) setCurrentForeignId(genState.currentForeignId); + if (genState.currentStyleText) setCurrentStyleText(genState.currentStyleText); + if (genState.currentColorText) setCurrentColorText(genState.currentColorText); + if (genState.currentText2) setCurrentText2(genState.currentText2); + if (genState.currentVersionNumber !== undefined) setCurrentVersionNumber(genState.currentVersionNumber); + if (genState.recordDetails && Array.isArray(genState.recordDetails)) { + setRecordDetails(genState.recordDetails); + } + } + + if (snapshot.version !== undefined) { + let vNum: number | null = null; + if (typeof snapshot.version === 'number') { + vNum = snapshot.version; + } else if (typeof snapshot.version === 'string') { + const match = snapshot.version.match(/\d+/); + if (match) vNum = parseInt(match[0], 10); + } + if (vNum !== null && !isNaN(vNum)) setCurrentVersionNumber(vNum); + } + + if (snapshot.timelineAdjustments) setTimelineAdjustments(snapshot.timelineAdjustments); + if (snapshot.expectedDateTimestamp) { + setExpectedDate(new Date(snapshot.expectedDateTimestamp)); + } else if (snapshot.expectedDateString) { + setExpectedDate(new Date(snapshot.expectedDateString)); + } + + // 优先从快照恢复起始时间 + let startTimeRestored = false; + if (snapshot.startTimestamp) { + setStartTime(new Date(snapshot.startTimestamp)); + startTimeRestored = true; + } else if (snapshot.startString) { + const parsed = new Date(snapshot.startString); + if (!isNaN(parsed.getTime())) { + setStartTime(parsed); + startTimeRestored = true; + } + } + + if (!startTimeRestored) { + const startTimeValue = deliveryRecord?.fields?.[DELIVERY_START_TIME_FIELD_ID]; + if (startTimeValue) { + let extractedStartTime: Date | null = null; + if (typeof startTimeValue === 'number') { + extractedStartTime = new Date(startTimeValue); + } else if (Array.isArray(startTimeValue) && startTimeValue.length > 0) { + const timestamp = startTimeValue[0]; + if (typeof timestamp === 'number') extractedStartTime = new Date(timestamp); + } + if (extractedStartTime && !isNaN(extractedStartTime.getTime())) setStartTime(extractedStartTime); + } + } + + // 完整快照直接包含timelineResults,优先使用 + if (Array.isArray(snapshot.timelineResults)) { + setTimelineResults(snapshot.timelineResults); + setTimelineVisible(true); + if (bitable.ui.showToast) { + await bitable.ui.showToast({ toastType: 'success', message: '已按货期记录快照还原流程数据' }); + } + setTimelineLoading(false); + setIsRestoringSnapshot(false); + return; + } + + // 兼容完整快照标识但没有直接timelineResults的情况 + if (snapshot.isCompleteSnapshot || snapshot.snapshotType === 'complete' || snapshot.isGlobalSnapshot) { + // 若没有timelineResults,视为旧格式,保持兼容:不在此分支拼装,后续走旧流程 + } else { + // 非完整快照则进入旧流程(从节点记录扫描快照) + } + } + } catch (e) { + console.warn('从货期记录快照字段还原失败,回退到旧流程:', e); + } + let recordIds: string[] = []; if (nodeDetailsVal && typeof nodeDetailsVal === 'object' && (nodeDetailsVal as any).recordIds) { recordIds = (nodeDetailsVal as any).recordIds as string[]; @@ -167,6 +351,7 @@ export default function App() { } } if (snapStr && snapStr.trim() !== '') { + setIsRestoringSnapshot(true); // 开始快照还原 const snapshot = JSON.parse(snapStr); // 恢复页面状态 if (snapshot.selectedLabels) setSelectedLabels(snapshot.selectedLabels); @@ -177,6 +362,26 @@ export default function App() { if (snapshot.styleText) setCurrentStyleText(snapshot.styleText); if (snapshot.colorText) setCurrentColorText(snapshot.colorText); if (snapshot.text2) setCurrentText2(snapshot.text2); + + // 恢复生成模式完整状态(如果存在) + if (snapshot.generationModeState) { + const genState = snapshot.generationModeState; + if (genState.currentForeignId) setCurrentForeignId(genState.currentForeignId); + if (genState.currentStyleText) setCurrentStyleText(genState.currentStyleText); + if (genState.currentColorText) setCurrentColorText(genState.currentColorText); + if (genState.currentText2) setCurrentText2(genState.currentText2); + if (genState.currentVersionNumber !== undefined) setCurrentVersionNumber(genState.currentVersionNumber); + if (genState.recordDetails && Array.isArray(genState.recordDetails)) { + // 恢复记录详情(如果需要的话) + console.log('恢复生成模式记录详情:', genState.recordDetails.length, '条记录'); + } + console.log('恢复生成模式状态:', { + hasSelectedLabels: genState.hasSelectedLabels, + labelSelectionComplete: genState.labelSelectionComplete, + recordCount: genState.recordDetails?.length || 0 + }); + } + if (snapshot.version !== undefined) { let vNum: number | null = null; if (typeof snapshot.version === 'number') { @@ -230,19 +435,312 @@ export default function App() { } if (Array.isArray(snapshot.timelineResults)) { + // 兼容旧版本的完整快照格式 setTimelineResults(snapshot.timelineResults); setTimelineVisible(true); if (bitable.ui.showToast) { await bitable.ui.showToast({ toastType: 'success', message: '已按快照一模一样还原流程数据' }); } setTimelineLoading(false); + setIsRestoringSnapshot(false); // 快照还原完成 return; // 快照还原完成,退出函数 + } else if (snapshot.isCompleteSnapshot || snapshot.snapshotType === 'complete' || + snapshot.isGlobalSnapshot || snapshot.isCombinedSnapshot) { + // 处理完整快照格式:每个节点都包含完整数据 + console.log('检测到完整快照格式,直接使用快照数据'); + + // 如果是完整快照,直接使用其中的timelineResults + if (snapshot.isCompleteSnapshot && snapshot.timelineResults) { + console.log('使用完整快照中的timelineResults数据'); + setTimelineResults(snapshot.timelineResults); + setTimelineVisible(true); + + // 恢复智能缓冲期状态 + if (snapshot.bufferManagement) { + console.log('恢复智能缓冲期状态:', snapshot.bufferManagement); + } + + // 恢复连锁调整系统状态 + if (snapshot.chainAdjustmentSystem) { + console.log('恢复连锁调整系统状态:', snapshot.chainAdjustmentSystem); + } + + // 恢复生成模式状态 + if (snapshot.generationModeState) { + console.log('恢复生成模式状态:', snapshot.generationModeState); + const genState = snapshot.generationModeState; + + // 恢复foreign_id状态 + if (genState.currentForeignId) { + setCurrentForeignId(genState.currentForeignId); + console.log('恢复foreign_id状态:', genState.currentForeignId); + } + + // 恢复款式和颜色状态 + if (genState.currentStyleText) { + setCurrentStyleText(genState.currentStyleText); + console.log('恢复款式状态:', genState.currentStyleText); + } + if (genState.currentColorText) { + setCurrentColorText(genState.currentColorText); + console.log('恢复颜色状态:', genState.currentColorText); + } + + // 恢复text2状态 + if (genState.currentText2) { + setCurrentText2(genState.currentText2); + console.log('恢复text2状态:', genState.currentText2); + } + + // 恢复版本号状态 + if (genState.currentVersionNumber !== undefined) { + setCurrentVersionNumber(genState.currentVersionNumber); + console.log('恢复版本号状态:', genState.currentVersionNumber); + } + + // 恢复记录详情状态 + if (genState.recordDetails && Array.isArray(genState.recordDetails)) { + setRecordDetails(genState.recordDetails); + console.log('恢复记录详情状态,记录数量:', genState.recordDetails.length); + } + } + + // 恢复时间线计算状态 + if (snapshot.timelineCalculationState) { + console.log('恢复时间线计算状态:', snapshot.timelineCalculationState); + } + + if (bitable.ui.showToast) { + await bitable.ui.showToast({ + toastType: 'success', + message: '已按完整快照还原流程数据' + }); + } + setTimelineLoading(false); + setIsRestoringSnapshot(false); + return; + } + + // 兼容旧版本分散快照格式的处理逻辑 + // 新版本的分散快照格式:需要从所有节点收集数据 + console.log('检测到新版本快照格式,开始收集所有节点的快照数据'); + + try { + // 收集所有节点的快照数据 + const nodeSnapshots: any[] = []; + let globalSnapshotData = snapshot.isCompleteSnapshot ? snapshot : null; + + // 遍历所有记录,收集快照数据 + for (const record of records) { + const fields = record?.fields || {}; + const snapshotField = fields[PROCESS_SNAPSHOT_JSON_FIELD_ID]; + + if (snapshotField) { + let nodeSnapStr = ''; + + // 解析快照字段(支持多种格式) + if (typeof snapshotField === 'string') { + nodeSnapStr = snapshotField; + } else if (Array.isArray(snapshotField)) { + const texts = snapshotField + .filter((el: any) => el && el.type === 'text' && typeof el.text === 'string') + .map((el: any) => el.text); + nodeSnapStr = texts.length > 0 ? texts.join('') : ''; + } else if (snapshotField && typeof snapshotField === 'object') { + if ((snapshotField as any).text && typeof (snapshotField as any).text === 'string') { + nodeSnapStr = (snapshotField as any).text; + } + } + + if (nodeSnapStr && nodeSnapStr.trim() !== '') { + try { + const nodeSnapshot = JSON.parse(nodeSnapStr); + + // 批量模式现在使用扁平化结构,直接从快照中提取全局数据 + if (nodeSnapshot.isCompleteSnapshot && !globalSnapshotData) { + globalSnapshotData = { + version: nodeSnapshot.version, + foreignId: nodeSnapshot.foreignId, + styleText: nodeSnapshot.styleText, + colorText: nodeSnapshot.colorText, + text2: nodeSnapshot.text2, + mode: nodeSnapshot.mode, + selectedLabels: nodeSnapshot.selectedLabels, + expectedDateTimestamp: nodeSnapshot.expectedDateTimestamp, + expectedDateString: nodeSnapshot.expectedDateString, + startTimestamp: nodeSnapshot.startTimestamp, + startString: nodeSnapshot.startString, + timelineAdjustments: nodeSnapshot.timelineAdjustments, + // 恢复智能缓冲期管理状态 + bufferManagement: nodeSnapshot.bufferManagement, + // 恢复连锁调整系统状态 + chainAdjustmentSystem: nodeSnapshot.chainAdjustmentSystem, + // 恢复生成模式状态 + generationModeState: nodeSnapshot.generationModeState, + // 恢复时间线计算状态 + timelineCalculationState: nodeSnapshot.timelineCalculationState, + totalNodes: nodeSnapshot.totalNodes + }; + } + + // 扁平化结构中,每个快照都包含完整的节点数据 + if (nodeSnapshot.isCompleteSnapshot) { + // 确保adjustedTimelineValue有正确的默认值 + const adjustedTimelineValue = nodeSnapshot.adjustedTimelineValue !== undefined ? + nodeSnapshot.adjustedTimelineValue : nodeSnapshot.timelineValue; + + // 处理日期格式,优先使用时间戳格式 + let estimatedStart = nodeSnapshot.estimatedStart; + let estimatedEnd = nodeSnapshot.estimatedEnd; + + // 如果有时间戳格式的日期,使用时间戳重新格式化 + if (nodeSnapshot.estimatedStartTimestamp) { + try { + estimatedStart = formatDate(new Date(nodeSnapshot.estimatedStartTimestamp)); + } catch (error) { + console.warn('时间戳格式开始时间转换失败:', error); + } + } + + if (nodeSnapshot.estimatedEndTimestamp) { + try { + estimatedEnd = formatDate(new Date(nodeSnapshot.estimatedEndTimestamp)); + } catch (error) { + console.warn('时间戳格式结束时间转换失败:', error); + } + } + + // 调试日志 + console.log(`节点 ${nodeSnapshot.nodeName} 快照还原:`, { + timelineValue: nodeSnapshot.timelineValue, + adjustedTimelineValue: adjustedTimelineValue, + originalAdjustedTimelineValue: nodeSnapshot.adjustedTimelineValue, + estimatedStart: estimatedStart, + estimatedEnd: estimatedEnd, + hasTimestamps: { + start: Boolean(nodeSnapshot.estimatedStartTimestamp), + end: Boolean(nodeSnapshot.estimatedEndTimestamp) + }, + nodeCalculationState: nodeSnapshot.nodeCalculationState + }); + + nodeSnapshots.push({ + processOrder: nodeSnapshot.processOrder, + nodeName: nodeSnapshot.nodeName || nodeSnapshot.currentNodeName, + matchedLabels: nodeSnapshot.matchedLabels, + timelineValue: nodeSnapshot.timelineValue, + estimatedStart: estimatedStart, + estimatedEnd: estimatedEnd, + timelineRecordId: nodeSnapshot.timelineRecordId, + allMatchedRecords: nodeSnapshot.allMatchedRecords, + isAccumulated: nodeSnapshot.isAccumulated, + weekendDaysConfig: nodeSnapshot.weekendDaysConfig, + excludedDates: nodeSnapshot.excludedDates, + actualExcludedDates: nodeSnapshot.actualExcludedDates, + actualExcludedDatesCount: nodeSnapshot.actualExcludedDatesCount, + calculationMethod: nodeSnapshot.calculationMethod, + ruleDescription: nodeSnapshot.ruleDescription, + skippedWeekends: nodeSnapshot.skippedWeekends, + actualDays: nodeSnapshot.actualDays, + adjustedTimelineValue: adjustedTimelineValue, + adjustment: nodeSnapshot.adjustment || 0, + adjustmentDescription: nodeSnapshot.adjustmentDescription || '', + startDateRule: nodeSnapshot.startDateRule, + dateAdjustmentRule: nodeSnapshot.dateAdjustmentRule, + // 恢复节点计算状态 + nodeCalculationState: nodeSnapshot.nodeCalculationState, + // 恢复连锁调整节点状态 + chainAdjustmentNode: nodeSnapshot.chainAdjustmentNode + }); + } + } catch (parseError) { + console.warn('解析节点快照失败:', parseError); + } + } + } + } + + // 按流程顺序排序节点快照 + nodeSnapshots.sort((a, b) => (a.processOrder || 0) - (b.processOrder || 0)); + + console.log('收集到的节点快照数量:', nodeSnapshots.length); + console.log('全局快照数据:', globalSnapshotData); + + // 验证数据完整性 + if (globalSnapshotData && globalSnapshotData.totalNodes && + nodeSnapshots.length === globalSnapshotData.totalNodes) { + + // 重组完整的 timelineResults + setTimelineResults(nodeSnapshots); + setTimelineVisible(true); + + // 恢复智能缓冲期状态(如果存在) + if (globalSnapshotData.bufferManagement) { + console.log('恢复智能缓冲期状态:', globalSnapshotData.bufferManagement); + // 这里可以添加额外的状态恢复逻辑,如果需要的话 + } + + // 恢复连锁调整系统状态(如果存在) + if (globalSnapshotData.chainAdjustmentSystem) { + console.log('恢复连锁调整系统状态:', globalSnapshotData.chainAdjustmentSystem); + // 这里可以添加额外的状态恢复逻辑,如果需要的话 + } + + // 恢复生成模式状态(如果存在) + if (globalSnapshotData.generationModeState) { + console.log('恢复生成模式状态:', globalSnapshotData.generationModeState); + const genState = globalSnapshotData.generationModeState; + + // 确保生成模式的关键状态被正确恢复 + if (genState.currentForeignId && !currentForeignId) { + setCurrentForeignId(genState.currentForeignId); + } + if (genState.currentStyleText && !currentStyleText) { + setCurrentStyleText(genState.currentStyleText); + } + if (genState.currentColorText && !currentColorText) { + setCurrentColorText(genState.currentColorText); + } + if (genState.currentText2 && !currentText2) { + setCurrentText2(genState.currentText2); + } + if (genState.currentVersionNumber !== undefined && !currentVersionNumber) { + setCurrentVersionNumber(genState.currentVersionNumber); + } + } + + // 恢复时间线计算状态(如果存在) + if (globalSnapshotData.timelineCalculationState) { + console.log('恢复时间线计算状态:', globalSnapshotData.timelineCalculationState); + } + + if (bitable.ui.showToast) { + await bitable.ui.showToast({ + toastType: 'success', + message: `已从 ${nodeSnapshots.length} 个节点快照还原完整流程数据(包含智能缓冲期和连锁调整状态)` + }); + } + setTimelineLoading(false); + setIsRestoringSnapshot(false); // 快照还原完成 + return; // 快照还原完成,退出函数 + } else { + console.warn('快照数据不完整,降级为基于字段的还原'); + console.log('期望节点数:', globalSnapshotData?.totalNodes, '实际节点数:', nodeSnapshots.length); + } + + } catch (collectError) { + console.warn('收集节点快照数据失败,降级为基于字段的还原:', collectError); + } } } } catch (snapError) { console.warn('解析快照失败,降级为基于字段的还原:', snapError); + setIsRestoringSnapshot(false); // 快照还原失败,重置标志 } + // 如果到达这里,说明没有成功的快照还原,重置标志 + setIsRestoringSnapshot(false); + const results = records.map((rec: any) => { const fields = rec?.fields || {}; const processOrder = fields[PROCESS_ORDER_FIELD_ID_DATA]; @@ -328,6 +826,7 @@ export default function App() { const DELIVERY_CUSTOMER_EXPECTED_DATE_FIELD_ID = 'fldYNluU8D'; // 客户期望日期字段 const DELIVERY_ADJUSTMENT_INFO_FIELD_ID = 'fldNc6nNsz'; // 货期调整信息字段(需要替换为实际字段ID) const DELIVERY_VERSION_FIELD_ID = 'fld5OmvZrn'; // 版本字段(新增) + const DELIVERY_SNAPSHOT_JSON_FIELD_ID = 'fldEYIvHeP'; // 货期记录表:完整快照(JSON) // 起始时间字段(货期记录表新增) const DELIVERY_START_TIME_FIELD_ID = 'fld727qCAv'; // 文本2字段(货期记录表新增) @@ -362,6 +861,14 @@ export default function App() { return null; } + // 如果是时间戳格式,直接转换 + if (/^\d{13}$/.test(dateStr)) { + const date = new Date(parseInt(dateStr)); + if (!isNaN(date.getTime())) { + return date; + } + } + // 移除所有星期信息(支持"星期X"和"周X"格式) let cleanStr = dateStr .replace(/\s*星期[一二三四五六日天]\s*/g, ' ') @@ -394,7 +901,7 @@ export default function App() { throw new Error(`无法解析日期格式: ${cleanStr}`); } catch (error) { - console.error('日期解析失败:', error, { dateStr }); + console.error('日期解析失败:', { dateStr, error: error.message }); return null; } }; @@ -737,6 +1244,8 @@ export default function App() { return result; }; + + // 获取标签数据 const fetchLabelOptions = async () => { setLabelLoading(true); @@ -910,6 +1419,8 @@ export default function App() { // 处理表选择变化 const handleBatchTableChange = (tableId: string) => { + // 切换批量来源表时重置全局变量,避免旧状态残留 + resetGlobalState(); setSelectedBatchTableId(tableId); setSelectedBatchViewId(''); // 重置视图选择 loadAvailableViews(tableId); // 加载新表的视图列表 @@ -930,60 +1441,99 @@ export default function App() { }; // 计算预计开始和完成时间 - const handleCalculateTimeline = async () => { - // 检查是否选择了多条记录 - if (selectedRecords.length > 1) { - if (bitable.ui.showToast) { - await bitable.ui.showToast({ - toastType: 'warning', - message: '计算时效功能仅支持单条记录,请重新选择单条记录后再试' - }); - } - return; - } + const handleCalculateTimeline = async ( + skipValidation: boolean = false, + overrideData?: { + selectedRecords?: string[], + recordDetails?: any[], + selectedLabels?: {[key: string]: string | string[]}, + expectedDate?: Date | null, + startTime?: Date | null + }, + showUI: boolean = true // 新增参数控制是否显示UI + ) => { + // 使用传入的数据或全局状态 + const currentSelectedRecords = overrideData?.selectedRecords || selectedRecords; + const currentRecordDetails = overrideData?.recordDetails || recordDetails; + const currentSelectedLabels = overrideData?.selectedLabels || selectedLabels; + const currentExpectedDate = overrideData?.expectedDate || expectedDate; + const currentStartTime = overrideData?.startTime || startTime; - // 检查是否选择了记录 - if (selectedRecords.length === 0) { - if (bitable.ui.showToast) { - await bitable.ui.showToast({ - toastType: 'warning', - message: '请先选择一条记录' - }); - } - return; - } + console.log('=== handleCalculateTimeline - 使用的数据 ==='); + console.log('currentSelectedRecords:', currentSelectedRecords); + console.log('currentSelectedLabels:', currentSelectedLabels); + console.log('currentExpectedDate:', currentExpectedDate); + console.log('currentStartTime:', currentStartTime); - // 检查是否选择了标签 - const hasSelectedLabels = Object.values(selectedLabels).some(value => { - return Array.isArray(value) ? value.length > 0 : Boolean(value); - }); - - if (!hasSelectedLabels) { - if (bitable.ui.showToast) { - await bitable.ui.showToast({ - toastType: 'warning', - message: '请先选择至少一个标签' - }); + // 跳过验证(用于批量模式) + if (!skipValidation) { + // 检查是否选择了多条记录 + if (currentSelectedRecords.length > 1) { + if (bitable.ui.showToast) { + await bitable.ui.showToast({ + toastType: 'warning', + message: '计算时效功能仅支持单条记录,请重新选择单条记录后再试' + }); + } + return; } - return; - } - - // 可选:检查是否选择了客户期望日期 - if (!expectedDate) { - if (bitable.ui.showToast) { - await bitable.ui.showToast({ - toastType: 'info', - message: '建议选择客户期望日期以便更好地进行时效计算' - }); + + // 检查是否选择了记录 + if (currentSelectedRecords.length === 0) { + if (bitable.ui.showToast) { + await bitable.ui.showToast({ + toastType: 'warning', + message: '请先选择一条记录' + }); + } + return; + } + + // 检查是否选择了标签 + const hasSelectedLabels = Object.values(currentSelectedLabels).some(value => { + return Array.isArray(value) ? value.length > 0 : Boolean(value); + }); + + if (!hasSelectedLabels) { + if (bitable.ui.showToast) { + await bitable.ui.showToast({ + toastType: 'warning', + message: '请先选择至少一个标签' + }); + } + return; + } + + // 可选:检查是否选择了客户期望日期 + if (!currentExpectedDate) { + if (bitable.ui.showToast) { + await bitable.ui.showToast({ + toastType: 'info', + message: '建议选择客户期望日期以便更好地进行时效计算' + }); + } } } // 移除冗余日志:客户期望日期输出 setTimelineLoading(true); + // 生成模式:输出当前数据结构,便于与批量模式对比 + try { + console.group('=== 生成模式:计算时效 - 当前数据结构 ==='); + // 模式与核心输入 + console.log('mode:', mode); + console.log('selectedRecords:', currentSelectedRecords); + console.log('recordDetails:', currentRecordDetails); + console.log('selectedLabels:', currentSelectedLabels); + console.log('expectedDate:', currentExpectedDate); + console.groupEnd(); + } catch (logErr) { + console.warn('生成模式结构化日志输出失败:', logErr); + } try { // 构建业务选择的所有标签值集合(用于快速查找) const businessLabelValues = new Set(); - for (const [labelKey, selectedValue] of Object.entries(selectedLabels)) { + for (const [labelKey, selectedValue] of Object.entries(currentSelectedLabels)) { if (selectedValue) { const values = Array.isArray(selectedValue) ? selectedValue : [selectedValue]; values.forEach(value => { @@ -1084,7 +1634,7 @@ export default function App() { let isMatched = false; const matchedLabels: string[] = []; - for (const [labelKey, labelValue] of Object.entries(selectedLabels)) { + for (const [labelKey, labelValue] of Object.entries(currentSelectedLabels)) { if (!labelValue) continue; const valuesToCheck = Array.isArray(labelValue) ? labelValue : [labelValue]; @@ -1259,7 +1809,7 @@ export default function App() { const results: any[] = []; // 3. 按顺序为每个匹配的流程节点查找时效数据并计算累积时间 - let cumulativeStartTime = startTime ? new Date(startTime) : new Date(); // 累积开始时间 + let cumulativeStartTime = currentStartTime ? new Date(currentStartTime) : new Date(); // 累积开始时间 for (let i = 0; i < matchedProcessNodes.length; i++) { const processNode = matchedProcessNodes[i]; @@ -1478,6 +2028,8 @@ export default function App() { // matchedTimelineRecord 已在上面的处理逻辑中设置 } + + // 计算当前节点的开始和完成时间(使用工作日计算) const calculateTimeline = (startDate: Date, timelineValue: number, calculationMethod: string = '外部') => { // 根据计算方式调整开始时间 @@ -1632,10 +2184,14 @@ export default function App() { } setTimelineResults(results); - setTimelineVisible(true); + if (showUI) { + setTimelineVisible(true); + } console.log('按流程顺序计算的时效结果:', results); + return results; // 返回计算结果 + } catch (error) { console.error('计算时效失败:', error); if (bitable.ui.showToast) { @@ -1644,11 +2200,98 @@ export default function App() { message: '计算时效失败,请检查表格配置' }); } + throw error; // 重新抛出错误 } finally { setTimelineLoading(false); } }; + // 复合调整处理函数:根据缓冲期和交期余量状态决定调整方式 + const handleComplexAdjustment = (nodeIndex: number, adjustment: number) => { + // 计算当前状态 + const totalAdjustments = Object.values(timelineAdjustments).reduce((sum, adj) => sum + adj, 0); + const baseBuferDays = baseBufferDays; + + // 智能缓冲期计算逻辑 + let dynamicBufferDays = Math.max(0, baseBuferDays - totalAdjustments); + + // 只有在缓冲期为0时才检查最终限制:最后节点预计完成时间是否已达到客户期望日期 + let hasReachedFinalLimit = false; + if (dynamicBufferDays === 0 && expectedDate && timelineResults.length > 0) { + // 获取有效的最后流程完成日期 + let effectiveLastProcess = null; + let lastCompletionDate = null; + + for (let i = timelineResults.length - 1; i >= 0; i--) { + const process = timelineResults[i]; + const processDate = new Date(process.estimatedEnd); + + if (!isNaN(processDate.getTime()) && process.estimatedEnd !== '时效值为0') { + effectiveLastProcess = process; + lastCompletionDate = processDate; + break; + } + } + + if (!effectiveLastProcess) { + effectiveLastProcess = timelineResults[timelineResults.length - 1]; + lastCompletionDate = new Date(effectiveLastProcess.estimatedEnd); + } + + // 计算最后流程完成日期(包含调整) + const adjustedCompletionDate = new Date(lastCompletionDate); + adjustedCompletionDate.setDate(adjustedCompletionDate.getDate() + completionDateAdjustment); + + // 检查是否已达到客户期望日期 + const timeDiffToExpected = expectedDate.getTime() - adjustedCompletionDate.getTime(); + const daysToExpected = Math.ceil(timeDiffToExpected / (1000 * 60 * 60 * 24)); + hasReachedFinalLimit = daysToExpected <= 0; + } + + // 如果已达到最终限制且是正向调整,则禁止操作 + if (hasReachedFinalLimit && adjustment > 0) { + return; + } + + // 调整逻辑: + // 1. 优先使用缓冲期 + // 2. 缓冲期为0时,通过调整节点时效值实现连锁传递 + if (adjustment > 0) { + // 正数调整:需要扣减 + if (dynamicBufferDays > 0) { + // 1. 缓冲期 > 0,优先扣减缓冲期(通过调整节点时间) + const updatedResults = handleTimelineAdjustment(nodeIndex, adjustment); + // 如果调整失败,不进行后续操作 + if (!updatedResults) return; + } else { + // 2. 缓冲期 = 0,通过调整节点时效值实现连锁传递 + const updatedResults = handleTimelineAdjustment(nodeIndex, adjustment); + // 如果调整失败,不进行后续操作 + if (!updatedResults) return; + } + } else if (adjustment < 0) { + // 负数调整:需要恢复 + if (completionDateAdjustment > 0) { + // 1. 优先恢复最后流程完成日期的调整 + const restoreAmount = Math.min(-adjustment, completionDateAdjustment); + setCompletionDateAdjustment(completionDateAdjustment - restoreAmount); + const remainingRestore = -adjustment - restoreAmount; + + if (remainingRestore > 0) { + // 2. 如果还有剩余恢复量,恢复缓冲期(通过调整节点时间) + const updatedResults = handleTimelineAdjustment(nodeIndex, -remainingRestore); + // 如果调整失败,不进行后续操作 + if (!updatedResults) return; + } + } else if (totalAdjustments > 0) { + // 直接恢复缓冲期 + const updatedResults = handleTimelineAdjustment(nodeIndex, adjustment); + // 如果调整失败,不进行后续操作 + if (!updatedResults) return; + } + } + }; + // 调整时效值的函数 const handleTimelineAdjustment = (nodeIndex: number, adjustment: number) => { const newAdjustments = { ...timelineAdjustments }; @@ -1662,18 +2305,50 @@ export default function App() { ? timelineResults[nodeIndex]!.adjustedTimelineValue : 0; if (baseValue + newAdjustment < 0) { - return; + return null; + } + + // 检查当前调整的节点是否为周转周期节点 + const currentNodeName = timelineResults[nodeIndex]?.nodeName; + const isTurnoverNode = currentNodeName === '周转周期'; + + // 如果调整的是周转周期节点,直接返回(禁用手动调整) + if (isTurnoverNode) { + return null; } newAdjustments[nodeIndex] = newAdjustment; + + // 查找周转周期节点的索引 + const turnoverNodeIndex = timelineResults.findIndex(result => result.nodeName === '周转周期'); + + // 如果存在周转周期节点,进行反向调整 + if (turnoverNodeIndex !== -1 && turnoverNodeIndex !== nodeIndex) { + const turnoverCurrentAdjustment = newAdjustments[turnoverNodeIndex] || 0; + const turnoverBaseValue = (typeof timelineResults[turnoverNodeIndex]?.timelineValue === 'number') + ? timelineResults[turnoverNodeIndex]!.timelineValue + : (typeof timelineResults[turnoverNodeIndex]?.adjustedTimelineValue === 'number') + ? timelineResults[turnoverNodeIndex]!.adjustedTimelineValue + : 0; + + // 计算周转周期的反向调整值 + const turnoverNewAdjustment = turnoverCurrentAdjustment - adjustment; + + // 确保周转周期调整后的值不小于0 + if (turnoverBaseValue + turnoverNewAdjustment >= 0) { + newAdjustments[turnoverNodeIndex] = turnoverNewAdjustment; + } + } + setTimelineAdjustments(newAdjustments); - // 重新计算所有节点的时间 + // 使用智能重算逻辑,只重算被调整的节点及其后续节点 recalculateTimeline(newAdjustments); + return timelineResults; }; - // 重新计算时间线的函数 - const recalculateTimeline = (adjustments: {[key: number]: number}) => { + // 获取重新计算后的时间线结果(不更新状态) + const getRecalculatedTimeline = (adjustments: {[key: number]: number}) => { const updatedResults = [...timelineResults]; let cumulativeStartTime = startTime ? new Date(startTime) : new Date(); // 从起始时间开始 @@ -1690,6 +2365,133 @@ export default function App() { // 计算当前节点的开始时间 let nodeStartTime = new Date(cumulativeStartTime); + // 获取节点的计算方式、周末天数和排除日期 + const nodeCalculationMethod = result.calculationMethod || 'external'; + const nodeWeekendDays = result.weekendDays || []; + const nodeExcludedDates = result.excludedDates || []; + + // 获取调整规则描述 + const ruleDescription = result.ruleDescription || ''; + + // 计算节点的结束时间 + let nodeEndTime; + if (adjustedTimelineValue > 0) { + const adjustedStartTime = adjustToNextWorkingHour(nodeStartTime, nodeCalculationMethod, nodeWeekendDays, nodeExcludedDates); + if (nodeCalculationMethod === 'internal') { + nodeEndTime = addInternalBusinessTime(adjustedStartTime, adjustedTimelineValue, nodeWeekendDays, nodeExcludedDates); + } else { + nodeEndTime = addBusinessDaysWithHolidays(adjustedStartTime, adjustedTimelineValue, nodeWeekendDays, nodeExcludedDates); + } + } else { + nodeEndTime = new Date(nodeStartTime); + } + + // 计算跳过的天数 + const adjustedStartTime = adjustedTimelineValue > 0 ? adjustToNextWorkingHour(nodeStartTime, nodeCalculationMethod, nodeWeekendDays, nodeExcludedDates) : nodeStartTime; + const skippedWeekends = calculateSkippedWeekends(adjustedStartTime, nodeEndTime, nodeWeekendDays); + const estimatedStartStr = formatDate(adjustedStartTime); + const estimatedEndStr = adjustedTimelineValue > 0 ? formatDate(nodeEndTime) : '时效值为0'; + const actualDays = calculateActualDays(estimatedStartStr, estimatedEndStr); + + // 计算时间范围内实际跳过的自定义日期 + const excludedDatesInRange = calculateExcludedDatesInRange(adjustedStartTime, nodeEndTime, nodeExcludedDates); + + // 更新结果 + updatedResults[i] = { + ...result, + adjustedTimelineValue: adjustedTimelineValue, + estimatedStart: estimatedStartStr, + estimatedEnd: estimatedEndStr, + adjustment: adjustment, + calculationMethod: nodeCalculationMethod, // 保持计算方式 + skippedWeekends: skippedWeekends, + actualDays: actualDays, + // 更新时间范围内实际跳过的日期 + actualExcludedDates: excludedDatesInRange.dates, + actualExcludedDatesCount: excludedDatesInRange.count, + adjustmentDescription: result.adjustmentDescription, // 保持调整规则描述 + ruleDescription: ruleDescription // 添加更新后的规则描述 + }; + + // 更新累积时间:当前节点的完成时间成为下一个节点的开始时间 + if (adjustedTimelineValue > 0) { + cumulativeStartTime = new Date(nodeEndTime); + } + } + + return updatedResults; + }; + + // 重新计算时间线的函数 + const recalculateTimeline = (adjustments: {[key: number]: number}, forceRecalculateAll: boolean = false) => { + const updatedResults = [...timelineResults]; + + // 找到第一个被调整的节点索引 + const adjustedIndices = Object.keys(adjustments).map(k => parseInt(k)).filter(i => adjustments[i] !== 0); + + // 找到有实际完成时间的节点 + const actualCompletionIndices = Object.keys(actualCompletionDates) + .map(k => parseInt(k)) + .filter(i => actualCompletionDates[i] !== null && actualCompletionDates[i] !== undefined); + + // 如果没有调整且不是强制重算,但有实际完成时间,需要重新计算 + if (adjustedIndices.length === 0 && !forceRecalculateAll && actualCompletionIndices.length === 0) { + return; // 没有调整也没有实际完成时间,直接返回 + } + + // 确定第一个需要重新计算的节点索引 + let firstAdjustedIndex: number; + + if (forceRecalculateAll) { + firstAdjustedIndex = 0; + } else if (actualCompletionIndices.length > 0) { + // 如果有实际完成时间,从最早有实际完成时间的节点的下一个节点开始重新计算 + const earliestActualCompletionIndex = Math.min(...actualCompletionIndices); + const earliestAdjustmentIndex = adjustedIndices.length > 0 ? Math.min(...adjustedIndices) : Infinity; + firstAdjustedIndex = Math.min(earliestActualCompletionIndex + 1, earliestAdjustmentIndex); + console.log(`检测到实际完成时间,从节点 ${firstAdjustedIndex} 开始重新计算`); + } else { + firstAdjustedIndex = adjustedIndices.length > 0 ? Math.min(...adjustedIndices) : 0; + } + + // 确保索引不超出范围 + firstAdjustedIndex = Math.max(0, Math.min(firstAdjustedIndex, updatedResults.length - 1)); + + // 确定累积开始时间 + let cumulativeStartTime: Date; + if (firstAdjustedIndex === 0) { + // 如果调整的是第一个节点,从起始时间开始 + cumulativeStartTime = startTime ? new Date(startTime) : new Date(); + } else { + // 如果调整的不是第一个节点,从前一个节点的结束时间开始 + const previousResult = updatedResults[firstAdjustedIndex - 1]; + const previousIndex = firstAdjustedIndex - 1; + + // 检查前一个节点是否有实际完成时间 + if (actualCompletionDates[previousIndex]) { + // 使用实际完成时间作为下一个节点的开始时间 + cumulativeStartTime = new Date(actualCompletionDates[previousIndex]!); + console.log(`节点 ${previousIndex} 使用实际完成时间: ${formatDate(cumulativeStartTime)}`); + } else { + // 使用预计完成时间 + cumulativeStartTime = new Date(previousResult.estimatedEnd); + } + } + + // 只重新计算从第一个调整节点开始的后续节点 + for (let i = firstAdjustedIndex; i < updatedResults.length; i++) { + const result = updatedResults[i]; + const baseTimelineValue = (typeof result.timelineValue === 'number') + ? result.timelineValue + : (typeof result.adjustedTimelineValue === 'number') + ? result.adjustedTimelineValue + : 0; + const adjustment = adjustments[i] || 0; + const adjustedTimelineValue = baseTimelineValue + adjustment; + + // 计算当前节点的开始时间 + let nodeStartTime = new Date(cumulativeStartTime); + // 重新应用起始日期调整规则 if (result.startDateRule) { let ruleJson = ''; @@ -1772,39 +2574,71 @@ export default function App() { ruleDescription: ruleDescription // 添加更新后的规则描述 }; - // 更新累积时间:当前节点的完成时间成为下一个节点的开始时间 + // 更新累积时间:优先使用当前节点的实际完成时间,否则使用预计完成时间 if (adjustedTimelineValue > 0) { - cumulativeStartTime = new Date(nodeEndTime); + if (actualCompletionDates[i]) { + // 如果当前节点有实际完成时间,使用实际完成时间 + cumulativeStartTime = new Date(actualCompletionDates[i]!); + } else { + // 否则使用预计完成时间 + cumulativeStartTime = new Date(nodeEndTime); + } } } setTimelineResults(updatedResults); }; + // 添加快照还原状态标志 + const [isRestoringSnapshot, setIsRestoringSnapshot] = useState(false); + // 当起始时间变更时,重新以最新起始时间为基准重算全流程 useEffect(() => { - if (timelineResults.length > 0) { - recalculateTimeline(timelineAdjustments); + if (timelineResults.length > 0 && !isRestoringSnapshot) { + recalculateTimeline(timelineAdjustments, true); // 强制重算所有节点 } - }, [startTime]); + }, [startTime, isRestoringSnapshot]); // 重置调整的函数 const resetTimelineAdjustments = () => { setTimelineAdjustments({}); - recalculateTimeline({}); + setDeliveryMarginDeductions(0); // 同时重置交期余量扣减 + setCompletionDateAdjustment(0); // 重置最后流程完成日期调整 + setActualCompletionDates({}); // 重置实际完成日期 + recalculateTimeline({}, true); // 强制重算所有节点 }; // 已移除未使用的 getTimelineLabelFieldId 辅助函数 // 写入货期记录表的函数 - const writeToDeliveryRecordTable = async (timelineResults: any[], processRecordIds: string[], timelineAdjustments: {[key: number]: number} = {}) => { + const writeToDeliveryRecordTable = async ( + timelineResults: any[], + processRecordIds: string[], + timelineAdjustments: {[key: number]: number} = {}, + batchData?: { + selectedRecords: string[], + recordDetails: any[], + labels?: Record, + expectedDate?: any, + startTime?: any + } + ) => { try { - // 写入货期记录表 + console.log('=== 开始写入货期记录表 ==='); + console.log('当前模式:', mode); + console.log('timelineResults数量:', timelineResults?.length || 0); + console.log('processRecordIds数量:', processRecordIds?.length || 0); + console.log('timelineAdjustments:', timelineAdjustments); + console.log('timelineResults详情:', timelineResults); + console.log('processRecordIds详情:', processRecordIds); // 获取货期记录表 + console.log('正在获取货期记录表...'); const deliveryRecordTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID); + console.log('成功获取货期记录表'); // 检查字段是否存在 + console.log('正在检查和获取所有必需字段...'); const fieldsToCheck = [ DELIVERY_FOREIGN_ID_FIELD_ID, DELIVERY_LABELS_FIELD_ID, @@ -1817,8 +2651,10 @@ export default function App() { DELIVERY_ADJUSTMENT_INFO_FIELD_ID, // 添加货期调整信息字段 DELIVERY_START_TIME_FIELD_ID // 新增:起始时间字段 ]; + console.log('需要检查的字段ID列表:', fieldsToCheck); // 获取各个字段 + console.log('正在获取各个字段对象...'); const foreignIdField = await deliveryRecordTable.getField(DELIVERY_FOREIGN_ID_FIELD_ID); const labelsField = await deliveryRecordTable.getField(DELIVERY_LABELS_FIELD_ID); const styleField = await deliveryRecordTable.getField(DELIVERY_STYLE_FIELD_ID); @@ -1831,6 +2667,8 @@ export default function App() { const adjustmentInfoField = await deliveryRecordTable.getField(DELIVERY_ADJUSTMENT_INFO_FIELD_ID); const versionField = await deliveryRecordTable.getField(DELIVERY_VERSION_FIELD_ID); const startTimeField = await deliveryRecordTable.getField(DELIVERY_START_TIME_FIELD_ID); + const snapshotField = await deliveryRecordTable.getField(DELIVERY_SNAPSHOT_JSON_FIELD_ID); + console.log('成功获取所有字段对象'); // 检查标签汇总字段的类型 console.log('标签汇总字段信息:', { @@ -1841,43 +2679,47 @@ export default function App() { }); // 获取foreign_id:调整模式严格使用快照数据,生成模式优先使用选择记录 + console.log('=== 开始获取foreign_id ==='); let foreignId = ''; + + // 使用传递的数据或全局状态 + const currentSelectedRecords = batchData?.selectedRecords || selectedRecords; + const currentRecordDetails = batchData?.recordDetails || recordDetails; + if (mode === 'adjust') { // 调整模式:严格使用快照回填的foreign_id,即使为空也不回退 foreignId = currentForeignId; console.log('调整模式:严格使用快照恢复的foreign_id:', foreignId); - } else if (selectedRecords.length > 0) { + } else if (currentSelectedRecords.length > 0) { // 生成模式:从选择记录获取 - const table = await bitable.base.getTable(TABLE_ID); - const firstRecord = await table.getRecordById(selectedRecords[0]); - const fieldValue = firstRecord.fields['fldpvBfeC0']; + console.log('生成模式:从选择记录获取foreign_id'); + console.log('selectedRecords[0]:', currentSelectedRecords[0]); - if (Array.isArray(fieldValue) && fieldValue.length > 0) { - const firstItem = fieldValue[0]; - if (firstItem && firstItem.text) { - foreignId = firstItem.text; - } else if (typeof firstItem === 'string') { - foreignId = firstItem; + if (batchData) { + // 批量模式:直接从传递的数据中获取 + const firstRecord = currentRecordDetails[0]; + if (firstRecord && firstRecord.fields && firstRecord.fields['fldpvBfeC0']) { + const fieldValue = firstRecord.fields['fldpvBfeC0']; + foreignId = extractText(fieldValue); + console.log('批量模式:从传递数据获取foreign_id:', foreignId); } - } else if (typeof fieldValue === 'string') { - foreignId = fieldValue; - } else if (fieldValue && fieldValue.text) { - foreignId = fieldValue.text; + } else { + // 生成模式:从数据库获取 + const table = await bitable.base.getTable(TABLE_ID); + const firstRecord = await table.getRecordById(currentSelectedRecords[0]); + console.log('获取到的记录:', firstRecord); + const fieldValue = firstRecord.fields['fldpvBfeC0']; + console.log('fldpvBfeC0字段值:', fieldValue); + + foreignId = extractText(fieldValue); } } // 生成模式的回退逻辑:记录详情 - if (mode !== 'adjust' && !foreignId && recordDetails.length > 0) { - const first = recordDetails[0]; + if (mode !== 'adjust' && !foreignId && currentRecordDetails.length > 0) { + const first = currentRecordDetails[0]; const val = first.fields['fldpvBfeC0']; - if (Array.isArray(val) && val.length > 0) { - const item = val[0]; - foreignId = typeof item === 'string' ? item : (item?.text || item?.name || ''); - } else if (typeof val === 'string') { - foreignId = val; - } else if (val && typeof val === 'object') { - foreignId = val.text || val.name || ''; - } + foreignId = extractText(val); } // 生成模式的最后回退:快照状态 if (mode !== 'adjust' && !foreignId && currentForeignId) { @@ -1887,17 +2729,6 @@ export default function App() { // 获取款式与颜色:调整模式优先使用快照数据,生成模式优先使用记录详情 let style = ''; let color = ''; - const extractText = (val: any) => { - if (Array.isArray(val) && val.length > 0) { - const item = val[0]; - return typeof item === 'string' ? item : (item?.text || item?.name || ''); - } else if (typeof val === 'string') { - return val; - } else if (val && typeof val === 'object') { - return val.text || val.name || ''; - } - return ''; - }; if (mode === 'adjust') { // 调整模式:严格使用快照回填的数据,即使为空也不回退 @@ -1906,8 +2737,8 @@ export default function App() { console.log('调整模式:严格使用快照恢复的款式:', style, '颜色:', color); } else { // 生成模式:优先使用记录详情 - if (recordDetails.length > 0) { - const first = recordDetails[0]; + if (currentRecordDetails.length > 0) { + const first = currentRecordDetails[0]; style = extractText(first.fields['fld6Uw95kt']) || currentStyleText || ''; color = extractText(first.fields['flde85ni4O']) || currentColorText || ''; } else { @@ -1915,46 +2746,86 @@ export default function App() { style = currentStyleText || ''; color = currentColorText || ''; // 若仍为空且有选择记录,仅做一次读取 - if ((!style || !color) && selectedRecords.length > 0) { + if ((!style || !color) && currentSelectedRecords.length > 0) { const table = await bitable.base.getTable(TABLE_ID); - const firstRecord = await table.getRecordById(selectedRecords[0]); + const firstRecord = await table.getRecordById(currentSelectedRecords[0]); style = style || extractText(firstRecord.fields['fld6Uw95kt']); color = color || extractText(firstRecord.fields['flde85ni4O']); } } } - // 获取文本2:调整模式优先使用快照数据,生成模式不填写 + // 获取文本2:调整模式优先使用快照数据;生成模式在批量模式下填写 let text2 = ''; if (mode === 'adjust') { // 调整模式:严格使用快照回填的数据,即使为空也不回退 text2 = currentText2; // 直接使用快照值,不使用 || '' 的回退逻辑 console.log('调整模式:严格使用快照恢复的文本2:', text2); } else { - // 生成模式:不填写文本2字段,保持为空 - text2 = ''; - console.log('生成模式:文本2字段保持为空'); + if (batchData && currentRecordDetails.length > 0) { + const first = currentRecordDetails[0]; + if (first?.fields?.['fldG6LZnmU']) { + text2 = extractText(first.fields['fldG6LZnmU']); + } + // 兜底:若批量数据中显式传递了text2,则使用之 + if (!text2 && (batchData as any).text2) { + try { text2 = extractText((batchData as any).text2); } catch { text2 = (batchData as any).text2; } + } + console.log('生成模式(批量):文本2来自批量数据:', text2); + } else { + // 非批量的生成模式:保持为空 + text2 = ''; + console.log('生成模式:文本2字段保持为空'); + } } - // 获取标签汇总(从业务选择的标签中获取) - const selectedLabelValues = Object.values(selectedLabels).flat().filter(Boolean); + // 获取标签汇总:批量模式优先使用传入的labels + const selectedLabelValues = batchData?.labels + ? Object.values(batchData.labels).flat().filter(Boolean) + : Object.values(selectedLabels).flat().filter(Boolean); - // 获取预计交付日期(最后节点的预计完成时间) + // 获取预计交付日期(交期余量的日期版本:最后流程完成日期 + 基础缓冲期) let expectedDeliveryDate = null; if (timelineResults.length > 0) { - const lastNode = timelineResults[timelineResults.length - 1]; - if (lastNode.estimatedEnd && !lastNode.estimatedEnd.includes('未找到')) { + // 从后往前查找第一个有效的流程完成日期(与交期余量计算逻辑一致) + let effectiveLastProcess = null; + for (let i = timelineResults.length - 1; i >= 0; i--) { + const process = timelineResults[i]; + if (process.estimatedEnd && + !process.estimatedEnd.includes('未找到') && + !process.estimatedEnd.includes('时效值为0') && + process.estimatedEnd !== 'Invalid Date') { + effectiveLastProcess = process; + break; + } + } + + if (effectiveLastProcess) { try { - expectedDeliveryDate = new Date(lastNode.estimatedEnd).getTime(); + const lastCompletionDate = new Date(effectiveLastProcess.estimatedEnd); + + // 计算动态缓冲期:基础缓冲期 - 节点总调整量,最小为0天 + const totalAdjustments = Object.values(timelineAdjustments).reduce((sum, adj) => sum + adj, 0); + const baseBuferDays = baseBufferDays; + const dynamicBufferDays = Math.max(0, baseBuferDays - totalAdjustments); + + // 加上动态缓冲期 + const deliveryDate = new Date(lastCompletionDate); + deliveryDate.setDate(deliveryDate.getDate() + dynamicBufferDays); + expectedDeliveryDate = deliveryDate.getTime(); } catch (error) { console.error('转换预计交付日期失败:', error); } } } - // 获取客户期望日期 + // 获取客户期望日期:批量模式优先使用传入的expectedDate let customerExpectedDate = null; - if (expectedDate) { + if (batchData?.expectedDate) { + try { + customerExpectedDate = new Date(batchData.expectedDate).getTime(); + } catch { /* 忽略转换错误,保持null */ } + } else if (expectedDate) { customerExpectedDate = expectedDate.getTime(); // 转换为时间戳 } @@ -1996,7 +2867,162 @@ export default function App() { } // 在创建Cell之前进行数据校验(移除冗余日志) - + + // ===== 构建完整快照(保持与流程数据表写入时的一致内容)并写入到货期记录表 ===== + const expectedDateTimestamp = (batchData?.expectedDate || expectedDate) + ? (batchData?.expectedDate || expectedDate).getTime() + : null; + const expectedDateString = (batchData?.expectedDate || expectedDate) + ? format((batchData?.expectedDate || expectedDate), DATE_FORMATS.STORAGE_FORMAT) + : null; + const currentStartTime = batchData?.startTime || startTime; + const currentSelectedLabels = batchData?.labels || selectedLabels; + + // 与快照字段保持相同的命名 + const styleText = style || ''; + const colorText = color || ''; + + const totalAdjustments = Object.values(timelineAdjustments).reduce((sum, adj) => sum + adj, 0); + const baseBuferDays = baseBufferDays; + const dynamicBufferDays = Math.max(0, baseBuferDays - totalAdjustments); + + // 检查是否达到最终限制 + let hasReachedFinalLimit = false; + const currentExpectedDate = batchData?.expectedDate || expectedDate; + if (dynamicBufferDays === 0 && currentExpectedDate && timelineResults.length > 0) { + const lastNode = timelineResults[timelineResults.length - 1]; + if (lastNode && lastNode.estimatedEnd && !lastNode.estimatedEnd.includes('未找到')) { + try { + const lastCompletionDate = new Date(lastNode.estimatedEnd); + const daysToExpected = Math.ceil((currentExpectedDate.getTime() - lastCompletionDate.getTime()) / (1000 * 60 * 60 * 24)); + hasReachedFinalLimit = daysToExpected <= 0; + } catch (error) { + console.warn('计算最终限制状态失败:', error); + } + } + } + + const globalSnapshot = { + version: versionNumber, + foreignId, + styleText, + colorText, + text2, + mode, + selectedLabels: currentSelectedLabels, + expectedDateTimestamp, + expectedDateString, + startTimestamp: currentStartTime ? currentStartTime.getTime() : undefined, + startString: currentStartTime ? formatDate(currentStartTime, 'STORAGE_FORMAT') : undefined, + timelineAdjustments, + generationModeState: { + currentForeignId, + currentStyleText, + currentColorText, + currentText2, + currentVersionNumber: versionNumber, + recordDetails: recordDetails || [], + hasSelectedLabels: Object.values(selectedLabels).some(value => { + return Array.isArray(value) ? value.length > 0 : Boolean(value); + }), + labelSelectionComplete: Object.keys(selectedLabels).length > 0 + }, + bufferManagement: { + baseDays: baseBuferDays, + totalAdjustments, + dynamicBufferDays, + hasReachedFinalLimit + }, + chainAdjustmentSystem: { + enabled: true, + lastCalculationTime: new Date().getTime(), + adjustmentHistory: timelineAdjustments + }, + timelineCalculationState: { + calculationTimestamp: new Date().getTime(), + totalNodes: timelineResults.length, + hasValidResults: timelineResults.length > 0, + lastCalculationMode: mode + }, + totalNodes: timelineResults.length, + isGlobalSnapshot: true + }; + + // 选择用于快照的最后一个有效节点(与流程写入时的节点快照结构一致) + let selectedIndex = -1; + for (let i = timelineResults.length - 1; i >= 0; i--) { + const r = timelineResults[i]; + if (r.estimatedEnd && !r.estimatedEnd.includes('未找到') && r.estimatedEnd.trim() !== '') { + selectedIndex = i; + break; + } + } + if (selectedIndex === -1) selectedIndex = 0; // 无有效结束时间时兜底为第一个 + + const selectedResult = timelineResults[selectedIndex]; + let nodeStartTs = null as number | null; + let nodeEndTs = null as number | null; + if (selectedResult.estimatedStart && !selectedResult.estimatedStart.includes('未找到')) { + try { nodeStartTs = new Date(selectedResult.estimatedStart).getTime(); } catch {} + } + if (selectedResult.estimatedEnd && !selectedResult.estimatedEnd.includes('未找到')) { + try { nodeEndTs = new Date(selectedResult.estimatedEnd).getTime(); } catch {} + } + + const nodeSnapshot = { + processOrder: selectedResult.processOrder, + nodeName: selectedResult.nodeName, + matchedLabels: selectedResult.matchedLabels, + timelineValue: selectedResult.timelineValue, + estimatedStart: selectedResult.estimatedStart, + estimatedEnd: selectedResult.estimatedEnd, + estimatedStartTimestamp: nodeStartTs, + estimatedEndTimestamp: nodeEndTs, + timelineRecordId: selectedResult.timelineRecordId, + allMatchedRecords: selectedResult.allMatchedRecords, + isAccumulated: selectedResult.isAccumulated, + weekendDaysConfig: selectedResult.weekendDaysConfig, + excludedDates: selectedResult.excludedDates, + actualExcludedDates: selectedResult.actualExcludedDates, + actualExcludedDatesCount: selectedResult.actualExcludedDatesCount, + calculationMethod: selectedResult.calculationMethod, + ruleDescription: selectedResult.ruleDescription, + skippedWeekends: selectedResult.skippedWeekends, + actualDays: selectedResult.actualDays, + adjustedTimelineValue: selectedResult.adjustedTimelineValue, + adjustment: selectedResult.adjustment, + adjustmentDescription: selectedResult.adjustmentDescription, + startDateRule: selectedResult.startDateRule, + dateAdjustmentRule: selectedResult.dateAdjustmentRule, + nodeCalculationState: { + hasValidTimelineValue: selectedResult.timelineValue > 0, + hasValidStartTime: Boolean(nodeStartTs), + hasValidEndTime: Boolean(nodeEndTs), + calculationTimestamp: new Date().getTime(), + originalTimelineValue: selectedResult.timelineValue, + finalAdjustedValue: selectedResult.adjustedTimelineValue || selectedResult.timelineValue + }, + chainAdjustmentNode: { + nodeIndex: selectedIndex, + hasAdjustment: selectedResult.adjustment !== undefined && selectedResult.adjustment !== 0, + adjustmentValue: selectedResult.adjustment || 0, + isChainSource: selectedResult.adjustment !== undefined && selectedResult.adjustment !== 0, + affectedByChain: selectedIndex > 0 + }, + isNodeSnapshot: true + }; + + const completeSnapshot = { + ...globalSnapshot, + ...nodeSnapshot, + timelineResults: timelineResults, + currentNodeIndex: selectedIndex, + currentNodeName: selectedResult.nodeName, + isCompleteSnapshot: true, + snapshotType: 'complete' + }; + const snapshotJson = JSON.stringify(completeSnapshot); + // 使用createCell方法创建各个字段的Cell const foreignIdCell = await foreignIdField.createCell(foreignId); const labelsCell = selectedLabelValues.length > 0 ? await labelsField.createCell(selectedLabelValues) : null; @@ -2004,8 +3030,19 @@ export default function App() { const colorCell = await colorField.createCell(color); const text2Cell = await text2Field.createCell(text2); const createTimeCell = await createTimeField.createCell(currentTime); - const startTimestamp = startTime ? startTime.getTime() : currentTime; + + // 调试日志:检查startTime参数 + console.log('批量模式 - startTime参数:', startTime); + console.log('批量模式 - startTime类型:', typeof startTime); + console.log('批量模式 - currentTime:', currentTime, '对应日期:', new Date(currentTime).toLocaleString()); + + const startTimestamp = batchData?.startTime + ? new Date(batchData.startTime).getTime() + : (startTime ? startTime.getTime() : currentTime); + console.log('批量模式 - 最终使用的startTimestamp:', startTimestamp, '对应日期:', new Date(startTimestamp).toLocaleString()); + const startTimeCell = await startTimeField.createCell(startTimestamp); + const snapshotCell = await snapshotField.createCell(snapshotJson); const expectedDateCell = expectedDeliveryDate ? await expectedDateField.createCell(expectedDeliveryDate) : null; const customerExpectedDateCell = customerExpectedDate ? await customerExpectedDateField.createCell(customerExpectedDate) : null; // 对于关联记录字段,确保传入的是记录ID数组 @@ -2017,7 +3054,7 @@ export default function App() { const versionCell = await versionField.createCell(versionNumber); // 组合所有Cell到一个记录中 - const recordCells = [foreignIdCell, styleCell, colorCell, text2Cell, createTimeCell, startTimeCell, versionCell]; + const recordCells = [foreignIdCell, styleCell, colorCell, text2Cell, createTimeCell, startTimeCell, versionCell, snapshotCell]; // 只有当数据存在时才添加对应的Cell if (labelsCell) recordCells.push(labelsCell); @@ -2048,50 +3085,55 @@ export default function App() { }; // 写入流程数据表的函数 - const writeToProcessDataTable = async (timelineResults: any[]): Promise => { + const writeToProcessDataTable = async (timelineResults: any[], batchData?: { + selectedRecords: string[], + recordDetails: any[], + labels?: Record, + expectedDate?: any, + startTime?: any + }): Promise => { try { - console.log('开始写入流程数据表...'); - console.log('timelineResults:', timelineResults); + console.log('=== 开始写入流程数据表 ==='); + console.log('当前模式:', mode); + console.log('timelineResults数量:', timelineResults?.length || 0); + console.log('timelineResults详情:', timelineResults); // 获取流程数据表和流程配置表 + console.log('正在获取流程数据表和流程配置表...'); const processDataTable = await bitable.base.getTable(PROCESS_DATA_TABLE_ID); const processConfigTable = await bitable.base.getTable(PROCESS_CONFIG_TABLE_ID); + console.log('成功获取数据表'); // 获取所有需要的字段 + console.log('正在获取所有必需字段...'); const foreignIdField = await processDataTable.getField(FOREIGN_ID_FIELD_ID); const processNameField = await processDataTable.getField(PROCESS_NAME_FIELD_ID); const processOrderField = await processDataTable.getField(PROCESS_ORDER_FIELD_ID_DATA); const startDateField = await processDataTable.getField(ESTIMATED_START_DATE_FIELD_ID); const endDateField = await processDataTable.getField(ESTIMATED_END_DATE_FIELD_ID); - const snapshotField = await processDataTable.getField(PROCESS_SNAPSHOT_JSON_FIELD_ID); const versionField = await processDataTable.getField(PROCESS_VERSION_FIELD_ID); const timelinessField = await processDataTable.getField(PROCESS_TIMELINESS_FIELD_ID); + console.log('成功获取所有字段'); - // 获取foreign_id - 修改这部分逻辑 + // 获取foreign_id - 支持批量模式直接传递数据 + console.log('=== 开始获取foreign_id ==='); let foreignId = null; - console.log('selectedRecords:', selectedRecords); - console.log('recordDetails:', recordDetails); - if (selectedRecords.length > 0 && recordDetails.length > 0) { + // 使用传递的数据或全局状态 + const currentSelectedRecords = batchData?.selectedRecords || selectedRecords; + const currentRecordDetails = batchData?.recordDetails || recordDetails; + + console.log('selectedRecords数量:', currentSelectedRecords?.length || 0); + console.log('recordDetails数量:', currentRecordDetails?.length || 0); + console.log('selectedRecords:', currentSelectedRecords); + console.log('recordDetails:', currentRecordDetails); + + if (currentSelectedRecords.length > 0 && currentRecordDetails.length > 0) { // 从第一个选择的记录的详情中获取fldpvBfeC0字段的值 - const firstRecord = recordDetails[0]; + const firstRecord = currentRecordDetails[0]; if (firstRecord && firstRecord.fields && firstRecord.fields['fldpvBfeC0']) { const fieldValue = firstRecord.fields['fldpvBfeC0']; - - // 处理数组格式的字段值 - if (Array.isArray(fieldValue) && fieldValue.length > 0) { - const firstItem = fieldValue[0]; - if (firstItem && firstItem.text) { - foreignId = firstItem.text; - } else if (typeof firstItem === 'string') { - foreignId = firstItem; - } - } else if (typeof fieldValue === 'string') { - foreignId = fieldValue; - } else if (fieldValue && fieldValue.text) { - foreignId = fieldValue.text; - } - + foreignId = extractText(fieldValue); console.log('从fldpvBfeC0字段获取到的foreign_id:', foreignId); } else { console.warn('未在记录详情中找到fldpvBfeC0字段'); @@ -2117,7 +3159,10 @@ export default function App() { } // 先删除该foreign_id的所有现有记录 + console.log('=== 开始删除现有记录 ==='); + console.log('使用的foreign_id:', foreignId); try { + console.log('正在查询现有记录...'); const existingRecords = await processDataTable.getRecords({ pageSize: 5000, filter: { @@ -2130,10 +3175,13 @@ export default function App() { } }); + console.log('查询到现有记录数量:', existingRecords.records?.length || 0); + if (existingRecords.records && existingRecords.records.length > 0) { const recordIdsToDelete = existingRecords.records.map(record => record.id); + console.log('准备删除的记录ID:', recordIdsToDelete); await processDataTable.deleteRecords(recordIdsToDelete); - console.log(`已删除 ${recordIdsToDelete.length} 条现有记录`); + console.log(`成功删除 ${recordIdsToDelete.length} 条现有记录`); if (bitable.ui.showToast) { await bitable.ui.showToast({ @@ -2141,9 +3189,11 @@ export default function App() { message: `已删除 ${recordIdsToDelete.length} 条现有流程数据` }); } + } else { + console.log('没有找到需要删除的现有记录'); } } catch (deleteError) { - console.warn('删除现有记录时出错:', deleteError); + console.error('删除现有记录时出错:', deleteError); // 继续执行,不中断流程 } @@ -2174,82 +3224,12 @@ export default function App() { console.warn('计算版本号失败:', e); } - const expectedDateTimestamp = expectedDate ? expectedDate.getTime() : null; - const expectedDateString = expectedDate ? format(expectedDate, DATE_FORMATS.STORAGE_FORMAT) : null; - - // 从记录详情提取款式和颜色,用于快照存档(回填写入来源) - let styleText = ''; - let colorText = ''; - if (recordDetails.length > 0) { - const firstRecord = recordDetails[0]; - const styleFieldValue = firstRecord.fields['fld6Uw95kt']; - if (Array.isArray(styleFieldValue) && styleFieldValue.length > 0) { - const firstItem = styleFieldValue[0]; - styleText = typeof firstItem === 'string' ? firstItem : (firstItem?.text || firstItem?.name || ''); - } else if (typeof styleFieldValue === 'string') { - styleText = styleFieldValue; - } else if (styleFieldValue && typeof styleFieldValue === 'object') { - styleText = (styleFieldValue.text || styleFieldValue.name || ''); - } - - const colorFieldValue = firstRecord.fields['flde85ni4O']; - if (Array.isArray(colorFieldValue) && colorFieldValue.length > 0) { - const firstItemC = colorFieldValue[0]; - colorText = typeof firstItemC === 'string' ? firstItemC : (firstItemC?.text || firstItemC?.name || ''); - } else if (typeof colorFieldValue === 'string') { - colorText = colorFieldValue; - } else if (colorFieldValue && typeof colorFieldValue === 'object') { - colorText = (colorFieldValue.text || colorFieldValue.name || ''); - } - } - // 快照回填的备用值 - if (!styleText && currentStyleText) styleText = currentStyleText; - if (!colorText && currentColorText) colorText = currentColorText; - - // 获取text2用于快照存档:调整模式使用快照数据,生成模式保持为空 - let text2 = ''; - if (mode === 'adjust') { - // 调整模式:使用快照回填的数据 - text2 = currentText2 || ''; - if (!text2 && recordDetails.length > 0) { - const firstRecord = recordDetails[0]; - const text2FieldValue = firstRecord.fields['fldG6LZnmU']; - if (Array.isArray(text2FieldValue) && text2FieldValue.length > 0) { - const firstItem = text2FieldValue[0]; - text2 = typeof firstItem === 'string' ? firstItem : (firstItem?.text || firstItem?.name || ''); - } else if (typeof text2FieldValue === 'string') { - text2 = text2FieldValue; - } else if (text2FieldValue && typeof text2FieldValue === 'object') { - text2 = (text2FieldValue.text || text2FieldValue.name || ''); - } - } - } else { - // 生成模式:text2字段保持为空,不使用缓存数据 - text2 = ''; - console.log('生成模式:快照中text2字段保持为空'); - } - - const pageSnapshot = { - version: versionNumber, - foreignId, - styleText, - colorText, - text2, - mode, - selectedLabels, - expectedDateTimestamp, - expectedDateString, - startTimestamp: startTime ? startTime.getTime() : undefined, - startString: startTime ? formatDate(startTime, 'STORAGE_FORMAT') : undefined, - timelineAdjustments, - timelineResults - }; - const snapshotJson = JSON.stringify(pageSnapshot); // 使用createCell方法准备要写入的记录数据 const recordCellsToAdd = []; - for (const result of timelineResults) { + for (let index = 0; index < timelineResults.length; index++) { + const result = timelineResults[index]; console.log('处理节点数据:', result); // 检查是否有有效的预计完成时间(只检查结束时间) @@ -2285,6 +3265,7 @@ export default function App() { } } + // 使用createCell方法创建每个字段的Cell const foreignIdCell = await foreignIdField.createCell(foreignId); // 直接使用节点名称创建单元格 @@ -2294,7 +3275,6 @@ export default function App() { const endDateCell = endTimestamp ? await endDateField.createCell(endTimestamp) : null; // 组合所有Cell到一个记录中 - const snapshotCell = await snapshotField.createCell(snapshotJson); const versionCell = await versionField.createCell(versionNumber); const timelinessCell = (typeof result.timelineValue === 'number') ? await timelinessField.createCell(result.timelineValue) @@ -2303,7 +3283,6 @@ export default function App() { foreignIdCell, processNameCell, processOrderCell, - snapshotCell, versionCell ]; @@ -2373,7 +3352,142 @@ export default function App() { }; // 批量处理函数 + // 批量模式:模拟生成模式的操作(简化版本) + const simulateGenerationModeForBatch = async ( + recordId: string, + extractedLabels: {[key: string]: string | string[]}, + extractedStartTime: Date | null, + extractedExpectedDate: Date | null, + style: any, + color: any, + foreignId: any, + text2: any + ) => { + console.log('=== 批量模式:模拟生成模式操作 ==='); + console.log('recordId:', recordId); + console.log('extractedLabels:', extractedLabels); + console.log('extractedStartTime:', extractedStartTime); + console.log('extractedExpectedDate:', extractedExpectedDate); + + // 1. 构造虚拟的 selectedRecords 和 recordDetails + const virtualSelectedRecords = [recordId]; + const virtualRecordDetails = [{ + id: recordId, + fields: { + 'fldpvBfeC0': foreignId, // 外键ID字段 + 'fld6Uw95kt': style, // 样式字段 + 'flde85ni4O': color, // 颜色字段 + 'fldG6LZnmU': text2 // 文本字段2 + } + }]; + + try { + // 不再设置全局状态,直接通过overrideData传递数据 + console.log('批量模式:直接调用计算逻辑,传递起始时间:', extractedStartTime); + + // 3. 直接调用时效计算,通过overrideData传递所有必要数据 + const calculatedResults = await handleCalculateTimeline(true, { + selectedRecords: virtualSelectedRecords, + recordDetails: virtualRecordDetails, + selectedLabels: extractedLabels, + expectedDate: extractedExpectedDate, + startTime: extractedStartTime // 直接传递起始时间,避免异步状态更新问题 + }, false); // showUI: false,批量模式下不显示时间轴UI + + console.log('批量模式:时效计算完成,结果:', calculatedResults); + + // 批量模式:更新UI显示最后处理记录的时间设置 + console.log('批量模式:更新UI显示时间设置'); + setExpectedDate(extractedExpectedDate); + setStartTime(extractedStartTime); + + // 批量模式:自动保存逻辑 + console.log('批量模式:开始自动保存流程'); + + // 检查计算结果 + if (!calculatedResults || calculatedResults.length === 0) { + console.error('批量模式:时效计算结果为空,无法保存'); + throw new Error('时效计算结果为空,请检查流程配置和标签匹配'); + } + + // 自动触发保存 + console.log('批量模式:开始自动保存数据'); + await saveTimelineData({ + timelineResults: calculatedResults, // 使用直接返回的结果 + selectedRecords: virtualSelectedRecords, + recordDetails: virtualRecordDetails, + timelineAdjustments: {}, // 批量模式下没有手动调整 + labels: extractedLabels, + expectedDate: extractedExpectedDate, + startTime: extractedStartTime + }); + + console.log('批量模式:自动保存完成'); + + } catch (error) { + console.error('批量模式:处理失败', error); + throw error; // 重新抛出错误让上层处理 + } + }; + + // 保存时效数据的核心逻辑(从确定并保存按钮提取) + const saveTimelineData = async (batchData?: { + timelineResults?: any[], + selectedRecords?: string[], + recordDetails?: any[], + timelineAdjustments?: any, + labels?: Record, // 添加标签汇总 + expectedDate?: any, // 添加期望日期 + startTime?: any // 添加起始时间 + }) => { + try { + // 使用传入的数据或全局状态 + const currentTimelineResults = batchData?.timelineResults || timelineResults; + const currentTimelineAdjustments = batchData?.timelineAdjustments || timelineAdjustments; + + if (currentTimelineResults.length > 0) { + // 构造批量数据参数 + const batchDataForWriting = batchData ? { + selectedRecords: batchData.selectedRecords || [], + recordDetails: batchData.recordDetails || [], + labels: batchData.labels, // 传递标签汇总 + expectedDate: batchData.expectedDate, // 传递期望日期 + startTime: batchData.startTime // 传递起始时间 + } : undefined; + + // 写入流程数据表 + const processRecordIds = await writeToProcessDataTable(currentTimelineResults, batchDataForWriting); + + // 写入货期记录表 + await writeToDeliveryRecordTable(currentTimelineResults, processRecordIds, currentTimelineAdjustments, batchDataForWriting); + + if (bitable.ui.showToast) { + await bitable.ui.showToast({ + toastType: 'success', + message: Object.keys(currentTimelineAdjustments).length > 0 + ? '已保存调整后的时效数据到流程数据表和货期记录表' + : '已保存计算的时效数据到流程数据表和货期记录表' + }); + } + + return true; // 保存成功 + } + return false; // 没有数据需要保存 + } catch (error) { + console.error('保存数据时出错:', error); + if (bitable.ui.showToast) { + await bitable.ui.showToast({ + toastType: 'error', + message: '保存数据失败,请重试' + }); + } + throw error; // 重新抛出错误 + } + }; + const handleBatchProcess = async (rowCount: number) => { + // 切换到批量处理流程时重置全局变量 + resetGlobalState(); // 检查是否选择了表 if (!selectedBatchTableId) { if (bitable.ui.showToast) { @@ -2467,7 +3581,7 @@ export default function App() { } else if (fieldMeta.id === 'fldkKZecSv') { // foreign_id字段映射 fieldMapping['foreign_id'] = fieldMeta.id; - } else if (fieldMeta.name === '文本 2' || fieldMeta.name === '文本2' || fieldMeta.id === 'fldlaYgpYO') { + } else if (fieldMeta.name === 'oms看板record_id' || fieldMeta.id === 'fldlaYgpYO') { // 文本2字段映射 - 支持有空格和无空格的字段名称,以及直接ID匹配 fieldMapping['文本2'] = fieldMeta.id; } @@ -2485,6 +3599,10 @@ export default function App() { let errorCount = 0; const errors: string[] = []; + // 跟踪最后成功处理的记录的时间信息 + let lastSuccessfulStartTime: Date | null = null; + let lastSuccessfulExpectedDate: Date | null = null; + // 逐条处理记录 for (let i = 0; i < actualCount; i++) { const record = records[i]; @@ -2525,18 +3643,50 @@ export default function App() { }).filter(v => v); if (values.length > 0) { - extractedLabels[labelKey] = values; + // 处理可能包含逗号分隔的复合标签值,并与生成模式保持一致 + const expandedValues: string[] = []; + values.forEach(value => { + if (typeof value === 'string' && value.includes(',')) { + // 拆分逗号分隔的值 + const splitValues = value.split(',').map(v => v.trim()).filter(v => v !== ''); + expandedValues.push(...splitValues); + } else { + expandedValues.push(value); + } + }); + + // 与生成模式保持一致:单值返回字符串,多值返回数组 + // 特殊处理:标签7始终返回数组结构 + if (labelKey === '标签7') { + extractedLabels[labelKey] = expandedValues; + } else { + extractedLabels[labelKey] = expandedValues.length === 1 ? expandedValues[0] : expandedValues; + } } } else if (typeof fieldValue === 'string') { // 单行文本或选项ID + let finalValue: string; if (optionMapping[fieldId] && optionMapping[fieldId][fieldValue]) { - extractedLabels[labelKey] = optionMapping[fieldId][fieldValue]; + finalValue = optionMapping[fieldId][fieldValue]; } else { - extractedLabels[labelKey] = fieldValue; + finalValue = fieldValue; + } + + // 特殊处理:标签7始终返回数组结构 + if (labelKey === '标签7') { + extractedLabels[labelKey] = [finalValue]; + } else { + extractedLabels[labelKey] = finalValue; } } else if (fieldValue && fieldValue.text) { // 富文本等 - extractedLabels[labelKey] = fieldValue.text; + const textValue = fieldValue.text; + // 特殊处理:标签7始终返回数组结构 + if (labelKey === '标签7') { + extractedLabels[labelKey] = [textValue]; + } else { + extractedLabels[labelKey] = textValue; + } } else if (fieldValue && typeof fieldValue === 'object') { // 处理公式字段和其他复杂对象 if (fieldValue.value !== undefined) { @@ -2550,13 +3700,30 @@ export default function App() { }).filter(v => v); if (values.length > 0) { - extractedLabels[labelKey] = values; + // 与生成模式保持一致:单值返回字符串,多值返回数组 + // 特殊处理:标签7始终返回数组结构 + if (labelKey === '标签7') { + extractedLabels[labelKey] = values; + } else { + extractedLabels[labelKey] = values.length === 1 ? values[0] : values; + } } } else if (typeof fieldValue.value === 'string') { - extractedLabels[labelKey] = fieldValue.value; + // 特殊处理:标签7始终返回数组结构 + if (labelKey === '标签7') { + extractedLabels[labelKey] = [fieldValue.value]; + } else { + extractedLabels[labelKey] = fieldValue.value; + } } else if (fieldValue.value && fieldValue.value.name) { // 单选字段的选项对象 - extractedLabels[labelKey] = fieldValue.value.name; + const nameValue = fieldValue.value.name; + // 特殊处理:标签7始终返回数组结构 + if (labelKey === '标签7') { + extractedLabels[labelKey] = [nameValue]; + } else { + extractedLabels[labelKey] = nameValue; + } } } else if (fieldValue.displayValue !== undefined) { // 有些字段使用displayValue @@ -2569,17 +3736,40 @@ export default function App() { }).filter(v => v); if (values.length > 0) { - extractedLabels[labelKey] = values; + // 与生成模式保持一致:单值返回字符串,多值返回数组 + // 特殊处理:标签7始终返回数组结构 + if (labelKey === '标签7') { + extractedLabels[labelKey] = values; + } else { + extractedLabels[labelKey] = values.length === 1 ? values[0] : values; + } } } else if (typeof fieldValue.displayValue === 'string') { - extractedLabels[labelKey] = fieldValue.displayValue; + // 特殊处理:标签7始终返回数组结构 + if (labelKey === '标签7') { + extractedLabels[labelKey] = [fieldValue.displayValue]; + } else { + extractedLabels[labelKey] = fieldValue.displayValue; + } } else if (fieldValue.displayValue && fieldValue.displayValue.name) { // 单选字段的选项对象 - extractedLabels[labelKey] = fieldValue.displayValue.name; + const nameValue = fieldValue.displayValue.name; + // 特殊处理:标签7始终返回数组结构 + if (labelKey === '标签7') { + extractedLabels[labelKey] = [nameValue]; + } else { + extractedLabels[labelKey] = nameValue; + } } } else if (fieldValue.name) { // 直接的选项对象,有name属性 - extractedLabels[labelKey] = fieldValue.name; + const nameValue = fieldValue.name; + // 特殊处理:标签7始终返回数组结构 + if (labelKey === '标签7') { + extractedLabels[labelKey] = [nameValue]; + } else { + extractedLabels[labelKey] = nameValue; + } } } } @@ -2621,6 +3811,12 @@ export default function App() { } } + // 统一的字段提取函数(复用生成模式的逻辑) + // 将提取的文本转换为富文本格式 + const createRichTextFormat = (text: string) => { + return text ? [{ type: "text", text: text }] : null; + }; + // 提取款式、颜色、foreign_id字段 let extractedStyle: any = null; let extractedColor: any = null; @@ -2629,58 +3825,22 @@ export default function App() { // 提取款式字段 const styleFieldId = fieldMapping['款式']; if (styleFieldId && recordFields[styleFieldId]) { - const styleValue = recordFields[styleFieldId]; - if (Array.isArray(styleValue) && styleValue.length > 0) { - // 处理数组格式,转换为指定的text格式 - const firstValue = styleValue[0]; - if (typeof firstValue === 'string') { - extractedStyle = [{ type: "text", text: firstValue }]; - } else if (firstValue && firstValue.text) { - extractedStyle = [{ type: "text", text: firstValue.text }]; - } - } else if (typeof styleValue === 'string') { - extractedStyle = [{ type: "text", text: styleValue }]; - } else if (styleValue && styleValue.text) { - extractedStyle = [{ type: "text", text: styleValue.text }]; - } + const styleText = extractText(recordFields[styleFieldId]); + extractedStyle = createRichTextFormat(styleText); } // 提取颜色字段 const colorFieldId = fieldMapping['颜色']; if (colorFieldId && recordFields[colorFieldId]) { - const colorValue = recordFields[colorFieldId]; - if (Array.isArray(colorValue) && colorValue.length > 0) { - // 处理数组格式,转换为指定的text格式 - const firstValue = colorValue[0]; - if (typeof firstValue === 'string') { - extractedColor = [{ type: "text", text: firstValue }]; - } else if (firstValue && firstValue.text) { - extractedColor = [{ type: "text", text: firstValue.text }]; - } - } else if (typeof colorValue === 'string') { - extractedColor = [{ type: "text", text: colorValue }]; - } else if (colorValue && colorValue.text) { - extractedColor = [{ type: "text", text: colorValue.text }]; - } + const colorText = extractText(recordFields[colorFieldId]); + extractedColor = createRichTextFormat(colorText); } // 提取foreign_id字段 const foreignIdFieldId = fieldMapping['foreign_id']; if (foreignIdFieldId && recordFields[foreignIdFieldId]) { - const foreignIdValue = recordFields[foreignIdFieldId]; - if (Array.isArray(foreignIdValue) && foreignIdValue.length > 0) { - // 处理数组格式,转换为指定的text格式 - const firstValue = foreignIdValue[0]; - if (typeof firstValue === 'string') { - extractedForeignId = [{ type: "text", text: firstValue }]; - } else if (firstValue && firstValue.text) { - extractedForeignId = [{ type: "text", text: firstValue.text }]; - } - } else if (typeof foreignIdValue === 'string') { - extractedForeignId = [{ type: "text", text: foreignIdValue }]; - } else if (foreignIdValue && foreignIdValue.text) { - extractedForeignId = [{ type: "text", text: foreignIdValue.text }]; - } + const foreignIdText = extractText(recordFields[foreignIdFieldId]); + extractedForeignId = createRichTextFormat(foreignIdText); } // 提取文本2字段 @@ -2691,28 +3851,7 @@ export default function App() { console.log('fldlaYgpYO字段值:', recordFields['fldlaYgpYO']); if (text2FieldId && recordFields[text2FieldId]) { - const text2Value = recordFields[text2FieldId]; - console.log('原始text2Value:', text2Value); - - if (Array.isArray(text2Value) && text2Value.length > 0) { - // 如果已经是正确的格式,直接使用 - if (text2Value[0] && text2Value[0].type === 'text' && text2Value[0].text) { - extractedText2 = text2Value[0].text; - } else { - // 处理其他数组格式 - const firstValue = text2Value[0]; - if (typeof firstValue === 'string') { - extractedText2 = firstValue; - } else if (firstValue && firstValue.text) { - extractedText2 = firstValue.text; - } - } - } else if (typeof text2Value === 'string') { - extractedText2 = text2Value; - } else if (text2Value && text2Value.text) { - extractedText2 = text2Value.text; - } - + extractedText2 = extractText(recordFields[text2FieldId]); console.log('提取后的extractedText2:', extractedText2); } else { console.log('文本2字段未找到或无数据:', { @@ -2743,11 +3882,24 @@ export default function App() { continue; } - // 使用提取的数据进行时效计算 - await calculateTimelineForBatch(extractedLabels, extractedStartTime, extractedExpectedDate, record.id, extractedStyle, extractedColor, extractedForeignId, extractedText2); + // 使用模拟生成模式的方式进行时效计算和写入 + await simulateGenerationModeForBatch( + recordId, + extractedLabels, + extractedStartTime, + extractedExpectedDate, + extractedStyle, + extractedColor, + extractedForeignId, + extractedText2 + ); successCount++; + // 记录最后成功处理的记录的时间信息 + lastSuccessfulStartTime = extractedStartTime; + lastSuccessfulExpectedDate = extractedExpectedDate; + // 添加延迟避免API限制 if (i < actualCount - 1) { await new Promise(resolve => setTimeout(resolve, 100)); @@ -2768,6 +3920,16 @@ export default function App() { }); } + // 如果有成功处理的记录,更新UI时间显示为最后一条成功记录的时间 + if (successCount > 0 && lastSuccessfulStartTime && lastSuccessfulExpectedDate) { + console.log('批量处理完成:更新UI时间显示'); + console.log('最后成功记录的起始时间:', lastSuccessfulStartTime); + console.log('最后成功记录的期望日期:', lastSuccessfulExpectedDate); + + setStartTime(lastSuccessfulStartTime); + setExpectedDate(lastSuccessfulExpectedDate); + } + if (errors.length > 0) { console.error('批量处理错误详情:', errors); } @@ -2798,6 +3960,21 @@ export default function App() { text2?: any ) => { try { + // 批量模式:输出当前数据结构,便于与生成模式对比 + try { + console.group('=== 批量模式:计算时效 - 当前数据结构 ==='); + console.log('sourceRecordId:', sourceRecordId); + console.log('labels:', labels); + console.log('startTime:', startTime); + console.log('expectedDate:', expectedDate); + console.log('style:', style); + console.log('color:', color); + console.log('foreignId:', foreignId); + console.log('text2:', text2); + console.groupEnd(); + } catch (logErr) { + console.warn('批量模式结构化日志输出失败:', logErr); + } // 1. 获取流程配置表数据 const processConfigTable = await bitable.base.getTable(PROCESS_CONFIG_TABLE_ID); const processConfigRecords = await processConfigTable.getRecords({ @@ -2808,19 +3985,35 @@ export default function App() { // 2. 构建业务选择的标签值集合 const businessLabelValues = new Set(); + console.log('批量模式 - 开始处理标签数据:', labels); + for (const [labelKey, selectedValue] of Object.entries(labels)) { + console.log(`批量模式 - 处理标签 ${labelKey}:`, selectedValue, '类型:', typeof selectedValue); + if (selectedValue) { const values = Array.isArray(selectedValue) ? selectedValue : [selectedValue]; - values.forEach(value => { + values.forEach((value, index) => { + console.log(`批量模式 - ${labelKey} 值${index}:`, value, '类型:', typeof value); + if (typeof value === 'string' && value.trim()) { - businessLabelValues.add(value.trim()); + // 处理可能包含逗号分隔的复合标签值 + const splitValues = value.split(',').map(v => v.trim()).filter(v => v !== ''); + console.log(`批量模式 - ${labelKey} 分割后的值:`, splitValues); + + splitValues.forEach(splitValue => { + businessLabelValues.add(splitValue); + console.log(`批量模式 - 添加到业务标签集合: "${splitValue}"`); + }); } }); } } + console.log('批量模式 - 最终业务标签值集合:', Array.from(businessLabelValues)); + // 3. 匹配流程节点 const matchedProcessNodes: any[] = []; + console.log('批量模式 - 开始匹配流程节点,总节点数:', processNodes.length); for (const processNode of processNodes) { const processFields = processNode.fields; @@ -2842,24 +4035,55 @@ export default function App() { if (!nodeNameText) continue; - // 处理流程标签匹配 - let isMatched = true; - const matchedLabels: string[] = []; - + // 处理流程标签数据,与生成模式保持一致 + const processLabelTexts: string[] = []; if (processLabels && Array.isArray(processLabels)) { - // 改为OR逻辑:只要有一个标签匹配就认为节点匹配 - isMatched = false; - for (const labelItem of processLabels) { - const labelText = typeof labelItem === 'string' ? labelItem : (labelItem?.text || ''); - - if (labelText && businessLabelValues.has(labelText.trim())) { - matchedLabels.push(labelText.trim()); - isMatched = true; // 只要有一个匹配就设为true + const labelText = extractText(labelItem); + if (labelText && labelText.trim()) { + processLabelTexts.push(labelText.trim()); } } } + // 与生成模式保持一致:如果流程标签为空,跳过该节点 + if (processLabelTexts.length === 0) { + console.log(`批量模式 - 流程节点 "${nodeNameText}" 标签为空,跳过`); + continue; + } + + console.log(`批量模式 - 检查流程节点 "${nodeNameText}":`, { + 流程标签: processLabelTexts, + 流程顺序: processOrder + }); + + // 处理流程标签匹配 + let isMatched = false; + const matchedLabels: string[] = []; + + // 改为OR逻辑:只要有一个标签匹配就认为节点匹配 + for (const processLabelText of processLabelTexts) { + console.log(`批量模式 - 检查流程标签: "${processLabelText}"`); + + // 对流程标签也进行分割处理,以支持复合标签值 + const processLabelValues = processLabelText.split(',').map(v => v.trim()).filter(v => v !== ''); + console.log(`批量模式 - 流程标签分割后: [${processLabelValues.join(', ')}]`); + + // 检查分割后的每个流程标签值是否在业务标签集合中 + for (const processLabelValue of processLabelValues) { + if (businessLabelValues.has(processLabelValue)) { + matchedLabels.push(processLabelValue); + isMatched = true; + console.log(`批量模式 - 流程节点 "${nodeNameText}" 匹配成功,匹配的标签: "${processLabelValue}"`); + break; // 找到一个匹配就足够了 + } else { + console.log(`批量模式 - 流程标签值 "${processLabelValue}" 未匹配`); + } + } + + if (isMatched) break; // 已经匹配,不需要继续检查其他标签 + } + if (isMatched) { matchedProcessNodes.push({ nodeName: nodeNameText, @@ -2870,6 +4094,9 @@ export default function App() { startDateRule, dateAdjustmentRule }); + console.log(`批量模式 - 添加匹配的流程节点: "${nodeNameText}"`); + } else { + console.log(`批量模式 - 流程节点 "${nodeNameText}" 未匹配`); } } @@ -2886,6 +4113,30 @@ export default function App() { pageSize: 5000 }); + // 获取时效表的标签字段映射(只需要获取一次) + const timelineFieldMetaList = await timelineTable.getFieldMetaList(); + const timelineLabelFields: {[key: string]: string} = {}; + + // 查找关系字段和计算方式字段 + let relationshipFieldId = ''; + let calculationMethodFieldId = ''; + + for (const fieldMeta of timelineFieldMetaList) { + const match = fieldMeta.name.match(/^标签([1-9]|10)$/); + if (match) { + const labelKey = `标签${match[1]}`; + timelineLabelFields[labelKey] = fieldMeta.id; + } + // 查找关系字段 + if (fieldMeta.name === '关系' || fieldMeta.id === 'fldaIDAhab') { + relationshipFieldId = fieldMeta.id; + } + // 查找时效计算方式字段 + if (fieldMeta.name === '时效计算方式' || fieldMeta.id === 'fldxfLZNUu') { + calculationMethodFieldId = fieldMeta.id; + } + } + // 构建时效数据索引 const timelineIndexByNode = new Map(); @@ -2922,43 +4173,95 @@ export default function App() { } } - // 5. 计算每个节点的时效 + // 5. 计算每个节点的时效(遵循生成模式的匹配逻辑) const results: any[] = []; let cumulativeStartTime = startTime || new Date(); for (const processNode of matchedProcessNodes) { + console.log(`批量模式 - 开始处理流程节点: "${processNode.nodeName}"`); + + // 根据流程节点名称,在时效表中查找匹配的记录(与生成模式一致) const normalizedProcessName = processNode.nodeName.trim().toLowerCase(); const candidateRecords = timelineIndexByNode.get(normalizedProcessName) || []; + console.log(`批量模式 - 节点 "${processNode.nodeName}" 找到 ${candidateRecords.length} 个候选时效记录`); + let timelineValue = null; let matchedTimelineRecord = null; - // 在候选记录中进行标签匹配 + // 收集所有匹配的候选记录(与生成模式保持一致) + const matchedCandidates: any[] = []; + + // 在候选记录中进行标签匹配,收集所有匹配的记录 for (const candidate of candidateRecords) { const { record: timelineRecord, fields: timelineFields } = candidate; - // 进行标签匹配逻辑(简化版) + // 进行完整的标签匹配逻辑(与生成模式保持一致) let isLabelsMatched = true; - // 这里可以添加更详细的标签匹配逻辑 - // 为了简化,我们假设找到节点名称匹配就使用第一个记录 + // 检查时效表中每个有值的标签是否都包含在业务标签中 + for (const [labelKey, timelineFieldId] of Object.entries(timelineLabelFields)) { + const timelineLabelValue = timelineFields[timelineFieldId]; + + // 处理时效表中的标签值 + let timelineLabelTexts: string[] = []; + + if (typeof timelineLabelValue === 'string' && timelineLabelValue.trim()) { + timelineLabelTexts = [timelineLabelValue.trim()]; + } else if (timelineLabelValue && timelineLabelValue.text && timelineLabelValue.text.trim()) { + timelineLabelTexts = [timelineLabelValue.text.trim()]; + } else if (Array.isArray(timelineLabelValue) && timelineLabelValue.length > 0) { + // 多选字段,获取所有值 + timelineLabelTexts = timelineLabelValue.map(item => { + if (typeof item === 'string') return item.trim(); + if (item && item.text) return item.text.trim(); + return ''; + }).filter(text => text); + } + + // 如果时效表中该标签有值,则检查该标签的所有值是否都包含在业务标签中 + if (timelineLabelTexts.length > 0) { + let allValuesMatched = true; + + for (const timelineText of timelineLabelTexts) { + if (!businessLabelValues.has(timelineText)) { + allValuesMatched = false; + console.log(`批量模式 - 时效表标签 ${labelKey} 的值 "${timelineText}" 不在业务选择的标签中`); + break; + } + } + + if (!allValuesMatched) { + console.log(`批量模式 - 时效表标签 ${labelKey} 的值 [${timelineLabelTexts.join(', ')}] 不完全包含在业务选择的标签中`); + isLabelsMatched = false; + break; + } else { + console.log(`批量模式 - 时效表标签 ${labelKey} 的值 [${timelineLabelTexts.join(', ')}] 完全匹配成功`); + } + } + // 如果时效表中该标签为空,则跳过检查(空标签不影响匹配) + } if (isLabelsMatched) { const timelineValueField = timelineFields[TIMELINE_FIELD_ID]; - const calculationMethodField = timelineFields[CALCULATION_METHOD_FIELD_ID]; + const calculationMethodField = timelineFields[calculationMethodFieldId]; + const relationshipField = timelineFields[relationshipFieldId]; if (timelineValueField !== null && timelineValueField !== undefined) { let candidateTimelineValue = 0; + // 解析时效值(与生成模式保持一致) if (typeof timelineValueField === 'number') { candidateTimelineValue = timelineValueField; } else if (typeof timelineValueField === 'string') { const parsedValue = parseFloat(timelineValueField); candidateTimelineValue = isNaN(parsedValue) ? 0 : parsedValue; + } else if (timelineValueField && typeof timelineValueField === 'object' && timelineValueField.value !== undefined) { + candidateTimelineValue = timelineValueField.value; } // 获取计算方式 - let calculationMethod = '外部'; + let calculationMethod = '外部'; // 默认值 if (calculationMethodField) { if (typeof calculationMethodField === 'string') { calculationMethod = calculationMethodField; @@ -2967,7 +4270,17 @@ export default function App() { } } - // 转换时效值 + // 获取关系类型 + let relationshipType = '默认'; + if (relationshipField) { + if (typeof relationshipField === 'string') { + relationshipType = relationshipField; + } else if (relationshipField && typeof relationshipField === 'object' && relationshipField.text) { + relationshipType = relationshipField.text; + } + } + + // 根据计算方式转换时效值(小时转天) let convertedTimelineValue = 0; if (calculationMethod === '内部') { convertedTimelineValue = Math.round((candidateTimelineValue / 9) * 1000) / 1000; @@ -2975,22 +4288,101 @@ export default function App() { convertedTimelineValue = Math.round((candidateTimelineValue / 24) * 1000) / 1000; } - timelineValue = convertedTimelineValue; - matchedTimelineRecord = timelineRecord; - break; + // 添加到匹配候选列表 + matchedCandidates.push({ + record: timelineRecord, + timelineValue: convertedTimelineValue, + originalHours: candidateTimelineValue, + calculationMethod: calculationMethod, + relationshipType: relationshipType + }); } } } - // 计算节点时间 + // 处理匹配的候选记录(与生成模式保持一致) + let finalTimelineValue = 0; + let processingRule = '默认'; + + if (matchedCandidates.length > 0) { + // 获取主要关系类型(用于决定处理方式) + const relationshipTypes = matchedCandidates.map(c => c.relationshipType); + const primaryRelationshipType = relationshipTypes.find(type => type === '累加值' || type === '最大值') || '默认'; + + // 使用关系字段决定处理方式(累加值、最大值、默认) + processingRule = primaryRelationshipType || '默认'; + + console.log('批量模式 - processingRule:', processingRule); + + if (processingRule === '累加值') { + finalTimelineValue = matchedCandidates.reduce((sum, candidate) => sum + candidate.timelineValue, 0); + const totalOriginalHours = matchedCandidates.reduce((sum, candidate) => sum + candidate.originalHours, 0); + + console.log(`批量模式 - 节点 ${processNode.nodeName} 累加值处理 - 找到 ${matchedCandidates.length} 条匹配记录:`); + matchedCandidates.forEach((candidate, index) => { + console.log(` 记录${index + 1}: ID=${candidate.record.id || candidate.record.recordId}, 时效=${candidate.originalHours}小时(${candidate.timelineValue}天), 计算方式=${candidate.calculationMethod}`); + }); + console.log(`累加结果: 总计${totalOriginalHours}小时 → ${finalTimelineValue}天`); + + matchedTimelineRecord = matchedCandidates[0].record; + } else if (processingRule === '最大值') { + finalTimelineValue = Math.max(...matchedCandidates.map(c => c.timelineValue)); + const maxCandidate = matchedCandidates.find(c => c.timelineValue === finalTimelineValue); + + console.log(`批量模式 - 节点 ${processNode.nodeName} 最大值处理 - 找到 ${matchedCandidates.length} 条匹配记录:`); + matchedCandidates.forEach((candidate, index) => { + console.log(` 记录${index + 1}: ID=${candidate.record.id || candidate.record.recordId}, 时效=${candidate.originalHours}小时(${candidate.timelineValue}天), 计算方式=${candidate.calculationMethod}`); + }); + console.log(`最大值结果: ${maxCandidate?.originalHours}小时(${maxCandidate?.calculationMethod}) → ${finalTimelineValue}天`); + + matchedTimelineRecord = maxCandidate?.record || matchedCandidates[0].record; + } else { + finalTimelineValue = matchedCandidates[0].timelineValue; + + console.log(`批量模式 - 节点 ${processNode.nodeName} 默认处理 - 找到 ${matchedCandidates.length} 条匹配记录:`); + matchedCandidates.forEach((candidate, index) => { + console.log(` 记录${index + 1}: ID=${candidate.record.id || candidate.record.recordId}, 时效=${candidate.originalHours}小时(${candidate.timelineValue}天), 计算方式=${candidate.calculationMethod}`); + }); + console.log(`默认结果: 使用第一条记录 ${matchedCandidates[0].originalHours}小时(${matchedCandidates[0].calculationMethod}) → ${finalTimelineValue}天`); + + matchedTimelineRecord = matchedCandidates[0].record; + } + + timelineValue = finalTimelineValue; + } + + // 计算节点时间(与生成模式保持一致) let nodeStartTime = new Date(cumulativeStartTime); let nodeEndTime = new Date(nodeStartTime); + let nodeCalculationMethod = '外部'; // 默认计算方式 + + // 如果有匹配的候选记录,使用其计算方式 + if (matchedCandidates.length > 0) { + if (processingRule === '累加值') { + nodeCalculationMethod = matchedCandidates[0].calculationMethod; + } else if (processingRule === '最大值') { + const maxCandidate = matchedCandidates.find(c => c.timelineValue === finalTimelineValue); + nodeCalculationMethod = maxCandidate?.calculationMethod || matchedCandidates[0].calculationMethod; + } else { + nodeCalculationMethod = matchedCandidates[0].calculationMethod; + } + } if (timelineValue && timelineValue > 0) { - // 使用现有的时间计算函数 - const adjustedStartTime = adjustToNextWorkingHour(nodeStartTime, '外部', processNode.weekendDays, processNode.excludedDates || []); - nodeEndTime = addBusinessDaysWithHolidays(adjustedStartTime, timelineValue, processNode.weekendDays, processNode.excludedDates || []); + // 根据计算方式调整开始时间 + const adjustedStartTime = adjustToNextWorkingHour(nodeStartTime, nodeCalculationMethod, processNode.weekendDays, processNode.excludedDates || []); + + let endDate: Date; + if (nodeCalculationMethod === '内部') { + // 使用内部工作时间计算 + endDate = addInternalBusinessTime(adjustedStartTime, timelineValue, processNode.weekendDays, processNode.excludedDates || []); + } else { + // 使用原有的24小时制计算 + endDate = addBusinessDaysWithHolidays(adjustedStartTime, timelineValue, processNode.weekendDays, processNode.excludedDates || []); + } + nodeStartTime = adjustedStartTime; + nodeEndTime = endDate; } results.push({ @@ -2998,15 +4390,25 @@ export default function App() { nodeName: processNode.nodeName, matchedLabels: processNode.matchedLabels, timelineValue: timelineValue, - estimatedStart: formatDate(nodeStartTime, 'STORAGE_FORMAT'), - estimatedEnd: formatDate(nodeEndTime, 'STORAGE_FORMAT'), + estimatedStart: formatDate(nodeStartTime), // 使用默认格式,与生成模式一致 + estimatedEnd: formatDate(nodeEndTime), // 使用默认格式,与生成模式一致 timelineRecordId: matchedTimelineRecord ? (matchedTimelineRecord.id || matchedTimelineRecord.recordId) : null, - calculationMethod: '外部', + calculationMethod: nodeCalculationMethod, // 使用实际的计算方式 weekendDaysConfig: processNode.weekendDays, skippedWeekends: 0, - actualDays: calculateActualDays(formatDate(nodeStartTime, 'STORAGE_FORMAT'), formatDate(nodeEndTime, 'STORAGE_FORMAT')), + actualDays: calculateActualDays(formatDate(nodeStartTime), formatDate(nodeEndTime)), // 使用一致的格式 startDateRule: processNode.startDateRule, - dateAdjustmentRule: processNode.dateAdjustmentRule + dateAdjustmentRule: processNode.dateAdjustmentRule, + // 新增:保存所有匹配记录的信息(用于累加情况) + allMatchedRecords: matchedCandidates.length > 1 ? matchedCandidates.map(candidate => ({ + recordId: candidate.record.id || candidate.record.recordId || candidate.record._id || candidate.record.record_id, + timelineValue: candidate.timelineValue, + originalHours: candidate.originalHours, + calculationMethod: candidate.calculationMethod, + relationshipType: candidate.relationshipType + })) : null, + // 新增:标识是否为累加处理 + isAccumulated: processingRule === '累加值' && matchedCandidates.length > 1 }); // 更新累积时间 @@ -3019,11 +4421,71 @@ export default function App() { // 6. 写入数据到货期记录表和流程数据表 if (results.length > 0) { - // 写入流程数据表 - const processRecordIds = await writeToProcessDataTableForBatch(results, sourceRecordId, labels, expectedDate, style, color, foreignId, text2); + console.log('=== 批量模式:开始写入数据 ==='); + console.log('批量写入 - sourceRecordId:', sourceRecordId); + console.log('批量写入 - labels:', labels); + console.log('批量写入 - expectedDate:', expectedDate); + console.log('批量写入 - style:', style); + console.log('批量写入 - color:', color); + console.log('批量写入 - foreignId:', foreignId); + console.log('批量写入 - text2:', text2); + console.log('批量写入 - startTime:', startTime); - // 写入货期记录表 - await writeToDeliveryRecordTableForBatch(results, processRecordIds, {}, sourceRecordId, labels, startTime, expectedDate, style, color, foreignId, text2); + try { + // 为标准写入函数准备数据:构造临时的数据结构 + console.log('批量写入 - 设置临时状态以适配标准写入函数'); + + // 构造临时的recordDetails,包含当前记录的字段信息 + const tempRecordDetails = [{ + id: sourceRecordId, + fields: { + 'fldpvBfeC0': foreignId, // 外键ID字段 + 'fld6Uw95kt': style, // 样式字段 + 'flde85ni4O': color, // 颜色字段 + 'fldG6LZnmU': text2 // 文本字段2 + } + }]; + + // 构造批量数据对象(包含必要的业务字段) + const batchDataForWriting = { + selectedRecords: [sourceRecordId], + recordDetails: tempRecordDetails, + labels, // 标签汇总(对象) + expectedDate, // 客户期望日期(Date) + startTime // 起始时间(Date) + }; + + console.log('批量写入 - 临时数据构造完成,开始调用标准写入函数'); + console.log('批量写入 - batchDataForWriting:', batchDataForWriting); + + // 写入流程数据表 - 使用标准函数,传递批量数据 + console.log('批量写入 - 调用标准writeToProcessDataTable函数'); + const processRecordIds = await writeToProcessDataTable(results, batchDataForWriting); + console.log('批量写入 - 流程数据表写入完成,记录ID:', processRecordIds); + + // 写入货期记录表 - 使用标准函数,传递批量数据和空的调整信息 + console.log('批量写入 - 调用标准writeToDeliveryRecordTable函数'); + await writeToDeliveryRecordTable(results, processRecordIds, {}, batchDataForWriting); + console.log('批量写入 - 货期记录表写入完成'); + + // 批量写入成功后,设置全局状态以便快照保存 + console.log('批量写入 - 设置全局状态以便快照保存'); + setSelectedRecords([sourceRecordId]); + setRecordDetails(tempRecordDetails); + setSelectedLabels(labels); + setCurrentStyleText(extractText(style)); + setCurrentColorText(extractText(color)); + setCurrentText2(extractText(text2)); + setCurrentForeignId(extractText(foreignId)); + setExpectedDate(expectedDate); + setStartTime(startTime); + + } catch (error) { + console.error('批量写入失败:', error); + throw error; + } + + console.log('=== 批量模式:数据写入完成 ==='); } } catch (error) { @@ -3032,384 +4494,7 @@ export default function App() { } }; - // 为批量处理优化的流程数据表写入函数 - const writeToProcessDataTableForBatch = async ( - timelineResults: any[], - sourceRecordId: string, - labels: {[key: string]: string | string[]}, - expectedDate: Date | null, - style?: any, - color?: any, - foreignId?: any, - text2?: any - ): Promise => { - try { - // 获取流程数据表 - const processDataTable = await bitable.base.getTable(PROCESS_DATA_TABLE_ID); - - // 获取所有需要的字段 - const foreignIdField = await processDataTable.getField(FOREIGN_ID_FIELD_ID); - const processNameField = await processDataTable.getField(PROCESS_NAME_FIELD_ID); - const processOrderField = await processDataTable.getField(PROCESS_ORDER_FIELD_ID_DATA); - const startDateField = await processDataTable.getField(ESTIMATED_START_DATE_FIELD_ID); - const endDateField = await processDataTable.getField(ESTIMATED_END_DATE_FIELD_ID); - const snapshotField = await processDataTable.getField(PROCESS_SNAPSHOT_JSON_FIELD_ID); - const versionField = await processDataTable.getField(PROCESS_VERSION_FIELD_ID); - const timelinessField = await processDataTable.getField(PROCESS_TIMELINESS_FIELD_ID); - - // 使用传入的foreignId参数,如果没有则使用sourceRecordId作为foreign_id - const actualForeignId = foreignId || sourceRecordId; - - // 先删除该foreign_id的所有现有记录 - try { - const existingRecords = await processDataTable.getRecords({ - pageSize: 5000, - filter: { - conjunction: 'And', - conditions: [{ - fieldId: FOREIGN_ID_FIELD_ID, - operator: 'is', - value: [foreignId] - }] - } - }); - - if (existingRecords.records && existingRecords.records.length > 0) { - const recordIdsToDelete = existingRecords.records.map(record => record.id); - await processDataTable.deleteRecords(recordIdsToDelete); - console.log(`已删除 ${recordIdsToDelete.length} 条现有记录`); - } - } catch (deleteError) { - console.warn('删除现有记录时出错:', deleteError); - } - - // 构建页面快照JSON - const versionNumber = 1; // 批量处理默认版本号为1 - - // 提取实际的文本值 - const extractTextValue = (value: any) => { - if (!value) return ''; - if (typeof value === 'string') return value; - if (Array.isArray(value) && value.length > 0) { - const firstItem = value[0]; - if (typeof firstItem === 'string') return firstItem; - if (firstItem && firstItem.text) return firstItem.text; - } - if (value.text) return value.text; - return ''; - }; - - const expectedDateTimestamp = expectedDate ? expectedDate.getTime() : null; - const expectedDateString = expectedDate ? format(expectedDate, DATE_FORMATS.STORAGE_FORMAT) : null; - - const pageSnapshot = { - version: versionNumber, - foreignId: extractTextValue(foreignId), - styleText: extractTextValue(style), - colorText: extractTextValue(color), - text2: extractTextValue(text2), - mode: 'batch', - selectedLabels: labels, - expectedDateTimestamp, - expectedDateString, - timelineResults - }; - const snapshotJson = JSON.stringify(pageSnapshot); - // 准备要写入的记录数据 - const recordCellsToAdd = []; - - for (const result of timelineResults) { - // 检查是否有有效的预计完成时间 - const hasValidEndTime = ( - result.estimatedEnd && - !result.estimatedEnd.includes('未找到') && - result.estimatedEnd.trim() !== '' - ); - - if (!hasValidEndTime) { - continue; - } - - // 转换日期格式为时间戳 - let startTimestamp = null; - let endTimestamp = null; - - if (result.estimatedStart && !result.estimatedStart.includes('未找到')) { - try { - startTimestamp = new Date(result.estimatedStart).getTime(); - } catch (error) { - console.error(`转换开始时间失败:`, error); - } - } - - if (result.estimatedEnd && !result.estimatedEnd.includes('未找到')) { - try { - endTimestamp = new Date(result.estimatedEnd).getTime(); - } catch (error) { - console.error(`转换结束时间失败:`, error); - } - } - - // 使用createCell方法创建每个字段的Cell - // 使用传入的foreignId参数,如果没有则使用sourceRecordId - const actualForeignId = foreignId || sourceRecordId; - const foreignIdCell = await foreignIdField.createCell(actualForeignId); - const processNameCell = await processNameField.createCell(result.nodeName); - const processOrderCell = await processOrderField.createCell(result.processOrder); - const startDateCell = startTimestamp ? await startDateField.createCell(startTimestamp) : null; - const endDateCell = endTimestamp ? await endDateField.createCell(endTimestamp) : null; - - const snapshotCell = await snapshotField.createCell(snapshotJson); - const versionCell = await versionField.createCell(versionNumber); - const timelinessCell = (typeof result.timelineValue === 'number') - ? await timelinessField.createCell(result.timelineValue) - : null; - - const recordCells = [ - foreignIdCell, - processNameCell, - processOrderCell, - snapshotCell, - versionCell - ]; - - if (startDateCell) recordCells.push(startDateCell); - if (endDateCell) recordCells.push(endDateCell); - if (timelinessCell) recordCells.push(timelinessCell); - - recordCellsToAdd.push(recordCells); - } - - // 批量写入记录 - const addedRecordIds: string[] = []; - - if (recordCellsToAdd.length > 0) { - try { - const addedRecords = await processDataTable.addRecords(recordCellsToAdd); - addedRecordIds.push(...addedRecords); - } catch (error) { - console.error('批量写入失败,尝试逐条写入:', error); - for (const recordCells of recordCellsToAdd) { - const addedRecord = await processDataTable.addRecord(recordCells); - addedRecordIds.push(addedRecord); - } - } - - return addedRecordIds; - } else { - return []; - } - - } catch (error) { - console.error('批量写入流程数据表失败:', error); - throw error; - } - }; - - // 为批量处理优化的货期记录表写入函数 - const writeToDeliveryRecordTableForBatch = async ( - timelineResults: any[], - processRecordIds: string[], - timelineAdjustments: {[key: number]: number} = {}, - sourceRecordId: string, - labels: {[key: string]: string | string[]}, - startTime: Date | null, - expectedDate: Date | null, - style?: any, - color?: any, - foreignId?: any, - text2?: any - ) => { - try { - // 获取货期记录表 - const deliveryRecordTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID); - - // 获取各个字段 - const foreignIdField = await deliveryRecordTable.getField(DELIVERY_FOREIGN_ID_FIELD_ID); - const labelsField = await deliveryRecordTable.getField(DELIVERY_LABELS_FIELD_ID); - const styleField = await deliveryRecordTable.getField(DELIVERY_STYLE_FIELD_ID); - const colorField = await deliveryRecordTable.getField(DELIVERY_COLOR_FIELD_ID); - const text2Field = await deliveryRecordTable.getField(DELIVERY_TEXT2_FIELD_ID); - const createTimeField = await deliveryRecordTable.getField(DELIVERY_CREATE_TIME_FIELD_ID); - const expectedDateField = await deliveryRecordTable.getField(DELIVERY_EXPECTED_DATE_FIELD_ID); - const nodeDetailsField = await deliveryRecordTable.getField(DELIVERY_NODE_DETAILS_FIELD_ID); - const customerExpectedDateField = await deliveryRecordTable.getField(DELIVERY_CUSTOMER_EXPECTED_DATE_FIELD_ID); - const adjustmentInfoField = await deliveryRecordTable.getField(DELIVERY_ADJUSTMENT_INFO_FIELD_ID); - const versionField = await deliveryRecordTable.getField(DELIVERY_VERSION_FIELD_ID); - const startTimeField = await deliveryRecordTable.getField(DELIVERY_START_TIME_FIELD_ID); - - // 使用传入的foreignId参数,如果没有则使用sourceRecordId作为foreign_id - const actualForeignId = foreignId || sourceRecordId; - - // 获取标签汇总 - 批量模式下正确处理标签数据 - // 批量模式下,labels参数包含从当前记录中提取的标签数据 - // 需要将这些标签数据合并为标签汇总 - - console.log('传入的labels参数:', labels); - console.log('labels参数类型:', typeof labels); - console.log('labels参数键值:', Object.keys(labels)); - console.log('labels参数值详情:', JSON.stringify(labels, null, 2)); - - // 从当前记录的标签数据中提取所有标签值 - const selectedLabelValues: string[] = []; - - console.log('开始处理标签数据...'); - - // 遍历当前记录的标签数据 - Object.entries(labels).forEach(([labelKey, labelValue]) => { - console.log(`处理标签字段 ${labelKey}:`, labelValue, '类型:', typeof labelValue); - - if (labelValue) { - if (Array.isArray(labelValue)) { - console.log(`${labelKey} 是数组,长度:`, labelValue.length); - // 多选标签,展开所有值 - labelValue.forEach((value, index) => { - console.log(` 数组项 ${index}:`, value, '类型:', typeof value); - if (typeof value === 'string' && value.trim() !== '') { - // 处理可能包含逗号分隔的复合标签值 - const splitValues = value.split(',').map(v => v.trim()).filter(v => v !== ''); - console.log(` 分割后的值:`, splitValues); - selectedLabelValues.push(...splitValues); - } - }); - } else if (typeof labelValue === 'string' && labelValue.trim() !== '') { - console.log(`${labelKey} 是字符串:`, labelValue); - // 单选标签,处理可能包含逗号分隔的复合标签值 - const splitValues = labelValue.split(',').map(v => v.trim()).filter(v => v !== ''); - console.log(` 分割后的值:`, splitValues); - selectedLabelValues.push(...splitValues); - } - } else { - console.log(`${labelKey} 为空或无效`); - } - }); - - console.log('提取的所有标签值:', selectedLabelValues); - - // 去重处理 - const uniqueLabelValues = [...new Set(selectedLabelValues)]; - - console.log('当前记录的标签数据:', labels); - console.log('处理后的标签汇总:', uniqueLabelValues); - - // 获取预计交付日期(最后节点的预计完成时间) - let expectedDeliveryDate = null; - if (timelineResults.length > 0) { - const lastNode = timelineResults[timelineResults.length - 1]; - if (lastNode.estimatedEnd && !lastNode.estimatedEnd.includes('未找到')) { - try { - expectedDeliveryDate = new Date(lastNode.estimatedEnd).getTime(); - } catch (error) { - console.error('转换预计交付日期失败:', error); - } - } - } - - // 获取客户期望日期 - let customerExpectedDate = null; - if (expectedDate) { - customerExpectedDate = expectedDate.getTime(); - } - - // 创建当前时间戳 - const currentTime = new Date().getTime(); - const versionNumber = 1; // 批量处理默认版本号为1 - - let adjustmentInfo = `版本:V${versionNumber}`; - if (Object.keys(timelineAdjustments).length > 0) { - const adjustmentTexts = Object.entries(timelineAdjustments).map(([nodeIndex, adjustment]) => { - const nodeName = timelineResults[parseInt(nodeIndex)]?.nodeName || `节点${parseInt(nodeIndex) + 1}`; - return `${nodeName}: ${adjustment > 0 ? '+' : ''}${adjustment.toFixed(1)} 天`; - }); - adjustmentInfo += `\n当前调整:\n${adjustmentTexts.join('\n')}`; - } - - // 提取实际的文本值 - const extractTextValue = (value: any) => { - if (!value) return ''; - if (typeof value === 'string') return value; - if (Array.isArray(value) && value.length > 0) { - const firstItem = value[0]; - if (typeof firstItem === 'string') return firstItem; - if (firstItem && firstItem.text) return firstItem.text; - } - if (value.text) return value.text; - return ''; - }; - - // 创建所有必要的Cell - const foreignIdCell = await foreignIdField.createCell(extractTextValue(foreignId) || actualForeignId); - const styleCell = await styleField.createCell(extractTextValue(style)); - const colorCell = await colorField.createCell(extractTextValue(color)); - const text2Cell = await text2Field.createCell(extractTextValue(text2)); - const createTimeCell = await createTimeField.createCell(currentTime); - const startTimeCell = await startTimeField.createCell(startTime ? startTime.getTime() : currentTime); - const versionCell = await versionField.createCell(versionNumber); - - // 创建可选的Cell - const expectedDateCell = expectedDeliveryDate ? await expectedDateField.createCell(expectedDeliveryDate) : null; - const customerExpectedDateCell = customerExpectedDate ? await customerExpectedDateField.createCell(customerExpectedDate) : null; - const nodeDetailsCell = processRecordIds.length > 0 ? await nodeDetailsField.createCell({ recordIds: processRecordIds }) : null; - const adjustmentInfoCell = adjustmentInfo ? await adjustmentInfoField.createCell(adjustmentInfo) : null; - - // 只有当数据存在时才添加对应的Cell - console.log('uniqueLabelValues长度:', uniqueLabelValues.length); - console.log('uniqueLabelValues内容:', uniqueLabelValues); - - // 使用与正常模式相同的createCell方法创建标签汇总字段 - const labelsCell = uniqueLabelValues.length > 0 ? await labelsField.createCell(uniqueLabelValues) : null; - - if (labelsCell) { - console.log('标签汇总字段Cell创建成功'); - } else { - console.log('标签汇总字段为空,不创建Cell'); - } - - // 组合所有Cell到一个记录中 - const recordCells = [foreignIdCell, styleCell, colorCell, text2Cell, createTimeCell, startTimeCell, versionCell]; - - // 只有当数据存在时才添加对应的Cell - if (labelsCell) recordCells.push(labelsCell); - if (expectedDateCell) recordCells.push(expectedDateCell); - if (customerExpectedDateCell) recordCells.push(customerExpectedDateCell); - if (nodeDetailsCell) recordCells.push(nodeDetailsCell); - if (adjustmentInfoCell) recordCells.push(adjustmentInfoCell); - - console.log('准备写入的RecordCells数量:', recordCells.length); - console.log('标签汇总字段Cell是否存在:', !!labelsCell); - - // 使用Cell数组格式添加记录(与正常模式相同) - console.log('即将调用 deliveryRecordTable.addRecord...'); - const addedRecord = await deliveryRecordTable.addRecord(recordCells); - - console.log('货期记录表写入成功,记录ID:', addedRecord); - - // 验证写入的记录 - try { - console.log('开始验证写入的记录...'); - const writtenRecord = await deliveryRecordTable.getRecordById(addedRecord); - console.log('写入后的记录验证:', writtenRecord); - console.log('写入后的标签汇总字段:', writtenRecord.fields[DELIVERY_LABELS_FIELD_ID]); - - // 检查标签汇总字段是否为空 - const labelsFieldValue = writtenRecord.fields[DELIVERY_LABELS_FIELD_ID]; - if (!labelsFieldValue || (Array.isArray(labelsFieldValue) && labelsFieldValue.length === 0)) { - console.error('警告:标签汇总字段写入后为空!'); - console.error('原始数据:', uniqueLabelValues); - console.error('字段ID:', DELIVERY_LABELS_FIELD_ID); - } else { - console.log('标签汇总字段写入成功,包含', Array.isArray(labelsFieldValue) ? labelsFieldValue.length : 1, '个值'); - } - } catch (verifyError) { - console.error('验证写入记录失败:', verifyError); - } - return addedRecord; - } catch (error) { - console.error('批量写入货期记录表失败:', error); - throw error; - } - }; // 执行定价数据查询 const executeQuery = async (packId: string, packType: string) => { @@ -3670,7 +4755,8 @@ export default function App() { // 选择记录 const handleSelectRecords = async () => { - + // 切换版单数据时重置全局变量,清空旧结果与回填状态 + resetGlobalState(); setLoading(true); // 清空标签选择 setSelectedLabels({}); @@ -4108,42 +5194,53 @@ export default function App() { onCancel={() => { setTimelineVisible(false); setTimelineAdjustments({}); // 关闭时重置调整 + setDeliveryMarginDeductions(0); // 关闭时重置交期余量扣减 + setCompletionDateAdjustment(0); // 关闭时重置最后流程完成日期调整 }} footer={
- + + 缓冲期(天): + { + const n = Number(val); + setBaseBufferDays(Number.isFinite(n) && n >= 0 ? n : 0); + }} + style={{ width: 90 }} + /> + + @@ -4263,15 +5568,29 @@ export default function App() {
@@ -4355,6 +5674,371 @@ export default function App() { ); })} + {/* 计算公式展示卡片 */} + {timelineResults.length > 0 && ( + +
+ + 📅 交期余量计算 + + +
+ {(() => { + // 获取有效的最后流程完成日期 + let effectiveLastProcess = null; + let lastCompletionDate = null; + + // 从后往前查找第一个有效的流程完成日期 + for (let i = timelineResults.length - 1; i >= 0; i--) { + const process = timelineResults[i]; + const processDate = new Date(process.estimatedEnd); + + // 检查日期是否有效且不是"时效值为0"的情况 + if (!isNaN(processDate.getTime()) && process.estimatedEnd !== '时效值为0') { + effectiveLastProcess = process; + lastCompletionDate = processDate; + break; + } + } + + // 如果没有找到有效的完成日期,使用最后一个流程 + if (!effectiveLastProcess) { + effectiveLastProcess = timelineResults[timelineResults.length - 1]; + lastCompletionDate = new Date(effectiveLastProcess.estimatedEnd); + } + + // 计算所有节点的总调整量(用于动态缓冲期计算) + const totalAdjustments = Object.values(timelineAdjustments).reduce((sum, adj) => sum + adj, 0); + + // 计算动态缓冲期:基础14天 - 节点总调整量,最小为0天 + const baseBuferDays = baseBufferDays; + const dynamicBufferDays = Math.max(0, baseBuferDays - totalAdjustments); + + // 计算最后完成日期 + 动态缓冲期(考虑最后流程完成日期的调整) + const adjustedCompletionDate = new Date(lastCompletionDate); + adjustedCompletionDate.setDate(adjustedCompletionDate.getDate() + completionDateAdjustment); + + const deliveryDate = new Date(adjustedCompletionDate); + deliveryDate.setDate(deliveryDate.getDate() + dynamicBufferDays); + + // 计算交期余量 + // 如果客户期望日期不为空:交期余量 = 客户期望日期 - (最后流程完成 + 缓冲期) + // 如果客户期望日期为空:交期余量 = (最后流程完成 + 缓冲期) + let timeDiff, baseDaysDiff, finalDaysDiff; + if (expectedDate) { + // 有客户期望日期:计算期望日期与交付日期的差值 + timeDiff = expectedDate.getTime() - deliveryDate.getTime(); + baseDaysDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24)); + } else { + // 无客户期望日期:直接显示交付日期(最后流程完成 + 缓冲期) + const today = new Date(); + today.setHours(0, 0, 0, 0); + timeDiff = deliveryDate.getTime() - today.getTime(); + baseDaysDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24)); + } + + // 计算最终交期余量:基础余量 - 扣减天数 + finalDaysDiff = baseDaysDiff - deliveryMarginDeductions; + + return ( + <> + {expectedDate && ( + <> + {/* 客户期望日期 */} +
+ + 客户期望日期 + + + {formatDate(expectedDate)} + + + {getDayOfWeek(expectedDate)} + +
+ + {/* 减号 */} +
+ - +
+ + {/* 最后流程完成 */} +
+ + {effectiveLastProcess === timelineResults[timelineResults.length - 1] + ? '最后流程完成' + : `有效流程完成 (${effectiveLastProcess.nodeName})`} + + + {formatDate(adjustedCompletionDate)} + + + {getDayOfWeek(adjustedCompletionDate)} + + {effectiveLastProcess !== timelineResults[timelineResults.length - 1] && ( + + (最后流程时效为0,使用此流程) + + )} + {completionDateAdjustment > 0 && ( + + (已调整 +{completionDateAdjustment}天) + + )} +
+ + {/* 加号 */} +
+ + +
+ + {/* 缓冲期 */} +
+ + 缓冲期 + + + {dynamicBufferDays}天 + + + 固定缓冲 + +
+ + {/* 等号 */} +
+ = +
+ + )} + + {!expectedDate && ( + <> + {/* 最后流程完成 */} +
+ + {effectiveLastProcess === timelineResults[timelineResults.length - 1] + ? '最后流程完成' + : `有效流程完成 (${effectiveLastProcess.nodeName})`} + + + {formatDate(effectiveLastProcess.estimatedEnd)} + + + {getDayOfWeek(effectiveLastProcess.estimatedEnd)} + + {effectiveLastProcess !== timelineResults[timelineResults.length - 1] && ( + + (最后流程时效为0,使用此流程) + + )} +
+ + {/* 加号 */} +
+ + +
+ + {/* 缓冲期 */} +
+ + 缓冲期 + + + {dynamicBufferDays}天 + + + 固定缓冲 + +
+ + {/* 减号 */} +
+ - +
+ + {/* 今天 */} +
+ + 今天 + + + {formatDate(new Date())} + + + {getDayOfWeek(new Date())} + +
+ + {/* 等号 */} +
+ = +
+ + )} + + {/* 交期余量结果 */} +
= 0 ? '#f6ffed' : '#fff2f0', + borderRadius: '8px', + border: `2px solid ${finalDaysDiff >= 0 ? '#52c41a' : '#ff4d4f'}` + }}> + + 交期余量 + + = 0 ? '#52c41a' : '#ff4d4f', + display: 'block' + }}> + {finalDaysDiff >= 0 ? '+' : ''}{finalDaysDiff}天 + + = 0 ? '#52c41a' : '#ff4d4f', + fontWeight: 'bold' + }}> + {finalDaysDiff >= 0 ? '✅ 时间充裕' : '⚠️ 时间紧张'} + +
+ + ); + })()} +
+ +
+ {(() => { + // 计算动态缓冲期和总调整量 + const totalAdjustments = Object.values(timelineAdjustments).reduce((sum, adj) => sum + adj, 0); + const baseBuferDays = baseBufferDays; + const dynamicBufferDays = Math.max(0, baseBuferDays - totalAdjustments); + + return ( + + 💡 说明: + {expectedDate + ? `交期余量 = 客户期望日期 - (最后流程完成日期 + ${dynamicBufferDays}天缓冲期)` + : `交期余量 = (最后流程完成日期 + ${dynamicBufferDays}天缓冲期) - 今天` + } +
+ {expectedDate + ? '• 正值表示有充裕时间,负值表示可能延期' + : '• 显示预计交付日期距离今天的天数' + } +
+ • 缓冲期 = 基础{baseBuferDays}天 - 节点调整总量(最小0天),包含质检、包装、物流等后续环节 + {totalAdjustments > 0 && ( + <> +
+ • 当前节点总调整量:+{totalAdjustments}天,缓冲期相应减少 + + )} +
+ ); + })()} +
+
+
+ )} +
📊 计算说明: