1
1
This commit is contained in:
491
src/App.tsx
491
src/App.tsx
@ -1,5 +1,5 @@
|
|||||||
import { bitable, FieldType, ToastType } from '@lark-base-open/js-sdk';
|
import { bitable, FieldType, ToastType } from '@lark-base-open/js-sdk';
|
||||||
import { Button, Typography, List, Card, Space, Divider, Spin, Table, Select, Modal, DatePicker, InputNumber, Input, Progress, Switch } from '@douyinfe/semi-ui';
|
import { Button, Typography, List, Card, Space, Divider, Spin, Table, Select, Modal, DatePicker, InputNumber, Input, Progress, Switch, Tag } from '@douyinfe/semi-ui';
|
||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
import { addDays, format, differenceInCalendarDays } from 'date-fns';
|
import { addDays, format, differenceInCalendarDays } from 'date-fns';
|
||||||
import { zhCN } from 'date-fns/locale';
|
import { zhCN } from 'date-fns/locale';
|
||||||
@ -49,6 +49,14 @@ export default function App() {
|
|||||||
const [labelAdjustmentFlow, setLabelAdjustmentFlow] = useState(false);
|
const [labelAdjustmentFlow, setLabelAdjustmentFlow] = useState(false);
|
||||||
const [selectedLabels, setSelectedLabels] = useState<{[key: string]: string | string[]}>({});
|
const [selectedLabels, setSelectedLabels] = useState<{[key: string]: string | string[]}>({});
|
||||||
const [labelLoading, setLabelLoading] = useState(false);
|
const [labelLoading, setLabelLoading] = useState(false);
|
||||||
|
const [excludedDatesOverride, setExcludedDatesOverride] = useState<string[]>([]);
|
||||||
|
const [excludedDatesOverrideText, setExcludedDatesOverrideText] = useState<string>('');
|
||||||
|
const [excludedDatesByNodeOverride, setExcludedDatesByNodeOverride] = useState<Record<string, string[]>>({});
|
||||||
|
const excludedDatesByNodeOverrideRef = useRef<Record<string, string[]>>({});
|
||||||
|
const pendingRecalculateAfterExcludedDatesRef = useRef(false);
|
||||||
|
const [excludedDatesAdjustVisible, setExcludedDatesAdjustVisible] = useState(false);
|
||||||
|
const [excludedDatesByNodeDraft, setExcludedDatesByNodeDraft] = useState<Record<string, string[]>>({});
|
||||||
|
const [excludedDatesAddDraft, setExcludedDatesAddDraft] = useState<Record<string, Date | null>>({});
|
||||||
|
|
||||||
// 客户期望日期状态
|
// 客户期望日期状态
|
||||||
const [expectedDate, setExpectedDate] = useState<Date | null>(null);
|
const [expectedDate, setExpectedDate] = useState<Date | null>(null);
|
||||||
@ -130,6 +138,13 @@ export default function App() {
|
|||||||
setSelectedRecords([]);
|
setSelectedRecords([]);
|
||||||
setRecordDetails([]);
|
setRecordDetails([]);
|
||||||
setSelectedLabels({});
|
setSelectedLabels({});
|
||||||
|
setExcludedDatesOverride([]);
|
||||||
|
setExcludedDatesOverrideText('');
|
||||||
|
setExcludedDatesByNodeOverride({});
|
||||||
|
excludedDatesByNodeOverrideRef.current = {};
|
||||||
|
setExcludedDatesAdjustVisible(false);
|
||||||
|
setExcludedDatesByNodeDraft({});
|
||||||
|
setExcludedDatesAddDraft({});
|
||||||
setExpectedDate(null);
|
setExpectedDate(null);
|
||||||
setStartTime(null);
|
setStartTime(null);
|
||||||
setCalculatedRequiredStartTime(null);
|
setCalculatedRequiredStartTime(null);
|
||||||
@ -223,6 +238,10 @@ export default function App() {
|
|||||||
ensureStyleColorDefaults();
|
ensureStyleColorDefaults();
|
||||||
}
|
}
|
||||||
}, [timelineVisible]);
|
}, [timelineVisible]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
excludedDatesByNodeOverrideRef.current = excludedDatesByNodeOverride || {};
|
||||||
|
}, [excludedDatesByNodeOverride]);
|
||||||
const VIEW_ID = 'vewb28sjuX';
|
const VIEW_ID = 'vewb28sjuX';
|
||||||
|
|
||||||
// 标签表ID
|
// 标签表ID
|
||||||
@ -350,6 +369,212 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const normalizeExcludedDatesOverride = (raw: any): string[] => {
|
||||||
|
const parts: string[] = [];
|
||||||
|
if (Array.isArray(raw)) {
|
||||||
|
for (const el of raw) {
|
||||||
|
const s = typeof el === 'string' ? el : extractText(el);
|
||||||
|
if (s) parts.push(s);
|
||||||
|
}
|
||||||
|
} else if (typeof raw === 'string') {
|
||||||
|
parts.push(...raw.split(/[\,\n\r\s]+/));
|
||||||
|
} else if (raw && typeof raw === 'object') {
|
||||||
|
const s = extractText(raw);
|
||||||
|
if (s) parts.push(...s.split(/[\,\n\r\s]+/));
|
||||||
|
}
|
||||||
|
|
||||||
|
const out: string[] = [];
|
||||||
|
const seen = new Set<string>();
|
||||||
|
for (const p of parts) {
|
||||||
|
const t = (p || '').trim();
|
||||||
|
if (!t) continue;
|
||||||
|
const d = parseDate(t);
|
||||||
|
if (!d || isNaN(d.getTime())) continue;
|
||||||
|
const normalized = formatDate(d, 'DISPLAY_DATE_ONLY');
|
||||||
|
if (!normalized) continue;
|
||||||
|
if (!seen.has(normalized)) {
|
||||||
|
seen.add(normalized);
|
||||||
|
out.push(normalized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.sort();
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
const restoreExcludedDatesOverrideFromSnapshot = (snapshot: any, timelineResultsCandidate?: any[]) => {
|
||||||
|
const candidates: any[] = [
|
||||||
|
snapshot?.excludedDatesOverride,
|
||||||
|
snapshot?.timelineCalculationState?.excludedDatesOverride,
|
||||||
|
snapshot?.timelineCalculationState?.excludedDates,
|
||||||
|
];
|
||||||
|
for (const c of candidates) {
|
||||||
|
const normalized = normalizeExcludedDatesOverride(c);
|
||||||
|
if (normalized.length > 0) {
|
||||||
|
setExcludedDatesOverride(normalized);
|
||||||
|
setExcludedDatesOverrideText(normalized.join('\n'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = Array.isArray(timelineResultsCandidate)
|
||||||
|
? timelineResultsCandidate
|
||||||
|
: (Array.isArray(snapshot?.timelineResults) ? snapshot.timelineResults : []);
|
||||||
|
|
||||||
|
if (results.length > 0) {
|
||||||
|
const union: string[] = [];
|
||||||
|
for (const r of results) {
|
||||||
|
const arr = Array.isArray(r?.excludedDates) ? r.excludedDates : [];
|
||||||
|
union.push(...arr);
|
||||||
|
}
|
||||||
|
const normalized = normalizeExcludedDatesOverride(union);
|
||||||
|
if (normalized.length > 0) {
|
||||||
|
setExcludedDatesOverride(normalized);
|
||||||
|
setExcludedDatesOverrideText(normalized.join('\n'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setExcludedDatesOverride([]);
|
||||||
|
setExcludedDatesOverrideText('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleExcludedDatesOverrideTextChange = (next: any) => {
|
||||||
|
const text = typeof next === 'string'
|
||||||
|
? next
|
||||||
|
: (next?.target?.value ?? '');
|
||||||
|
setExcludedDatesOverrideText(text);
|
||||||
|
setExcludedDatesOverride(normalizeExcludedDatesOverride(text));
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildExcludedDatesNodeKey = (nodeName: any, processOrder: any, indexFallback?: number) => {
|
||||||
|
const name = (typeof nodeName === 'string' ? nodeName : extractText(nodeName)).trim();
|
||||||
|
const order = (typeof processOrder === 'number' || typeof processOrder === 'string')
|
||||||
|
? String(processOrder)
|
||||||
|
: '';
|
||||||
|
if (name && order) return `${order}::${name}`;
|
||||||
|
if (name) return name;
|
||||||
|
if (indexFallback !== undefined) return `#${indexFallback + 1}`;
|
||||||
|
return '#unknown';
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildExcludedDatesByNodeFromTimeline = (results: any[]): Record<string, string[]> => {
|
||||||
|
const map: Record<string, string[]> = {};
|
||||||
|
for (let i = 0; i < results.length; i++) {
|
||||||
|
const r = results[i];
|
||||||
|
const key = buildExcludedDatesNodeKey(r?.nodeName, r?.processOrder, i);
|
||||||
|
const arr = Array.isArray(r?.excludedDates) ? r.excludedDates : [];
|
||||||
|
map[key] = normalizeExcludedDatesOverride(arr);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeExcludedDatesByNodeMap = (raw: any): Record<string, string[]> => {
|
||||||
|
const out: Record<string, string[]> = {};
|
||||||
|
if (!raw) return out;
|
||||||
|
if (typeof raw !== 'object') return out;
|
||||||
|
|
||||||
|
for (const [k, v] of Object.entries(raw)) {
|
||||||
|
const key = (k || '').trim();
|
||||||
|
if (!key) continue;
|
||||||
|
const normalized = normalizeExcludedDatesOverride(v);
|
||||||
|
out[key] = normalized;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
const restoreExcludedDatesByNodeOverrideFromSnapshot = (snapshot: any, timelineResultsCandidate?: any[]) => {
|
||||||
|
const candidates: any[] = [
|
||||||
|
snapshot?.excludedDatesByNodeOverride,
|
||||||
|
snapshot?.excludedDatesByNode,
|
||||||
|
snapshot?.timelineCalculationState?.excludedDatesByNodeOverride,
|
||||||
|
snapshot?.timelineCalculationState?.excludedDatesByNode,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const c of candidates) {
|
||||||
|
const normalized = normalizeExcludedDatesByNodeMap(c);
|
||||||
|
if (Object.keys(normalized).length > 0) {
|
||||||
|
excludedDatesByNodeOverrideRef.current = normalized;
|
||||||
|
setExcludedDatesByNodeOverride(normalized);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = Array.isArray(timelineResultsCandidate)
|
||||||
|
? timelineResultsCandidate
|
||||||
|
: (Array.isArray(snapshot?.timelineResults) ? snapshot.timelineResults : []);
|
||||||
|
if (results.length > 0) {
|
||||||
|
const derived = buildExcludedDatesByNodeFromTimeline(results);
|
||||||
|
excludedDatesByNodeOverrideRef.current = derived;
|
||||||
|
setExcludedDatesByNodeOverride(derived);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
excludedDatesByNodeOverrideRef.current = {};
|
||||||
|
setExcludedDatesByNodeOverride({});
|
||||||
|
};
|
||||||
|
|
||||||
|
const groupDatesByMonth = (dates: string[]) => {
|
||||||
|
const groups = new Map<string, string[]>();
|
||||||
|
for (const d of dates) {
|
||||||
|
const ds = (d || '').trim();
|
||||||
|
if (!ds) continue;
|
||||||
|
const month = ds.slice(0, 7);
|
||||||
|
if (!groups.has(month)) groups.set(month, []);
|
||||||
|
groups.get(month)!.push(ds);
|
||||||
|
}
|
||||||
|
const out = Array.from(groups.entries())
|
||||||
|
.map(([month, ds]) => ({
|
||||||
|
month,
|
||||||
|
dates: Array.from(new Set(ds)).sort(),
|
||||||
|
}))
|
||||||
|
.sort((a, b) => a.month.localeCompare(b.month));
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openExcludedDatesAdjustModal = () => {
|
||||||
|
const baseResults = Array.isArray(timelineResults) ? timelineResults : [];
|
||||||
|
const draft: Record<string, string[]> = {};
|
||||||
|
for (let i = 0; i < baseResults.length; i++) {
|
||||||
|
const r = baseResults[i];
|
||||||
|
const key = buildExcludedDatesNodeKey(r?.nodeName, r?.processOrder, i);
|
||||||
|
const existing = excludedDatesByNodeOverrideRef.current?.[key];
|
||||||
|
const fromResult = Array.isArray(r?.excludedDates) ? r.excludedDates : [];
|
||||||
|
draft[key] = normalizeExcludedDatesOverride(existing ?? fromResult);
|
||||||
|
}
|
||||||
|
setExcludedDatesByNodeDraft(draft);
|
||||||
|
setExcludedDatesAddDraft({});
|
||||||
|
setExcludedDatesAdjustVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeExcludedDatesAdjustModal = () => {
|
||||||
|
setExcludedDatesAdjustVisible(false);
|
||||||
|
setExcludedDatesByNodeDraft({});
|
||||||
|
setExcludedDatesAddDraft({});
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeExcludedDateFromDraft = (nodeKey: string, date: string) => {
|
||||||
|
setExcludedDatesByNodeDraft(prev => {
|
||||||
|
const current = Array.isArray(prev?.[nodeKey]) ? prev[nodeKey] : [];
|
||||||
|
const next = current.filter(d => d !== date);
|
||||||
|
return { ...(prev || {}), [nodeKey]: next };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const addExcludedDateToDraft = (nodeKey: string, date: Date | null) => {
|
||||||
|
if (!(date instanceof Date) || isNaN(date.getTime())) return;
|
||||||
|
const normalized = formatDate(date, 'DISPLAY_DATE_ONLY');
|
||||||
|
if (!normalized) return;
|
||||||
|
setExcludedDatesByNodeDraft(prev => {
|
||||||
|
const current = Array.isArray(prev?.[nodeKey]) ? prev[nodeKey] : [];
|
||||||
|
const next = Array.from(new Set([...current, normalized])).sort();
|
||||||
|
return { ...(prev || {}), [nodeKey]: next };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearExcludedDatesDraftForNode = (nodeKey: string) => {
|
||||||
|
setExcludedDatesByNodeDraft(prev => ({ ...(prev || {}), [nodeKey]: [] }));
|
||||||
|
};
|
||||||
|
|
||||||
// 根据货期记录ID读取节点详情并还原流程数据
|
// 根据货期记录ID读取节点详情并还原流程数据
|
||||||
const loadProcessDataFromDeliveryRecord = async (deliveryRecordId: string) => {
|
const loadProcessDataFromDeliveryRecord = async (deliveryRecordId: string) => {
|
||||||
if (!deliveryRecordId) {
|
if (!deliveryRecordId) {
|
||||||
@ -434,6 +659,8 @@ export default function App() {
|
|||||||
const snapshot = JSON.parse(deliverySnapStr);
|
const snapshot = JSON.parse(deliverySnapStr);
|
||||||
restoreTimelineDirectionFromSnapshot(snapshot);
|
restoreTimelineDirectionFromSnapshot(snapshot);
|
||||||
restoreBaseBufferDaysFromSnapshot(snapshot);
|
restoreBaseBufferDaysFromSnapshot(snapshot);
|
||||||
|
restoreExcludedDatesOverrideFromSnapshot(snapshot, snapshot?.timelineResults);
|
||||||
|
restoreExcludedDatesByNodeOverrideFromSnapshot(snapshot, snapshot?.timelineResults);
|
||||||
|
|
||||||
// 恢复页面与全局状态
|
// 恢复页面与全局状态
|
||||||
if (snapshot.selectedLabels) setSelectedLabels(snapshot.selectedLabels);
|
if (snapshot.selectedLabels) setSelectedLabels(snapshot.selectedLabels);
|
||||||
@ -487,6 +714,8 @@ export default function App() {
|
|||||||
setIsExpectedDeliveryDateLocked(false);
|
setIsExpectedDeliveryDateLocked(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
restoreExcludedDatesOverrideFromSnapshot(snapshot, snapshot.timelineResults);
|
||||||
|
restoreExcludedDatesByNodeOverrideFromSnapshot(snapshot, snapshot.timelineResults);
|
||||||
if (snapshot.expectedDateTimestamp) {
|
if (snapshot.expectedDateTimestamp) {
|
||||||
setExpectedDate(new Date(snapshot.expectedDateTimestamp));
|
setExpectedDate(new Date(snapshot.expectedDateTimestamp));
|
||||||
} else if (snapshot.expectedDateString) {
|
} else if (snapshot.expectedDateString) {
|
||||||
@ -594,6 +823,8 @@ export default function App() {
|
|||||||
setIsRestoringSnapshot(true);
|
setIsRestoringSnapshot(true);
|
||||||
restoreTimelineDirectionFromSnapshot(snapshot);
|
restoreTimelineDirectionFromSnapshot(snapshot);
|
||||||
restoreBaseBufferDaysFromSnapshot(snapshot);
|
restoreBaseBufferDaysFromSnapshot(snapshot);
|
||||||
|
restoreExcludedDatesOverrideFromSnapshot(snapshot, snapshot?.timelineResults);
|
||||||
|
restoreExcludedDatesByNodeOverrideFromSnapshot(snapshot, snapshot?.timelineResults);
|
||||||
|
|
||||||
if (snapshot.selectedLabels) setSelectedLabels(snapshot.selectedLabels);
|
if (snapshot.selectedLabels) setSelectedLabels(snapshot.selectedLabels);
|
||||||
if (!mode && snapshot.mode) setMode(snapshot.mode);
|
if (!mode && snapshot.mode) setMode(snapshot.mode);
|
||||||
@ -782,6 +1013,8 @@ export default function App() {
|
|||||||
if (vNum !== null && !isNaN(vNum)) setCurrentVersionNumber(vNum);
|
if (vNum !== null && !isNaN(vNum)) setCurrentVersionNumber(vNum);
|
||||||
}
|
}
|
||||||
if (snapshot.timelineAdjustments) setTimelineAdjustments(snapshot.timelineAdjustments);
|
if (snapshot.timelineAdjustments) setTimelineAdjustments(snapshot.timelineAdjustments);
|
||||||
|
restoreExcludedDatesOverrideFromSnapshot(snapshot, snapshot.timelineResults);
|
||||||
|
restoreExcludedDatesByNodeOverrideFromSnapshot(snapshot, snapshot.timelineResults);
|
||||||
if (snapshot.expectedDateTimestamp) {
|
if (snapshot.expectedDateTimestamp) {
|
||||||
setExpectedDate(new Date(snapshot.expectedDateTimestamp));
|
setExpectedDate(new Date(snapshot.expectedDateTimestamp));
|
||||||
} else if (snapshot.expectedDateString) {
|
} else if (snapshot.expectedDateString) {
|
||||||
@ -1084,6 +1317,8 @@ export default function App() {
|
|||||||
// 重组完整的 timelineResults
|
// 重组完整的 timelineResults
|
||||||
restoreTimelineDirectionFromSnapshot(globalSnapshotData);
|
restoreTimelineDirectionFromSnapshot(globalSnapshotData);
|
||||||
restoreBaseBufferDaysFromSnapshot(globalSnapshotData);
|
restoreBaseBufferDaysFromSnapshot(globalSnapshotData);
|
||||||
|
restoreExcludedDatesOverrideFromSnapshot(globalSnapshotData, nodeSnapshots);
|
||||||
|
restoreExcludedDatesByNodeOverrideFromSnapshot(globalSnapshotData, nodeSnapshots);
|
||||||
setTimelineResults(nodeSnapshots);
|
setTimelineResults(nodeSnapshots);
|
||||||
setTimelineVisible(true);
|
setTimelineVisible(true);
|
||||||
|
|
||||||
@ -1902,7 +2137,7 @@ export default function App() {
|
|||||||
try {
|
try {
|
||||||
const deliveryTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID);
|
const deliveryTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID);
|
||||||
const versionField: any = await deliveryTable.getField(DELIVERY_VERSION_FIELD_ID);
|
const versionField: any = await deliveryTable.getField(DELIVERY_VERSION_FIELD_ID);
|
||||||
const planFilter = {
|
const planFilter: any = {
|
||||||
conjunction: 'and',
|
conjunction: 'and',
|
||||||
conditions: [{ fieldId: DELIVERY_RECORD_IDS_FIELD_ID, operator: 'is', value: planText }]
|
conditions: [{ fieldId: DELIVERY_RECORD_IDS_FIELD_ID, operator: 'is', value: planText }]
|
||||||
};
|
};
|
||||||
@ -1910,8 +2145,55 @@ export default function App() {
|
|||||||
? [{ fieldId: DELIVERY_CREATE_TIME_FIELD_ID, desc: true }]
|
? [{ fieldId: DELIVERY_CREATE_TIME_FIELD_ID, desc: true }]
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
let token: any = undefined;
|
|
||||||
const eps = 1e-9;
|
const eps = 1e-9;
|
||||||
|
const normalizePlanText = (s: string) => s.replace(/\s+/g, '').trim();
|
||||||
|
const parseVersionFromRaw = (raw: any): number | null => {
|
||||||
|
if (typeof raw === 'number' && Number.isFinite(raw)) return raw;
|
||||||
|
const s = typeof raw === 'string' ? raw : extractText(raw);
|
||||||
|
const m = (s || '').match(/\d+(?:\.\d+)?/);
|
||||||
|
return m ? parseFloat(m[0]) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
let token: any = undefined;
|
||||||
|
for (let i = 0; i < 10000; i++) {
|
||||||
|
const res: any = await deliveryTable.getRecordsByPage({
|
||||||
|
pageSize: 200,
|
||||||
|
pageToken: token,
|
||||||
|
filter: planFilter,
|
||||||
|
sort,
|
||||||
|
});
|
||||||
|
const recs: any[] = Array.isArray(res?.records) ? res.records : [];
|
||||||
|
|
||||||
|
for (const r of recs) {
|
||||||
|
const recordId = r?.recordId || r?.id || null;
|
||||||
|
if (!recordId) continue;
|
||||||
|
|
||||||
|
const recordPlanRaw = (r?.fields || {})[DELIVERY_RECORD_IDS_FIELD_ID];
|
||||||
|
const recordPlanText = typeof recordPlanRaw === 'string' ? recordPlanRaw : extractText(recordPlanRaw);
|
||||||
|
if (!recordPlanText) continue;
|
||||||
|
|
||||||
|
if (normalizePlanText(recordPlanText) !== normalizePlanText(planText)) continue;
|
||||||
|
|
||||||
|
const recordVersionRaw = (r?.fields || {})[DELIVERY_VERSION_FIELD_ID];
|
||||||
|
const recordVersion = parseVersionFromRaw(recordVersionRaw);
|
||||||
|
if (recordVersion !== null && Math.abs(recordVersion - planVersion) < eps) {
|
||||||
|
return recordId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextToken = res?.pageToken;
|
||||||
|
const hasMore = !!res?.hasMore;
|
||||||
|
token = nextToken;
|
||||||
|
if (!hasMore && !nextToken) break;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
let token: any = undefined;
|
||||||
for (let i = 0; i < 10000; i++) {
|
for (let i = 0; i < 10000; i++) {
|
||||||
const res: any = await versionField.getFieldValueListByPage({
|
const res: any = await versionField.getFieldValueListByPage({
|
||||||
pageSize: 200,
|
pageSize: 200,
|
||||||
@ -1924,15 +2206,7 @@ export default function App() {
|
|||||||
|
|
||||||
for (const fv of fieldValues) {
|
for (const fv of fieldValues) {
|
||||||
const recordId = fv?.recordId;
|
const recordId = fv?.recordId;
|
||||||
const raw = fv?.value;
|
const v = parseVersionFromRaw(fv?.value);
|
||||||
let v: number | null = null;
|
|
||||||
if (typeof raw === 'number') {
|
|
||||||
v = raw;
|
|
||||||
} else {
|
|
||||||
const s = typeof raw === 'string' ? raw : extractText(raw);
|
|
||||||
const m = (s || '').match(/\d+(?:\.\d+)?/);
|
|
||||||
if (m) v = parseFloat(m[0]);
|
|
||||||
}
|
|
||||||
if (v !== null && Math.abs(v - planVersion) < eps) {
|
if (v !== null && Math.abs(v - planVersion) < eps) {
|
||||||
return recordId || null;
|
return recordId || null;
|
||||||
}
|
}
|
||||||
@ -1943,6 +2217,44 @@ export default function App() {
|
|||||||
token = nextToken;
|
token = nextToken;
|
||||||
if (!hasMore && !nextToken) break;
|
if (!hasMore && !nextToken) break;
|
||||||
}
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
let token: any = undefined;
|
||||||
|
for (let i = 0; i < 10000; i++) {
|
||||||
|
const res: any = await deliveryTable.getRecordsByPage({
|
||||||
|
pageSize: 200,
|
||||||
|
pageToken: token,
|
||||||
|
sort,
|
||||||
|
});
|
||||||
|
const recs: any[] = Array.isArray(res?.records) ? res.records : [];
|
||||||
|
|
||||||
|
for (const r of recs) {
|
||||||
|
const recordId = r?.recordId || r?.id || null;
|
||||||
|
if (!recordId) continue;
|
||||||
|
|
||||||
|
const recordPlanRaw = (r?.fields || {})[DELIVERY_RECORD_IDS_FIELD_ID];
|
||||||
|
const recordPlanText = typeof recordPlanRaw === 'string' ? recordPlanRaw : extractText(recordPlanRaw);
|
||||||
|
if (!recordPlanText) continue;
|
||||||
|
|
||||||
|
if (normalizePlanText(recordPlanText) !== normalizePlanText(planText)) continue;
|
||||||
|
|
||||||
|
const recordVersionRaw = (r?.fields || {})[DELIVERY_VERSION_FIELD_ID];
|
||||||
|
const recordVersion = parseVersionFromRaw(recordVersionRaw);
|
||||||
|
if (recordVersion !== null && Math.abs(recordVersion - planVersion) < eps) {
|
||||||
|
return recordId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextToken = res?.pageToken;
|
||||||
|
const hasMore = !!res?.hasMore;
|
||||||
|
token = nextToken;
|
||||||
|
if (!hasMore && !nextToken) break;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('匹配货期记录失败:', error);
|
console.error('匹配货期记录失败:', error);
|
||||||
@ -1986,6 +2298,8 @@ export default function App() {
|
|||||||
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 currentExcludedDates = Array.isArray(overrideData?.excludedDates) ? overrideData!.excludedDates : [];
|
||||||
|
const globalExcludedDates = normalizeExcludedDatesOverride(currentExcludedDates);
|
||||||
|
const excludedByNode = excludedDatesByNodeOverrideRef.current || {};
|
||||||
const isBackward = timelineDirection === 'backward';
|
const isBackward = timelineDirection === 'backward';
|
||||||
|
|
||||||
console.log('=== handleCalculateTimeline - 使用的数据 ===');
|
console.log('=== handleCalculateTimeline - 使用的数据 ===');
|
||||||
@ -2381,7 +2695,16 @@ 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);
|
const nodeKey = buildExcludedDatesNodeKey(processNode?.nodeName, processNode?.processOrder, i);
|
||||||
|
const overrideList = Object.prototype.hasOwnProperty.call(excludedByNode, nodeKey)
|
||||||
|
? excludedByNode[nodeKey]
|
||||||
|
: undefined;
|
||||||
|
const baseList = Array.isArray(processNode?.excludedDates) ? processNode.excludedDates : [];
|
||||||
|
const selectedList = Array.isArray(overrideList) ? overrideList : baseList;
|
||||||
|
const nodeExcludedDates = Array.from(new Set([
|
||||||
|
...normalizeExcludedDatesOverride(selectedList),
|
||||||
|
...globalExcludedDates,
|
||||||
|
])).filter(Boolean);
|
||||||
let timelineValue = null;
|
let timelineValue = null;
|
||||||
let matchedTimelineRecord = null;
|
let matchedTimelineRecord = null;
|
||||||
|
|
||||||
@ -3479,6 +3802,15 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
}, [actualCompletionDates, isRestoringSnapshot]);
|
}, [actualCompletionDates, isRestoringSnapshot]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!pendingRecalculateAfterExcludedDatesRef.current) return;
|
||||||
|
if (isRestoringSnapshot) return;
|
||||||
|
pendingRecalculateAfterExcludedDatesRef.current = false;
|
||||||
|
if (timelineResults.length > 0) {
|
||||||
|
recalculateTimeline(timelineAdjustments, true);
|
||||||
|
}
|
||||||
|
}, [timelineResults, isRestoringSnapshot]);
|
||||||
|
|
||||||
// 捕获初始状态快照(在首次生成/还原出完整时间线后)
|
// 捕获初始状态快照(在首次生成/还原出完整时间线后)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!hasCapturedInitialSnapshotRef.current && timelineResults.length > 0) {
|
if (!hasCapturedInitialSnapshotRef.current && timelineResults.length > 0) {
|
||||||
@ -3821,6 +4153,8 @@ export default function App() {
|
|||||||
text2,
|
text2,
|
||||||
mode,
|
mode,
|
||||||
timelineDirection,
|
timelineDirection,
|
||||||
|
excludedDatesOverride,
|
||||||
|
excludedDatesByNodeOverride: excludedDatesByNodeOverrideRef.current || excludedDatesByNodeOverride,
|
||||||
lockedExpectedDeliveryDateTs,
|
lockedExpectedDeliveryDateTs,
|
||||||
isExpectedDeliveryDateLocked,
|
isExpectedDeliveryDateLocked,
|
||||||
selectedLabels: currentSelectedLabels,
|
selectedLabels: currentSelectedLabels,
|
||||||
@ -3863,7 +4197,9 @@ export default function App() {
|
|||||||
totalNodes: timelineResults.length,
|
totalNodes: timelineResults.length,
|
||||||
hasValidResults: timelineResults.length > 0,
|
hasValidResults: timelineResults.length > 0,
|
||||||
lastCalculationMode: mode,
|
lastCalculationMode: mode,
|
||||||
timelineDirection
|
timelineDirection,
|
||||||
|
excludedDatesOverride,
|
||||||
|
excludedDatesByNodeOverride: excludedDatesByNodeOverrideRef.current || excludedDatesByNodeOverride
|
||||||
},
|
},
|
||||||
totalNodes: timelineResults.length,
|
totalNodes: timelineResults.length,
|
||||||
isGlobalSnapshot: true
|
isGlobalSnapshot: true
|
||||||
@ -5362,6 +5698,8 @@ export default function App() {
|
|||||||
setMode(next);
|
setMode(next);
|
||||||
setIsExpectedDeliveryDateLocked(false);
|
setIsExpectedDeliveryDateLocked(false);
|
||||||
setLockedExpectedDeliveryDateTs(null);
|
setLockedExpectedDeliveryDateTs(null);
|
||||||
|
setExcludedDatesOverride([]);
|
||||||
|
setExcludedDatesOverrideText('');
|
||||||
}}
|
}}
|
||||||
optionList={[
|
optionList={[
|
||||||
{ value: 'generate', label: '生成流程日期' },
|
{ value: 'generate', label: '生成流程日期' },
|
||||||
@ -5645,6 +5983,120 @@ export default function App() {
|
|||||||
})()}
|
})()}
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
title="跳过日期调整(按节点)"
|
||||||
|
visible={excludedDatesAdjustVisible}
|
||||||
|
maskClosable={false}
|
||||||
|
onCancel={closeExcludedDatesAdjustModal}
|
||||||
|
footer={
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
|
<Text type="tertiary">
|
||||||
|
共 {Array.isArray(timelineResults) ? timelineResults.length : 0} 个节点,
|
||||||
|
已配置 {Object.values(excludedDatesByNodeDraft || {}).filter(v => Array.isArray(v) && v.length > 0).length} 个节点
|
||||||
|
</Text>
|
||||||
|
<Space>
|
||||||
|
<Button onClick={closeExcludedDatesAdjustModal}>取消</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
const next = normalizeExcludedDatesByNodeMap(excludedDatesByNodeDraft || {});
|
||||||
|
excludedDatesByNodeOverrideRef.current = next;
|
||||||
|
setExcludedDatesByNodeOverride(next);
|
||||||
|
pendingRecalculateAfterExcludedDatesRef.current = true;
|
||||||
|
setTimelineResults(prev => {
|
||||||
|
const base = Array.isArray(prev) ? prev : [];
|
||||||
|
return base.map((r: any, idx: number) => {
|
||||||
|
const nodeKey = buildExcludedDatesNodeKey(r?.nodeName, r?.processOrder, idx);
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(next, nodeKey)) return r;
|
||||||
|
const list = Array.isArray(next[nodeKey]) ? next[nodeKey] : [];
|
||||||
|
return { ...r, excludedDates: list };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
setExcludedDatesAdjustVisible(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
应用并重算
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
style={{ width: 980 }}
|
||||||
|
bodyStyle={{ maxHeight: '70vh', overflowY: 'auto' }}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||||
|
{(Array.isArray(timelineResults) ? timelineResults : []).map((r: any, idx: number) => {
|
||||||
|
const nodeKey = buildExcludedDatesNodeKey(r?.nodeName, r?.processOrder, idx);
|
||||||
|
const dates = Array.isArray(excludedDatesByNodeDraft?.[nodeKey]) ? excludedDatesByNodeDraft[nodeKey] : [];
|
||||||
|
const groups = groupDatesByMonth(dates);
|
||||||
|
const pickerValue = excludedDatesAddDraft?.[nodeKey] ?? null;
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
key={nodeKey}
|
||||||
|
className="card-enhanced"
|
||||||
|
bodyStyle={{ padding: 12 }}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
|
||||||
|
<Text strong style={{ fontSize: 14 }}>
|
||||||
|
{typeof r?.processOrder !== 'undefined' ? `#${r.processOrder} ` : ''}{r?.nodeName || nodeKey}
|
||||||
|
</Text>
|
||||||
|
<Space>
|
||||||
|
<DatePicker
|
||||||
|
style={{ width: 160 }}
|
||||||
|
value={pickerValue ?? undefined}
|
||||||
|
placeholder="选择日期"
|
||||||
|
format="yyyy-MM-dd"
|
||||||
|
onChange={(v) => {
|
||||||
|
let d: Date | null = null;
|
||||||
|
if (v instanceof Date) d = v;
|
||||||
|
else if (typeof v === 'string') {
|
||||||
|
const parsed = parseDate(v);
|
||||||
|
d = parsed && !isNaN(parsed.getTime()) ? parsed : null;
|
||||||
|
}
|
||||||
|
setExcludedDatesAddDraft(prev => ({ ...(prev || {}), [nodeKey]: d }));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
const d = excludedDatesAddDraft?.[nodeKey] ?? null;
|
||||||
|
addExcludedDateToDraft(nodeKey, d);
|
||||||
|
setExcludedDatesAddDraft(prev => ({ ...(prev || {}), [nodeKey]: null }));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
添加
|
||||||
|
</Button>
|
||||||
|
<Button type="tertiary" onClick={() => clearExcludedDatesDraftForNode(nodeKey)}>清空</Button>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginTop: 10 }}>
|
||||||
|
{groups.length === 0 ? (
|
||||||
|
<Text type="tertiary">无跳过日期</Text>
|
||||||
|
) : (
|
||||||
|
groups.map(g => (
|
||||||
|
<div key={g.month} style={{ marginBottom: 10 }}>
|
||||||
|
<Text style={{ display: 'block', marginBottom: 6, color: '#595959' }}>{g.month}</Text>
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
|
||||||
|
{g.dates.map(d => (
|
||||||
|
<Tag
|
||||||
|
key={d}
|
||||||
|
closable
|
||||||
|
onClose={() => removeExcludedDateFromDraft(nodeKey, d)}
|
||||||
|
style={{ backgroundColor: '#fff7e6', borderColor: '#ffd591', color: '#ad4e00' }}
|
||||||
|
>
|
||||||
|
{d}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
{/* 时效计算结果模态框 */}
|
{/* 时效计算结果模态框 */}
|
||||||
<Modal
|
<Modal
|
||||||
title={
|
title={
|
||||||
@ -6346,6 +6798,17 @@ export default function App() {
|
|||||||
• 不同节点可配置不同的休息日和计算方式(如:内部节点按工作时间,外部节点按自然时间)
|
• 不同节点可配置不同的休息日和计算方式(如:内部节点按工作时间,外部节点按自然时间)
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{ marginTop: '8px', padding: '8px', backgroundColor: '#fffbe6', borderRadius: '4px' }}>
|
||||||
|
<Space align="center" spacing={8} style={{ width: '100%', justifyContent: 'space-between' }}>
|
||||||
|
<Text strong style={{ color: '#fa8c16' }}>跳过日期</Text>
|
||||||
|
<Space align="center" spacing={8}>
|
||||||
|
<Text type="tertiary">
|
||||||
|
已配置 {Object.values(excludedDatesByNodeOverride || {}).filter(v => Array.isArray(v) && v.length > 0).length} 个节点
|
||||||
|
</Text>
|
||||||
|
<Button size="small" onClick={openExcludedDatesAdjustModal}>按节点调整</Button>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
{Object.keys(timelineAdjustments).length > 0 && (
|
{Object.keys(timelineAdjustments).length > 0 && (
|
||||||
<div style={{ marginTop: '8px', padding: '8px', backgroundColor: '#fff7e6', borderRadius: '4px' }}>
|
<div style={{ marginTop: '8px', padding: '8px', backgroundColor: '#fff7e6', borderRadius: '4px' }}>
|
||||||
<Text strong style={{ color: '#fa8c16' }}>当前调整:</Text>
|
<Text strong style={{ color: '#fa8c16' }}>当前调整:</Text>
|
||||||
|
|||||||
Reference in New Issue
Block a user