Files
tms-erp-web/src/option/business/transport-plan.js
T
2026-09-09 14:43:38 +08:00

642 lines
17 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
auditColumns,
createCrudOption,
phoneRule,
planStatusOptions,
selectRule,
textRule,
withSearchPlaceholders,
} from './common';
const transportPlanDataSourceOptions = [
{ label: '批量导入', value: '批量导入' },
{ label: '手工创建', value: '手工创建' },
{ label: '外部系统', value: '外部系统' },
];
const listDicFormatter = res => {
const data = res?.data || res;
if (Array.isArray(data)) return data;
if (Array.isArray(data?.records)) return data.records;
if (Array.isArray(data?.data)) return data.data;
if (Array.isArray(data?.data?.records)) return data.data.records;
return [];
};
const projectNameDicFormatter = res => {
const list = listDicFormatter(res);
// 过滤掉 projectName 为空的记录
return list.filter(item => item && item.projectName && String(item.projectName).trim());
};
const parseGoodsRows = value => {
if (!value) return [];
if (Array.isArray(value)) return value;
if (typeof value === 'object') return [value];
try {
const data = JSON.parse(value);
return Array.isArray(data) ? data : [data];
} catch (error) {
return [];
}
};
const formatGoodsField = (row, propList) => {
const values = propList.map(prop => row[prop]).filter(Boolean);
if (values.length) return values.join('、');
return parseGoodsRows(row.goodsJson)
.map(item => propList.map(prop => item?.[prop]).find(Boolean))
.filter(Boolean)
.join('、');
};
const getGoodsQuantity = item => {
const value = item?.quantity ?? item?.cargoQuantity ?? item?.goodsQuantity ?? 0;
const number = Number(String(value).replace(/,/g, ''));
return Number.isFinite(number) ? number : 0;
};
const formatGoodsQuantity = value => {
const number = Math.round((value + Number.EPSILON) * 1000) / 1000;
return Number.isInteger(number) ? String(number) : String(number).replace(/\.?0+$/, '');
};
const getSecondCargoTypeName = item =>
item?.secondCargoTypeName ||
item?.secondCargoType ||
item?.cargoType ||
item?.goodsType ||
item?.type ||
item?.cargoTypeName ||
item?.firstCargoTypeName ||
'货物';
const formatGoodsInfo = row => {
const rows =
[row.goodsJson, row.goodsList, row.goodsRows, row.cargoList, row.cargoRows, row.goods]
.map(source => parseGoodsRows(source))
.filter(source => source.length)
.sort((left, right) => right.length - left.length)[0] || [];
if (!rows.length) return row.goodsInfo || '';
const groups = rows.reduce((result, item = {}) => {
const hasGoodsInfo = [
item.cargoName,
item.goodsName,
item.name,
item.cargoType,
item.secondCargoTypeName,
item.goodsType,
item.type,
item.quantity,
item.cargoQuantity,
item.goodsQuantity,
item.quantityUnit,
item.goodsQuantityUnit,
item.cargoUnit,
item.unit,
].some(Boolean);
if (!hasGoodsInfo) return result;
const unit = String(
item.quantityUnit ||
item.goodsQuantityUnit ||
item.cargoUnit ||
item.unit ||
row.quantityUnit ||
row.goodsQuantityUnit ||
row.cargoUnit ||
row.unit ||
''
).trim();
if (!result[unit]) {
result[unit] = {
typeName: getSecondCargoTypeName(item),
count: 0,
quantity: 0,
};
}
result[unit].count += 1;
result[unit].quantity += getGoodsQuantity(item);
return result;
}, {});
const text = Object.entries(groups)
.map(([unit, group]) => {
const quantity = formatGoodsQuantity(group.quantity);
return `${group.typeName}${group.count}种货物 | ${quantity}${unit ? ` ${unit}` : ''}`;
})
.join(' ');
return text || row.goodsInfo || '';
};
const parseDispatchRows = row => {
const sources = [
row.dispatchRows,
row.dispatchList,
row.dispatchRecords,
row.waybillList,
row.waybillRows,
];
for (const source of sources) {
const rows = parseGoodsRows(source);
if (rows.length) return rows;
}
return [];
};
const formatDispatchQuantity = value => {
const number = Number(value || 0);
if (!Number.isFinite(number)) return '0';
const fixed = Math.round((number + Number.EPSILON) * 1000) / 1000;
return Number.isInteger(fixed) ? String(fixed) : String(fixed).replace(/\.?0+$/, '');
};
const formatDispatchProgress = row => {
const goodsRows = parseGoodsRows(row.goodsJson);
const totalRows = goodsRows.length ? goodsRows : [row];
const dispatchRows = parseDispatchRows(row);
const summary = new Map();
const add = (item, field) => {
const unit = item.quantityUnit || item.goodsQuantityUnit || item.unit || '吨';
const quantity = Number(item.quantity || item.cargoQuantity || item.goodsQuantity || 0);
if (!summary.has(unit)) summary.set(unit, { total: 0, assigned: 0 });
if (Number.isFinite(quantity)) summary.get(unit)[field] += quantity;
};
totalRows.forEach(item => add(item, 'total'));
dispatchRows.forEach(item => add(item, 'assigned'));
if (!dispatchRows.length) {
const assigned = Number(row.dispatchedQuantity || row.dispatchQuantity || 0);
if (Number.isFinite(assigned)) {
const unit = row.quantityUnit || row.goodsQuantityUnit || row.unit || '吨';
if (!summary.has(unit)) summary.set(unit, { total: 0, assigned: 0 });
summary.get(unit).assigned = assigned;
}
}
const lines = Array.from(summary.entries()).map(
([unit, item]) =>
`${formatDispatchQuantity(item.assigned)}/${formatDispatchQuantity(item.total)}${unit}`
);
return lines.length
? lines.join('\n')
: row.dispatchProgressName || row.dispatchProgress || row.progress || '';
};
const auditColumn = prop => ({ ...auditColumns.find(item => item.prop === prop) });
export const config = {
title: '运输计划',
permission: 'transport_plan',
exportUrl: '/blade-transport/transport-plan/export-transport-plan',
exportName: '运输计划',
enableProjectSelect: true,
projectQueryParams: {
approvalStatuses: 'approved,change_approved',
},
contractQueryParams: {
contractCategory: '客户合同',
},
templateCreateTarget: 'transport-plan',
enableAttachmentTable: true,
enableTransportPlanForm: true,
enableTransportPlanCreateActions: true,
enableTransportPlanImport: true,
fixedTransportAddressType: true,
transportPlanImportTitle: '导入运输计划',
transportPlanTemplateUrl: '/blade-transport/transport-plan/export-template',
attachmentTitle: '附件',
searchRangeMap: {
planStartDateRange: ['planStartDateStart', 'planStartDateEnd'],
planEndDateRange: ['planEndDateStart', 'planEndDateEnd'],
},
actions: ['copy', 'complete'],
statusProp: 'businessStatus',
statusTextProp: 'businessStatusName',
deleteStatus: ['draft', 'waiting_dispatch'],
editStatus: ['draft', 'waiting_dispatch', 'dispatching'],
detailButton: true,
detailSections: [
{
title: '基本信息',
fields: [
['planNo', '计划单号'],
['planName', '计划名称'],
['projectName', '项目名称'],
['customerName', '客户名称'],
['transportType', '运输方式'],
['businessStatus', '调度状态'],
['planStartDate', '计划开始日期'],
['planEndDate', '计划结束日期'],
['dataSource', '数据来源'],
],
},
{
title: '收发货信息',
fields: [
['departureName', '发货地'],
['departureContact', '发货联系人'],
['departureAddress', '发货地址'],
['departurePhone', '发货联系方式'],
['arrivalName', '收货地'],
['arrivalContact', '收货联系人'],
['arrivalAddress', '到货地址'],
['arrivalPhone', '收货联系方式'],
],
},
{
title: '其它信息',
fields: [
['remark', '备注'],
['updateUserName', '更新人'],
['updateTime', '更新时间'],
['createTime', '创建时间'],
],
},
],
operations: [
{
action: 'dispatch',
label: '调度',
statusProp: 'businessStatus',
status: ['waiting_dispatch', 'dispatching'],
permission: 'transport_plan_edit',
confirm: '确定调度该运输计划?',
},
],
};
export const option = {
...createCrudOption(
withSearchPlaceholders([
{
label: '',
prop: 'basicInfoTitle',
formslot: true,
span: 24,
order: 400,
hide: true,
labelWidth: 0,
},
{
label: '计划单号',
prop: 'planNo',
search: true,
searchOrder: 13,
span: 6,
order: 370,
minWidth: 150,
disabled: true,
placeholder: '系统自动生成',
},
{
label: '计划名称',
prop: 'planName',
search: true,
searchOrder: 12,
span: 6,
order: 380,
minWidth: 150,
rules: textRule('计划名称', 50, true),
},
{
label: '运输方式',
prop: 'transportType',
type: 'select',
search: true,
searchOrder: 10,
span: 6,
order: 360,
dicUrl: '/blade-system/dict-biz/dictionary?code=transport_type',
props: {
label: 'dictValue',
value: 'dictKey',
},
minWidth: 130,
rules: selectRule('运输类型'),
},
{
label: '货物信息',
prop: 'goodsInfo',
formatter: row => formatGoodsInfo(row),
minWidth: 320,
overHidden: false,
showOverflowTooltip: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{
label: '调度进度',
prop: 'dispatchProgress',
formatter: row => formatDispatchProgress(row),
minWidth: 120,
overHidden: false,
showOverflowTooltip: false,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{
label: '发货地址',
prop: 'departureAddress',
formslot: true,
search: true,
searchOrder: 7,
span: 24,
order: 310,
minWidth: 220,
rules: textRule('发货地址', 255, true),
},
{
label: '到货地址',
prop: 'arrivalAddress',
formslot: true,
search: true,
searchOrder: 6,
span: 24,
order: 300,
minWidth: 220,
rules: textRule('收货地址', 255, true),
},
{
label: '项目名称',
prop: 'projectName',
formslot: true,
search: true,
searchOrder: 11,
searchType: 'select',
span: 6,
order: 400,
dicUrl: '/blade-transport/project-apply/list?current=1&size=9999',
dicFormatter: projectNameDicFormatter,
props: {
label: 'projectName',
value: 'projectName',
},
filterable: true,
minWidth: 150,
rules: textRule('项目', 100, true),
},
{
label: '客户名称',
prop: 'customerName',
search: true,
searchOrder: 5,
minWidth: 150,
addDisplay: false,
editDisplay: false,
},
{
label: '计划开始日期',
prop: 'planStartDate',
type: 'date',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
span: 6,
order: 350,
minWidth: 150,
rules: [{ required: true, message: '请选择计划开始日期', trigger: 'change' }],
},
{
label: '计划结束日期',
prop: 'planEndDate',
type: 'date',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
span: 6,
order: 340,
minWidth: 150,
rules: [{ required: true, message: '请选择计划结束日期', trigger: 'change' }],
},
{
label: '数据来源',
prop: 'dataSource',
type: 'select',
search: true,
searchOrder: 1,
dicData: transportPlanDataSourceOptions,
minWidth: 120,
addDisplay: false,
editDisplay: false,
},
{
label: '货物类型',
prop: 'cargoType',
search: true,
searchOrder: 8,
formatter: row => formatGoodsField(row, ['cargoType', 'goodsType']),
minWidth: 130,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{
label: '货物名称',
prop: 'cargoName',
search: true,
searchOrder: 9,
formatter: row => formatGoodsField(row, ['cargoName', 'goodsName']),
minWidth: 150,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{
label: '备注',
prop: 'remark',
type: 'textarea',
minRows: 2,
span: 24,
order: 330,
minWidth: 180,
maxlength: 200,
showWordLimit: true,
rules: textRule('备注', 200),
},
{
...auditColumn('updateUserName'),
},
{
...auditColumn('createTime'),
},
{
...auditColumn('updateTime'),
},
{
label: '调度状态',
prop: 'businessStatus',
type: 'select',
slot: true,
search: true,
searchLabel: '状态',
searchOrder: 2,
dicData: planStatusOptions,
minWidth: 82,
fixed: 'right',
addDisplay: false,
editDisplay: false,
},
{
label: '',
prop: 'shippingInfoTitle',
formslot: true,
span: 24,
order: 320,
hide: true,
labelWidth: 0,
},
{
label: '项目ID',
prop: 'projectId',
hide: true,
display: false,
},
{
label: '客户合同',
prop: 'contractName',
formslot: true,
hide: true,
span: 6,
order: 390,
minWidth: 160,
rules: textRule('客户合同', 100, true),
},
{
label: '客户合同ID',
prop: 'contractId',
hide: true,
display: false,
},
{
label: '发货地',
prop: 'departureName',
hide: true,
display: false,
minWidth: 140,
rules: textRule('发货地', 100, true),
},
{
label: '收货地',
prop: 'arrivalName',
hide: true,
display: false,
minWidth: 140,
rules: textRule('收货地', 100, true),
},
{
label: '发货联系人',
prop: 'departureContact',
hide: true,
display: false,
rules: textRule('发货联系人', 50),
},
{
label: '发货联系方式',
prop: 'departurePhone',
hide: true,
display: false,
rules: [phoneRule('发货联系方式'), ...textRule('发货联系方式', 50)],
},
{
label: '收货联系人',
prop: 'arrivalContact',
hide: true,
display: false,
rules: textRule('收货联系人', 50),
},
{
label: '收货联系方式',
prop: 'arrivalPhone',
hide: true,
display: false,
rules: [phoneRule('收货联系方式'), ...textRule('收货联系方式', 50)],
},
{
label: '',
prop: 'goodsInfoTitle',
formslot: true,
span: 24,
order: 280,
hide: true,
labelWidth: 0,
},
{
label: '计划开始日期',
prop: 'planStartDateRange',
type: 'date',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
search: true,
searchRange: true,
searchOrder: 4,
hide: true,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{
label: '计划结束日期',
prop: 'planEndDateRange',
type: 'date',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
search: true,
searchRange: true,
searchOrder: 3,
hide: true,
addDisplay: false,
editDisplay: false,
viewDisplay: false,
},
{
label: '所属组织',
prop: 'deptName',
hide: true,
minWidth: 150,
addDisplay: false,
editDisplay: false,
},
{
label: '',
prop: 'goodsJson',
type: 'textarea',
formslot: true,
minRows: 2,
span: 24,
order: 270,
hide: true,
labelWidth: '0px',
className: 'transport-plan-page__full-form-item',
},
{
label: '',
prop: 'attachmentTitle',
formslot: true,
span: 24,
order: 265,
hide: true,
labelWidth: 0,
},
{
label: '',
prop: 'attachmentsJson',
type: 'textarea',
formslot: true,
minRows: 2,
span: 24,
order: 260,
hide: true,
labelWidth: '0px',
className: 'transport-plan-page__full-form-item',
},
])
),
// label 固定宽度(容纳 6 汉字),覆盖 createCrudOption 默认 labelWidth:'auto'
labelWidth: 100,
dialogWidth: '96%',
dialogTop: '4vh',
dialogCustomClass: 'transport-plan-dialog',
};