5
This commit is contained in:
2026-03-13 14:18:19 +08:00
parent b8b65e684a
commit 24fa103a01

View File

@ -172,6 +172,7 @@ export default function App() {
},
showUI: boolean
} | null>(null);
const skipNextGroupConfigPopupRef = useRef(false);
// 客户期望日期状态
const [expectedDate, setExpectedDate] = useState<Date | null>(null);
@ -203,8 +204,8 @@ export default function App() {
const [completionDateAdjustment, setCompletionDateAdjustment] = useState<number>(0);
// 实际完成日期状态:记录每个节点的实际完成日期
const [actualCompletionDates, setActualCompletionDates] = useState<{[key: number]: Date | null}>({});
// 基础缓冲期天数(可配置),用于计算动态缓冲期,默认14
const [baseBufferDays, setBaseBufferDays] = useState<number>(14);
// 基础缓冲期天数(可配置),用于计算动态缓冲期,默认0
const [baseBufferDays, setBaseBufferDays] = useState<number>(0);
const [lockedExpectedDeliveryDateTs, setLockedExpectedDeliveryDateTs] = useState<number | null>(null);
const [isExpectedDeliveryDateLocked, setIsExpectedDeliveryDateLocked] = useState<boolean>(false);
// 快照回填来源foreign_id、款式、颜色、文本2
@ -276,7 +277,7 @@ export default function App() {
setTimelineResults([]);
setTimelineAdjustments({});
// 新增:重置固定缓冲期、实际完成日期以及一次性建议缓冲期应用标志
setBaseBufferDays(14);
setBaseBufferDays(0);
setActualCompletionDates({});
setHasAppliedSuggestedBuffer(false);
setIsRestoringSnapshot(false);
@ -3115,8 +3116,15 @@ export default function App() {
)
);
if (allGroups.length > 1 && groupOrderConfig.length === 0 && showUI) {
const initial = deriveGroupOrderDraftByProcessOrder(matchedProcessNodes);
if (showUI && allGroups.length > 0) {
if (skipNextGroupConfigPopupRef.current) {
skipNextGroupConfigPopupRef.current = false;
} else {
const groupSet = new Set(allGroups);
const existing = Array.isArray(groupOrderConfig)
? groupOrderConfig.filter(inst => groupSet.has((inst?.groupName || '').trim()))
: [];
const initial = existing.length > 0 ? existing : deriveGroupOrderDraftByProcessOrder(matchedProcessNodes);
setGroupOrderDraft(initial);
setGroupConfigVisible(true);
setTimelineLoading(false);
@ -3132,6 +3140,7 @@ export default function App() {
showUI,
};
return [];
}
}
let orderedProcessNodes: any[] = matchedProcessNodes;
@ -3681,43 +3690,6 @@ export default function App() {
pendingRecalculateAfterCalculateAdjustmentsRef.current = nextAdjustments;
pendingRecalculateAfterCalculateRef.current = true;
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) {
@ -4105,24 +4077,57 @@ export default function App() {
}
};
const getAdjustedLastCompletionDate = (results: any[], completionAdjustmentOverride?: number): Date | null => {
const lastCompletionDate = getLastValidCompletionDateFromResults(results);
if (!lastCompletionDate) return null;
const adjusted = new Date(lastCompletionDate);
const adj = Number.isFinite(completionAdjustmentOverride as number)
? (completionAdjustmentOverride as number)
: completionDateAdjustment;
if (Number.isFinite(adj) && adj !== 0) {
adjusted.setDate(adjusted.getDate() + adj);
}
return adjusted;
};
const computeAutoBufferDaysUsingExpectedDate = (
results: any[],
expectedDateOverride?: Date | null,
completionAdjustmentOverride?: number
): number => {
try {
if (timelineDirection === 'backward') return 0;
const expected = expectedDateOverride ?? expectedDate;
if (!expected || isNaN(expected.getTime())) return 0;
const adjustedCompletionDate = getAdjustedLastCompletionDate(results, completionAdjustmentOverride);
if (!adjustedCompletionDate) return 0;
return differenceInCalendarDays(expected, adjustedCompletionDate);
} catch {
return 0;
}
};
const computeBufferDeficitDaysUsingEndDelta = (adjustments: Record<string, number>): number => {
const deltaDays = computeLastNodeEndDeltaDays(adjustments);
const base = Math.max(0, Math.ceil(baseBufferDays));
return Math.max(0, deltaDays - base);
const autoBufferDays = computeAutoBufferDaysUsingExpectedDate(timelineResults, expectedDate, completionDateAdjustment);
return Math.max(0, deltaDays - autoBufferDays);
};
const computeExpectedDeliveryDateTsFromResults = (
results: any[],
adjustments: Record<string, number>,
baseBufferDaysOverride?: number
expectedDateOverride?: Date | null,
completionAdjustmentOverride?: number
): number | null => {
if (timelineDirection === 'backward') return null;
const lastCompletionDate = getLastValidCompletionDateFromResults(results);
if (!lastCompletionDate) return null;
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(adjustments, baseBufferDaysOverride);
const deliveryDate = new Date(lastCompletionDate);
deliveryDate.setDate(deliveryDate.getDate() + dynamicBufferDays);
const ts = deliveryDate.getTime();
const adjustedCompletionDate = getAdjustedLastCompletionDate(results, completionAdjustmentOverride);
if (!adjustedCompletionDate) return null;
const expected = expectedDateOverride ?? expectedDate;
if (expected && !isNaN(expected.getTime())) {
const ts = expected.getTime();
return Number.isFinite(ts) ? ts : null;
}
const ts = adjustedCompletionDate.getTime();
return Number.isFinite(ts) ? ts : null;
};
@ -4144,17 +4149,18 @@ export default function App() {
setLockedExpectedDeliveryDateTs(expectedDate.getTime());
};
const computeDynamicBufferDaysUsingEndDelta = (adjustments: Record<string, number>, baseBufferDaysOverride?: number): number => {
try {
if (timelineDirection === 'backward') return 0;
const deltaDays = computeLastNodeEndDeltaDays(adjustments);
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(baseBufferDaysOverride ?? baseBufferDays));
return base;
}
const computeDynamicBufferDaysUsingEndDelta = (
adjustments: Record<string, number>,
expectedDateOverride?: Date | null,
resultsOverride?: any[],
completionAdjustmentOverride?: number
): number => {
const results = resultsOverride ?? timelineResults;
const expected = expectedDateOverride ?? expectedDate;
const completionAdj = Number.isFinite(completionAdjustmentOverride as number)
? (completionAdjustmentOverride as number)
: completionDateAdjustment;
return computeAutoBufferDaysUsingExpectedDate(results, expected, completionAdj);
};
// 重新计算时间线的函数
@ -4441,7 +4447,7 @@ export default function App() {
setDeliveryMarginDeductions(0); // 同时重置交期余量扣减
setCompletionDateAdjustment(0); // 重置最后流程完成日期调整
setActualCompletionDates({}); // 重置实际完成日期
setBaseBufferDays(14); // 重置固定缓冲期为默认值
setBaseBufferDays(0); // 重置固定缓冲期为默认值
setIsExpectedDeliveryDateLocked(false);
try { lastBufferDeficitRef.current = 0; } catch {}
setHasAppliedSuggestedBuffer(false); // 重置建议缓冲期应用标志
@ -4470,7 +4476,7 @@ export default function App() {
setSelectedLabels(s.selectedLabels || {});
const normalizedAdjustments = normalizeTimelineAdjustmentsFromSnapshot(s.timelineAdjustments || {}, s.timelineResults || []);
setTimelineAdjustments(normalizedAdjustments);
setBaseBufferDays(s.baseBufferDays ?? 14);
setBaseBufferDays(s.baseBufferDays ?? 0);
const shouldLock = s.isExpectedDeliveryDateLocked === true || (s.isExpectedDeliveryDateLocked === undefined && s.lockedExpectedDeliveryDateTs !== null && s.lockedExpectedDeliveryDateTs !== undefined);
if (shouldLock && typeof s.lockedExpectedDeliveryDateTs === 'number' && Number.isFinite(s.lockedExpectedDeliveryDateTs)) {
setLockedExpectedDeliveryDateTs(s.lockedExpectedDeliveryDateTs);
@ -4669,11 +4675,9 @@ 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, baseBufferDaysToUse);
if (mode === 'adjust' && isExpectedDeliveryDateLocked && lockedExpectedDeliveryDateTs !== null) {
// 获取预计交付日期(交期余量的日期版本:根据客户期望日期自动计算缓冲期)
let expectedDeliveryDate = computeExpectedDeliveryDateTsFromResults(timelineResults, timelineAdjustments, overrides?.expectedDate ?? expectedDate, completionDateAdjustment);
if (isExpectedDeliveryDateLocked && lockedExpectedDeliveryDateTs !== null) {
expectedDeliveryDate = lockedExpectedDeliveryDateTs;
}
@ -4734,26 +4738,21 @@ export default function App() {
const styleText = style || '';
const colorText = color || '';
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments, baseBufferDaysToUse);
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments, expectedDateToUse, timelineResults, completionDateAdjustment);
// 检查是否达到最终限制
let hasReachedFinalLimit = false;
const currentExpectedDate = 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 currentExpectedDate = expectedDateToUse;
if (currentExpectedDate) {
const adjustedLastCompletion = getAdjustedLastCompletionDate(timelineResults, completionDateAdjustment);
if (adjustedLastCompletion) {
const daysToExpected = differenceInCalendarDays(currentExpectedDate, adjustedLastCompletion);
hasReachedFinalLimit = daysToExpected <= 0;
}
}
// 为快照提供基础缓冲与节点调整总量(用于兼容历史字段),尽管动态缓冲期已改为“自然日差”口径
const baseBuferDays = baseBufferDaysToUse;
const baseBuferDays = dynamicBufferDays;
const totalAdjustments = Object.values(timelineAdjustments).reduce((sum, adj) => sum + adj, 0);
const globalSnapshot = {
@ -5566,16 +5565,6 @@ export default function App() {
try {
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,
@ -5587,8 +5576,7 @@ export default function App() {
color: colorText,
expectedDate: expectedDateObj || null,
startTime: startDate || null,
selectedLabels: labels,
baseBufferDays: suggestedBaseBufferDays
selectedLabels: labels
}
);
try {
@ -6718,6 +6706,7 @@ export default function App() {
onClick={() => {
const nextConfig = Array.isArray(groupOrderDraft) ? groupOrderDraft : [];
setGroupOrderConfig(nextConfig);
skipNextGroupConfigPopupRef.current = true;
setGroupConfigVisible(false);
if (!pendingGroupConfigCalcRef.current && mode === 'adjust') {
const next = applyGroupOrderConfigToTimelineResults(timelineResults, nextConfig);
@ -6958,16 +6947,14 @@ export default function App() {
>
<span style={{ fontSize: 14, lineHeight: 1 }}></span>
</Button>
{mode === 'adjust' && (
<>
<Text type="tertiary" style={{ marginLeft: 8 }}></Text>
<Switch
checked={isExpectedDeliveryDateLocked}
onChange={handleExpectedDeliveryDateLockChange}
style={{ marginLeft: 4 }}
/>
</>
)}
<>
<Text type="tertiary" style={{ marginLeft: 8 }}></Text>
<Switch
checked={isExpectedDeliveryDateLocked}
onChange={handleExpectedDeliveryDateLockChange}
style={{ marginLeft: 4 }}
/>
</>
</div>
</div>
}
@ -6978,31 +6965,38 @@ export default function App() {
setDeliveryMarginDeductions(0); // 关闭时重置交期余量扣减
setCompletionDateAdjustment(0); // 关闭时重置最后流程完成日期调整
setStyleColorEditable(false); // 关闭弹窗后恢复为锁定状态
if (mode !== 'adjust') {
setLockedExpectedDeliveryDateTs(null);
setIsExpectedDeliveryDateLocked(false);
}
}}
footer={
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Space align="center">
<Text>{mode === 'adjust' && isExpectedDeliveryDateLocked ? '基础缓冲期(天)(不影响已锁交付)' : '基础缓冲期(天)'}</Text>
<InputNumber
min={0}
step={1}
value={baseBufferDays}
onChange={(val) => {
const n = Number(val);
setBaseBufferDays(Number.isFinite(n) && n >= 0 ? Math.ceil(n) : 0);
}}
style={{ width: 90 }}
/>
<Text>()</Text>
<InputNumber
value={computeDynamicBufferDaysUsingEndDelta(timelineAdjustments)}
disabled
style={{ width: 90 }}
/>
<Text>{isExpectedDeliveryDateLocked ? '缓冲期(天)(不影响已锁交付)' : '缓冲期(天)'}</Text>
{(() => {
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments, expectedDate, timelineResults, completionDateAdjustment);
const canEdit = !isExpectedDeliveryDateLocked && !!getAdjustedLastCompletionDate(timelineResults, completionDateAdjustment);
return (
<InputNumber
value={dynamicBufferDays}
disabled={!canEdit}
step={1}
onChange={(val) => {
if (!canEdit) return;
const n = Number(val);
if (!Number.isFinite(n)) return;
const adjustedCompletion = getAdjustedLastCompletionDate(timelineResults, completionDateAdjustment);
if (!adjustedCompletion) return;
const nextExpected = new Date(adjustedCompletion);
nextExpected.setDate(nextExpected.getDate() + n);
setExpectedDate(nextExpected);
}}
style={{
width: 90,
backgroundColor: dynamicBufferDays < 0 ? '#fff1f0' : undefined,
borderColor: dynamicBufferDays < 0 ? '#ff4d4f' : undefined,
color: dynamicBufferDays < 0 ? '#cf1322' : undefined
}}
/>
);
})()}
<Button onClick={resetToInitialState}>
</Button>
@ -7082,8 +7076,8 @@ export default function App() {
// 当前节点是否为零值的周转周期节点
const isCurrentTurnoverZero = isTurnoverNode && adjustedValue === 0;
// 计算动态缓冲期(按“最后完成时间变动天数”扣减缓冲期)
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments);
// 计算动态缓冲期
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments, expectedDate, timelineResults, completionDateAdjustment);
return (
<Card key={index} style={{ marginBottom: '8px', padding: '12px', position: 'relative' }}>
@ -7522,12 +7516,12 @@ export default function App() {
}
// 缓冲期(动态)与调整后的最后完成日期
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments);
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments, expectedDate, timelineResults, completionDateAdjustment);
const adjustedCompletionDate = new Date(lastCompletionDate!);
adjustedCompletionDate.setDate(adjustedCompletionDate.getDate() + completionDateAdjustment);
const deliveryDate = new Date(adjustedCompletionDate);
deliveryDate.setDate(deliveryDate.getDate() + dynamicBufferDays);
const lockedDeliveryDate = (mode === 'adjust' && isExpectedDeliveryDateLocked && lockedExpectedDeliveryDateTs !== null)
const lockedDeliveryDate = (isExpectedDeliveryDateLocked && lockedExpectedDeliveryDateTs !== null)
? new Date(lockedExpectedDeliveryDateTs)
: null;
@ -7558,9 +7552,9 @@ export default function App() {
<span style={{ fontSize: 13, color: '#666', fontWeight: 600 }}></span>
<span style={{
fontSize: 13,
color: '#1890ff',
backgroundColor: 'rgba(24, 144, 255, 0.08)',
border: '1px solid rgba(24, 144, 255, 0.2)',
color: dynamicBufferDays < 0 ? '#cf1322' : '#1890ff',
backgroundColor: dynamicBufferDays < 0 ? '#fff1f0' : 'rgba(24, 144, 255, 0.08)',
border: dynamicBufferDays < 0 ? '1px solid #ff4d4f' : '1px solid rgba(24, 144, 255, 0.2)',
borderRadius: 16,
padding: '3px 8px',
fontWeight: 700,
@ -7610,7 +7604,7 @@ export default function App() {
style={{ width: '200px' }}
placeholder="请选择客户期望日期"
value={expectedDate ?? undefined}
disabled={mode === 'adjust' && isExpectedDeliveryDateLocked}
disabled={isExpectedDeliveryDateLocked}
onChange={(date) => {
if (date instanceof Date) {
setExpectedDate(date);