6
6
This commit is contained in:
149
src/App.tsx
149
src/App.tsx
@ -1976,7 +1976,8 @@ export default function App() {
|
||||
recordDetails?: any[],
|
||||
selectedLabels?: {[key: string]: string | string[]},
|
||||
expectedDate?: Date | null,
|
||||
startTime?: Date | null
|
||||
startTime?: Date | null,
|
||||
excludedDates?: string[]
|
||||
},
|
||||
showUI: boolean = true // 新增参数控制是否显示UI
|
||||
) => {
|
||||
@ -1986,6 +1987,7 @@ export default function App() {
|
||||
const currentSelectedLabels = overrideData?.selectedLabels || selectedLabels;
|
||||
const currentExpectedDate = overrideData?.expectedDate || expectedDate;
|
||||
const currentStartTime = overrideData?.startTime || startTime;
|
||||
const currentExcludedDates = Array.isArray(overrideData?.excludedDates) ? overrideData!.excludedDates : [];
|
||||
const isBackward = timelineDirection === 'backward';
|
||||
|
||||
console.log('=== handleCalculateTimeline - 使用的数据 ===');
|
||||
@ -2381,6 +2383,7 @@ export default function App() {
|
||||
|
||||
for (let i = 0; i < nodesToProcess.length; i++) {
|
||||
const processNode = nodesToProcess[i];
|
||||
const nodeExcludedDates = Array.from(new Set([...(processNode.excludedDates || []), ...currentExcludedDates])).filter(Boolean);
|
||||
let timelineValue = null;
|
||||
let matchedTimelineRecord = null;
|
||||
|
||||
@ -2587,15 +2590,15 @@ export default function App() {
|
||||
// 计算当前节点的开始和完成时间(使用工作日计算)
|
||||
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;
|
||||
if (calculationMethod === '内部') {
|
||||
// 使用内部工作时间计算
|
||||
endDate = addInternalBusinessTime(adjustedStartDate, timelineValue, processNode.weekendDays, processNode.excludedDates || []);
|
||||
endDate = addInternalBusinessTime(adjustedStartDate, timelineValue, processNode.weekendDays, nodeExcludedDates);
|
||||
} else {
|
||||
// 使用原有的24小时制计算
|
||||
endDate = addBusinessDaysWithHolidays(adjustedStartDate, timelineValue, processNode.weekendDays, processNode.excludedDates || []);
|
||||
endDate = addBusinessDaysWithHolidays(adjustedStartDate, timelineValue, processNode.weekendDays, nodeExcludedDates);
|
||||
}
|
||||
|
||||
return {
|
||||
@ -2674,11 +2677,11 @@ export default function App() {
|
||||
};
|
||||
|
||||
if (timelineValue) {
|
||||
const adjustedStartTime = adjustToNextWorkingHour(nodeStartTime, nodeCalculationMethod, processNode.weekendDays, processNode.excludedDates || []);
|
||||
const adjustedStartTime = adjustToNextWorkingHour(nodeStartTime, nodeCalculationMethod, processNode.weekendDays, nodeExcludedDates);
|
||||
if (nodeCalculationMethod === '内部') {
|
||||
nodeEndTime = addInternalBusinessTime(adjustedStartTime, timelineValue, processNode.weekendDays, processNode.excludedDates || []);
|
||||
nodeEndTime = addInternalBusinessTime(adjustedStartTime, timelineValue, processNode.weekendDays, nodeExcludedDates);
|
||||
} else {
|
||||
nodeEndTime = addBusinessDaysWithHolidays(adjustedStartTime, timelineValue, processNode.weekendDays, processNode.excludedDates || []);
|
||||
nodeEndTime = addBusinessDaysWithHolidays(adjustedStartTime, timelineValue, processNode.weekendDays, nodeExcludedDates);
|
||||
}
|
||||
} else {
|
||||
nodeEndTime = new Date(nodeStartTime);
|
||||
@ -2687,9 +2690,9 @@ export default function App() {
|
||||
nodeEndTime = new Date(cumulativeTime);
|
||||
if (timelineValue) {
|
||||
if (nodeCalculationMethod === '内部') {
|
||||
nodeStartTime = addInternalBusinessTime(nodeEndTime, -timelineValue, processNode.weekendDays, processNode.excludedDates || []);
|
||||
nodeStartTime = addInternalBusinessTime(nodeEndTime, -timelineValue, processNode.weekendDays, nodeExcludedDates);
|
||||
} else {
|
||||
nodeStartTime = addBusinessDaysWithHolidays(nodeEndTime, -timelineValue, processNode.weekendDays, processNode.excludedDates || []);
|
||||
nodeStartTime = addBusinessDaysWithHolidays(nodeEndTime, -timelineValue, processNode.weekendDays, nodeExcludedDates);
|
||||
}
|
||||
} else {
|
||||
nodeStartTime = new Date(nodeEndTime);
|
||||
@ -2711,7 +2714,7 @@ export default function App() {
|
||||
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))({
|
||||
processOrder: processNode.processOrder,
|
||||
@ -2733,7 +2736,7 @@ export default function App() {
|
||||
// 新增:标识是否为累加处理
|
||||
isAccumulated: processingRule === '累加值' && matchedCandidates.length > 1,
|
||||
weekendDaysConfig: processNode.weekendDays, // 新增:保存休息日配置用于显示
|
||||
excludedDates: processNode.excludedDates || [], // 新增:保存不参与计算日期用于显示与快照
|
||||
excludedDates: nodeExcludedDates, // 新增:保存不参与计算日期用于显示与快照
|
||||
// 新增:保存时间范围内实际跳过的日期
|
||||
actualExcludedDates: excludedDatesInRange.dates,
|
||||
actualExcludedDatesCount: excludedDatesInRange.count,
|
||||
@ -2792,6 +2795,43 @@ export default function App() {
|
||||
}
|
||||
|
||||
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) {
|
||||
setTimelineVisible(true);
|
||||
} else if (showUI && delayShowTimelineModal) {
|
||||
@ -3160,12 +3200,13 @@ export default function App() {
|
||||
|
||||
const computeExpectedDeliveryDateTsFromResults = (
|
||||
results: any[],
|
||||
adjustments: { [key: number]: number }
|
||||
adjustments: { [key: number]: number },
|
||||
baseBufferDaysOverride?: number
|
||||
): number | null => {
|
||||
if (timelineDirection === 'backward') return null;
|
||||
const lastCompletionDate = getLastValidCompletionDateFromResults(results);
|
||||
if (!lastCompletionDate) return null;
|
||||
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(adjustments);
|
||||
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(adjustments, baseBufferDaysOverride);
|
||||
const deliveryDate = new Date(lastCompletionDate);
|
||||
deliveryDate.setDate(deliveryDate.getDate() + dynamicBufferDays);
|
||||
const ts = deliveryDate.getTime();
|
||||
@ -3181,15 +3222,15 @@ export default function App() {
|
||||
if (computed !== null) setLockedExpectedDeliveryDateTs(computed);
|
||||
};
|
||||
|
||||
const computeDynamicBufferDaysUsingEndDelta = (adjustments: { [key: number]: number }): number => {
|
||||
const computeDynamicBufferDaysUsingEndDelta = (adjustments: { [key: number]: number }, baseBufferDaysOverride?: number): number => {
|
||||
try {
|
||||
if (timelineDirection === 'backward') return 0;
|
||||
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;
|
||||
return Math.max(0, Math.min(base, remaining));
|
||||
} catch {
|
||||
const base = Math.max(0, Math.ceil(baseBufferDays));
|
||||
const base = Math.max(0, Math.ceil(baseBufferDaysOverride ?? baseBufferDays));
|
||||
return base;
|
||||
}
|
||||
};
|
||||
@ -3502,7 +3543,7 @@ export default function App() {
|
||||
timelineResults: any[],
|
||||
processRecordIds: string[],
|
||||
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;
|
||||
try {
|
||||
@ -3664,8 +3705,10 @@ export default function App() {
|
||||
// 获取标签汇总:批量模式优先使用传入的labels
|
||||
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) {
|
||||
expectedDeliveryDate = lockedExpectedDeliveryDateTs;
|
||||
}
|
||||
@ -3721,7 +3764,7 @@ export default function App() {
|
||||
const styleText = style || '';
|
||||
const colorText = color || '';
|
||||
|
||||
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments);
|
||||
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments, baseBufferDaysToUse);
|
||||
|
||||
// 检查是否达到最终限制
|
||||
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 globalSnapshot = {
|
||||
@ -4436,6 +4479,45 @@ export default function App() {
|
||||
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);
|
||||
}
|
||||
|
||||
const normalizeExcludedDates = (raw: any): string[] => {
|
||||
const out: string[] = [];
|
||||
const pushToken = (token: any) => {
|
||||
if (token == null) return;
|
||||
if (typeof token === 'number' && Number.isFinite(token)) {
|
||||
const d = new Date(token);
|
||||
if (!isNaN(d.getTime())) out.push(format(d, 'yyyy-MM-dd'));
|
||||
return;
|
||||
}
|
||||
const text = (typeof token === 'string') ? token : extractText(token);
|
||||
if (!text) return;
|
||||
const parts = String(text).split(/[,,;;、\n\r\t ]+/).map(s => s.trim()).filter(Boolean);
|
||||
for (const p of parts) {
|
||||
const head = p.slice(0, 10);
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(head)) {
|
||||
out.push(head);
|
||||
continue;
|
||||
}
|
||||
const parsed = parseDate(p);
|
||||
if (parsed && !isNaN(parsed.getTime())) out.push(format(parsed, 'yyyy-MM-dd'));
|
||||
}
|
||||
};
|
||||
const walk = (v: any) => {
|
||||
if (Array.isArray(v)) {
|
||||
v.forEach(walk);
|
||||
return;
|
||||
}
|
||||
if (v && typeof v === 'object' && Array.isArray((v as any).value)) {
|
||||
walk((v as any).value);
|
||||
return;
|
||||
}
|
||||
pushToken(v);
|
||||
};
|
||||
walk(raw);
|
||||
return Array.from(new Set(out)).filter(Boolean);
|
||||
};
|
||||
|
||||
const batchExcludedDates = normalizeExcludedDates(f['fldQNxtHnd']);
|
||||
const splitVals = (s: string) => (s || '').split(/[,,、]+/).map(v => v.trim()).filter(Boolean);
|
||||
const normalizeToStringList = (raw: any): string[] => {
|
||||
if (!raw) return [];
|
||||
@ -4479,10 +4561,33 @@ export default function App() {
|
||||
setStartTime(startDate || null);
|
||||
setSelectedLabels(labels);
|
||||
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) {
|
||||
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 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 {
|
||||
const candidateNames = ['状态','record_id','记录ID','货期记录ID','deliveryRecordId'];
|
||||
let statusFieldId = '';
|
||||
|
||||
Reference in New Issue
Block a user