/** * 坯布出仓单(其他出货单)常量与数据工具 * * 仅存放与接口无关的常量 / 状态映射 / 字段规范化 / 单位换算等纯工具, * 真实接口请求统一注册在 src/common/http.api.js 的 vm.$u.api.gfmOtherDeliveryOrder 下。 */ import dayjs from 'dayjs'; // 审核状态(对应 openapi common.OrderStatus) export const AUDIT_STATUS = { PENDING: 1, // 待审核 APPROVED: 2, // 已审核 REJECTED: 3, // 已驳回 VOIDED: 4, // 已作废 }; /** 前端草稿态(未保存) */ export const DRAFT_AUDIT_STATUS = 0; export const DRAFT_AUDIT_STATUS_NAME = '待保存'; export const AUDIT_STATUS_MAP = { 1: '待审核', 2: '已审核', 3: '已驳回', 4: '已作废', }; /** 坯布资料弹窗字段 */ export const FABRIC_INFO_FIELDS = [ { label: '坯布编号', key: 'grey_fabric_code' }, { label: '名称', key: 'grey_fabric_name' }, { label: '客户', key: 'customer_name' }, { label: '幅宽', key: 'grey_fabric_width' }, { label: '克重', key: 'grey_fabric_gram_weight' }, { label: '纱批', key: 'yarn_batch' }, { label: '针寸数', key: 'needle_size' }, { label: '原料纱名称', key: 'raw_material_yarn_name' }, { label: '原料批号', key: 'raw_material_batch_num' }, { label: '原料品牌', key: 'raw_material_batch_brand' }, { label: '颜色', key: 'gray_fabric_color_name' }, { label: '等级', key: 'grey_fabric_level_name' }, { label: '机台号', key: 'machine_number' }, { label: '织造工艺', key: 'weaving_process' }, { label: '坯布备注', key: 'grey_fabric_remark' }, ]; /** 细码表格列(不含操作列) */ export const FINE_CODE_TABLE_COLUMNS = [ { label: '序号', key: 'seq', width: 80 }, { label: '卷号', key: 'roll_no', width: 110 }, { label: '外部卷号', key: 'extern_volume_number', width: 140 }, { label: '匹数', key: 'roll', width: 100 }, { label: '数量(重量)', key: 'weight', width: 140 }, { label: '条码', key: 'bar_code', width: 280 }, { label: '仓位', key: 'position', width: 130 }, ]; /** 审核状态变更操作配置(apiKey 对应 gfmOtherDeliveryOrder 下的方法名) */ export const STATUS_ACTION_CONFIG = { approve: { status: AUDIT_STATUS.APPROVED, msg: '确认审核该单据?', apiKey: 'statusPass' }, void: { status: AUDIT_STATUS.VOIDED, msg: '确认作废该单据?', apiKey: 'statusCancel' }, unapprove: { status: AUDIT_STATUS.PENDING, msg: '确认消审该单据?', apiKey: 'statusWait' }, }; // 出仓类型:当前后端仅提供「坯布其他出货单」一种类型的接口 export const BILL_TYPES = [ { value: 'other', label: '坯布其他出货单' }, ]; /** 列表分页拉取每页条数 */ export const LIST_PAGE_SIZE = 100; /** 往来单位类型:染厂(染厂下拉筛选) */ export const BUSINESS_UNIT_TYPE_DYE_FACTORY = 12; export function getStatusOptions() { return Object.keys(AUDIT_STATUS_MAP).map((value) => ({ value, label: AUDIT_STATUS_MAP[value], })); } /** 将下拉/列表接口响应转为 u-select 所需的 { value, label } */ export function mapToSelectOptions(data) { const list = Array.isArray(data) ? data : (data && (data.list || data.items || data.records)) || []; return list.map((item) => ({ value: item.id, label: item.name || item.label || item.code || String(item.id), })); } // 匹数:后端以 0.01 匹存储(乘以100的整数),展示需除以100 export function formatRoll(value) { const n = Number(value || 0) / 100; return Number.isInteger(n) ? n : Number(n.toFixed(2)); } // 重量:后端整数存储(×10000),展示需除以 10000 为 KG export function formatWeightKg(value) { const n = Number(value || 0) / 10000; return Number(n.toFixed(2)); } /** 从详情对象取展示用总重量(优先用原始值换算) */ export function getOrderDisplayWeight(order) { if (!order) return 0; if (order._raw?.total_weight != null) return formatWeightKg(order._raw.total_weight); return order.total_weight ?? 0; } /** 从细码取展示用重量(优先用原始值换算) */ export function getFineCodeDisplayWeight(fineCode) { if (!fineCode) return 0; if (fineCode.raw_weight != null) return formatWeightKg(fineCode.raw_weight); return fineCode.weight || 0; } /** * 将接口详情数据规范化为页面使用的结构 */ export function normalizeOrder(order) { if (!order) return null; const itemData = groupItemData((order.item_data || []).map((item) => normalizeItem(item))); return { id: order.id, order_no: order.order_no || '', audit_status: order.audit_status, audit_status_name: order.audit_status_name || AUDIT_STATUS_MAP[order.audit_status] || '', // 表头 sale_system_id: order.sale_system_id || '', sale_system_name: order.sale_system_name || '', delivery_unit_id: order.delivery_unit_id || '', delivery_unit_name: order.delivery_unit_name || '', business_unit_id: order.business_unit_id || '', business_unit_name: order.business_unit_name || '', delivery_time: (order.delivery_time || '').slice(0, 10), remark: order.remark || '', // 人员/时间 creator_name: order.creator_name || '', create_time: order.create_time || '', auditer_name: order.auditer_name || '', audit_time: order.audit_time || '', // 汇总 total_roll: formatRoll(order.total_roll), total_weight: formatWeightKg(order.total_weight), item_data: itemData, _raw: order, }; } /** 扫码详情区表格列 */ export const SCAN_DETAIL_TABLE_COLUMNS = [ { label: '坯布编号', key: 'grey_fabric_code', width: 160 }, { label: '坯布名称', key: 'grey_fabric_name', width: 160 }, { label: '纱批', key: 'yarn_batch', width: 130 }, { label: '机号', key: 'machine_number', width: 110 }, { label: '条数', key: 'roll', width: 90 }, { label: '重量', key: 'weight', width: 110 }, ]; /** 扫码详情区可勾选锁定维度(对应 PDAScanGfmOtherDeliveryOrderParam 的 *_lock;勾选后参与表格分组并随扫码请求传给后端) */ export const SCAN_GROUP_OPTIONS = [ { key: 'produce_order_no', label: '合同' }, { key: 'grey_fabric_code', label: '布编' }, { key: 'yarn_batch', label: '纱批' }, { key: 'machine_number', label: '机号' }, { key: 'grey_fabric_level_name', label: '等级' }, ]; /** 勾选维度对应扫码接口锁定字段名(PDAScanGfmOtherDeliveryOrderParam) */ export const SCAN_MERGE_FIELD_MAP = { produce_order_no: 'produce_order_no_lock', grey_fabric_code: 'grey_fabric_code_lock', yarn_batch: 'yarn_batch_lock', machine_number: 'machine_number_lock', grey_fabric_level_name: 'grey_fabric_level_id_lock', }; /** 扫码锁定维度不匹配时的语音文案(按匹配优先级排列) */ const SCAN_MISMATCH_VOICE_RULES = [ { patterns: ['机台', '机号', 'machine_number'], voice: '机台不同' }, { patterns: ['纱批', 'yarn_batch'], voice: '纱批不同' }, { patterns: ['合同号', '合同', '生产通知单', 'produce_order'], voice: '合同号不同' }, { patterns: ['布编', 'grey_fabric_code'], voice: '布编不同' }, { patterns: ['等级', 'grey_fabric_level', 'level'], voice: '等级不同' }, ]; /** * 根据后端错误信息解析锁定维度不匹配的播报文案 * @param {string} message * @returns {string} 如「机台不同」;无法识别则返回空串 */ export function resolveScanMismatchVoice(message) { const msg = String(message || ''); if (!msg) return ''; for (let i = 0; i < SCAN_MISMATCH_VOICE_RULES.length; i++) { const rule = SCAN_MISMATCH_VOICE_RULES[i]; if (rule.patterns.some((p) => msg.indexOf(p) !== -1)) { return rule.voice; } } return ''; } /** 构建扫码新增/删除请求参数(勾选锁定维度时附带当前详情区字段值) */ export function buildScanUpdateParams({ id, scanCode, scanType, groupKeys = [], detail = {} }) { const params = { id: Number(id), scan_code: scanCode, scan_type: scanType, }; (groupKeys || []).forEach((key) => { const apiKey = SCAN_MERGE_FIELD_MAP[key]; if (!apiKey) return; if (apiKey === 'grey_fabric_level_id_lock') { const levelIds = detail.grey_fabric_level_id; params.grey_fabric_level_id_lock = Array.isArray(levelIds) ? levelIds : []; return; } const value = detail[key]; params[apiKey] = value != null && value !== '' ? value : ''; }); return params; } export function getEmptyScanDetail() { return { customer_id: '', customer_name: '', produce_order_no: '', grey_fabric_code: '', grey_fabric_name: '', yarn_batch: '', machine_number: '', volume_number: '', extern_volume_number: '', grey_fabric_level_id: [], grey_fabric_level_name: '', weight: 0, scanned_roll: 0, machine_stock: 0, total_roll: 0, total_weight: 0, }; } /** 扫码接口响应是否包含条码详情字段 */ export function hasScanDetailFields(raw) { if (!raw) return false; return raw.scanned_total_roll != null || raw.summary_stock_roll != null || !!raw.customer_name || !!raw.grey_fabric_code || !!raw.produce_order_no; } /** 从扫码接口返回的详情构建扫码详情区数据 */ export function buildScanDetailFromOrderResponse(order) { const raw = order?._raw || order; if (!hasScanDetailFields(raw)) return null; const detail = { customer_id: raw.customer_id || '', customer_name: raw.customer_name || '', produce_order_no: raw.produce_order_no || '', grey_fabric_code: raw.grey_fabric_code || '', grey_fabric_name: raw.grey_fabric_name || '', yarn_batch: raw.yarn_batch || '', machine_number: raw.machine_number || '', volume_number: raw.volume_number || '', extern_volume_number: raw.extern_volume_number != null && raw.extern_volume_number !== '' ? String(raw.extern_volume_number) : '', grey_fabric_level_id: raw.grey_fabric_level_id || [], grey_fabric_level_name: raw.grey_fabric_level_name || '', weight: 0, scanned_roll: formatRoll(raw.scanned_total_roll), machine_stock: raw.summary_stock_roll != null ? formatRoll(raw.summary_stock_roll) : 0, total_roll: order?.total_roll ?? 0, total_weight: getOrderDisplayWeight(order), }; return detail; } /** 在单据明细中定位本次扫描的坯布行与细码 */ export function findScanContext(itemData, scanCode) { const code = (scanCode || '').trim(); if (!code) return null; for (const item of itemData || []) { for (const fc of item.fine_codes || []) { const candidates = [ fc.fabric_piece_code, fc.bar_code, fc.roll_no, fc.scan_code, ].filter(Boolean).map(String); if (candidates.includes(code)) { return { item, fineCode: fc }; } } } return null; } /** 根据扫码上下文构建详情区展示数据 */ export function buildScanDetailFromContext(context, order) { if (!context) return getEmptyScanDetail(); const { item, fineCode } = context; const groupKey = getItemGroupKey(item); let scannedRoll = 0; (order.item_data || []).forEach((row) => { if (getItemGroupKey(row) !== groupKey) return; (row.fine_codes || []).forEach((fc) => { scannedRoll += Number(fc.roll || 0); }); }); return { customer_name: item.customer_name || '', produce_order_no: item.produce_order_no || item.contract_no || '', grey_fabric_code: item.grey_fabric_code || '', grey_fabric_name: item.grey_fabric_name || '', yarn_batch: item.yarn_batch || '', machine_number: item.machine_number || '', volume_number: fineCode.fabric_piece_code || fineCode.roll_no || fineCode.bar_code || '', extern_volume_number: fineCode.extern_volume_number || '', grey_fabric_level_name: item.grey_fabric_level_name || '', weight: getFineCodeDisplayWeight(fineCode), scanned_roll: Number(scannedRoll.toFixed(2)), machine_stock: item.machine_stock ?? 0, total_roll: order.total_roll ?? 0, total_weight: getOrderDisplayWeight(order), }; } function aggregateScanTableRows(rows, keys) { const map = new Map(); rows.forEach((row) => { const groupId = keys.map((k) => row[k] || '').join('|'); const existing = map.get(groupId); if (!existing) { map.set(groupId, { ...row, roll: 0, raw_weight: 0 }); } const agg = map.get(groupId); agg.roll = Number((agg.roll + Number(row.roll || 0)).toFixed(2)); agg.raw_weight = Number(agg.raw_weight || 0) + Number(row.raw_weight || 0); }); return Array.from(map.values()).map((row) => ({ ...row, weight: formatWeightKg(row.raw_weight), })); } /** 构建扫码详情区汇总表格数据 */ export function buildScanDetailTableRows(itemData, groupKeys = []) { const rows = []; (itemData || []).forEach((item) => { (item.fine_codes || []).forEach((fc) => { rows.push({ customer_name: item.customer_name || '', produce_order_no: item.produce_order_no || item.contract_no || '', grey_fabric_code: item.grey_fabric_code || '', grey_fabric_name: item.grey_fabric_name || '', yarn_batch: item.yarn_batch || '', machine_number: item.machine_number || '', volume_number: fc.roll_no || fc.fabric_piece_code || '', grey_fabric_level_name: item.grey_fabric_level_name || '', roll: fc.roll || 0, raw_weight: fc.raw_weight != null ? fc.raw_weight : 0, }); }); }); if (!rows.length) return []; const keys = (groupKeys || []).filter(Boolean); if (!keys.length) { return aggregateScanTableRows(rows, ['grey_fabric_code', 'grey_fabric_name', 'yarn_batch', 'machine_number']); } return aggregateScanTableRows(rows, keys); } export function getScanDetailTableWidth(columns) { const total = (columns || []).reduce((sum, col) => sum + (col.width || 120), 0); return `${total}rpx`; } export function normalizeItem(item) { const machineStockRaw = item.machine_stock_roll ?? item.machine_stock ?? item.machine_inventory_roll; return { id: item.id, grey_fabric_id: item.grey_fabric_id || '', grey_fabric_code: item.grey_fabric_code || '', grey_fabric_name: item.grey_fabric_name || '', customer_name: item.customer_name || '', produce_order_no: item.produce_order_no || item.contract_no || item.sale_contract_no || item.grey_fabric_contract_no || '', machine_stock: machineStockRaw != null ? formatRoll(machineStockRaw) : 0, grey_fabric_width: item.grey_fabric_width_and_unit_name || item.grey_fabric_width || '', grey_fabric_gram_weight: item.grey_fabric_gram_weight_and_unit_name || item.grey_fabric_gram_weight || '', yarn_batch: item.yarn_batch || '', needle_size: item.needle_size || '', raw_material_yarn_name: item.raw_material_yarn_name || '', raw_material_batch_num: item.raw_material_batch_num || '', raw_material_batch_brand: item.raw_material_batch_brand || '', gray_fabric_color_name: item.gray_fabric_color_name || '', grey_fabric_level_name: item.grey_fabric_level_name || '', machine_number: item.machine_number || '', weaving_process: item.weaving_process || '', grey_fabric_remark: item.grey_fabric_remark || '', roll: formatRoll(item.roll), weight: formatWeightKg(item.total_weight), fine_codes: (item.item_fc_data || []).map((fc) => ({ id: fc.id, roll_no: fc.volume_number || '', extern_volume_number: fc.extern_volume_number != null && fc.extern_volume_number !== '' ? String(fc.extern_volume_number) : '', fabric_piece_code: fc.fabric_piece_code || '', bar_code: fc.fabric_piece_code || fc.bar_code || '', position: fc.position || '', raw_weight: fc.weight, weight: formatWeightKg(fc.weight), roll: formatRoll(fc.roll), // 删除细码走 scanUpdate,优先 fabric_piece_code,其次卷号 scan_code: fc.fabric_piece_code || fc.volume_number || fc.bar_code || '', })), }; } /** 细码扫码/删除时传给 scanUpdate 的 scan_code(fabric_piece_code 或卷号) */ export function getFineCodeScanCode(fineCode) { if (!fineCode) return ''; return fineCode.fabric_piece_code || fineCode.scan_code || fineCode.bar_code || fineCode.roll_no || ''; } /** 坯布明细归类 key:优先 grey_fabric_id,否则 code + name */ export function getItemGroupKey(item) { if (item.grey_fabric_id) return `id:${item.grey_fabric_id}`; return `code:${item.grey_fabric_code}|${item.grey_fabric_name}`; } /** 将相同坯布的 item_data 合并为一行,细码 item_fc_data 汇总展示 */ export function groupItemData(items) { const grouped = new Map(); (items || []).forEach((item) => { const groupKey = getItemGroupKey(item); const existing = grouped.get(groupKey); if (!existing) { grouped.set(groupKey, { ...item, group_key: groupKey, source_ids: [item.id], fine_codes: [...(item.fine_codes || [])], }); return; } existing.source_ids.push(item.id); existing.fine_codes.push(...(item.fine_codes || [])); existing.roll = Number((existing.roll + item.roll).toFixed(2)); existing.weight = Number((existing.weight + item.weight).toFixed(2)); }); return Array.from(grouped.values()).map((row) => ({ ...row, id: row.grey_fabric_id || row.group_key, })); } // 构造审核状态变更接口的入参(id 为字符串) export function buildStatusParam(id, auditStatus) { return { id: String(id), audit_status: auditStatus }; } export function formatDisplayTime(value, format = 'YYYY-MM-DD') { if (!value) return ''; const parsed = dayjs(value); return parsed.isValid() ? parsed.format(format) : String(value).slice(0, 10); } export function canScanAtStatus(status) { return [AUDIT_STATUS.PENDING, AUDIT_STATUS.REJECTED].includes(status); } export function isLockedOrderStatus(status) { return [AUDIT_STATUS.APPROVED, AUDIT_STATUS.VOIDED].includes(status); } /** 分页拉取全部列表(直至末页) */ export function fetchAllOrderList(listFn, params = {}, pageSize = LIST_PAGE_SIZE) { const loadPage = (page, acc) => listFn({ ...params, page, size: pageSize }) .then((res) => { const batch = Array.isArray(res) ? res : (res && res.list) || []; const merged = acc.concat(batch); if (batch.length < pageSize) return merged; return loadPage(page + 1, merged); }); return loadPage(1, []); } /** 新增/编辑页底部:审核、作废 */ export function getFormStatusActionButtons(headerSaved, orderId, status) { if (!headerSaved || !orderId || !canScanAtStatus(status)) return []; return [ { label: '审核', action: 'approve' }, { label: '作废', action: 'void' }, ]; } /** 查看页底部:编辑、审核、作废 / 消审 */ export function getViewStatusActionButtons(status) { if (status === AUDIT_STATUS.PENDING || status === AUDIT_STATUS.REJECTED) { return [ { label: '编辑', action: 'edit' }, { label: '审核', action: 'approve' }, { label: '作废', action: 'void' }, ]; } if (status === AUDIT_STATUS.APPROVED) { return [{ label: '消审', action: 'unapprove' }]; } return []; } /** 将 normalizeOrder 结果映射为表单字段 */ export function orderToFormFields(order) { return { order_no: order.order_no, business_unit_id: order.business_unit_id, business_unit_name: order.business_unit_name, delivery_time: order.delivery_time, remark: order.remark, item_data: order.item_data, }; } export function getFineCodeTableColumns(readonly) { const cols = [...FINE_CODE_TABLE_COLUMNS]; if (!readonly) cols.push({ label: '操作', key: 'action', width: 100 }); return cols; } export function mapFineCodeTableRows(list, readonly) { return (list || []).map((item, index) => ({ seq: index + 1, roll_no: item.roll_no || '-', extern_volume_number: item.extern_volume_number || '-', roll: item.roll != null && item.roll !== '' ? item.roll : '-', weight: item.weight != null && item.weight !== '' ? `${item.weight}` : '-', bar_code: item.bar_code || '-', position: item.position || '-', action: readonly ? '' : '删除', })); } export function getFineCodeTableWidth(columns) { const total = (columns || []).reduce((sum, col) => sum + (col.width || 120), 0); return `${total}rpx`; } /** * 确认并执行审核状态变更(新增/编辑/查看页共用) * @param {Vue} vm 页面实例,需有 $u.api 与 showError * @param {{ orderId: number, action: string, onSuccess?: Function }} options */ export function confirmOrderStatusAction(vm, { orderId, action, onSuccess }) { const cfg = STATUS_ACTION_CONFIG[action]; if (!cfg || !orderId) return; const api = vm.$u.api.gfmOtherDeliveryOrder; const requestFn = api[cfg.apiKey]; if (typeof requestFn !== 'function') return; uni.showModal({ title: '提示', content: cfg.msg, success: (res) => { if (!res.confirm) return; uni.showLoading({ title: '处理中...' }); requestFn(buildStatusParam(orderId, cfg.status)) .then(() => { if (typeof onSuccess === 'function') return onSuccess(); uni.hideLoading(); }) .catch((e) => { uni.hideLoading(); if (vm.showError) vm.showError(e.message || '操作失败'); }); }, }); }