5
This commit is contained in:
2025-12-17 16:14:36 +08:00
parent b29d18bee5
commit 76f946c248

View File

@ -3,6 +3,7 @@ import { Button, Typography, List, Card, Space, Divider, Spin, Table, Select, Mo
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect, useRef } from 'react';
import { addDays, format } from 'date-fns'; import { addDays, format } from 'date-fns';
import { zhCN } from 'date-fns/locale'; import { zhCN } from 'date-fns/locale';
import { executePricingQuery, executeSecondaryProcessQuery, executePricingDetailsQuery } from './services/apiService';
const { Title, Text } = Typography; const { Title, Text } = Typography;
@ -36,7 +37,12 @@ export default function App() {
const [selectedRecords, setSelectedRecords] = useState<string[]>([]); const [selectedRecords, setSelectedRecords] = useState<string[]>([]);
const [recordDetails, setRecordDetails] = useState<any[]>([]); const [recordDetails, setRecordDetails] = useState<any[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [queryResults, setQueryResults] = useState<any[]>([]);
const [queryLoading, setQueryLoading] = useState(false);
const [secondaryProcessResults, setSecondaryProcessResults] = useState<any[]>([]);
const [secondaryProcessLoading, setSecondaryProcessLoading] = useState(false);
const [pricingDetailsResults, setPricingDetailsResults] = useState<any[]>([]);
const [pricingDetailsLoading, setPricingDetailsLoading] = useState(false);
// 标签相关状态 // 标签相关状态
const [labelOptions, setLabelOptions] = useState<{[key: string]: any[]}>({}); const [labelOptions, setLabelOptions] = useState<{[key: string]: any[]}>({});
@ -88,8 +94,6 @@ export default function App() {
const [batchProgressList, setBatchProgressList] = useState<{ index: number; foreignId: string; status: 'success' | 'failed'; message?: string }[]>([]); const [batchProgressList, setBatchProgressList] = useState<{ index: number; foreignId: string; status: 'success' | 'failed'; message?: string }[]>([]);
const [batchCurrentRowInfo, setBatchCurrentRowInfo] = useState<{ index: number; foreignId: string; style: string; color: string } | null>(null); const [batchCurrentRowInfo, setBatchCurrentRowInfo] = useState<{ index: number; foreignId: string; style: string; color: string } | null>(null);
const batchAbortRef = useRef<boolean>(false); const batchAbortRef = useRef<boolean>(false);
const [lastSavedDeliveryRecordId, setLastSavedDeliveryRecordId] = useState<string | null>(null);
const [lastSavedDeliveryVersion, setLastSavedDeliveryVersion] = useState<number | null>(null);
// 删除未使用的 deliveryRecords 状态 // 删除未使用的 deliveryRecords 状态
const [selectedDeliveryRecordId, setSelectedDeliveryRecordId] = useState<string>(''); const [selectedDeliveryRecordId, setSelectedDeliveryRecordId] = useState<string>('');
// 从货期记录读取到的record_ids用于保存回写 // 从货期记录读取到的record_ids用于保存回写
@ -103,6 +107,9 @@ export default function App() {
const resetGlobalState = (opts?: { resetMode?: boolean }) => { const resetGlobalState = (opts?: { resetMode?: boolean }) => {
// 运行时加载状态 // 运行时加载状态
setLoading(false); setLoading(false);
setQueryLoading(false);
setSecondaryProcessLoading(false);
setPricingDetailsLoading(false);
setLabelLoading(false); setLabelLoading(false);
setAdjustLoading(false); setAdjustLoading(false);
setTimelineLoading(false); setTimelineLoading(false);
@ -216,6 +223,26 @@ export default function App() {
// 新表ID批量生成表 // 新表ID批量生成表
const BATCH_TABLE_ID = 'tblXO7iSxBYxrqtY'; const BATCH_TABLE_ID = 'tblXO7iSxBYxrqtY';
const fetchAllRecordsByPage = async (table: any, params?: any) => {
let token: any = undefined;
let all: any[] = [];
for (let i = 0; i < 10000; i++) {
const res: any = await table.getRecordsByPage({ pageSize: Math.min(200, (params && params.pageSize) ? params.pageSize : 200), pageToken: token, ...(params || {}) });
const recs: any[] = Array.isArray(res?.records) ? res.records : [];
all = all.concat(recs);
const nextToken = res?.pageToken;
const hm = !!res?.hasMore;
token = nextToken;
if (!hm && !nextToken) break;
}
return all;
};
const getRecordTotalByPage = async (table: any, params?: any) => {
const res: any = await table.getRecordIdListByPage({ pageSize: 1, ...(params || {}) });
return res?.total || 0;
};
// 已移除:调整模式不再加载货期记录列表 // 已移除:调整模式不再加载货期记录列表
// 入口选择处理 // 入口选择处理
@ -229,8 +256,7 @@ export default function App() {
const openBatchModal = async () => { const openBatchModal = async () => {
try { try {
const batchTable = await bitable.base.getTable(BATCH_TABLE_ID); const batchTable = await bitable.base.getTable(BATCH_TABLE_ID);
const res = await batchTable.getRecords({ pageSize: 5000 }); const total = await getRecordTotalByPage(batchTable);
const total = res.records?.length || 0;
setBatchTotalRows(total); setBatchTotalRows(total);
setBatchStartRow(1); setBatchStartRow(1);
setBatchEndRow(total > 0 ? total : 1); setBatchEndRow(total > 0 ? total : 1);
@ -258,11 +284,15 @@ export default function App() {
setTimelineLoading(true); setTimelineLoading(true);
try { try {
const deliveryTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID); const deliveryTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID);
const deliveryRecord = await deliveryTable.getRecordById(deliveryRecordId); const nodeDetailsField: any = await deliveryTable.getField(DELIVERY_NODE_DETAILS_FIELD_ID);
const nodeDetailsVal = deliveryRecord?.fields?.[DELIVERY_NODE_DETAILS_FIELD_ID]; const recordIdsField: any = await deliveryTable.getField(DELIVERY_RECORD_IDS_FIELD_ID);
const snapshotField: any = await deliveryTable.getField(DELIVERY_SNAPSHOT_JSON_FIELD_ID);
const startTimeField: any = await deliveryTable.getField(DELIVERY_START_TIME_FIELD_ID);
const nodeDetailsVal = await nodeDetailsField.getValue(deliveryRecordId);
// 读取record_ids文本字段并保留原始文本用于原样写回 // 读取record_ids文本字段并保留原始文本用于原样写回
try { try {
const recordIdsTextVal = deliveryRecord?.fields?.[DELIVERY_RECORD_IDS_FIELD_ID]; const recordIdsTextVal = await recordIdsField.getValue(deliveryRecordId);
const raw = extractText(recordIdsTextVal); const raw = extractText(recordIdsTextVal);
if (raw && raw.trim() !== '') { if (raw && raw.trim() !== '') {
setRestoredRecordIdsText(raw.trim()); setRestoredRecordIdsText(raw.trim());
@ -284,7 +314,7 @@ export default function App() {
// 优先使用货期记录表中的快照字段进行一键还原(新方案) // 优先使用货期记录表中的快照字段进行一键还原(新方案)
try { try {
const deliverySnapVal = deliveryRecord?.fields?.[DELIVERY_SNAPSHOT_JSON_FIELD_ID]; const deliverySnapVal = await snapshotField.getValue(deliveryRecordId);
let deliverySnapStr: string | null = null; let deliverySnapStr: string | null = null;
if (typeof deliverySnapVal === 'string') { if (typeof deliverySnapVal === 'string') {
deliverySnapStr = deliverySnapVal; deliverySnapStr = deliverySnapVal;
@ -357,7 +387,7 @@ export default function App() {
} }
if (!startTimeRestored) { if (!startTimeRestored) {
const startTimeValue = deliveryRecord?.fields?.[DELIVERY_START_TIME_FIELD_ID]; const startTimeValue = await startTimeField.getValue(deliveryRecordId);
if (startTimeValue) { if (startTimeValue) {
let extractedStartTime: Date | null = null; let extractedStartTime: Date | null = null;
if (typeof startTimeValue === 'number') { if (typeof startTimeValue === 'number') {
@ -427,6 +457,120 @@ export default function App() {
} }
const processTable = await bitable.base.getTable(PROCESS_DATA_TABLE_ID); const processTable = await bitable.base.getTable(PROCESS_DATA_TABLE_ID);
try {
const processSnapshotField: any = await processTable.getField(PROCESS_SNAPSHOT_JSON_FIELD_ID);
let snapStr: string | null = null;
for (const id of recordIds) {
const snapVal = await processSnapshotField.getValue(id);
const candidate = extractText(snapVal);
if (candidate && candidate.trim() !== '') {
snapStr = candidate;
break;
}
}
if (snapStr && snapStr.trim() !== '') {
const snapshot = JSON.parse(snapStr);
if (Array.isArray(snapshot.timelineResults)) {
setIsRestoringSnapshot(true);
if (snapshot.selectedLabels) setSelectedLabels(snapshot.selectedLabels);
if (!mode && snapshot.mode) setMode(snapshot.mode);
if (snapshot.foreignId) setCurrentForeignId(snapshot.foreignId);
if (snapshot.styleText) setCurrentStyleText(snapshot.styleText);
if (snapshot.colorText) setCurrentColorText(snapshot.colorText);
if (snapshot.text2) setCurrentText2(snapshot.text2);
if (snapshot.generationModeState) {
const genState = snapshot.generationModeState;
if (genState.currentForeignId) setCurrentForeignId(genState.currentForeignId);
if (genState.currentStyleText) setCurrentStyleText(genState.currentStyleText);
if (genState.currentColorText) setCurrentColorText(genState.currentColorText);
if (genState.currentText2) setCurrentText2(genState.currentText2);
if (genState.currentVersionNumber !== undefined) setCurrentVersionNumber(genState.currentVersionNumber);
if (genState.recordDetails && Array.isArray(genState.recordDetails)) {
setRecordDetails(genState.recordDetails);
}
}
if (snapshot.version !== undefined) {
let vNum: number | null = null;
if (typeof snapshot.version === 'number') {
vNum = snapshot.version;
} else if (typeof snapshot.version === 'string') {
const match = snapshot.version.match(/\d+/);
if (match) {
vNum = parseInt(match[0], 10);
}
}
if (vNum !== null && !isNaN(vNum)) setCurrentVersionNumber(vNum);
}
if (snapshot.timelineAdjustments) setTimelineAdjustments(snapshot.timelineAdjustments);
if (snapshot.expectedDateTimestamp) {
setExpectedDate(new Date(snapshot.expectedDateTimestamp));
} else if (snapshot.expectedDateString) {
setExpectedDate(new Date(snapshot.expectedDateString));
}
let startTimeRestored = false;
if (snapshot.startTimestamp) {
setStartTime(new Date(snapshot.startTimestamp));
startTimeRestored = true;
} else if (snapshot.startString) {
const parsed = new Date(snapshot.startString);
if (!isNaN(parsed.getTime())) {
setStartTime(parsed);
startTimeRestored = true;
}
}
if (!startTimeRestored) {
const startTimeValue = await startTimeField.getValue(deliveryRecordId);
if (startTimeValue) {
let extractedStartTime: Date | null = null;
if (typeof startTimeValue === 'number') {
extractedStartTime = new Date(startTimeValue);
} else if (Array.isArray(startTimeValue) && startTimeValue.length > 0) {
const timestamp = startTimeValue[0];
if (typeof timestamp === 'number') {
extractedStartTime = new Date(timestamp);
}
}
if (extractedStartTime && !isNaN(extractedStartTime.getTime())) {
setStartTime(extractedStartTime);
}
}
}
setTimelineResults(snapshot.timelineResults);
Modal.confirm({
title: '是否调整标签?',
content: '选择“是”将允许修改标签并重新生成计划版本按V2/V3/V4递增',
okText: '是,调整标签',
cancelText: '否,直接还原',
onOk: async () => {
setLabelAdjustmentFlow(true);
setTimelineVisible(false);
if (bitable.ui.showToast) {
await bitable.ui.showToast({ toastType: 'info', message: '请在下方修改标签后点击重新生成计划' });
}
},
onCancel: async () => {
setLabelAdjustmentFlow(false);
setTimelineVisible(true);
if (bitable.ui.showToast) {
await bitable.ui.showToast({ toastType: 'success', message: '已按快照一模一样还原流程数据' });
}
}
});
setTimelineLoading(false);
setIsRestoringSnapshot(false);
return;
}
}
} catch (e) {
console.warn('从节点快照快速还原失败,继续旧流程:', e);
}
const records = await Promise.all(recordIds.map(id => processTable.getRecordById(id))); const records = await Promise.all(recordIds.map(id => processTable.getRecordById(id)));
// 优先使用文本2快照一模一样还原 // 优先使用文本2快照一模一样还原
@ -523,7 +667,7 @@ export default function App() {
// 如果快照中没有起始时间信息,则从当前选中的货期记录中获取 // 如果快照中没有起始时间信息,则从当前选中的货期记录中获取
if (!startTimeRestored) { if (!startTimeRestored) {
const startTimeValue = deliveryRecord?.fields?.[DELIVERY_START_TIME_FIELD_ID]; const startTimeValue = await startTimeField.getValue(deliveryRecordId);
if (startTimeValue) { if (startTimeValue) {
let extractedStartTime: Date | null = null; let extractedStartTime: Date | null = null;
if (typeof startTimeValue === 'number') { if (typeof startTimeValue === 'number') {
@ -897,7 +1041,7 @@ export default function App() {
}); });
// 如果没有快照恢复起始时间,则从当前货期记录中获取起始时间 // 如果没有快照恢复起始时间,则从当前货期记录中获取起始时间
const startTimeValue = deliveryRecord?.fields?.[DELIVERY_START_TIME_FIELD_ID]; const startTimeValue = await startTimeField.getValue(deliveryRecordId);
if (startTimeValue) { if (startTimeValue) {
let extractedStartTime: Date | null = null; let extractedStartTime: Date | null = null;
if (typeof startTimeValue === 'number') { if (typeof startTimeValue === 'number') {
@ -963,6 +1107,7 @@ export default function App() {
const DELIVERY_TEXT2_FIELD_ID = 'fldG6LZnmU'; const DELIVERY_TEXT2_FIELD_ID = 'fldG6LZnmU';
// 记录ID文本字段货期记录表新增 // 记录ID文本字段货期记录表新增
const DELIVERY_RECORD_IDS_FIELD_ID = 'fldq3u7h7H'; const DELIVERY_RECORD_IDS_FIELD_ID = 'fldq3u7h7H';
const DELIVERY_FACTORY_DEPARTURE_DATE_FIELD_ID = 'fldZFdZDKj';
// OMS看板表相关常量新增 // OMS看板表相关常量新增
const OMS_BOARD_TABLE_ID = 'tbl7j8bCpUbFmGuk'; // OMS看板表ID const OMS_BOARD_TABLE_ID = 'tbl7j8bCpUbFmGuk'; // OMS看板表ID
@ -1516,8 +1661,8 @@ export default function App() {
if (recordId && tableId === DELIVERY_RECORD_TABLE_ID) { if (recordId && tableId === DELIVERY_RECORD_TABLE_ID) {
// 如果选中的是货期记录表的记录,从中获取起始时间 // 如果选中的是货期记录表的记录,从中获取起始时间
const deliveryTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID); const deliveryTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID);
const deliveryRecord = await deliveryTable.getRecordById(recordId); const startTimeField: any = await deliveryTable.getField(DELIVERY_START_TIME_FIELD_ID);
const startTimeValue = deliveryRecord?.fields?.[DELIVERY_START_TIME_FIELD_ID]; const startTimeValue = await startTimeField.getValue(recordId);
if (startTimeValue) { if (startTimeValue) {
let extractedStartTime: Date | null = null; let extractedStartTime: Date | null = null;
@ -1539,22 +1684,23 @@ export default function App() {
// 从OMS看板匹配对应的货期记录后尝试获取其起始时间 // 从OMS看板匹配对应的货期记录后尝试获取其起始时间
try { try {
const omsTable = await bitable.base.getTable(OMS_BOARD_TABLE_ID); const omsTable = await bitable.base.getTable(OMS_BOARD_TABLE_ID);
const omsRecord = await omsTable.getRecordById(recordId); const planTextField: any = await omsTable.getField(OMS_PLAN_TEXT_FIELD_ID);
const planTextRaw = omsRecord?.fields?.[OMS_PLAN_TEXT_FIELD_ID]; const planVersionField: any = await omsTable.getField(OMS_PLAN_VERSION_FIELD_ID);
const planVersionRaw = omsRecord?.fields?.[OMS_PLAN_VERSION_FIELD_ID]; const planTextRaw = await planTextField.getValue(recordId);
const planVersionRaw = await planVersionField.getValue(recordId);
const planText = extractText(planTextRaw)?.trim(); const planText = extractText(planTextRaw)?.trim();
let planVersion: number | null = null; let planVersion: number | null = null;
if (typeof planVersionRaw === 'number') { if (typeof planVersionRaw === 'number') {
planVersion = planVersionRaw; planVersion = planVersionRaw;
} else if (typeof planVersionRaw === 'string') { } else if (typeof planVersionRaw === 'string') {
const m = planVersionRaw.match(/\d+/); const m = planVersionRaw.match(/\d+(?:\.\d+)?/);
if (m) planVersion = parseInt(m[0], 10); if (m) planVersion = parseFloat(m[0]);
} else if (planVersionRaw && typeof planVersionRaw === 'object') { } else if (planVersionRaw && typeof planVersionRaw === 'object') {
const v = (planVersionRaw as any).value ?? (planVersionRaw as any).text; const v = (planVersionRaw as any).value ?? (planVersionRaw as any).text;
if (typeof v === 'number') planVersion = v; if (typeof v === 'number') planVersion = v;
else if (typeof v === 'string') { else if (typeof v === 'string') {
const m = v.match(/\d+/); const m = v.match(/\d+(?:\.\d+)?/);
if (m) planVersion = parseInt(m[0], 10); if (m) planVersion = parseFloat(m[0]);
} }
} }
@ -1562,8 +1708,8 @@ export default function App() {
const deliveryRecordId = await findDeliveryRecordIdByPlan(planText, planVersion); const deliveryRecordId = await findDeliveryRecordIdByPlan(planText, planVersion);
if (deliveryRecordId) { if (deliveryRecordId) {
const deliveryTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID); const deliveryTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID);
const deliveryRecord = await deliveryTable.getRecordById(deliveryRecordId); const startTimeField: any = await deliveryTable.getField(DELIVERY_START_TIME_FIELD_ID);
const startTimeValue = deliveryRecord?.fields?.[DELIVERY_START_TIME_FIELD_ID]; const startTimeValue = await startTimeField.getValue(deliveryRecordId);
if (startTimeValue) { if (startTimeValue) {
let extractedStartTime: Date | null = null; let extractedStartTime: Date | null = null;
if (typeof startTimeValue === 'number') { if (typeof startTimeValue === 'number') {
@ -1597,30 +1743,47 @@ export default function App() {
const findDeliveryRecordIdByPlan = async (planText: string, planVersion: number): Promise<string | null> => { const findDeliveryRecordIdByPlan = async (planText: string, planVersion: number): Promise<string | null> => {
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 recordsResult = await deliveryTable.getRecords({ pageSize: 5000 }); const planFilter = {
const records = recordsResult.records || []; conjunction: 'and',
for (const rec of records) { conditions: [{ fieldId: DELIVERY_RECORD_IDS_FIELD_ID, operator: 'is', value: planText }]
const fields = rec?.fields || {}; };
const recordIdsTextVal = fields[DELIVERY_RECORD_IDS_FIELD_ID]; const sort = DELIVERY_CREATE_TIME_FIELD_ID
const versionVal = fields[DELIVERY_VERSION_FIELD_ID]; ? [{ fieldId: DELIVERY_CREATE_TIME_FIELD_ID, desc: true }]
const recordIdsText = extractText(recordIdsTextVal)?.trim(); : undefined;
let versionNum: number | null = null;
if (typeof versionVal === 'number') versionNum = versionVal; let token: any = undefined;
else if (typeof versionVal === 'string') { const eps = 1e-9;
const m = versionVal.match(/\d+/); for (let i = 0; i < 10000; i++) {
if (m) versionNum = parseInt(m[0], 10); const res: any = await versionField.getFieldValueListByPage({
} else if (versionVal && typeof versionVal === 'object') { pageSize: 200,
const v = (versionVal as any).value ?? (versionVal as any).text; pageToken: token,
if (typeof v === 'number') versionNum = v; filter: planFilter,
else if (typeof v === 'string') { sort,
const m = v.match(/\d+/); stringValue: true
if (m) versionNum = parseInt(m[0], 10); });
const fieldValues: any[] = Array.isArray(res?.fieldValues) ? res.fieldValues : [];
for (const fv of fieldValues) {
const recordId = fv?.recordId;
const raw = 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) {
return recordId || null;
} }
} }
if (recordIdsText && versionNum !== null && recordIdsText === planText && versionNum === planVersion) {
return rec.id || rec.recordId || null; const nextToken = res?.pageToken;
} const hasMore = !!res?.hasMore;
token = nextToken;
if (!hasMore && !nextToken) break;
} }
return null; return null;
} catch (error) { } catch (error) {
@ -1779,11 +1942,7 @@ export default function App() {
// 1. 先获取匹配的流程节点(复用预览功能的逻辑) // 1. 先获取匹配的流程节点(复用预览功能的逻辑)
const processTable = await bitable.base.getTable(PROCESS_CONFIG_TABLE_ID); const processTable = await bitable.base.getTable(PROCESS_CONFIG_TABLE_ID);
const processRecordsResult = await processTable.getRecords({ const processRecords = await fetchAllRecordsByPage(processTable);
pageSize: 5000
});
const processRecords = processRecordsResult.records || [];
const matchedProcessNodes: any[] = []; const matchedProcessNodes: any[] = [];
// 匹配流程配置节点 // 匹配流程配置节点
@ -1965,11 +2124,7 @@ export default function App() {
console.log('按顺序排列的流程节点:', matchedProcessNodes); console.log('按顺序排列的流程节点:', matchedProcessNodes);
// 2. 优化:预先获取所有时效数据并建立索引 // 2. 优化:预先获取所有时效数据并建立索引
const timelineRecordsResult = await timelineTable.getRecords({ const timelineRecords = await fetchAllRecordsByPage(timelineTable);
pageSize: 5000
});
const timelineRecords = timelineRecordsResult.records || [];
// 优化2预处理时效数据建立节点名称到记录的映射 // 优化2预处理时效数据建立节点名称到记录的映射
const timelineIndexByNode = new Map<string, any[]>(); const timelineIndexByNode = new Map<string, any[]>();
@ -3008,7 +3163,8 @@ export default function App() {
DELIVERY_CUSTOMER_EXPECTED_DATE_FIELD_ID, DELIVERY_CUSTOMER_EXPECTED_DATE_FIELD_ID,
DELIVERY_NODE_DETAILS_FIELD_ID, DELIVERY_NODE_DETAILS_FIELD_ID,
DELIVERY_ADJUSTMENT_INFO_FIELD_ID, // 添加货期调整信息字段 DELIVERY_ADJUSTMENT_INFO_FIELD_ID, // 添加货期调整信息字段
DELIVERY_START_TIME_FIELD_ID // 新增:起始时间字段 DELIVERY_START_TIME_FIELD_ID, // 新增:起始时间字段
DELIVERY_FACTORY_DEPARTURE_DATE_FIELD_ID
]; ];
console.log('需要检查的字段ID列表:', fieldsToCheck); console.log('需要检查的字段ID列表:', fieldsToCheck);
@ -3028,6 +3184,7 @@ export default function App() {
const startTimeField = await deliveryRecordTable.getField(DELIVERY_START_TIME_FIELD_ID); const startTimeField = await deliveryRecordTable.getField(DELIVERY_START_TIME_FIELD_ID);
const snapshotField = await deliveryRecordTable.getField(DELIVERY_SNAPSHOT_JSON_FIELD_ID); const snapshotField = await deliveryRecordTable.getField(DELIVERY_SNAPSHOT_JSON_FIELD_ID);
const recordIdsTextField = await deliveryRecordTable.getField(DELIVERY_RECORD_IDS_FIELD_ID); const recordIdsTextField = await deliveryRecordTable.getField(DELIVERY_RECORD_IDS_FIELD_ID);
const factoryDepartureDateField = await deliveryRecordTable.getField(DELIVERY_FACTORY_DEPARTURE_DATE_FIELD_ID);
console.log('成功获取所有字段对象'); console.log('成功获取所有字段对象');
// 检查标签汇总字段的类型 // 检查标签汇总字段的类型
@ -3159,33 +3316,28 @@ export default function App() {
if (expectedDateToUse) { if (expectedDateToUse) {
customerExpectedDate = expectedDateToUse.getTime(); customerExpectedDate = expectedDateToUse.getTime();
} }
let factoryDepartureDate = null as number | null;
const reservationInbound = timelineResults.find(r => r?.nodeName === '预约入库');
const reservationEnd = reservationInbound?.estimatedEnd;
if (reservationEnd instanceof Date && !isNaN(reservationEnd.getTime())) {
factoryDepartureDate = reservationEnd.getTime();
} else if (typeof reservationEnd === 'number' && Number.isFinite(reservationEnd)) {
factoryDepartureDate = reservationEnd;
} else if (typeof reservationEnd === 'string' && reservationEnd.trim() !== '') {
const parsed = parseDate(reservationEnd);
if (parsed) {
factoryDepartureDate = parsed.getTime();
}
}
// 创建当前时间戳 // 创建当前时间戳
const currentTime = new Date().getTime(); const currentTime = new Date().getTime();
// 计算版本号(数字)并格式化货期调整信息 // 计算版本号(数字)并格式化货期调整信息
let versionNumber = 1; let versionNumber = 1;
try { if (mode === 'adjust' && currentVersionNumber !== null) {
if (mode === 'adjust' && currentVersionNumber !== null) { versionNumber = currentVersionNumber + 1;
// 调整模式优先使用快照version +1
versionNumber = currentVersionNumber + 1;
} else if (foreignId) {
const existing = await deliveryRecordTable.getRecords({
pageSize: 5000,
filter: {
conjunction: 'and',
conditions: [{
fieldId: DELIVERY_FOREIGN_ID_FIELD_ID,
operator: 'is',
value: [foreignId]
}]
}
});
const count = existing.records?.length || 0;
versionNumber = count + 1;
}
} catch (e) {
console.warn('计算版本号失败:', e);
} }
let adjustmentInfo = `版本V${versionNumber}`; let adjustmentInfo = `版本V${versionNumber}`;
@ -3374,6 +3526,7 @@ export default function App() {
const snapshotCell = await snapshotField.createCell(snapshotJson); const snapshotCell = await snapshotField.createCell(snapshotJson);
const expectedDateCell = expectedDeliveryDate ? await expectedDateField.createCell(expectedDeliveryDate) : null; const expectedDateCell = expectedDeliveryDate ? await expectedDateField.createCell(expectedDeliveryDate) : null;
const customerExpectedDateCell = customerExpectedDate ? await customerExpectedDateField.createCell(customerExpectedDate) : null; const customerExpectedDateCell = customerExpectedDate ? await customerExpectedDateField.createCell(customerExpectedDate) : null;
const factoryDepartureDateCell = factoryDepartureDate ? await factoryDepartureDateField.createCell(factoryDepartureDate) : null;
// 对于关联记录字段确保传入的是记录ID数组 // 对于关联记录字段确保传入的是记录ID数组
const nodeDetailsCell = processRecordIds.length > 0 ? const nodeDetailsCell = processRecordIds.length > 0 ?
await nodeDetailsField.createCell({ recordIds: processRecordIds }) : null; await nodeDetailsField.createCell({ recordIds: processRecordIds }) : null;
@ -3394,22 +3547,13 @@ export default function App() {
if (labelsCell) recordCells.push(labelsCell); if (labelsCell) recordCells.push(labelsCell);
if (expectedDateCell) recordCells.push(expectedDateCell); if (expectedDateCell) recordCells.push(expectedDateCell);
if (customerExpectedDateCell) recordCells.push(customerExpectedDateCell); if (customerExpectedDateCell) recordCells.push(customerExpectedDateCell);
if (factoryDepartureDateCell) recordCells.push(factoryDepartureDateCell);
if (nodeDetailsCell) recordCells.push(nodeDetailsCell); if (nodeDetailsCell) recordCells.push(nodeDetailsCell);
if (adjustmentInfoCell) recordCells.push(adjustmentInfoCell); if (adjustmentInfoCell) recordCells.push(adjustmentInfoCell);
// 添加记录到货期记录表 // 添加记录到货期记录表
const addedRecord = await deliveryRecordTable.addRecord(recordCells); const addedRecord = await deliveryRecordTable.addRecord(recordCells);
// 保存最近一次写入的记录ID与版本号
try {
const savedId = typeof addedRecord === 'string'
? addedRecord
: (((addedRecord as any)?.id || (addedRecord as any)?.recordId) as string);
if (savedId) {
setLastSavedDeliveryRecordId(savedId);
setLastSavedDeliveryVersion(versionNumber);
}
} catch {}
return addedRecord; return addedRecord;
} catch (error) { } catch (error) {
console.error('写入货期记录表详细错误:', { console.error('写入货期记录表详细错误:', {
@ -3533,70 +3677,13 @@ export default function App() {
} }
} }
// 先删除该foreign_id的所有现有记录
console.log('=== 开始删除现有记录 ===');
console.log('使用的foreign_id:', foreignId);
try {
console.log('正在查询现有记录...');
const existingRecords = await processDataTable.getRecords({
pageSize: 5000,
filter: {
conjunction: 'And',
conditions: [{
fieldId: FOREIGN_ID_FIELD_ID,
operator: 'is',
value: [foreignId]
}]
}
});
console.log('查询到现有记录数量:', existingRecords.records?.length || 0);
if (existingRecords.records && existingRecords.records.length > 0) {
const recordIdsToDelete = existingRecords.records.map(record => record.id);
console.log('准备删除的记录ID:', recordIdsToDelete);
await processDataTable.deleteRecords(recordIdsToDelete);
console.log(`成功删除 ${recordIdsToDelete.length} 条现有记录`);
if (bitable.ui.showToast) {
await bitable.ui.showToast({
toastType: 'info',
message: `已删除 ${recordIdsToDelete.length} 条现有流程数据`
});
}
} else {
console.log('没有找到需要删除的现有记录');
}
} catch (deleteError) {
console.error('删除现有记录时出错:', deleteError);
// 继续执行,不中断流程
}
// 构建页面快照JSON确保可一模一样还原 // 构建页面快照JSON确保可一模一样还原
// 计算版本号:数字。默认按 foreign_id 在货期记录表的记录数量 + 1 // 计算版本号:仅在调整模式下递增
let versionNumber = 1; let versionNumber = 1;
try { if (mode === 'adjust' && currentVersionNumber !== null) {
const deliveryRecordTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID); versionNumber = currentVersionNumber + 1;
if (mode === 'adjust' && currentVersionNumber !== null) {
// 调整模式优先使用快照version +1避免公式字段读取错误
versionNumber = currentVersionNumber + 1;
} else if (foreignId) {
const existing = await deliveryRecordTable.getRecords({
pageSize: 5000,
filter: {
conjunction: 'and',
conditions: [{
fieldId: DELIVERY_FOREIGN_ID_FIELD_ID,
operator: 'is',
value: [foreignId]
}]
}
});
const count = existing.records?.length || 0;
versionNumber = count + 1;
}
} catch (e) {
console.warn('计算版本号失败:', e);
} }
@ -3606,6 +3693,12 @@ export default function App() {
for (let index = 0; index < timelineResults.length; index++) { for (let index = 0; index < timelineResults.length; index++) {
const result = timelineResults[index]; const result = timelineResults[index];
console.log('处理节点数据:', result); console.log('处理节点数据:', result);
const hasValidTimelineValue = typeof result.timelineValue === 'number' && Number.isFinite(result.timelineValue) && result.timelineValue > 0;
if (!hasValidTimelineValue) {
console.log(`跳过节点 "${result.nodeName}" - 未匹配到时效值`);
continue;
}
// 检查是否有有效的预计完成时间(只检查结束时间) // 检查是否有有效的预计完成时间(只检查结束时间)
const hasValidEndTime = ( const hasValidEndTime = (
@ -3653,22 +3746,20 @@ export default function App() {
// 组合所有Cell到一个记录中 // 组合所有Cell到一个记录中
const versionCell = await versionField.createCell(versionNumber); const versionCell = await versionField.createCell(versionNumber);
const timelinessCell = (typeof result.timelineValue === 'number') const timelinessCell = await timelinessField.createCell(result.timelineValue);
? await timelinessField.createCell(result.timelineValue)
: null;
const recordCells = [ const recordCells = [
foreignIdCell, foreignIdCell,
processNameCell, processNameCell,
processOrderCell, processOrderCell,
styleCell, styleCell,
colorCell, colorCell,
versionCell versionCell,
timelinessCell
]; ];
// 只有当时间戳存在时才添加日期Cell // 只有当时间戳存在时才添加日期Cell
if (startDateCell) recordCells.push(startDateCell); if (startDateCell) recordCells.push(startDateCell);
if (endDateCell) recordCells.push(endDateCell); if (endDateCell) recordCells.push(endDateCell);
if (timelinessCell) recordCells.push(timelinessCell);
console.log(`准备写入的Cell记录 - ${result.nodeName}:`, recordCells); console.log(`准备写入的Cell记录 - ${result.nodeName}:`, recordCells);
recordCellsToAdd.push(recordCells); recordCellsToAdd.push(recordCells);
@ -3712,7 +3803,7 @@ export default function App() {
if (bitable.ui.showToast) { if (bitable.ui.showToast) {
await bitable.ui.showToast({ await bitable.ui.showToast({
toastType: 'warning', toastType: 'warning',
message: '没有有效的流程数据可以写入 - 所有节点都缺少时效数据' message: '没有有效的流程数据可以写入 - 未匹配到时效值'
}); });
} }
return []; return [];
@ -3782,9 +3873,15 @@ export default function App() {
const batchTable = await bitable.base.getTable(BATCH_TABLE_ID); const batchTable = await bitable.base.getTable(BATCH_TABLE_ID);
const fieldMetaList = await batchTable.getFieldMetaList(); const fieldMetaList = await batchTable.getFieldMetaList();
const nameToId = new Map<string, string>(); const nameToId = new Map<string, string>();
for (const f of fieldMetaList) nameToId.set(f.name, f.id); for (const meta of (fieldMetaList || [])) {
const res = await batchTable.getRecords({ pageSize: 5000 }); if (!meta) continue;
const rows = res.records || []; const nm = (meta as any).name;
const id = (meta as any).id;
if (typeof nm === 'string' && typeof id === 'string' && nm.trim() && id.trim()) {
nameToId.set(nm, id);
}
}
const rows = await fetchAllRecordsByPage(batchTable);
const total = rows.length; const total = rows.length;
const startIndex1 = range?.start && range.start > 0 ? range.start : 1; const startIndex1 = range?.start && range.start > 0 ? range.start : 1;
const endIndex1 = range?.end && range.end > 0 ? range.end : total; const endIndex1 = range?.end && range.end > 0 ? range.end : total;
@ -3933,7 +4030,169 @@ export default function App() {
// 执行定价数据查询
const executeQuery = async (packId: string, packType: string) => {
if (queryLoading) return;
setQueryLoading(true);
try {
// 使用 apiService 中的函数
const data = await executePricingQuery(packId, packType, selectedLabels);
setQueryResults(data);
if (bitable.ui.showToast) {
await bitable.ui.showToast({
toastType: 'success',
message: '查询成功'
});
}
} catch (error: any) {
console.error('数据库查询出错:', error);
if (bitable.ui.showToast) {
await bitable.ui.showToast({
toastType: 'error',
message: `数据库查询失败: ${error.message}`
});
}
} finally {
setQueryLoading(false);
}
};
// 执行二次工艺查询
const executeSecondaryProcessQueryLocal = async (packId: string, packType: string) => {
if (secondaryProcessLoading) return;
setSecondaryProcessLoading(true);
try {
const data = await executeSecondaryProcessQuery(packId, packType);
setSecondaryProcessResults(data);
if (bitable.ui.showToast) {
await bitable.ui.showToast({
toastType: 'success',
message: '二次工艺查询成功'
});
}
} catch (error: any) {
console.error('二次工艺查询出错:', error);
if (bitable.ui.showToast) {
await bitable.ui.showToast({
toastType: 'error',
message: `二次工艺查询失败: ${error.message}`
});
}
} finally {
setSecondaryProcessLoading(false);
}
};
// 执行定价详情查询
const executePricingDetailsQueryLocal = async (packId: string) => {
if (pricingDetailsLoading) return;
setPricingDetailsLoading(true);
try {
const data = await executePricingDetailsQuery(packId);
setPricingDetailsResults(data);
if (bitable.ui.showToast) {
await bitable.ui.showToast({
toastType: 'success',
message: '定价详情查询成功'
});
}
} catch (error: any) {
console.error('定价详情查询出错:', error);
if (bitable.ui.showToast) {
await bitable.ui.showToast({
toastType: 'error',
message: `定价详情查询失败: ${error.message}`
});
}
} finally {
setPricingDetailsLoading(false);
}
};
// 处理数据库查询
const handleQueryDatabase = async (record: any) => {
// 从记录字段中提取 packId 和 packType
let packId = '';
let packType = '';
// 提取 pack_id (fldpvBfeC0)
if (record.fields.fldpvBfeC0 && Array.isArray(record.fields.fldpvBfeC0) && record.fields.fldpvBfeC0.length > 0) {
packId = record.fields.fldpvBfeC0[0].text;
}
// 提取 pack_type (fldSAF9qXe)
if (record.fields.fldSAF9qXe && record.fields.fldSAF9qXe.text) {
packType = record.fields.fldSAF9qXe.text;
}
if (!packId || !packType) {
if (bitable.ui.showToast) {
await bitable.ui.showToast({
toastType: 'error',
message: '缺少必要的查询参数 (pack_id 或 pack_type)'
});
}
return;
}
await executeQuery(packId, packType);
};
// 处理二次工艺查询
const handleSecondaryProcessQuery = async (record: any) => {
// 从记录字段中提取 packId 和 packType
let packId = '';
let packType = '';
// 提取 pack_id (fldpvBfeC0)
if (record.fields.fldpvBfeC0 && Array.isArray(record.fields.fldpvBfeC0) && record.fields.fldpvBfeC0.length > 0) {
packId = record.fields.fldpvBfeC0[0].text;
}
// 提取 pack_type (fldSAF9qXe)
if (record.fields.fldSAF9qXe && record.fields.fldSAF9qXe.text) {
packType = record.fields.fldSAF9qXe.text;
}
if (!packId || !packType) {
if (bitable.ui.showToast) {
await bitable.ui.showToast({
toastType: 'error',
message: '缺少必要的查询参数 (pack_id 或 pack_type)'
});
}
return;
}
await executeSecondaryProcessQueryLocal(packId, packType);
};
// 处理定价详情查询
const handlePricingDetailsQuery = async (record: any) => {
// 从记录字段中提取 packId
let packId = '';
// 提取 pack_id (fldpvBfeC0)
if (record.fields.fldpvBfeC0 && Array.isArray(record.fields.fldpvBfeC0) && record.fields.fldpvBfeC0.length > 0) {
packId = record.fields.fldpvBfeC0[0].text;
}
if (!packId) {
if (bitable.ui.showToast) {
await bitable.ui.showToast({
toastType: 'error',
message: '缺少必要的查询参数 (pack_id)'
});
}
return;
}
await executePricingDetailsQueryLocal(packId);
};
@ -4173,12 +4432,115 @@ export default function App() {
const handleClearRecords = () => { const handleClearRecords = () => {
setSelectedRecords([]); setSelectedRecords([]);
setRecordDetails([]); setRecordDetails([]);
setQueryResults([]);
setSecondaryProcessResults([]);
setPricingDetailsResults([]);
// 同时清空标签选择 // 同时清空标签选择
setSelectedLabels({}); setSelectedLabels({});
setExpectedDate(null); setExpectedDate(null);
}; };
// 定价数据表格列定义
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
},
{
title: 'Pack ID',
dataIndex: 'pack_id',
key: 'pack_id',
},
{
title: 'Pack Type',
dataIndex: 'pack_type',
key: 'pack_type',
},
{
title: '物料编码',
dataIndex: 'material_code',
key: 'material_code',
},
{
title: '一级分类',
dataIndex: 'category1_name',
key: 'category1_name',
},
{
title: '二级分类',
dataIndex: 'category2_name',
key: 'category2_name',
},
{
title: '三级分类',
dataIndex: 'category3_name',
key: 'category3_name',
},
{
title: '品类属性',
dataIndex: '品类属性',
key: '品类属性',
},
];
// 二次工艺表格列定义
const secondaryProcessColumns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
},
{
title: 'Foreign ID',
dataIndex: 'foreign_id',
key: 'foreign_id',
},
{
title: 'Pack Type',
dataIndex: 'pack_type',
key: 'pack_type',
},
{
title: '项目',
dataIndex: 'costs_item',
key: 'costs_item',
},
{
title: '二次工艺',
dataIndex: 'costs_type',
key: 'costs_type',
},
{
title: '备注',
dataIndex: 'remarks',
key: 'remarks',
},
];
// 定价详情表格列定义
const pricingDetailsColumns = [
{
title: 'Pack ID',
dataIndex: 'pack_id',
key: 'pack_id',
},
{
title: '倍率',
dataIndex: '倍率',
key: '倍率',
},
{
title: '加工费',
dataIndex: '加工费',
key: '加工费',
},
{
title: '总价',
dataIndex: '总价',
key: '总价',
},
];
return ( return (
<div style={{ padding: '20px' }}> <div style={{ padding: '20px' }}>
@ -4330,30 +4692,6 @@ export default function App() {
</Title> </Title>
<Text type="tertiary" style={{ fontSize: '14px' }}>线</Text> <Text type="tertiary" style={{ fontSize: '14px' }}>线</Text>
{lastSavedDeliveryRecordId && lastSavedDeliveryVersion !== null && (
<Card style={{ marginTop: '12px', padding: '12px', backgroundColor: '#f6f8fa', borderRadius: 8 }}>
<Text strong></Text>
<div style={{ marginTop: 8, display: 'flex', gap: 12, flexWrap: 'wrap' }}>
<span style={{
fontSize: 12,
color: '#1f2937',
background: 'rgba(245, 158, 11, 0.12)',
border: '1px solid rgba(245, 158, 11, 0.32)',
borderRadius: 6,
padding: '4px 8px',
fontWeight: 600
}}>V{lastSavedDeliveryVersion}</span>
<span style={{
fontSize: 12,
color: '#111827',
background: 'rgba(17, 24, 39, 0.08)',
border: '1px solid rgba(17, 24, 39, 0.18)',
borderRadius: 6,
padding: '4px 8px'
}}>record_id{lastSavedDeliveryRecordId}</span>
</div>
</Card>
)}
</div> </div>
)} )}
{/* 功能入口切换与调整入口 */} {/* 功能入口切换与调整入口 */}
@ -5534,7 +5872,49 @@ export default function App() {
{/* 批量处理配置已移除 */} {/* 批量处理配置已移除 */}
</main> </main>
)} )}
{mode === 'generate' && (
<>
{/* 面料数据查询结果 */}
{queryResults.length > 0 && (
<>
<Divider />
<Title heading={4}> ({queryResults.length} )</Title>
<Table
columns={columns}
dataSource={queryResults.map((item, index) => ({ ...item, key: index }))}
pagination={{ pageSize: 10 }}
style={{ marginTop: '10px' }}
/>
</>
)}
{/* 二次工艺查询结果 */}
{secondaryProcessResults.length > 0 && (
<>
<Divider />
<Title heading={4}> ({secondaryProcessResults.length} )</Title>
<Table
columns={secondaryProcessColumns}
dataSource={secondaryProcessResults.map((item, index) => ({ ...item, key: index }))}
pagination={{ pageSize: 10 }}
style={{ marginTop: '10px' }}
/>
</>
)}
{/* 工艺价格查询结果 */}
{pricingDetailsResults.length > 0 && (
<>
<Divider />
<Title heading={4}> ({pricingDetailsResults.length} )</Title>
<Table
columns={pricingDetailsColumns}
dataSource={pricingDetailsResults.map((item, index) => ({ ...item, key: index }))}
pagination={{ pageSize: 10 }}
style={{ marginTop: '10px' }}
/>
</>
)}
</>
)}
</div> </div>
); );
} }