import { BasicColumn, FormSchema } from '/@/components/Table'; import { JVxeColumn, JVxeTypes } from '/@/components/jeecg/JVxeTable/types'; import { buildUUID } from '/@/utils/uuid'; import { createLocalStorage } from '/@/utils/cache'; import { loadCategoryData } from '/@/api/common/api'; import { loadTreeData as loadCategoryTreeRoot } from '/@/views/system/category/category.api'; import { MATERIAL_CATEGORY_ROOT_CODE } from '/@/views/mes/material/MesMixerMaterialSysCategory.api'; const formulaLineColumnStorage = createLocalStorage(); /** 明细列隐藏偏好 localStorage 键 */ export const FORMULA_LINE_COLUMN_CACHE_KEY = 'mes_xsl_formula_spec_line_hidden_columns'; /** 不允许隐藏的明细列 */ export const FORMULA_LINE_LOCKED_COLUMN_KEYS = ['phr', 'mixerMaterialId']; export interface FormulaLineColumnSettingItem { key: string; title: string; locked?: boolean; } const colHalf = { span: 12 }; const colThird = { span: 8 }; const colQuarter = { span: 6 }; /** 汇总区隐藏字段(明细表 footer 行录入,表单仅承载数据) */ export const summaryFooterHiddenFields: FormSchema[] = [ { field: 'aRubberTotalPhr', label: '', component: 'InputNumber', show: false }, { field: 'totalPhr', label: '', component: 'InputNumber', show: false }, ...Array.from({ length: 7 }, (_, index) => ({ field: `stage${index + 1}Total`, label: '', component: 'InputNumber', show: false, })), ]; export const SUMMARY_FOOTER_FIELD_KEYS = [ 'aRubberTotalPhr', 'totalPhr', 'stage1Total', 'stage2Total', 'stage3Total', 'stage4Total', 'stage5Total', 'stage6Total', 'stage7Total', ] as const; export const SUMMARY_METRICS_FIELD_KEYS = [ 'naturalRubber', 'syntheticRubber', 'totalAmount', 'weightUnitPrice', 'volumeUnitPrice', 'qRubberSg', 'aRubberSg', ] as const; /** 汇总指标隐藏字段(自定义表格录入,表单仅承载数据) */ export const summaryMetricsHiddenFields: FormSchema[] = SUMMARY_METRICS_FIELD_KEYS.map((field) => ({ field, label: '', component: 'InputNumber', show: false, })); export const stageTotalNumberProps = { min: 0, precision: 4, style: { width: '100%' }, bordered: false, controls: false, }; export const summaryMetricNumberProps = { min: 0, precision: 4, style: { width: '100%' }, bordered: false, controls: false, }; export const summarySgNumberProps = { min: 0, precision: 6, style: { width: '100%' }, bordered: false, controls: false, }; /** 明细行 PHR 合计(作为重量%计算的分母) */ export function calcTotalPhrFromLines(lines: Recordable[]): number | null { const sum = (lines || []).reduce((acc, row) => { const value = Number(row?.phr); return acc + (Number.isFinite(value) ? value : 0); }, 0); return sum > 0 ? Number(sum.toFixed(4)) : null; } /** 按 单条PHR / 总PHR × 100 回填各行重量% */ export function applyWeightPercentToLines(lines: Recordable[]): void { const totalPhr = calcTotalPhrFromLines(lines); (lines || []).forEach((row) => { if (!row) { return; } const phr = Number(row.phr); if (!Number.isFinite(phr) || totalPhr == null || totalPhr <= 0) { row.weightPercent = null; return; } row.weightPercent = Number(((phr / totalPhr) * 100).toFixed(1)); }); } /** STEP=A 的 PHR 合计(A胶 TOTAL PHR) */ export function calcARubberTotalPhrFromLines(lines: Recordable[]): number | null { const sum = (lines || []).reduce((acc, row) => { if (row?.step !== 'A') { return acc; } const value = Number(row?.phr); return acc + (Number.isFinite(value) ? value : 0); }, 0); return sum > 0 ? Number(sum.toFixed(4)) : null; } /** * 混合段累计合计(仅统计可编辑段数): * stage1Total=列1总和,stage2Total=列1+列2,…;超出混合段数的框置空。 */ export function calcStageTotalsFromLines(lines: Recordable[], mixingStages?: number | string | null): Recordable { const totals: Recordable = {}; const stageCount = getActiveStageCount(mixingStages); for (let i = 1; i <= 7; i++) { totals[`stage${i}Total`] = null; } if (stageCount <= 0) { return totals; } const colSums: number[] = []; for (let i = 1; i <= stageCount; i++) { const key = `stage${i}`; const sum = (lines || []).reduce((acc, row) => { const value = Number(row?.[key]); return acc + (Number.isFinite(value) ? value : 0); }, 0); colSums.push(sum); } let cumulative = 0; for (let i = 1; i <= stageCount; i++) { cumulative += colSums[i - 1]; totals[`stage${i}Total`] = cumulative > 0 ? Number(cumulative.toFixed(4)) : null; } return totals; } /** 底部汇总区:A胶 TOTAL PHR、TOTAL PHR、混合段 1..N 累计合计(N=混合段数) */ export function calcFooterSummaryFromLines(lines: Recordable[], mixingStages?: number | string | null): Recordable { return { aRubberTotalPhr: calcARubberTotalPhrFromLines(lines), totalPhr: calcTotalPhrFromLines(lines), ...calcStageTotalsFromLines(lines, mixingStages), }; } /** 明细行有效体积(优先使用已计算的 volume) */ function getLineVolumeValue(row: Recordable): number | null { const volume = Number(row?.volume); return Number.isFinite(volume) && volume > 0 ? volume : null; } /** * A胶比重 = STEP=A 各行 PHR 合计 ÷ STEP=A 各行体积合计 * 单行体积 = PHR ÷ 物料比重 */ export function calcARubberSgFromLines(lines: Recordable[]): number | null { let totalPhr = 0; let totalVolume = 0; (lines || []).forEach((row) => { if (row?.step !== 'A') { return; } const phr = Number(row?.phr); const volume = getLineVolumeValue(row); if (!Number.isFinite(phr) || phr <= 0 || volume == null) { return; } totalPhr += phr; totalVolume += volume; }); if (totalPhr <= 0 || totalVolume <= 0) { return null; } return Number((totalPhr / totalVolume).toFixed(6)); } /** * Q胶比重 = STEP=Q 各行 PHR 合计 ÷ STEP=Q 各行体积合计 * 单行体积 = PHR ÷ 物料比重 */ export function calcQRubberSgFromLines(lines: Recordable[]): number | null { let totalPhr = 0; let totalVolume = 0; (lines || []).forEach((row) => { if (row?.step !== 'Q') { return; } const phr = Number(row?.phr); const volume = getLineVolumeValue(row); if (!Number.isFinite(phr) || phr <= 0 || volume == null) { return; } totalPhr += phr; totalVolume += volume; }); if (totalPhr <= 0 || totalVolume <= 0) { return null; } return Number((totalPhr / totalVolume).toFixed(6)); } /** 默认物料小类编码(仅作初始配置参考) */ export const MIXER_MINOR_CATEGORY_CODE = { NATURAL_RUBBER: 'XSLMES_MATERIAL_RAW_AUX_TRJ', SYNTHETIC_RUBBER: 'XSLMES_MATERIAL_RAW_AUX_HCJ', } as const; export type RubberContentSetting = { naturalMinorCategoryIds: string[]; syntheticMinorCategoryIds: string[]; }; export const EMPTY_RUBBER_CONTENT_SETTING: RubberContentSetting = { naturalMinorCategoryIds: [], syntheticMinorCategoryIds: [], }; /** 将 MES物料分类 树递归展开为下拉选项(含大类、小类等全部节点) */ function flattenMaterialCategoryTreeToOptions( nodes: Recordable[], parentLabel = '', ): Array<{ label: string; value: string }> { const options: Array<{ label: string; value: string }> = []; (nodes || []).forEach((node) => { const id = String(node?.key ?? node?.id ?? node?.value ?? ''); const name = String(node?.title ?? node?.name ?? node?.text ?? ''); if (!id || !name) { return; } const label = parentLabel ? `${parentLabel} / ${name}` : name; options.push({ label, value: id }); const children = (node?.children as Recordable[]) || []; if (children.length) { options.push(...flattenMaterialCategoryTreeToOptions(children, label)); } }); return options; } /** 一次性加载 MES物料分类 全部节点,供含胶率设置手动多选 */ export async function fetchMaterialCategoryOptions(): Promise> { try { const list = await loadCategoryData({ code: MATERIAL_CATEGORY_ROOT_CODE }); const rows = Array.isArray(list) ? list : []; if (rows.length) { return rows .map((item: Recordable) => ({ label: String(item?.text ?? item?.label ?? item?.title ?? item?.name ?? ''), value: String(item?.value ?? item?.id ?? item?.key ?? ''), })) .filter((item) => item.value && item.label); } } catch { // 回退到与密炼物料页一致的分类树加载 } const treeRaw = await loadCategoryTreeRoot({ async: false, pcode: MATERIAL_CATEGORY_ROOT_CODE }); const tree = Array.isArray(treeRaw) ? treeRaw : []; return flattenMaterialCategoryTreeToOptions(tree); } /** @deprecated 请使用 fetchMaterialCategoryOptions */ export const fetchMixerMinorCategoryOptions = fetchMaterialCategoryOptions; /** 汇总区比重指标:Q胶比重、A胶比重 */ export function calcRubberSgMetricsFromLines(lines: Recordable[]): Recordable { return { qRubberSg: calcQRubberSgFromLines(lines), aRubberSg: calcARubberSgFromLines(lines), }; } /** * 含胶率:天然橡胶 / 合成橡胶 = 对应小类密炼物料的重量%之和,合计为两者之和 */ export function calcRubberContentMetricsFromLines( lines: Recordable[], naturalMinorCategoryIds?: string[] | null, syntheticMinorCategoryIds?: string[] | null, ): Recordable { const naturalIdSet = new Set((naturalMinorCategoryIds || []).filter(Boolean)); const syntheticIdSet = new Set((syntheticMinorCategoryIds || []).filter(Boolean)); let naturalRubber = 0; let syntheticRubber = 0; let hasNatural = false; let hasSynthetic = false; (lines || []).forEach((row) => { if (!row?.mixerMinorCategoryId) { return; } const weightPercent = Number(row?.weightPercent); if (!Number.isFinite(weightPercent)) { return; } if (naturalIdSet.has(row.mixerMinorCategoryId)) { naturalRubber += weightPercent; hasNatural = true; } else if (syntheticIdSet.has(row.mixerMinorCategoryId)) { syntheticRubber += weightPercent; hasSynthetic = true; } }); const naturalValue = hasNatural ? Number(naturalRubber.toFixed(4)) : null; const syntheticValue = hasSynthetic ? Number(syntheticRubber.toFixed(4)) : null; const totalAmount = naturalValue != null || syntheticValue != null ? Number(((naturalValue ?? 0) + (syntheticValue ?? 0)).toFixed(4)) : null; return { naturalRubber: naturalValue, syntheticRubber: syntheticValue, totalAmount, }; } /** 明细行仅前端展示字段(不落库) */ export const FORMULA_LINE_DISPLAY_FIELDS = ['mixerMajorCategoryText', 'mixerMinorCategoryText', 'mixerMinorCategoryId'] as const; /** 写入密炼物料关联的大类/小类展示文本 */ export function applyMixerCategoryDisplay(row: Recordable, material?: Recordable | null) { if (!row) { return; } if (!material) { row.mixerMajorCategoryText = ''; row.mixerMinorCategoryText = ''; row.mixerMinorCategoryId = null; return; } row.mixerMajorCategoryText = material.majorCategoryId_dictText || material.majorCategoryText || ''; row.mixerMinorCategoryText = material.minorCategoryId_dictText || material.minorCategoryText || ''; row.mixerMinorCategoryId = material.minorCategoryId || null; } let materialCategoryNameCache: Map | null = null; async function getMaterialCategoryNameCache(): Promise> { if (materialCategoryNameCache) { return materialCategoryNameCache; } try { const list = await loadCategoryData({ code: MATERIAL_CATEGORY_ROOT_CODE }); const rows = Array.isArray(list) ? list : []; materialCategoryNameCache = new Map( rows .map((item: Recordable) => [String(item?.value ?? item?.id ?? ''), String(item?.text ?? item?.label ?? '')] as const) .filter(([id, name]) => id && name), ); } catch { materialCategoryNameCache = new Map(); } return materialCategoryNameCache; } /** 关联查询并补全大类/小类展示(queryById 无 dictText 时回退分类字典) */ export async function hydrateMixerCategoryDisplay(row: Recordable, material?: Recordable | null) { applyMixerMaterialMeta(row, material); if (!row || !material) { return; } const needMajor = !row.mixerMajorCategoryText && material.majorCategoryId; const needMinor = !row.mixerMinorCategoryText && material.minorCategoryId; if (!needMajor && !needMinor) { return; } const cache = await getMaterialCategoryNameCache(); if (needMajor) { row.mixerMajorCategoryText = cache.get(String(material.majorCategoryId)) || ''; } if (needMinor) { row.mixerMinorCategoryText = cache.get(String(material.minorCategoryId)) || ''; } } /** 选择配合剂后回填物料关联信息(含大类/小类展示) */ export function applyMixerMaterialMeta(row: Recordable, material?: Recordable | null) { if (!row) { return; } if (!material) { row.mixerMaterialName = ''; row.mixerMaterialCode = ''; applyMixerCategoryDisplay(row, null); return; } row.mixerMaterialName = material.materialName || row.mixerMaterialName || ''; row.mixerMaterialCode = material.materialCode || row.mixerMaterialCode || ''; applyMixerCategoryDisplay(row, material); if (!row.materialDesc) { row.materialDesc = material.materialDesc || material.materialName || ''; } } /** 提交前剔除明细行展示字段 */ export function stripFormulaLineDisplayFields(line: Recordable) { const { mixerMajorCategoryText, mixerMinorCategoryText, mixerMinorCategoryId, ...rest } = line; return rest; } /** 明细表默认空行数(参照原系统约 20 行) */ export const DEFAULT_LINE_ROW_COUNT = 20; export function createEmptyLineRows(count = DEFAULT_LINE_ROW_COUNT): Recordable[] { return Array.from({ length: count }, () => ({ id: buildUUID() })); } /** 确保每行有唯一 id(JVxeTable rowKey 依赖 id,否则单元格无法渲染) */ export function normalizeLineRows(rows: Recordable[]): Recordable[] { return (rows || []).map((r) => ({ ...r, id: r?.id || buildUUID() })); } function sectionTitle(label: string, field: string): FormSchema { return { field, label, component: 'Divider', componentProps: { orientation: 'left', plain: false }, colProps: { span: 24 }, }; } const hasWorkflowInfo = ({ values }) => !!(values.proofreadBy || values.proofreadTime || values.auditBy || values.auditTime || values.approveBy || values.approveTime); export const columns: BasicColumn[] = [ { title: '示方编号', align: 'center', dataIndex: 'specCode', width: 150, fixed: 'left' }, { title: '发行编号', align: 'center', dataIndex: 'issueNumber', width: 140 }, { title: '胶料代号', align: 'center', dataIndex: 'rubberCode', width: 140 }, { title: '分类', align: 'center', dataIndex: 'category_dictText', width: 80 }, { title: '用途', align: 'center', dataIndex: 'purpose', width: 120, ellipsis: true }, { title: '人工配料', align: 'center', dataIndex: 'hasManualBatch', width: 90, customRender: ({ text }) => (text === 1 ? '是' : '否'), }, { title: '发行日期', align: 'center', dataIndex: 'issueDate', width: 110 }, { title: '制定人', align: 'center', dataIndex: 'createBy', width: 100, customRender: ({ record }) => record?.createBy_dictText || record?.createBy || '', }, { title: '审核人', align: 'center', dataIndex: 'auditBy', width: 100, defaultHidden: true }, { title: '批准人', align: 'center', dataIndex: 'approveBy', width: 100, defaultHidden: true }, { title: '状态', align: 'center', dataIndex: 'status_dictText', width: 120 }, { title: '混合段数', align: 'center', dataIndex: 'mixingStages', width: 90, defaultHidden: true }, { title: 'TOTAL PHR', align: 'center', dataIndex: 'totalPhr', width: 100, defaultHidden: true }, { title: '修改时间', align: 'center', dataIndex: 'updateTime', width: 165 }, ]; export const searchFormSchema: FormSchema[] = [ { label: '关键字', field: 'keyword', component: 'Input', colProps: { span: 6 }, componentProps: { placeholder: '示方编号/胶料代号/发行编号/用途' } }, { label: '分类', field: 'category', component: 'JDictSelectTag', componentProps: { dictCode: 'xslmes_formula_spec_category', placeholder: '请选择分类' }, colProps: { span: 6 }, }, { label: '状态', field: 'status', component: 'JDictSelectTag', componentProps: { dictCode: 'xslmes_formula_spec_status', placeholder: '请选择状态' }, colProps: { span: 6 }, }, { label: '发行日期起', field: 'issueDate_begin', component: 'DatePicker', componentProps: { valueFormat: 'YYYY-MM-DD', placeholder: '开始日期' }, colProps: { span: 6 }, }, { label: '发行日期止', field: 'issueDate_end', component: 'DatePicker', componentProps: { valueFormat: 'YYYY-MM-DD', placeholder: '结束日期' }, colProps: { span: 6 }, }, ]; /** 基本信息(顶部) */ export const basicFormSchema: FormSchema[] = [ { label: '', field: 'id', component: 'Input', show: false }, { label: '分类', field: 'category', component: 'JDictSelectTag', defaultValue: 'S', show: false, componentProps: { dictCode: 'xslmes_formula_spec_category', type: 'radioButton' }, colProps: { span: 24 }, }, { label: '示方编号', field: 'specCode', component: 'Input', show: false, dynamicRules: () => [{ required: true, message: '请选择胶料并生成示方编号' }], }, { label: '胶料代号', field: 'rubberCode', component: 'Input', show: false, dynamicRules: () => [{ required: true, message: '请选择胶料并生成胶料代号' }], }, { label: '', field: 'rubberMaterialId', component: 'Input', show: false, }, { label: '基本配合', field: 'basicFormula', component: 'Input', show: false, }, { label: '发行日期', field: 'issueDate', component: 'Input', show: false, }, { label: '发行编号', field: 'issueNumber', component: 'Input', show: false, }, { label: '用途', field: 'purpose', component: 'Input', show: false, }, { label: '混合段数', field: 'mixingStages', component: 'InputNumber', show: false, }, { label: '混合机器', field: 'mixingMachine', component: 'Input', show: false, }, { label: '发行部门', field: 'issueDeptName', component: 'Input', show: false, }, { label: '', field: 'issueDeptId', component: 'Input', show: false, }, { label: '状态', field: 'status', component: 'JDictSelectTag', defaultValue: 'compile', show: false, componentProps: { dictCode: 'xslmes_formula_spec_status', disabled: true }, colProps: colThird, }, ]; /** 汇总信息(底部,参照原配合施工表布局) */ export const summaryFormSchema: FormSchema[] = [ ...summaryFooterHiddenFields, ...summaryMetricsHiddenFields, { label: '发行理由', field: 'issueReason', component: 'InputTextArea', colProps: { span: 24 }, componentProps: { rows: 2, placeholder: '请输入发行理由', maxlength: 1000, showCount: true, bordered: false }, }, ]; /** 审批记录(详情时展示) */ export const workflowFormSchema: FormSchema[] = [ sectionTitle('审批记录', 'dividerWorkflow'), { label: '校对人', field: 'proofreadBy', component: 'Input', componentProps: { disabled: true, bordered: false }, colProps: colHalf, ifShow: ({ values }) => !!values.proofreadBy, }, { label: '校对时间', field: 'proofreadTime', component: 'Input', componentProps: { disabled: true, bordered: false }, colProps: colHalf, ifShow: ({ values }) => !!values.proofreadTime, }, { label: '审核人', field: 'auditBy', component: 'Input', componentProps: { disabled: true, bordered: false }, colProps: colHalf, ifShow: ({ values }) => !!values.auditBy, }, { label: '审核时间', field: 'auditTime', component: 'Input', componentProps: { disabled: true, bordered: false }, colProps: colHalf, ifShow: ({ values }) => !!values.auditTime, }, { label: '批准人', field: 'approveBy', component: 'Input', componentProps: { disabled: true, bordered: false }, colProps: colHalf, ifShow: ({ values }) => !!values.approveBy, }, { label: '批准时间', field: 'approveTime', component: 'Input', componentProps: { disabled: true, bordered: false }, colProps: colHalf, ifShow: ({ values }) => !!values.approveTime, }, ]; /** 有效混合段数(1-7),未配置时返回 0 */ export function getActiveStageCount(mixingStages?: number | string | null): number { if (mixingStages == null || mixingStages === '') { return 0; } const n = Number(mixingStages); if (Number.isNaN(n) || n <= 0) { return 0; } return Math.min(Math.max(Math.floor(n), 1), 7); } /** * STEP=A:PHR 写入第 1 混合段;STEP=Q:PHR 写入当前混合段数的最后一列。 * 返回需写回表格的 stage1-stage7 补丁;非 A/Q 或无可编辑段时返回 null。 */ export function buildStepStageValues(row: Recordable, mixingStages?: number | null): Recordable | null { const step = row?.step; if (step !== 'A' && step !== 'Q') { return null; } const stageCount = getActiveStageCount(mixingStages); if (stageCount <= 0) { return null; } const phrRaw = row?.phr; const phr = phrRaw != null && phrRaw !== '' && Number.isFinite(Number(phrRaw)) ? Number(phrRaw) : null; const targetStage = step === 'A' ? 1 : stageCount; const patch: Recordable = {}; for (let i = 1; i <= 7; i++) { patch[`stage${i}`] = i === targetStage ? phr : null; } return patch; } /** 将 STEP 规则应用到行对象(就地修改) */ export function applyStepPhrToLineRow(row: Recordable, mixingStages?: number | null): Recordable | null { const patch = buildStepStageValues(row, mixingStages); if (!patch) { return null; } Object.assign(row, patch); return patch; } /** 解析行 PHR(无效时返回 null) */ export function getLinePhrValue(row: Recordable): number | null { const phr = Number(row?.phr); return Number.isFinite(phr) && phr > 0 ? phr : null; } /** 可编辑混合段列合计(仅统计 1..stageCount) */ export function calcLineActiveStageSum(row: Recordable, stageCount: number): number { let sum = 0; for (let i = 1; i <= stageCount; i++) { const v = Number(row[`stage${i}`]); if (Number.isFinite(v)) { sum += v; } } return sum; } /** 混合段合计超过 PHR 时,扣减当前编辑列 */ export function clampStageToPhr(row: Recordable, stageCount: number, editedKey: string): number | null { const phr = getLinePhrValue(row); if (phr == null || stageCount <= 0) { return null; } const total = calcLineActiveStageSum(row, stageCount); if (total <= phr) { return null; } const current = Number(row[editedKey]); if (!Number.isFinite(current)) { return null; } const clamped = Math.max(0, Number((current - (total - phr)).toFixed(4))); row[editedKey] = clamped; return clamped; } /** STEP=A:手动编辑 2..N 列时,第 1 列 = PHR − 其余可编辑段之和 */ export function balanceStepAStages(row: Recordable, stageCount: number, editedKey?: string): Recordable { const patch: Recordable = {}; if (editedKey === 'stage1') { return patch; } const phr = getLinePhrValue(row); if (phr == null || stageCount <= 0) { return patch; } let otherSum = 0; for (let i = 2; i <= stageCount; i++) { const v = Number(row[`stage${i}`]); if (Number.isFinite(v)) { otherSum += v; } } const stage1 = Math.max(0, Number((phr - otherSum).toFixed(4))); const next = otherSum > 0 ? stage1 : phr; if (Number(row.stage1) !== next) { row.stage1 = next; patch.stage1 = next; } return patch; } /** STEP=Q:手动编辑非末列时,末列 = PHR − 其余可编辑段之和 */ export function balanceStepQStages(row: Recordable, stageCount: number, editedKey?: string): Recordable { const patch: Recordable = {}; const phr = getLinePhrValue(row); if (phr == null || stageCount <= 0) { return patch; } const lastKey = `stage${stageCount}`; if (editedKey === lastKey) { return patch; } let otherSum = 0; for (let i = 1; i < stageCount; i++) { const v = Number(row[`stage${i}`]); if (Number.isFinite(v)) { otherSum += v; } } const lastVal = Math.max(0, Number((phr - otherSum).toFixed(4))); const next = otherSum > 0 ? lastVal : phr; if (Number(row[lastKey]) !== next) { row[lastKey] = next; patch[lastKey] = next; } return patch; } /** * 混合段单元格编辑后:合计不超过 PHR;STEP=A/Q 自动平衡锚定列。 * STEP=A 编辑 2..N 列、STEP=Q 编辑非末列时:先扣减锚定列,再判断是否需截断当前列。 */ export function processStageCellEdit( row: Recordable, editedKey: string, mixingStages?: number | string | null ): { patch: Recordable; exceeded: boolean; needPhr: boolean } { const patch: Recordable = {}; const stageCount = getActiveStageCount(mixingStages); if (stageCount <= 0 || !/^stage\d+$/.test(editedKey)) { return { patch, exceeded: false, needPhr: false }; } const phr = getLinePhrValue(row); if (phr == null) { return { patch, exceeded: false, needPhr: true }; } const lastKey = `stage${stageCount}`; let exceeded = false; const mergePatch = (p: Recordable) => { Object.keys(p).forEach((k) => { patch[k] = p[k]; }); }; // STEP=A:编辑第 2..N 列时先从第 1 列扣减,避免误把当前列截断为 0 if (row.step === 'A' && editedKey !== 'stage1') { mergePatch(balanceStepAStages(row, stageCount, editedKey)); const clamped = clampStageToPhr(row, stageCount, editedKey); if (clamped != null) { patch[editedKey] = clamped; exceeded = true; mergePatch(balanceStepAStages(row, stageCount, editedKey)); } return { patch, exceeded, needPhr: false }; } // STEP=Q:编辑非末列时先从末列扣减 if (row.step === 'Q' && editedKey !== lastKey) { mergePatch(balanceStepQStages(row, stageCount, editedKey)); const clamped = clampStageToPhr(row, stageCount, editedKey); if (clamped != null) { patch[editedKey] = clamped; exceeded = true; mergePatch(balanceStepQStages(row, stageCount, editedKey)); } return { patch, exceeded, needPhr: false }; } // 编辑锚定列或无 STEP:仅截断当前列 const clamped = clampStageToPhr(row, stageCount, editedKey); if (clamped != null) { patch[editedKey] = clamped; exceeded = true; } return { patch, exceeded, needPhr: false }; } /** 根据混合段数动态生成明细列(1-7 段列按混合段数控制可编辑;未填混合段数时全部禁用) */ export function buildLineJVxeColumns(mixingStages?: number | null, tableDisabled = false): JVxeColumn[] { const stageCount = getActiveStageCount(mixingStages); const hasStages = stageCount > 0; const baseCols: JVxeColumn[] = [ { title: 'PHR', key: 'phr', type: JVxeTypes.inputNumber, minWidth: 90, align: 'center', }, { title: '配合剂', key: 'mixerMaterialId', type: JVxeTypes.slot, slotName: 'mixerMaterialSlot', minWidth: 200, }, { title: '物料大类', key: 'mixerMajorCategoryText', type: JVxeTypes.normal, minWidth: 110, align: 'center', }, { title: '物料小类', key: 'mixerMinorCategoryText', type: JVxeTypes.normal, minWidth: 110, align: 'center', }, { title: '物料描述', key: 'materialDesc', type: JVxeTypes.input, minWidth: 160, }, { title: 'STEP', key: 'step', type: JVxeTypes.select, minWidth: 80, align: 'center', dictCode: 'xslmes_formula_spec_step', }, { title: '自动/人工', key: 'weighMode', type: JVxeTypes.select, minWidth: 110, align: 'center', dictCode: 'xslmes_formula_spec_weigh_mode', }, { title: '重量%', key: 'weightPercent', type: JVxeTypes.inputNumber, minWidth: 90, align: 'center', }, { title: '体积', key: 'volume', type: JVxeTypes.inputNumber, minWidth: 90, align: 'center', placeholder: '自动计算', }, { title: '备注', key: 'remark', type: JVxeTypes.input, minWidth: 80, }, ]; for (let i = 1; i <= 7; i++) { const stageNo = i; const stageDisabled = tableDisabled || !hasStages || stageNo > stageCount; baseCols.push({ title: String(i), key: `stage${i}`, type: JVxeTypes.inputNumber, minWidth: 56, align: 'center', disabled: stageDisabled, className: stageDisabled ? 'formula-stage-cell-disabled' : '', headerClassName: stageDisabled ? 'formula-stage-header-disabled' : '', props: { isDisabledCell: () => stageDisabled, }, }); } return baseCols; } /** 明细列设置项(1-7 段列固定展示在设置列表中) */ export function getFormulaLineColumnSettingItems(): FormulaLineColumnSettingItem[] { return buildLineJVxeColumns(null, false).map((col) => ({ key: String(col.key), title: String(col.title), locked: FORMULA_LINE_LOCKED_COLUMN_KEYS.includes(String(col.key)), })); } /** 读取已隐藏的明细列 key */ export function loadFormulaLineHiddenColumnKeys(): string[] { const saved = formulaLineColumnStorage.get(FORMULA_LINE_COLUMN_CACHE_KEY); if (!Array.isArray(saved)) { return []; } const validKeys = new Set(getFormulaLineColumnSettingItems().map((item) => item.key)); return saved.filter( (key) => typeof key === 'string' && !FORMULA_LINE_LOCKED_COLUMN_KEYS.includes(key) && validKeys.has(key), ); } /** 保存已隐藏的明细列 key */ export function saveFormulaLineHiddenColumnKeys(hiddenKeys: string[]) { const validKeys = hiddenKeys.filter((key) => !FORMULA_LINE_LOCKED_COLUMN_KEYS.includes(key)); if (validKeys.length) { formulaLineColumnStorage.set(FORMULA_LINE_COLUMN_CACHE_KEY, validKeys); return; } formulaLineColumnStorage.remove(FORMULA_LINE_COLUMN_CACHE_KEY); } /** 按隐藏配置过滤明细列 */ export function applyFormulaLineColumnVisibility(columns: JVxeColumn[], hiddenKeys: string[]): JVxeColumn[] { if (!hiddenKeys?.length) { return columns; } const hiddenSet = new Set(hiddenKeys); return columns.filter((col) => !col.key || !hiddenSet.has(String(col.key))); } /** 审批进度操作人展示:优先字典翻译姓名,其次当前登录用户匹配用户名时取 realname */ export function resolveFormulaSpecUserDisplayName( username?: string | null, dictText?: string | null, fallbackUser?: { username?: string; realname?: string }, ) { if (dictText != null && dictText !== '') { return String(dictText); } const user = fallbackUser || {}; if (username != null && username !== '') { const name = String(username); if (user.username && name === user.username && user.realname) { return user.realname; } return name; } return user.realname || user.username || ''; } export const superQuerySchema = { specCode: { title: '示方编号', order: 0, view: 'text' }, rubberCode: { title: '胶料代号', order: 1, view: 'text' }, issueNumber: { title: '发行编号', order: 2, view: 'text' }, category: { title: '分类', order: 3, view: 'list', dictCode: 'xslmes_formula_spec_category' }, purpose: { title: '用途', order: 4, view: 'text' }, status: { title: '状态', order: 5, view: 'list', dictCode: 'xslmes_formula_spec_status' }, issueDate: { title: '发行日期', order: 6, view: 'date' }, };