6
This commit is contained in:
2025-12-25 14:56:01 +08:00
parent 8768d8cee9
commit 7d52c3955b

View File

@ -1976,7 +1976,8 @@ export default function App() {
recordDetails?: any[], recordDetails?: any[],
selectedLabels?: {[key: string]: string | string[]}, selectedLabels?: {[key: string]: string | string[]},
expectedDate?: Date | null, expectedDate?: Date | null,
startTime?: Date | null startTime?: Date | null,
excludedDates?: string[]
}, },
showUI: boolean = true // 新增参数控制是否显示UI showUI: boolean = true // 新增参数控制是否显示UI
) => { ) => {
@ -1986,6 +1987,7 @@ export default function App() {
const currentSelectedLabels = overrideData?.selectedLabels || selectedLabels; const currentSelectedLabels = overrideData?.selectedLabels || selectedLabels;
const currentExpectedDate = overrideData?.expectedDate || expectedDate; const currentExpectedDate = overrideData?.expectedDate || expectedDate;
const currentStartTime = overrideData?.startTime || startTime; const currentStartTime = overrideData?.startTime || startTime;
const currentExcludedDates = Array.isArray(overrideData?.excludedDates) ? overrideData!.excludedDates : [];
const isBackward = timelineDirection === 'backward'; const isBackward = timelineDirection === 'backward';
console.log('=== handleCalculateTimeline - 使用的数据 ==='); console.log('=== handleCalculateTimeline - 使用的数据 ===');
@ -2381,6 +2383,7 @@ export default function App() {
for (let i = 0; i < nodesToProcess.length; i++) { for (let i = 0; i < nodesToProcess.length; i++) {
const processNode = nodesToProcess[i]; const processNode = nodesToProcess[i];
const nodeExcludedDates = Array.from(new Set([...(processNode.excludedDates || []), ...currentExcludedDates])).filter(Boolean);
let timelineValue = null; let timelineValue = null;
let matchedTimelineRecord = null; let matchedTimelineRecord = null;
@ -2587,15 +2590,15 @@ export default function App() {
// 计算当前节点的开始和完成时间(使用工作日计算) // 计算当前节点的开始和完成时间(使用工作日计算)
const calculateTimeline = (startDate: Date, timelineValue: number, calculationMethod: string = '外部') => { const calculateTimeline = (startDate: Date, timelineValue: number, calculationMethod: string = '外部') => {
// 根据计算方式调整开始时间 // 根据计算方式调整开始时间
const adjustedStartDate = adjustToNextWorkingHour(startDate, calculationMethod, processNode.weekendDays, processNode.excludedDates || []); const adjustedStartDate = adjustToNextWorkingHour(startDate, calculationMethod, processNode.weekendDays, nodeExcludedDates);
let endDate: Date; let endDate: Date;
if (calculationMethod === '内部') { if (calculationMethod === '内部') {
// 使用内部工作时间计算 // 使用内部工作时间计算
endDate = addInternalBusinessTime(adjustedStartDate, timelineValue, processNode.weekendDays, processNode.excludedDates || []); endDate = addInternalBusinessTime(adjustedStartDate, timelineValue, processNode.weekendDays, nodeExcludedDates);
} else { } else {
// 使用原有的24小时制计算 // 使用原有的24小时制计算
endDate = addBusinessDaysWithHolidays(adjustedStartDate, timelineValue, processNode.weekendDays, processNode.excludedDates || []); endDate = addBusinessDaysWithHolidays(adjustedStartDate, timelineValue, processNode.weekendDays, nodeExcludedDates);
} }
return { return {
@ -2674,11 +2677,11 @@ export default function App() {
}; };
if (timelineValue) { if (timelineValue) {
const adjustedStartTime = adjustToNextWorkingHour(nodeStartTime, nodeCalculationMethod, processNode.weekendDays, processNode.excludedDates || []); const adjustedStartTime = adjustToNextWorkingHour(nodeStartTime, nodeCalculationMethod, processNode.weekendDays, nodeExcludedDates);
if (nodeCalculationMethod === '内部') { if (nodeCalculationMethod === '内部') {
nodeEndTime = addInternalBusinessTime(adjustedStartTime, timelineValue, processNode.weekendDays, processNode.excludedDates || []); nodeEndTime = addInternalBusinessTime(adjustedStartTime, timelineValue, processNode.weekendDays, nodeExcludedDates);
} else { } else {
nodeEndTime = addBusinessDaysWithHolidays(adjustedStartTime, timelineValue, processNode.weekendDays, processNode.excludedDates || []); nodeEndTime = addBusinessDaysWithHolidays(adjustedStartTime, timelineValue, processNode.weekendDays, nodeExcludedDates);
} }
} else { } else {
nodeEndTime = new Date(nodeStartTime); nodeEndTime = new Date(nodeStartTime);
@ -2687,9 +2690,9 @@ export default function App() {
nodeEndTime = new Date(cumulativeTime); nodeEndTime = new Date(cumulativeTime);
if (timelineValue) { if (timelineValue) {
if (nodeCalculationMethod === '内部') { if (nodeCalculationMethod === '内部') {
nodeStartTime = addInternalBusinessTime(nodeEndTime, -timelineValue, processNode.weekendDays, processNode.excludedDates || []); nodeStartTime = addInternalBusinessTime(nodeEndTime, -timelineValue, processNode.weekendDays, nodeExcludedDates);
} else { } else {
nodeStartTime = addBusinessDaysWithHolidays(nodeEndTime, -timelineValue, processNode.weekendDays, processNode.excludedDates || []); nodeStartTime = addBusinessDaysWithHolidays(nodeEndTime, -timelineValue, processNode.weekendDays, nodeExcludedDates);
} }
} else { } else {
nodeStartTime = new Date(nodeEndTime); nodeStartTime = new Date(nodeEndTime);
@ -2711,7 +2714,7 @@ export default function App() {
const actualDays = calculateActualDays(timelineResult.startDate, timelineResult.endDate); const actualDays = calculateActualDays(timelineResult.startDate, timelineResult.endDate);
// 计算时间范围内实际跳过的自定义日期 // 计算时间范围内实际跳过的自定义日期
const excludedDatesInRange = calculateExcludedDatesInRange(nodeStartTime, nodeEndTime, processNode.excludedDates || []); const excludedDatesInRange = calculateExcludedDatesInRange(nodeStartTime, nodeEndTime, nodeExcludedDates);
(isBackward ? results.unshift.bind(results) : results.push.bind(results))({ (isBackward ? results.unshift.bind(results) : results.push.bind(results))({
processOrder: processNode.processOrder, processOrder: processNode.processOrder,
@ -2733,7 +2736,7 @@ export default function App() {
// 新增:标识是否为累加处理 // 新增:标识是否为累加处理
isAccumulated: processingRule === '累加值' && matchedCandidates.length > 1, isAccumulated: processingRule === '累加值' && matchedCandidates.length > 1,
weekendDaysConfig: processNode.weekendDays, // 新增:保存休息日配置用于显示 weekendDaysConfig: processNode.weekendDays, // 新增:保存休息日配置用于显示
excludedDates: processNode.excludedDates || [], // 新增:保存不参与计算日期用于显示与快照 excludedDates: nodeExcludedDates, // 新增:保存不参与计算日期用于显示与快照
// 新增:保存时间范围内实际跳过的日期 // 新增:保存时间范围内实际跳过的日期
actualExcludedDates: excludedDatesInRange.dates, actualExcludedDates: excludedDatesInRange.dates,
actualExcludedDatesCount: excludedDatesInRange.count, actualExcludedDatesCount: excludedDatesInRange.count,
@ -2792,6 +2795,43 @@ export default function App() {
} }
setTimelineResults(results); setTimelineResults(results);
if (!isBackward && mode === 'generate' && showUI && currentExpectedDate && results.length > 0) {
let lastCompletionDate: Date | null = null;
for (let i = results.length - 1; i >= 0; i--) {
const end = results[i]?.estimatedEnd;
if (!end || end === '时效值为0') continue;
const parsed = typeof end === 'string' ? parseDate(end) : (end as any as Date);
if (parsed && !isNaN(parsed.getTime())) {
lastCompletionDate = parsed;
break;
}
}
if (lastCompletionDate) {
const bufferDays = Math.max(0, Math.ceil(baseBufferDays));
const computedDeliveryDate = new Date(lastCompletionDate);
computedDeliveryDate.setDate(computedDeliveryDate.getDate() + bufferDays);
const slackDays = differenceInCalendarDays(currentExpectedDate, computedDeliveryDate);
if (slackDays > 0) {
const suggested = bufferDays + slackDays;
await new Promise<void>((resolve) => {
Modal.confirm({
title: '建议增加缓冲期',
content: `客户交期 ${formatDate(currentExpectedDate)}${getDayOfWeek(currentExpectedDate)})晚于当前预计交付日期 ${formatDate(computedDeliveryDate)}${getDayOfWeek(computedDeliveryDate)}),建议将缓冲期从 ${bufferDays} 天调整为 ${suggested} 天以对齐。`,
okText: '增加缓冲期',
cancelText: '不调整',
onOk: () => {
setBaseBufferDays(suggested);
setHasAppliedSuggestedBuffer(true);
setLastSuggestedApplied(suggested);
resolve();
},
onCancel: () => resolve()
});
});
}
}
}
if (showUI && !delayShowTimelineModal) { if (showUI && !delayShowTimelineModal) {
setTimelineVisible(true); setTimelineVisible(true);
} else if (showUI && delayShowTimelineModal) { } else if (showUI && delayShowTimelineModal) {
@ -3160,12 +3200,13 @@ export default function App() {
const computeExpectedDeliveryDateTsFromResults = ( const computeExpectedDeliveryDateTsFromResults = (
results: any[], results: any[],
adjustments: { [key: number]: number } adjustments: { [key: number]: number },
baseBufferDaysOverride?: number
): number | null => { ): number | null => {
if (timelineDirection === 'backward') return null; if (timelineDirection === 'backward') return null;
const lastCompletionDate = getLastValidCompletionDateFromResults(results); const lastCompletionDate = getLastValidCompletionDateFromResults(results);
if (!lastCompletionDate) return null; if (!lastCompletionDate) return null;
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(adjustments); const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(adjustments, baseBufferDaysOverride);
const deliveryDate = new Date(lastCompletionDate); const deliveryDate = new Date(lastCompletionDate);
deliveryDate.setDate(deliveryDate.getDate() + dynamicBufferDays); deliveryDate.setDate(deliveryDate.getDate() + dynamicBufferDays);
const ts = deliveryDate.getTime(); const ts = deliveryDate.getTime();
@ -3181,15 +3222,15 @@ export default function App() {
if (computed !== null) setLockedExpectedDeliveryDateTs(computed); if (computed !== null) setLockedExpectedDeliveryDateTs(computed);
}; };
const computeDynamicBufferDaysUsingEndDelta = (adjustments: { [key: number]: number }): number => { const computeDynamicBufferDaysUsingEndDelta = (adjustments: { [key: number]: number }, baseBufferDaysOverride?: number): number => {
try { try {
if (timelineDirection === 'backward') return 0; if (timelineDirection === 'backward') return 0;
const deltaDays = computeLastNodeEndDeltaDays(adjustments); const deltaDays = computeLastNodeEndDeltaDays(adjustments);
const base = Math.max(0, Math.ceil(baseBufferDays)); const base = Math.max(0, Math.ceil(baseBufferDaysOverride ?? baseBufferDays));
const remaining = base - deltaDays; const remaining = base - deltaDays;
return Math.max(0, Math.min(base, remaining)); return Math.max(0, Math.min(base, remaining));
} catch { } catch {
const base = Math.max(0, Math.ceil(baseBufferDays)); const base = Math.max(0, Math.ceil(baseBufferDaysOverride ?? baseBufferDays));
return base; return base;
} }
}; };
@ -3502,7 +3543,7 @@ export default function App() {
timelineResults: any[], timelineResults: any[],
processRecordIds: string[], processRecordIds: string[],
timelineAdjustments: {[key: number]: number} = {}, timelineAdjustments: {[key: number]: number} = {},
overrides?: { foreignId?: string; style?: string; color?: string; expectedDate?: Date | null; startTime?: Date | null; selectedLabels?: {[key: string]: string | string[]} } overrides?: { foreignId?: string; style?: string; color?: string; expectedDate?: Date | null; startTime?: Date | null; selectedLabels?: {[key: string]: string | string[]}; baseBufferDays?: number }
) => { ) => {
let recordCells: any[] | undefined; let recordCells: any[] | undefined;
try { try {
@ -3664,8 +3705,10 @@ export default function App() {
// 获取标签汇总批量模式优先使用传入的labels // 获取标签汇总批量模式优先使用传入的labels
const selectedLabelValues = Object.values(overrides?.selectedLabels ?? selectedLabels).flat().filter(Boolean); const selectedLabelValues = Object.values(overrides?.selectedLabels ?? selectedLabels).flat().filter(Boolean);
const baseBufferDaysToUse = Math.max(0, Math.ceil(overrides?.baseBufferDays ?? baseBufferDays));
// 获取预计交付日期(交期余量的日期版本:最后流程完成日期 + 基础缓冲期) // 获取预计交付日期(交期余量的日期版本:最后流程完成日期 + 基础缓冲期)
let expectedDeliveryDate = computeExpectedDeliveryDateTsFromResults(timelineResults, timelineAdjustments); let expectedDeliveryDate = computeExpectedDeliveryDateTsFromResults(timelineResults, timelineAdjustments, baseBufferDaysToUse);
if (mode === 'adjust' && isExpectedDeliveryDateLocked && lockedExpectedDeliveryDateTs !== null) { if (mode === 'adjust' && isExpectedDeliveryDateLocked && lockedExpectedDeliveryDateTs !== null) {
expectedDeliveryDate = lockedExpectedDeliveryDateTs; expectedDeliveryDate = lockedExpectedDeliveryDateTs;
} }
@ -3721,7 +3764,7 @@ export default function App() {
const styleText = style || ''; const styleText = style || '';
const colorText = color || ''; const colorText = color || '';
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments); const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments, baseBufferDaysToUse);
// 检查是否达到最终限制 // 检查是否达到最终限制
let hasReachedFinalLimit = false; let hasReachedFinalLimit = false;
@ -3740,7 +3783,7 @@ export default function App() {
} }
// 为快照提供基础缓冲与节点调整总量(用于兼容历史字段),尽管动态缓冲期已改为“自然日差”口径 // 为快照提供基础缓冲与节点调整总量(用于兼容历史字段),尽管动态缓冲期已改为“自然日差”口径
const baseBuferDays = baseBufferDays; const baseBuferDays = baseBufferDaysToUse;
const totalAdjustments = Object.values(timelineAdjustments).reduce((sum, adj) => sum + adj, 0); const totalAdjustments = Object.values(timelineAdjustments).reduce((sum, adj) => sum + adj, 0);
const globalSnapshot = { const globalSnapshot = {
@ -4436,6 +4479,45 @@ export default function App() {
const v = (rawExpected as any).value || (rawExpected as any).text || (rawExpected as any).name || ''; const v = (rawExpected as any).value || (rawExpected as any).text || (rawExpected as any).name || '';
if (typeof v === 'number') expectedDateObj = new Date(v); else expectedDateObj = parseDate(v); if (typeof v === 'number') expectedDateObj = new Date(v); else expectedDateObj = parseDate(v);
} }
const normalizeExcludedDates = (raw: any): string[] => {
const out: string[] = [];
const pushToken = (token: any) => {
if (token == null) return;
if (typeof token === 'number' && Number.isFinite(token)) {
const d = new Date(token);
if (!isNaN(d.getTime())) out.push(format(d, 'yyyy-MM-dd'));
return;
}
const text = (typeof token === 'string') ? token : extractText(token);
if (!text) return;
const parts = String(text).split(/[,;;、\n\r\t ]+/).map(s => s.trim()).filter(Boolean);
for (const p of parts) {
const head = p.slice(0, 10);
if (/^\d{4}-\d{2}-\d{2}$/.test(head)) {
out.push(head);
continue;
}
const parsed = parseDate(p);
if (parsed && !isNaN(parsed.getTime())) out.push(format(parsed, 'yyyy-MM-dd'));
}
};
const walk = (v: any) => {
if (Array.isArray(v)) {
v.forEach(walk);
return;
}
if (v && typeof v === 'object' && Array.isArray((v as any).value)) {
walk((v as any).value);
return;
}
pushToken(v);
};
walk(raw);
return Array.from(new Set(out)).filter(Boolean);
};
const batchExcludedDates = normalizeExcludedDates(f['fldQNxtHnd']);
const splitVals = (s: string) => (s || '').split(/[,,、]+/).map(v => v.trim()).filter(Boolean); const splitVals = (s: string) => (s || '').split(/[,,、]+/).map(v => v.trim()).filter(Boolean);
const normalizeToStringList = (raw: any): string[] => { const normalizeToStringList = (raw: any): string[] => {
if (!raw) return []; if (!raw) return [];
@ -4479,10 +4561,33 @@ export default function App() {
setStartTime(startDate || null); setStartTime(startDate || null);
setSelectedLabels(labels); setSelectedLabels(labels);
try { try {
const results = await handleCalculateTimeline(true, { selectedLabels: labels, expectedDate: expectedDateObj || null, startTime: startDate || null }, false); const results = await handleCalculateTimeline(true, { selectedLabels: labels, expectedDate: expectedDateObj || null, startTime: startDate || null, excludedDates: batchExcludedDates }, false);
if (results && results.length > 0) { if (results && results.length > 0) {
const baseForSuggestion = 14;
const suggestedBaseBufferDays = (() => {
if (!expectedDateObj || isNaN(expectedDateObj.getTime())) return baseForSuggestion;
const lastCompletionDate = getLastValidCompletionDateFromResults(results);
if (!lastCompletionDate) return baseForSuggestion;
const computedDeliveryDate = new Date(lastCompletionDate);
computedDeliveryDate.setDate(computedDeliveryDate.getDate() + baseForSuggestion);
const slackDays = differenceInCalendarDays(expectedDateObj, computedDeliveryDate);
return slackDays > 0 ? (baseForSuggestion + slackDays) : baseForSuggestion;
})();
const processRecordIds = await writeToProcessDataTable(results, { foreignId, style: styleText, color: colorText }); const processRecordIds = await writeToProcessDataTable(results, { foreignId, style: styleText, color: colorText });
const deliveryRecordId = await writeToDeliveryRecordTable(results, processRecordIds, {}, { foreignId, style: styleText, color: colorText, expectedDate: expectedDateObj || null, startTime: startDate || null, selectedLabels: labels }); const deliveryRecordId = await writeToDeliveryRecordTable(
results,
processRecordIds,
{},
{
foreignId,
style: styleText,
color: colorText,
expectedDate: expectedDateObj || null,
startTime: startDate || null,
selectedLabels: labels,
baseBufferDays: suggestedBaseBufferDays
}
);
try { try {
const candidateNames = ['状态','record_id','记录ID','货期记录ID','deliveryRecordId']; const candidateNames = ['状态','record_id','记录ID','货期记录ID','deliveryRecordId'];
let statusFieldId = ''; let statusFieldId = '';