5
This commit is contained in:
2025-11-14 18:56:14 +08:00
parent 7e76eb1fa7
commit ad6a3aaf2b

View File

@ -1,6 +1,6 @@
import { bitable, FieldType } from '@lark-base-open/js-sdk';
import { Button, Typography, List, Card, Space, Divider, Spin, Table, Select, Modal, DatePicker, InputNumber } from '@douyinfe/semi-ui';
import { useState, useEffect } from 'react';
import { useState, useEffect, useRef } from 'react';
import { addDays, format } from 'date-fns';
import { zhCN } from 'date-fns/locale';
import { executePricingQuery, executeSecondaryProcessQuery, executePricingDetailsQuery } from './services/apiService';
@ -136,6 +136,12 @@ export default function App() {
setRestoredRecordIds([]);
setRestoredRecordIdsText('');
// 重置初始快照捕获状态
try {
hasCapturedInitialSnapshotRef.current = false;
initialSnapshotRef.current = null;
} catch {}
// 当前记录与批量信息
setCurrentBatchRecord(null);
@ -2371,8 +2377,8 @@ export default function App() {
const totalAdjustments = Object.values(timelineAdjustments).reduce((sum, adj) => sum + adj, 0);
const baseBuferDays = baseBufferDays;
// 智能缓冲期计算逻辑
let dynamicBufferDays = Math.max(0, baseBuferDays - totalAdjustments);
// 智能缓冲期计算逻辑(缓冲期扣减=最后完成时间变动的自然天数)
let dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments);
// 只有在缓冲期为0时才检查最终限制最后节点预计完成时间是否已达到客户期望日期
let hasReachedFinalLimit = false;
@ -2503,8 +2509,8 @@ export default function App() {
return timelineResults;
};
// 获取重新计算后的时间线结果(不更新状态
const getRecalculatedTimeline = (adjustments: {[key: number]: number}) => {
// 获取重新计算后的时间线结果(不更新状态,逻辑对齐页面的重算口径
const getRecalculatedTimeline = (adjustments: { [key: number]: number }) => {
const updatedResults = [...timelineResults];
let cumulativeStartTime = startTime ? new Date(startTime) : new Date(); // 从起始时间开始
@ -2521,19 +2527,52 @@ export default function App() {
// 计算当前节点的开始时间
let nodeStartTime = new Date(cumulativeStartTime);
// 获取节点的计算方式、周末天数和排除日期
const nodeCalculationMethod = result.calculationMethod || 'external';
const nodeWeekendDays = result.weekendDays || [];
const nodeExcludedDates = result.excludedDates || [];
// 应用起始日期调整规则(与页面重算逻辑一致)
if (result.startDateRule) {
let ruleJson = '';
if (typeof result.startDateRule === 'string') {
ruleJson = result.startDateRule;
} else if (result.startDateRule && result.startDateRule.text) {
ruleJson = result.startDateRule.text;
}
if (ruleJson.trim()) {
nodeStartTime = adjustStartDateByRule(nodeStartTime, ruleJson);
}
}
// 获取调整规则描述
const ruleDescription = result.ruleDescription || '';
// 应用 JSON 日期调整规则(与页面重算逻辑一致)
let ruleDescription = result.ruleDescription || '';
if (result.dateAdjustmentRule) {
let ruleText = '';
if (typeof result.dateAdjustmentRule === 'string') {
ruleText = result.dateAdjustmentRule;
} else if (Array.isArray(result.dateAdjustmentRule)) {
ruleText = result.dateAdjustmentRule
.filter(item => item.type === 'text')
.map(item => item.text)
.join('');
} else if (result.dateAdjustmentRule && result.dateAdjustmentRule.text) {
ruleText = result.dateAdjustmentRule.text;
}
if (ruleText && ruleText.trim() !== '') {
const adjustmentResult = adjustStartDateByJsonRule(nodeStartTime, ruleText);
nodeStartTime = adjustmentResult.adjustedDate;
if (adjustmentResult.description) {
ruleDescription = adjustmentResult.description;
}
}
}
// 获取节点的计算方式、休息日与排除日期(与页面重算逻辑一致)
const nodeWeekendDays = result.weekendDaysConfig || [];
const nodeCalculationMethod = result.calculationMethod || '外部';
const nodeExcludedDates = Array.isArray(result.excludedDates) ? result.excludedDates : [];
// 计算节点的结束时间(允许负时效值向前回退)
let nodeEndTime;
let nodeEndTime: Date;
if (adjustedTimelineValue !== 0) {
const adjustedStartTime = adjustToNextWorkingHour(nodeStartTime, nodeCalculationMethod, nodeWeekendDays, nodeExcludedDates);
if (nodeCalculationMethod === 'internal') {
if (nodeCalculationMethod === '内部') {
nodeEndTime = addInternalBusinessTime(adjustedStartTime, adjustedTimelineValue, nodeWeekendDays, nodeExcludedDates);
} else {
nodeEndTime = addBusinessDaysWithHolidays(adjustedStartTime, adjustedTimelineValue, nodeWeekendDays, nodeExcludedDates);
@ -2542,34 +2581,31 @@ export default function App() {
nodeEndTime = new Date(nodeStartTime);
}
// 计算跳过的天数
// 计算跳过的天数及日期范围内的自定义跳过日期
const adjustedStartTime = adjustToNextWorkingHour(nodeStartTime, nodeCalculationMethod, nodeWeekendDays, nodeExcludedDates);
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,
adjustedTimelineValue,
estimatedStart: estimatedStartStr,
estimatedEnd: estimatedEndStr,
adjustment: adjustment,
calculationMethod: nodeCalculationMethod, // 保持计算方式
skippedWeekends: skippedWeekends,
actualDays: actualDays,
// 更新时间范围内实际跳过的日期
adjustment,
calculationMethod: nodeCalculationMethod,
skippedWeekends,
actualDays,
actualExcludedDates: excludedDatesInRange.dates,
actualExcludedDatesCount: excludedDatesInRange.count,
adjustmentDescription: result.adjustmentDescription, // 保持调整规则描述
ruleDescription: ruleDescription // 添加更新后的规则描述
adjustmentDescription: result.adjustmentDescription,
ruleDescription
};
// 更新累积时间:当前节点的完成时间成为下一个节点的开始时间
// 更新累积开始时间:使用当前节点的预计完成时间
if (adjustedTimelineValue !== 0) {
cumulativeStartTime = new Date(nodeEndTime);
}
@ -2578,6 +2614,56 @@ export default function App() {
return updatedResults;
};
// 获取有效的最后完成日期(忽略 '时效值为0'),若不存在则返回最后一项的日期
const getLastValidCompletionDateFromResults = (results: any[]): Date | null => {
if (!Array.isArray(results) || results.length === 0) return null;
for (let i = results.length - 1; i >= 0; i--) {
const endStr = results[i]?.estimatedEnd;
if (endStr && typeof endStr === 'string' && endStr !== '时效值为0') {
const d = new Date(endStr);
if (!isNaN(d.getTime())) return d;
}
}
const fallbackStr = results[results.length - 1]?.estimatedEnd;
const fallback = fallbackStr ? new Date(fallbackStr) : null;
return fallback && !isNaN(fallback.getTime()) ? fallback : null;
};
// 依据“最后流程节点的预计完成日期差(自然日)”计算剩余缓冲期
const computeDynamicBufferDaysUsingEndDelta = (adjustments: { [key: number]: number }): number => {
try {
const baseline = getRecalculatedTimeline({}); // 原始计划(不含任何调整)
const current = getRecalculatedTimeline(adjustments); // 当前计划(包含时效值调整)
// 严格取最后流程节点的预计完成日期;若不可用则回退到最后一个有效完成日期
const pickLastNodeEnd = (results: any[]): Date | null => {
if (!Array.isArray(results) || results.length === 0) return null;
const endStr = results[results.length - 1]?.estimatedEnd;
if (endStr && typeof endStr === 'string' && endStr !== '时效值为0') {
const d = new Date(endStr);
if (!isNaN(d.getTime())) return d;
}
return getLastValidCompletionDateFromResults(results);
};
const baselineLast = pickLastNodeEnd(baseline);
const currentLast = pickLastNodeEnd(current);
if (!baselineLast || !currentLast) {
return Math.max(0, Math.min(baseBufferDays, baseBufferDays));
}
// 仅使用“预计完成日期”差,不叠加 completionDateAdjustment
const dayMs = 1000 * 60 * 60 * 24;
const deltaDays = Math.ceil((currentLast.getTime() - baselineLast.getTime()) / dayMs);
// 缓冲期剩余 = clamp(基础缓冲期 - 差值, 0, 基础缓冲期)
const remaining = baseBufferDays - deltaDays;
return Math.max(0, Math.min(baseBufferDays, remaining));
} catch {
// 兜底:如出现异常,保持基础缓冲期
return Math.max(0, Math.min(baseBufferDays, baseBufferDays));
}
};
// 重新计算时间线的函数
const recalculateTimeline = (adjustments: {[key: number]: number}, forceRecalculateAll: boolean = false) => {
const updatedResults = [...timelineResults];
@ -2750,6 +2836,10 @@ export default function App() {
const [hasAppliedSuggestedBuffer, setHasAppliedSuggestedBuffer] = useState(false);
// 初始状态快照(仅捕获一次)
const initialSnapshotRef = useRef<any>(null);
const hasCapturedInitialSnapshotRef = useRef<boolean>(false);
// 当起始时间变更时,重新以最新起始时间为基准重算全流程
useEffect(() => {
if (timelineResults.length > 0 && !isRestoringSnapshot) {
@ -2764,6 +2854,26 @@ export default function App() {
}
}, [actualCompletionDates, isRestoringSnapshot]);
// 捕获初始状态快照(在首次生成/还原出完整时间线后)
useEffect(() => {
if (!hasCapturedInitialSnapshotRef.current && timelineResults.length > 0) {
initialSnapshotRef.current = {
startTime,
expectedDate,
selectedLabels,
timelineResults,
timelineAdjustments,
baseBufferDays,
actualCompletionDates,
completionDateAdjustment,
hasAppliedSuggestedBuffer,
deliveryMarginDeductions,
};
hasCapturedInitialSnapshotRef.current = true;
console.log('已捕获初始状态快照');
}
}, [timelineResults, startTime, expectedDate]);
// 重置调整的函数
const resetTimelineAdjustments = () => {
setTimelineAdjustments({});
@ -2775,6 +2885,40 @@ export default function App() {
recalculateTimeline({}, true); // 强制重算所有节点
};
// 一键还原到最初状态
const resetToInitialState = async () => {
try {
if (!hasCapturedInitialSnapshotRef.current || !initialSnapshotRef.current) {
// 若未捕获到初始快照,则退化为仅重置调整
resetTimelineAdjustments();
if (bitable.ui.showToast) {
await bitable.ui.showToast({ toastType: 'warning', message: '未检测到初始快照,已重置调整项' });
}
return;
}
const s = initialSnapshotRef.current;
setIsRestoringSnapshot(true);
setStartTime(s.startTime || null);
setExpectedDate(s.expectedDate || null);
setSelectedLabels(s.selectedLabels || {});
setTimelineAdjustments(s.timelineAdjustments || {});
setBaseBufferDays(s.baseBufferDays ?? 14);
setActualCompletionDates(s.actualCompletionDates || {});
setCompletionDateAdjustment(s.completionDateAdjustment || 0);
setHasAppliedSuggestedBuffer(!!s.hasAppliedSuggestedBuffer && s.hasAppliedSuggestedBuffer);
setDeliveryMarginDeductions(s.deliveryMarginDeductions || 0);
setTimelineResults(Array.isArray(s.timelineResults) ? s.timelineResults : []);
recalculateTimeline(s.timelineAdjustments || {}, true);
setIsRestoringSnapshot(false);
if (bitable.ui.showToast) {
await bitable.ui.showToast({ toastType: 'success', message: '已恢复至最初状态' });
}
} catch (e) {
console.error('恢复初始状态失败:', e);
setIsRestoringSnapshot(false);
}
};
// 已移除未使用的 getTimelineLabelFieldId 辅助函数
// 写入货期记录表的函数
@ -2972,10 +3116,8 @@ export default function App() {
try {
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 dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments);
// 加上动态缓冲期
const deliveryDate = new Date(lastCompletionDate);
@ -3050,9 +3192,7 @@ export default function App() {
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);
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments);
// 检查是否达到最终限制
let hasReachedFinalLimit = false;
@ -3070,6 +3210,10 @@ export default function App() {
}
}
// 为快照提供基础缓冲与节点调整总量(用于兼容历史字段),尽管动态缓冲期已改为“自然日差”口径
const baseBuferDays = baseBufferDays;
const totalAdjustments = Object.values(timelineAdjustments).reduce((sum, adj) => sum + adj, 0);
const globalSnapshot = {
version: versionNumber,
foreignId,
@ -5468,10 +5612,24 @@ export default function App() {
{/* 建议缓冲期增量:根据实际完成偏差与交期余量自动反算 */}
{(() => {
try {
// 计算节点总调整量与动态缓冲期
const totalAdjustments = Object.values(timelineAdjustments).reduce((sum, adj) => sum + adj, 0);
// 仅当用户本次会话发生改动时显示建议
const initialSnap = initialSnapshotRef.current;
const hasAdjustmentChanges = Object.values(timelineAdjustments || {}).some(v => (Number(v) || 0) !== 0) || (Number(completionDateAdjustment) || 0) !== 0;
const toMillisMap = (obj: any) => {
const entries = Object.entries(obj || {});
return Object.fromEntries(entries.map(([k, v]) => [k, v ? new Date(v as any).getTime() : null]));
};
const currentActualMap = toMillisMap(actualCompletionDates);
const initialActualMap = toMillisMap(initialSnap?.actualCompletionDates || {});
const hasActualChanges = JSON.stringify(currentActualMap) !== JSON.stringify(initialActualMap);
const hasUserSessionChanges = hasAdjustmentChanges || hasActualChanges;
if (!hasUserSessionChanges) {
return null;
}
// 计算动态缓冲期(按“最后完成时间自然日差”扣减基础缓冲期)
const baseBuferDays = baseBufferDays;
const dynamicBufferDays = Math.max(0, baseBuferDays - totalAdjustments);
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments);
// 获取有效的最后流程完成日期
let effectiveLastProcess: any = null;
@ -5562,7 +5720,7 @@ export default function App() {
return null;
}
})()}
<Button onClick={resetTimelineAdjustments}>
<Button onClick={resetToInitialState}>
</Button>
</Space>
@ -5649,10 +5807,10 @@ export default function App() {
// 当前节点是否为零值的周转周期节点
const isCurrentTurnoverZero = isTurnoverNode && adjustedValue === 0;
// 计算动态缓冲期,用于限制节点增加操作
// 计算动态缓冲期(按“最后完成时间变动天数”扣减缓冲期)
const totalAdjustments = Object.values(timelineAdjustments).reduce((sum, adj) => sum + adj, 0);
const baseBuferDays = baseBufferDays;
const dynamicBufferDays = Math.max(0, baseBuferDays - totalAdjustments);
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments);
// 新的复合限制逻辑:
// 1. 如果缓冲期 > 0允许操作
@ -5904,8 +6062,6 @@ export default function App() {
<div>
<Text strong style={{ display: 'block', marginBottom: '2px', fontSize: '15px' }}></Text>
<div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
{/* 注释掉 -0.5 功能按钮 */}
{false && (
<Button
size="small"
onClick={() => handleComplexAdjustment(index, -0.5)}
@ -5915,9 +6071,6 @@ export default function App() {
>
-0.5
</Button>
)}
{/* 注释掉 -1 功能按钮 */}
{false && (
<Button
size="small"
onClick={() => handleComplexAdjustment(index, -1)}
@ -5927,7 +6080,6 @@ export default function App() {
>
-1
</Button>
)}
<div style={{
minWidth: '70px',
textAlign: 'center',
@ -5952,8 +6104,6 @@ export default function App() {
</div>
)}
</div>
{/* 注释掉 +1 功能按钮 */}
{false && (
<Button
size="small"
onClick={() => handleComplexAdjustment(index, 1)}
@ -5968,9 +6118,6 @@ export default function App() {
>
+1
</Button>
)}
{/* 注释掉 +0.5 功能按钮 */}
{false && (
<Button
size="small"
onClick={() => handleComplexAdjustment(index, 0.5)}
@ -5985,7 +6132,6 @@ export default function App() {
>
+0.5
</Button>
)}
</div>
</div>
@ -6121,12 +6267,8 @@ export default function App() {
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 dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments);
// 计算最后完成日期 + 动态缓冲期(考虑最后流程完成日期的调整)
const adjustedCompletionDate = new Date(lastCompletionDate);
@ -6409,10 +6551,10 @@ export default function App() {
lineHeight: '1.5'
}}>
{(() => {
// 计算动态缓冲期和总调整量
const totalAdjustments = Object.values(timelineAdjustments).reduce((sum, adj) => sum + adj, 0);
// 计算动态缓冲期(按“最后完成时间自然日差”扣减基础缓冲期)
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments);
const baseBuferDays = baseBufferDays;
const dynamicBufferDays = Math.max(0, baseBuferDays - totalAdjustments);
const deltaDays = baseBuferDays - dynamicBufferDays; // 自然日差已按0..baseBuferDays夹取
return (
<Text>
@ -6427,11 +6569,11 @@ export default function App() {
: '• 显示预计交付日期距离今天的天数'
}
<br />
= {baseBuferDays} - 0
{totalAdjustments > 0 && (
= {baseBuferDays} - 0
{deltaDays > 0 && (
<>
<br />
+{totalAdjustments}
+{deltaDays}
</>
)}
</Text>