Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -7,6 +7,7 @@ const api = createCrudApi(baseUrl);
|
|||||||
export const getList = api.getList;
|
export const getList = api.getList;
|
||||||
export const getDetail = api.getDetail;
|
export const getDetail = api.getDetail;
|
||||||
export const submit = api.submit;
|
export const submit = api.submit;
|
||||||
|
export const saveDraft = data => request({ url: `${baseUrl}/save-draft`, method: 'post', data });
|
||||||
export const remove = api.remove;
|
export const remove = api.remove;
|
||||||
export const copy = api.copy;
|
export const copy = api.copy;
|
||||||
export const cancel = api.cancel;
|
export const cancel = api.cancel;
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ export const getAvailableOptions = (keyword, deptId, selectedId) =>
|
|||||||
method: 'get',
|
method: 'get',
|
||||||
params: { keyword, deptId, selectedId },
|
params: { keyword, deptId, selectedId },
|
||||||
});
|
});
|
||||||
|
export const getAvailablePage = (current, size, keyword, deptId, selectedId) =>
|
||||||
|
request({
|
||||||
|
url: `${baseUrl}/available-page`,
|
||||||
|
method: 'get',
|
||||||
|
params: { current, size, keyword, deptId, selectedId },
|
||||||
|
});
|
||||||
export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data });
|
export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data });
|
||||||
export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } });
|
export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } });
|
||||||
|
|
||||||
|
|||||||
@@ -24,11 +24,6 @@ export const paymentTypeOptions = [
|
|||||||
{ label: '进度预付', value: 'progress_advance' },
|
{ label: '进度预付', value: 'progress_advance' },
|
||||||
{ label: '结算付款', value: 'settlement_payment' },
|
{ label: '结算付款', value: 'settlement_payment' },
|
||||||
];
|
];
|
||||||
export const paymentMethodOptions = [
|
|
||||||
{ label: '银行转账', value: 'bank_transfer' },
|
|
||||||
{ label: '银行承兑汇票', value: 'bank_draft' },
|
|
||||||
{ label: '商业承兑汇票', value: 'commercial_draft' },
|
|
||||||
];
|
|
||||||
export const approvalStatusOptions = [
|
export const approvalStatusOptions = [
|
||||||
{ label: '草稿', value: 'draft' },
|
{ label: '草稿', value: 'draft' },
|
||||||
{ label: '审批中', value: 'reviewing' },
|
{ label: '审批中', value: 'reviewing' },
|
||||||
|
|||||||
@@ -10,6 +10,18 @@ export const getDetail = id => request({ url: `${baseUrl}/detail`, method: 'get'
|
|||||||
export const getContractOptions = keyword =>
|
export const getContractOptions = keyword =>
|
||||||
request({ url: `${baseUrl}/contract-options`, method: 'get', params: { keyword } });
|
request({ url: `${baseUrl}/contract-options`, method: 'get', params: { keyword } });
|
||||||
|
|
||||||
|
export const getContractList = (current, size, params) =>
|
||||||
|
request({
|
||||||
|
url: '/blade-transport/contract-manage/list',
|
||||||
|
method: 'get',
|
||||||
|
params: {
|
||||||
|
current,
|
||||||
|
size,
|
||||||
|
approvalStatuses: 'approved,change_approved',
|
||||||
|
...params,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const getFeeOptions = () => request({ url: `${baseUrl}/fee-options`, method: 'get' });
|
export const getFeeOptions = () => request({ url: `${baseUrl}/fee-options`, method: 'get' });
|
||||||
|
|
||||||
export const getCandidateDetails = (current, size, params) =>
|
export const getCandidateDetails = (current, size, params) =>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
auditColumns,
|
auditColumns,
|
||||||
createCrudOption,
|
createCrudOption,
|
||||||
dataSourceOptions,
|
|
||||||
phoneRule,
|
phoneRule,
|
||||||
planStatusOptions,
|
planStatusOptions,
|
||||||
selectRule,
|
selectRule,
|
||||||
@@ -9,6 +8,12 @@ import {
|
|||||||
withSearchPlaceholders,
|
withSearchPlaceholders,
|
||||||
} from './common';
|
} from './common';
|
||||||
|
|
||||||
|
const transportPlanDataSourceOptions = [
|
||||||
|
{ label: '批量导入', value: '批量导入' },
|
||||||
|
{ label: '手工创建', value: '手工创建' },
|
||||||
|
{ label: '外部系统', value: '外部系统' },
|
||||||
|
];
|
||||||
|
|
||||||
const listDicFormatter = res => {
|
const listDicFormatter = res => {
|
||||||
const data = res?.data || res;
|
const data = res?.data || res;
|
||||||
if (Array.isArray(data)) return data;
|
if (Array.isArray(data)) return data;
|
||||||
@@ -61,7 +66,17 @@ const getSecondCargoTypeName = item =>
|
|||||||
'货物';
|
'货物';
|
||||||
|
|
||||||
const formatGoodsInfo = row => {
|
const formatGoodsInfo = row => {
|
||||||
const rows = parseGoodsRows(row.goodsJson);
|
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 || '';
|
if (!rows.length) return row.goodsInfo || '';
|
||||||
|
|
||||||
const groups = rows.reduce((result, item = {}) => {
|
const groups = rows.reduce((result, item = {}) => {
|
||||||
@@ -78,11 +93,22 @@ const formatGoodsInfo = row => {
|
|||||||
item.goodsQuantity,
|
item.goodsQuantity,
|
||||||
item.quantityUnit,
|
item.quantityUnit,
|
||||||
item.goodsQuantityUnit,
|
item.goodsQuantityUnit,
|
||||||
|
item.cargoUnit,
|
||||||
item.unit,
|
item.unit,
|
||||||
].some(Boolean);
|
].some(Boolean);
|
||||||
if (!hasGoodsInfo) return result;
|
if (!hasGoodsInfo) return result;
|
||||||
|
|
||||||
const unit = item.quantityUnit || item.goodsQuantityUnit || item.unit || '';
|
const unit = String(
|
||||||
|
item.quantityUnit ||
|
||||||
|
item.goodsQuantityUnit ||
|
||||||
|
item.cargoUnit ||
|
||||||
|
item.unit ||
|
||||||
|
row.quantityUnit ||
|
||||||
|
row.goodsQuantityUnit ||
|
||||||
|
row.cargoUnit ||
|
||||||
|
row.unit ||
|
||||||
|
''
|
||||||
|
).trim();
|
||||||
if (!result[unit]) {
|
if (!result[unit]) {
|
||||||
result[unit] = {
|
result[unit] = {
|
||||||
typeName: getSecondCargoTypeName(item),
|
typeName: getSecondCargoTypeName(item),
|
||||||
@@ -388,7 +414,7 @@ export const option = {
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
search: true,
|
search: true,
|
||||||
searchOrder: 1,
|
searchOrder: 1,
|
||||||
dicData: dataSourceOptions,
|
dicData: transportPlanDataSourceOptions,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
addDisplay: false,
|
addDisplay: false,
|
||||||
editDisplay: false,
|
editDisplay: false,
|
||||||
|
|||||||
@@ -207,6 +207,26 @@ const getFirstValue = (row, props) => {
|
|||||||
|
|
||||||
const getGoodsRows = row => parseJsonArray(row.goodsList || row.goodsRows || row.goodsJson);
|
const getGoodsRows = row => parseJsonArray(row.goodsList || row.goodsRows || row.goodsJson);
|
||||||
|
|
||||||
|
const getDetailedGoodsRows = row => {
|
||||||
|
const goodsRows = [
|
||||||
|
row.goodsJson,
|
||||||
|
row.goodsList,
|
||||||
|
row.goodsRows,
|
||||||
|
row.cargoList,
|
||||||
|
row.cargoRows,
|
||||||
|
row.goods,
|
||||||
|
]
|
||||||
|
.map(source => {
|
||||||
|
if (source === undefined || source === null || source === '') return [];
|
||||||
|
if (Array.isArray(source)) return source;
|
||||||
|
if (typeof source === 'object') return [source];
|
||||||
|
return parseJsonArray(source);
|
||||||
|
})
|
||||||
|
.filter(source => source.length)
|
||||||
|
.sort((left, right) => right.length - left.length);
|
||||||
|
return goodsRows[0] || [];
|
||||||
|
};
|
||||||
|
|
||||||
const getBillingRows = row => {
|
const getBillingRows = row => {
|
||||||
const freightRows = parseJsonArray(row.freightList || row.freightRows || row.freightJson);
|
const freightRows = parseJsonArray(row.freightList || row.freightRows || row.freightJson);
|
||||||
if (freightRows.length) return freightRows;
|
if (freightRows.length) return freightRows;
|
||||||
@@ -218,9 +238,25 @@ const getBillingRows = row => {
|
|||||||
const joinText = list => list.filter(item => !isEmpty(item)).join('/');
|
const joinText = list => list.filter(item => !isEmpty(item)).join('/');
|
||||||
|
|
||||||
const formatGoodsInfo = row => {
|
const formatGoodsInfo = row => {
|
||||||
|
const goodsRows = [
|
||||||
|
row.goodsJson,
|
||||||
|
row.goodsList,
|
||||||
|
row.goodsRows,
|
||||||
|
row.cargoList,
|
||||||
|
row.cargoRows,
|
||||||
|
row.goods,
|
||||||
|
]
|
||||||
|
.map(source => {
|
||||||
|
if (source === undefined || source === null || source === '') return [];
|
||||||
|
if (Array.isArray(source)) return source;
|
||||||
|
if (typeof source === 'object') return [source];
|
||||||
|
return parseJsonArray(source);
|
||||||
|
})
|
||||||
|
.filter(source => source.length)
|
||||||
|
.sort((left, right) => right.length - left.length)[0] || [];
|
||||||
const text = getFirstValue(row, ['goodsInfo', 'cargoInfo']);
|
const text = getFirstValue(row, ['goodsInfo', 'cargoInfo']);
|
||||||
if (text) return text;
|
if (!goodsRows.length) return text || '';
|
||||||
return getGoodsRows(row)
|
return goodsRows
|
||||||
.map(item => {
|
.map(item => {
|
||||||
const name = getFirstValue(item, ['cargoName', 'goodsName', 'name']);
|
const name = getFirstValue(item, ['cargoName', 'goodsName', 'name']);
|
||||||
const type = getFirstValue(item, ['cargoType', 'goodsType', 'typeName']);
|
const type = getFirstValue(item, ['cargoType', 'goodsType', 'typeName']);
|
||||||
@@ -228,12 +264,13 @@ const formatGoodsInfo = row => {
|
|||||||
normalizeNumericDisplayValue(
|
normalizeNumericDisplayValue(
|
||||||
getFirstValue(item, ['quantity', 'cargoQuantity', 'goodsQuantity'])
|
getFirstValue(item, ['quantity', 'cargoQuantity', 'goodsQuantity'])
|
||||||
),
|
),
|
||||||
getFirstValue(item, ['quantityUnit', 'cargoUnit', 'unit']),
|
getFirstValue(item, ['quantityUnit', 'goodsQuantityUnit', 'cargoUnit', 'unit']) ||
|
||||||
|
getFirstValue(row, ['quantityUnit', 'goodsQuantityUnit', 'cargoUnit', 'unit']),
|
||||||
]);
|
]);
|
||||||
return joinText([name, type, quantity]);
|
return joinText([name, type, quantity]);
|
||||||
})
|
})
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join('; ');
|
.join('; ') || text || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatGoodsField = (row, props) => {
|
const formatGoodsField = (row, props) => {
|
||||||
@@ -246,6 +283,28 @@ const formatGoodsField = (row, props) => {
|
|||||||
const formatUnitPrice = row => {
|
const formatUnitPrice = row => {
|
||||||
const value = normalizeNumericDisplayValue(getFirstValue(row, ['unitPrice', 'price']));
|
const value = normalizeNumericDisplayValue(getFirstValue(row, ['unitPrice', 'price']));
|
||||||
const unit = getFirstValue(row, ['priceUnit', 'billingUnit', 'unit']);
|
const unit = getFirstValue(row, ['priceUnit', 'billingUnit', 'unit']);
|
||||||
|
|
||||||
|
const goodsRows = getDetailedGoodsRows(row);
|
||||||
|
if (goodsRows.length > 1) {
|
||||||
|
const billingRows = getBillingRows(row);
|
||||||
|
const goodsPrices = goodsRows
|
||||||
|
.map((item, index) => {
|
||||||
|
const itemPrice = normalizeNumericDisplayValue(
|
||||||
|
getFirstValue(item, ['unitPrice', 'price'])
|
||||||
|
);
|
||||||
|
if (!isEmpty(itemPrice)) return itemPrice;
|
||||||
|
return normalizeNumericDisplayValue(
|
||||||
|
getFirstValue(billingRows[index] || {}, ['unitPrice', 'price'])
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.filter(itemPrice => !isEmpty(itemPrice));
|
||||||
|
const priceKeys = goodsPrices.map(itemPrice => {
|
||||||
|
const numericPrice = Number(itemPrice);
|
||||||
|
return Number.isFinite(numericPrice) ? String(numericPrice) : String(itemPrice).trim();
|
||||||
|
});
|
||||||
|
if (new Set(priceKeys).size > 1) return '-';
|
||||||
|
}
|
||||||
|
|
||||||
if (!isEmpty(value)) return unit ? `${value}(${unit})` : value;
|
if (!isEmpty(value)) return unit ? `${value}(${unit})` : value;
|
||||||
const billing = getBillingRows(row).find(
|
const billing = getBillingRows(row).find(
|
||||||
item => !isEmpty(normalizeNumericDisplayValue(item.unitPrice))
|
item => !isEmpty(normalizeNumericDisplayValue(item.unitPrice))
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ export const invoiceApplicationDetailColumns = [
|
|||||||
{ prop: 'vehicleNo', label: '车号', minWidth: 120 },
|
{ prop: 'vehicleNo', label: '车号', minWidth: 120 },
|
||||||
{ prop: 'departureAddress', label: '发货地址', minWidth: 180 },
|
{ prop: 'departureAddress', label: '发货地址', minWidth: 180 },
|
||||||
{ prop: 'arrivalAddress', label: '到货地址', minWidth: 180 },
|
{ prop: 'arrivalAddress', label: '到货地址', minWidth: 180 },
|
||||||
{ prop: 'actualDepartureTime', label: '实际发货时间', minWidth: 170 },
|
{ prop: 'actualDepartureTime', label: '实际发货时间', minWidth: 170, dateTime: true },
|
||||||
{ prop: 'actualCompletionTime', label: '实际完成时间', minWidth: 170 },
|
{ prop: 'actualCompletionTime', label: '实际完成时间', minWidth: 170, dateTime: true },
|
||||||
{ prop: 'transportType', label: '运输类型', minWidth: 120 },
|
{ prop: 'transportType', label: '运输类型', minWidth: 120 },
|
||||||
{ prop: 'cargoName', label: '货物名称', minWidth: 140 },
|
{ prop: 'cargoName', label: '货物名称', minWidth: 140 },
|
||||||
{ prop: 'cargoType', label: '货物类型', minWidth: 130 },
|
{ prop: 'cargoType', label: '货物类型', minWidth: 130 },
|
||||||
|
|||||||
@@ -138,4 +138,11 @@ export const generatePreviewColumns = [
|
|||||||
{ label: '货物名称', prop: 'cargoName', minWidth: 140 },
|
{ label: '货物名称', prop: 'cargoName', minWidth: 140 },
|
||||||
{ label: '货物类型', prop: 'cargoType', minWidth: 140 },
|
{ label: '货物类型', prop: 'cargoType', minWidth: 140 },
|
||||||
{ label: '规格', prop: 'specification', minWidth: 140 },
|
{ label: '规格', prop: 'specification', minWidth: 140 },
|
||||||
|
{ label: '型号', prop: 'model', minWidth: 120 },
|
||||||
|
{ label: '计费要素', prop: 'billingFactor', minWidth: 130 },
|
||||||
|
{ label: '计费类型', prop: 'billingType', minWidth: 130 },
|
||||||
|
{ label: '运输量', prop: 'transportQuantityText', minWidth: 120 },
|
||||||
|
{ label: '运费计算单位', prop: 'priceUnit', minWidth: 130 },
|
||||||
|
{ label: '运输单价', prop: 'unitPrice', minWidth: 120 },
|
||||||
|
{ label: '里程(KM)', prop: 'mileage', minWidth: 120 },
|
||||||
];
|
];
|
||||||
|
|||||||
+49
-19
@@ -35,7 +35,6 @@
|
|||||||
v-model="form.feeCategory"
|
v-model="form.feeCategory"
|
||||||
class="fee-item-form-control"
|
class="fee-item-form-control"
|
||||||
clearable
|
clearable
|
||||||
:disabled="dialogType !== 'add'"
|
|
||||||
filterable
|
filterable
|
||||||
placeholder="请选择费用类型"
|
placeholder="请选择费用类型"
|
||||||
@change="handleFeeCategoryChange"
|
@change="handleFeeCategoryChange"
|
||||||
@@ -43,7 +42,7 @@
|
|||||||
<el-option
|
<el-option
|
||||||
v-for="item in feeCategoryOptions"
|
v-for="item in feeCategoryOptions"
|
||||||
:key="item.id || item.dictKey"
|
:key="item.id || item.dictKey"
|
||||||
:label="item.dictValue"
|
:label="`${item.dictValue}/${item.dictKey}`"
|
||||||
:value="item.dictKey"
|
:value="item.dictKey"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
@@ -54,7 +53,6 @@
|
|||||||
:maxlength="feeItemCodeMaxlength"
|
:maxlength="feeItemCodeMaxlength"
|
||||||
class="fee-item-form-control"
|
class="fee-item-form-control"
|
||||||
clearable
|
clearable
|
||||||
:disabled="dialogType !== 'add'"
|
|
||||||
placeholder="请输入费用项代码"
|
placeholder="请输入费用项代码"
|
||||||
@change="handleFeeItemCodeChange"
|
@change="handleFeeItemCodeChange"
|
||||||
>
|
>
|
||||||
@@ -339,31 +337,63 @@ export default {
|
|||||||
values.englishName = this.getFeeItemCodeSuffix(values.englishName, values.feeCategory);
|
values.englishName = this.getFeeItemCodeSuffix(values.englishName, values.feeCategory);
|
||||||
return values;
|
return values;
|
||||||
},
|
},
|
||||||
|
validateUnique(row) {
|
||||||
|
const rowId = String(row.id || '');
|
||||||
|
const hasDuplicate = (params, prop) => {
|
||||||
|
if (!row[prop]) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
return getList(1, 100, params).then(res => {
|
||||||
|
const records = res.data.data.records || [];
|
||||||
|
return records.some(
|
||||||
|
item =>
|
||||||
|
String(item[prop] || '').trim() === String(row[prop]).trim() &&
|
||||||
|
String(item.id || '') !== rowId
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return Promise.all([
|
||||||
|
hasDuplicate({ name: row.name }, 'name'),
|
||||||
|
hasDuplicate({ englishName: row.englishName }, 'englishName'),
|
||||||
|
]).then(([nameExists, codeExists]) => {
|
||||||
|
if (nameExists) {
|
||||||
|
return Promise.reject(new Error('该费用项已存在'));
|
||||||
|
}
|
||||||
|
if (codeExists) {
|
||||||
|
return Promise.reject(new Error('该费用项代码已存在'));
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleSubmitError(error, loading) {
|
||||||
|
const uniqueMessages = ['该费用项已存在', '该费用项代码已存在'];
|
||||||
|
if (uniqueMessages.includes(error.message)) {
|
||||||
|
this.$message.warning(error.message);
|
||||||
|
}
|
||||||
|
window.console.log(error);
|
||||||
|
loading();
|
||||||
|
},
|
||||||
rowSave(row, done, loading) {
|
rowSave(row, done, loading) {
|
||||||
submit(this.normalizeRow(row)).then(
|
const submitRow = this.normalizeRow(row);
|
||||||
() => {
|
this.validateUnique(submitRow)
|
||||||
|
.then(() => submit(submitRow))
|
||||||
|
.then(() => {
|
||||||
this.onLoad(this.page);
|
this.onLoad(this.page);
|
||||||
this.$message({ type: 'success', message: '操作成功!' });
|
this.$message({ type: 'success', message: '操作成功!' });
|
||||||
done();
|
done();
|
||||||
},
|
})
|
||||||
error => {
|
.catch(error => this.handleSubmitError(error, loading));
|
||||||
window.console.log(error);
|
|
||||||
loading();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
rowUpdate(row, index, done, loading) {
|
rowUpdate(row, index, done, loading) {
|
||||||
submit(this.normalizeRow(row)).then(
|
const submitRow = this.normalizeRow(row);
|
||||||
() => {
|
this.validateUnique(submitRow)
|
||||||
|
.then(() => submit(submitRow))
|
||||||
|
.then(() => {
|
||||||
this.onLoad(this.page);
|
this.onLoad(this.page);
|
||||||
this.$message({ type: 'success', message: '操作成功!' });
|
this.$message({ type: 'success', message: '操作成功!' });
|
||||||
done();
|
done();
|
||||||
},
|
})
|
||||||
error => {
|
.catch(error => this.handleSubmitError(error, loading));
|
||||||
window.console.log(error);
|
|
||||||
loading();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
rowDel(row) {
|
rowDel(row) {
|
||||||
this.$confirm('确定将选择数据删除?', {
|
this.$confirm('确定将选择数据删除?', {
|
||||||
|
|||||||
@@ -110,7 +110,9 @@
|
|||||||
maxlength="24"
|
maxlength="24"
|
||||||
placeholder="请输入码头标识"
|
placeholder="请输入码头标识"
|
||||||
@input="handleTerminalCodeChange"
|
@input="handleTerminalCodeChange"
|
||||||
/>
|
>
|
||||||
|
<template v-if="form.parentCode" #prepend>{{ form.parentCode }}</template>
|
||||||
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #menu="{ row, index }">
|
<template #menu="{ row, index }">
|
||||||
|
|||||||
@@ -358,7 +358,10 @@ export default {
|
|||||||
formslot: true,
|
formslot: true,
|
||||||
minWidth: 130,
|
minWidth: 130,
|
||||||
overHidden: false,
|
overHidden: false,
|
||||||
rules: [{ validator: validateLongitude, trigger: 'blur' }],
|
rules: [
|
||||||
|
{ required: true, message: '请输入经度', trigger: 'blur' },
|
||||||
|
{ validator: validateLongitude, trigger: 'blur' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '纬度',
|
label: '纬度',
|
||||||
@@ -366,7 +369,10 @@ export default {
|
|||||||
formslot: true,
|
formslot: true,
|
||||||
minWidth: 130,
|
minWidth: 130,
|
||||||
overHidden: false,
|
overHidden: false,
|
||||||
rules: [{ validator: validateLatitude, trigger: 'blur' }],
|
rules: [
|
||||||
|
{ required: true, message: '请输入纬度', trigger: 'blur' },
|
||||||
|
{ validator: validateLatitude, trigger: 'blur' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '行政区划编号',
|
label: '行政区划编号',
|
||||||
@@ -661,14 +667,14 @@ export default {
|
|||||||
return row;
|
return row;
|
||||||
},
|
},
|
||||||
validateCoordinateFields(row) {
|
validateCoordinateFields(row) {
|
||||||
const longitudeMessage = getCoordinateValidationMessage(row.longitude, 'longitude');
|
const longitudeMessage = getCoordinateValidationMessage(row.longitude, 'longitude', true);
|
||||||
if (longitudeMessage) {
|
if (longitudeMessage) {
|
||||||
this.$message.warning('经度范围不正确');
|
this.$message.warning(longitudeMessage);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const latitudeMessage = getCoordinateValidationMessage(row.latitude, 'latitude');
|
const latitudeMessage = getCoordinateValidationMessage(row.latitude, 'latitude', true);
|
||||||
if (latitudeMessage) {
|
if (latitudeMessage) {
|
||||||
this.$message.warning('纬度范围不正确');
|
this.$message.warning(latitudeMessage);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -146,7 +146,7 @@
|
|||||||
<span>{{
|
<span>{{
|
||||||
isTransportPlanPage
|
isTransportPlanPage
|
||||||
? formatTransportPlanProvinceCityDistrict(row.departureAddress)
|
? formatTransportPlanProvinceCityDistrict(row.departureAddress)
|
||||||
: formatTransportPlanProvinceCityDistrict(row.departureAddress)
|
: formatWaybillListAddress(row.departureAddress)
|
||||||
}}</span>
|
}}</span>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<span v-else>{{ row.departureAddress || '-' }}</span>
|
<span v-else>{{ row.departureAddress || '-' }}</span>
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
<span>{{
|
<span>{{
|
||||||
isTransportPlanPage
|
isTransportPlanPage
|
||||||
? formatTransportPlanProvinceCityDistrict(row.arrivalAddress)
|
? formatTransportPlanProvinceCityDistrict(row.arrivalAddress)
|
||||||
: formatTransportPlanProvinceCityDistrict(row.arrivalAddress)
|
: formatWaybillListAddress(row.arrivalAddress)
|
||||||
}}</span>
|
}}</span>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
<span v-else>{{ row.arrivalAddress || '-' }}</span>
|
<span v-else>{{ row.arrivalAddress || '-' }}</span>
|
||||||
@@ -644,7 +644,7 @@
|
|||||||
@change="handleTaskCarrierTypeChange"
|
@change="handleTaskCarrierTypeChange"
|
||||||
>
|
>
|
||||||
<el-radio-button
|
<el-radio-button
|
||||||
v-for="item in taskCarrierTypes"
|
v-for="item in waybillTaskCarrierTypes"
|
||||||
:key="item"
|
:key="item"
|
||||||
:label="item"
|
:label="item"
|
||||||
:value="item"
|
:value="item"
|
||||||
@@ -1135,8 +1135,9 @@
|
|||||||
<el-form-item :label="`运费${index + 1}`">
|
<el-form-item :label="`运费${index + 1}`">
|
||||||
<el-input
|
<el-input
|
||||||
:model-value="taskFullFreightAmount(cargo)"
|
:model-value="taskFullFreightAmount(cargo)"
|
||||||
disabled
|
placeholder="请输入运费"
|
||||||
placeholder="自动计算"
|
:disabled="dialogReadonly"
|
||||||
|
@input="value => handleTaskFullFreightAmountInput(cargo, value)"
|
||||||
>
|
>
|
||||||
<template #suffix>{{ taskFreightCurrencyLabel }}</template>
|
<template #suffix>{{ taskFreightCurrencyLabel }}</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
@@ -1195,7 +1196,7 @@
|
|||||||
@change="handleTaskCarrierTypeChange"
|
@change="handleTaskCarrierTypeChange"
|
||||||
>
|
>
|
||||||
<el-radio-button
|
<el-radio-button
|
||||||
v-for="item in taskCarrierTypes"
|
v-for="item in waybillTaskCarrierTypes"
|
||||||
:key="item"
|
:key="item"
|
||||||
:label="item"
|
:label="item"
|
||||||
:value="item"
|
:value="item"
|
||||||
@@ -2054,7 +2055,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #menu-form-before>
|
<template #menu-form>
|
||||||
<div
|
<div
|
||||||
v-if="showFooterFreightSummary && !dialogReadonly"
|
v-if="showFooterFreightSummary && !dialogReadonly"
|
||||||
class="business-crud-page__shipping-template-footer-summary"
|
class="business-crud-page__shipping-template-footer-summary"
|
||||||
@@ -2070,24 +2071,24 @@
|
|||||||
查看过程配置
|
查看过程配置
|
||||||
</el-link>
|
</el-link>
|
||||||
</div>
|
</div>
|
||||||
<el-button
|
<div
|
||||||
v-if="isStandaloneFormPage && !showTransportPlanCreateActions"
|
v-if="isStandaloneFormPage && !showTransportPlanCreateActions"
|
||||||
@click="closeCrudDialog"
|
class="business-crud-page__standalone-menu-actions"
|
||||||
>
|
>
|
||||||
取消
|
<el-button class="business-crud-page__form-close" @click="closeCrudDialog">
|
||||||
</el-button>
|
{{ isWaybillDetailLayout ? '关闭' : '取消' }}
|
||||||
<el-button
|
</el-button>
|
||||||
v-if="config.enableDraftSave && !dialogReadonly"
|
<el-button
|
||||||
type="primary"
|
v-if="config.enableDraftSave && !dialogReadonly"
|
||||||
plain
|
class="business-crud-page__form-draft"
|
||||||
:loading="draftSaveLoading"
|
type="primary"
|
||||||
@click="handleDraftSave"
|
plain
|
||||||
>
|
:loading="draftSaveLoading"
|
||||||
{{ config.draftSaveText || '保存' }}
|
@click="handleDraftSave"
|
||||||
</el-button>
|
>
|
||||||
</template>
|
{{ config.draftSaveText || '保存' }}
|
||||||
|
</el-button>
|
||||||
<template #menu-form>
|
</div>
|
||||||
<div v-if="showTransportPlanCreateActions" class="business-crud-page__create-actions">
|
<div v-if="showTransportPlanCreateActions" class="business-crud-page__create-actions">
|
||||||
<el-button :disabled="Boolean(transportPlanCreateActionLoading)" @click="closeCrudDialog">
|
<el-button :disabled="Boolean(transportPlanCreateActionLoading)" @click="closeCrudDialog">
|
||||||
取消
|
取消
|
||||||
@@ -5418,6 +5419,7 @@ const defaultTransportCargo = () => ({
|
|||||||
quantityUnit: '',
|
quantityUnit: '',
|
||||||
unitPrice: '',
|
unitPrice: '',
|
||||||
priceUnit: '元/吨',
|
priceUnit: '元/吨',
|
||||||
|
freightAmount: '',
|
||||||
packageType: '',
|
packageType: '',
|
||||||
brand: '',
|
brand: '',
|
||||||
specification: '',
|
specification: '',
|
||||||
@@ -5703,6 +5705,8 @@ export default {
|
|||||||
transportAddressTarget: '',
|
transportAddressTarget: '',
|
||||||
transportAddressLoading: false,
|
transportAddressLoading: false,
|
||||||
transportAddressQuery: {},
|
transportAddressQuery: {},
|
||||||
|
transportTypeOptions: [],
|
||||||
|
transportTypeOptionsLoading: false,
|
||||||
transportAddressRows: [],
|
transportAddressRows: [],
|
||||||
transportAddressSelected: null,
|
transportAddressSelected: null,
|
||||||
transportAddressPage: {
|
transportAddressPage: {
|
||||||
@@ -5743,6 +5747,9 @@ export default {
|
|||||||
taskCarrierOptions: [],
|
taskCarrierOptions: [],
|
||||||
taskCarrierLoading: false,
|
taskCarrierLoading: false,
|
||||||
taskCarrierRequestId: 0,
|
taskCarrierRequestId: 0,
|
||||||
|
waybillCarrierContractsLoaded: false,
|
||||||
|
waybillHasCarrierContracts: null,
|
||||||
|
waybillCarrierContractRequestId: 0,
|
||||||
taskDriverOptions: [],
|
taskDriverOptions: [],
|
||||||
taskDriverLoading: false,
|
taskDriverLoading: false,
|
||||||
taskCargoOptionsMap: {},
|
taskCargoOptionsMap: {},
|
||||||
@@ -5872,8 +5879,16 @@ export default {
|
|||||||
if (this.config.enableWaybillFooterFreightSummary) {
|
if (this.config.enableWaybillFooterFreightSummary) {
|
||||||
this.loadShippingTemplateCurrencyOptions();
|
this.loadShippingTemplateCurrencyOptions();
|
||||||
}
|
}
|
||||||
this.consumeTemplateCreatePayload();
|
if (this.shippingInfoFormEnabled) this.loadTransportTypeOptions();
|
||||||
if (this.isStandaloneFormPage) this.$nextTick(() => this.initStandaloneFormPage());
|
if (this.isStandaloneFormPage) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.initStandaloneFormPage();
|
||||||
|
// 先完成独立表单初始化,再回填模板数据,避免被新增表单默认值覆盖。
|
||||||
|
this.$nextTick(() => this.consumeTemplateCreatePayload());
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.consumeTemplateCreatePayload();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['permission', 'userInfo']),
|
...mapGetters(['permission', 'userInfo']),
|
||||||
@@ -6063,6 +6078,16 @@ export default {
|
|||||||
taskCarrierRequired() {
|
taskCarrierRequired() {
|
||||||
return this.isTaskCarrierRequired(this.form.carrierType);
|
return this.isTaskCarrierRequired(this.form.carrierType);
|
||||||
},
|
},
|
||||||
|
waybillTaskCarrierTypes() {
|
||||||
|
if (
|
||||||
|
this.isWaybillDetailLayout &&
|
||||||
|
this.waybillCarrierContractsLoaded &&
|
||||||
|
this.waybillHasCarrierContracts === false
|
||||||
|
) {
|
||||||
|
return ['自运'];
|
||||||
|
}
|
||||||
|
return this.taskCarrierTypes;
|
||||||
|
},
|
||||||
taskCarrierLabel() {
|
taskCarrierLabel() {
|
||||||
return this.form.carrierType === '网货平台' ? '承运方' : '承运商';
|
return this.form.carrierType === '网货平台' ? '承运方' : '承运商';
|
||||||
},
|
},
|
||||||
@@ -6636,11 +6661,36 @@ export default {
|
|||||||
return applyTableMenuWidth(nextOption, menuButtonCount);
|
return applyTableMenuWidth(nextOption, menuButtonCount);
|
||||||
},
|
},
|
||||||
formatTransportPlanGoodsInfo(row = {}) {
|
formatTransportPlanGoodsInfo(row = {}) {
|
||||||
const goodsRows = this.parseJsonArray(row.goodsJson);
|
// 列表接口的货物明细字段存在多种兼容命名,优先使用完整的 goodsJson。
|
||||||
|
const goodsRows = [
|
||||||
|
row.goodsJson,
|
||||||
|
row.goodsList,
|
||||||
|
row.goodsRows,
|
||||||
|
row.cargoList,
|
||||||
|
row.cargoRows,
|
||||||
|
row.goods,
|
||||||
|
]
|
||||||
|
.map(source => {
|
||||||
|
if (source === undefined || source === null || source === '') return [];
|
||||||
|
if (Array.isArray(source)) return source;
|
||||||
|
if (typeof source === 'object') return [source];
|
||||||
|
return this.parseJsonArray(source);
|
||||||
|
})
|
||||||
|
.filter(source => source.length)
|
||||||
|
.sort((left, right) => right.length - left.length)[0] || [];
|
||||||
if (!goodsRows.length) return String(row.goodsInfo || row.cargoInfo || '');
|
if (!goodsRows.length) return String(row.goodsInfo || row.cargoInfo || '');
|
||||||
const groups = goodsRows.reduce((result, item = {}) => {
|
const groups = goodsRows.reduce((result, item = {}) => {
|
||||||
const unit =
|
const unit = String(
|
||||||
item.quantityUnit || item.goodsQuantityUnit || item.cargoUnit || item.unit || '';
|
item.quantityUnit ||
|
||||||
|
item.goodsQuantityUnit ||
|
||||||
|
item.cargoUnit ||
|
||||||
|
item.unit ||
|
||||||
|
row.quantityUnit ||
|
||||||
|
row.goodsQuantityUnit ||
|
||||||
|
row.cargoUnit ||
|
||||||
|
row.unit ||
|
||||||
|
''
|
||||||
|
).trim();
|
||||||
const key = unit || '__empty__';
|
const key = unit || '__empty__';
|
||||||
if (!result[key]) {
|
if (!result[key]) {
|
||||||
result[key] = {
|
result[key] = {
|
||||||
@@ -6699,6 +6749,28 @@ export default {
|
|||||||
const cityMatch = text.match(/市/);
|
const cityMatch = text.match(/市/);
|
||||||
return cityMatch ? text.slice(0, cityMatch.index + 1) : text;
|
return cityMatch ? text.slice(0, cityMatch.index + 1) : text;
|
||||||
},
|
},
|
||||||
|
formatWaybillListAddress(value) {
|
||||||
|
const text = String(value || '')
|
||||||
|
.trim()
|
||||||
|
.replace(/[\\/||、,,\s]+/g, '');
|
||||||
|
if (!text) return '-';
|
||||||
|
const districtPattern = '(?:自治县|自治旗|林区|矿区|新区|开发区|区|县|旗)';
|
||||||
|
const parts = [];
|
||||||
|
let remainder = text;
|
||||||
|
const province = text.match(/^(.+?(?:省|自治区|特别行政区))/);
|
||||||
|
if (province) {
|
||||||
|
parts.push(province[1]);
|
||||||
|
remainder = text.slice(province[1].length);
|
||||||
|
}
|
||||||
|
const city = remainder.match(/^(.+?市)/);
|
||||||
|
if (city) {
|
||||||
|
parts.push(city[1]);
|
||||||
|
remainder = remainder.slice(city[1].length);
|
||||||
|
}
|
||||||
|
const district = remainder.match(new RegExp(`^(.+?${districtPattern})`));
|
||||||
|
if (district) parts.push(district[1]);
|
||||||
|
return parts.length ? parts.join('/') : text;
|
||||||
|
},
|
||||||
hasPermission(code) {
|
hasPermission(code) {
|
||||||
return this.isAdmin || this.validData(this.permission && this.permission[code], false);
|
return this.isAdmin || this.validData(this.permission && this.permission[code], false);
|
||||||
},
|
},
|
||||||
@@ -6831,41 +6903,43 @@ export default {
|
|||||||
if (payload.target !== target) return;
|
if (payload.target !== target) return;
|
||||||
sessionStorage.removeItem(storageKey);
|
sessionStorage.removeItem(storageKey);
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.crud.rowAdd();
|
// 独立表单页已经由路由控制新增状态,不能再调用 rowAdd 打开弹窗。
|
||||||
this.$nextTick(() => {
|
if (!this.isStandaloneFormPage) this.$refs.crud?.rowAdd?.();
|
||||||
// 模板主键不能带入目标业务单据,避免提交时被后端误判为更新。
|
this.$nextTick(() => this.applyTemplateCreatePayload(payload.data));
|
||||||
const row = { ...(payload.data || {}) };
|
|
||||||
[
|
|
||||||
'id',
|
|
||||||
'createUser',
|
|
||||||
'createUserName',
|
|
||||||
'createDept',
|
|
||||||
'createTime',
|
|
||||||
'updateUser',
|
|
||||||
'updateUserName',
|
|
||||||
'updateTime',
|
|
||||||
].forEach(key => delete row[key]);
|
|
||||||
this.suppressTransportTypeClear = true;
|
|
||||||
Object.assign(this.form, row);
|
|
||||||
this.applyWaybillTemplateTaskData(row);
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.suppressTransportTypeClear = false;
|
|
||||||
});
|
|
||||||
this.form.planName = this.form.planName || this.form.templateName || '';
|
|
||||||
this.selectedProjectId = this.form.projectId || '';
|
|
||||||
this.attachmentRows = this.parseJsonArray(this.form.attachmentsJson);
|
|
||||||
this.selectedAttachmentRows = [];
|
|
||||||
if (this.contractSelectEnabled) {
|
|
||||||
this.syncCurrentContractOption();
|
|
||||||
this.loadContractOptionsForProject();
|
|
||||||
}
|
|
||||||
if (this.config.enableProjectSelect) this.syncCurrentProjectOption();
|
|
||||||
this.loadProjectProcessConfigState();
|
|
||||||
if (this.config.enableTransportPlanForm) this.initTransportPlanFormRows();
|
|
||||||
if (this.taskInfoFormEnabled) this.initTaskInfoForm();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
applyTemplateCreatePayload(data) {
|
||||||
|
// 模板主键不能带入目标业务单据,避免提交时被后端误判为更新。
|
||||||
|
const row = { ...(data || {}) };
|
||||||
|
[
|
||||||
|
'id',
|
||||||
|
'createUser',
|
||||||
|
'createUserName',
|
||||||
|
'createDept',
|
||||||
|
'createTime',
|
||||||
|
'updateUser',
|
||||||
|
'updateUserName',
|
||||||
|
'updateTime',
|
||||||
|
].forEach(key => delete row[key]);
|
||||||
|
this.suppressTransportTypeClear = true;
|
||||||
|
Object.assign(this.form, row);
|
||||||
|
this.applyWaybillTemplateTaskData(row);
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.suppressTransportTypeClear = false;
|
||||||
|
});
|
||||||
|
this.form.planName = this.form.planName || this.form.templateName || '';
|
||||||
|
this.selectedProjectId = this.form.projectId || '';
|
||||||
|
this.attachmentRows = this.parseJsonArray(this.form.attachmentsJson);
|
||||||
|
this.selectedAttachmentRows = [];
|
||||||
|
if (this.contractSelectEnabled) {
|
||||||
|
this.syncCurrentContractOption();
|
||||||
|
this.loadContractOptionsForProject();
|
||||||
|
}
|
||||||
|
if (this.config.enableProjectSelect) this.syncCurrentProjectOption();
|
||||||
|
this.loadProjectProcessConfigState();
|
||||||
|
if (this.config.enableTransportPlanForm) this.initTransportPlanFormRows();
|
||||||
|
if (this.taskInfoFormEnabled) this.initTaskInfoForm();
|
||||||
|
},
|
||||||
applyWaybillTemplateTaskData(template = {}) {
|
applyWaybillTemplateTaskData(template = {}) {
|
||||||
if (this.config.permission !== 'waybill_manage') return;
|
if (this.config.permission !== 'waybill_manage') return;
|
||||||
const goodsRows = this.parseJsonArray(template.goodsJson);
|
const goodsRows = this.parseJsonArray(template.goodsJson);
|
||||||
@@ -7561,6 +7635,7 @@ export default {
|
|||||||
const submitRow = { ...row };
|
const submitRow = { ...row };
|
||||||
const skipValidation = saveAsDraft && this.config.skipDraftValidation === true;
|
const skipValidation = saveAsDraft && this.config.skipDraftValidation === true;
|
||||||
if (
|
if (
|
||||||
|
!skipValidation &&
|
||||||
['shipping_template', 'transport_plan', 'waybill_manage'].includes(
|
['shipping_template', 'transport_plan', 'waybill_manage'].includes(
|
||||||
this.config.permission
|
this.config.permission
|
||||||
) &&
|
) &&
|
||||||
@@ -8014,6 +8089,7 @@ export default {
|
|||||||
handleDraftSave() {
|
handleDraftSave() {
|
||||||
const request =
|
const request =
|
||||||
typeof this.api.saveDraft === 'function' ? this.api.saveDraft : this.api.submit;
|
typeof this.api.saveDraft === 'function' ? this.api.saveDraft : this.api.submit;
|
||||||
|
if (this.draftSaveLoading) return;
|
||||||
const submitRow = this.normalizeRow(this.form, true);
|
const submitRow = this.normalizeRow(this.form, true);
|
||||||
if (!submitRow) return;
|
if (!submitRow) return;
|
||||||
this.draftSaveLoading = true;
|
this.draftSaveLoading = true;
|
||||||
@@ -8115,6 +8191,7 @@ export default {
|
|||||||
this.taskCarrierRequestId += 1;
|
this.taskCarrierRequestId += 1;
|
||||||
this.taskCarrierOptions = [];
|
this.taskCarrierOptions = [];
|
||||||
this.taskCarrierLoading = false;
|
this.taskCarrierLoading = false;
|
||||||
|
this.resetWaybillCarrierContractState();
|
||||||
this.initTaskInfoForm();
|
this.initTaskInfoForm();
|
||||||
this.initTransportPlanFormRows();
|
this.initTransportPlanFormRows();
|
||||||
this.syncBillingPlanJson();
|
this.syncBillingPlanJson();
|
||||||
@@ -8150,6 +8227,7 @@ export default {
|
|||||||
this.suppressTransportTypeClear = false;
|
this.suppressTransportTypeClear = false;
|
||||||
});
|
});
|
||||||
this.selectedProjectId = this.form.projectId || '';
|
this.selectedProjectId = this.form.projectId || '';
|
||||||
|
this.resetWaybillCarrierContractState();
|
||||||
this.loadProjectProcessConfigState();
|
this.loadProjectProcessConfigState();
|
||||||
this.attachmentRows = this.parseJsonArray(this.form.attachmentsJson);
|
this.attachmentRows = this.parseJsonArray(this.form.attachmentsJson);
|
||||||
this.selectedAttachmentRows = [];
|
this.selectedAttachmentRows = [];
|
||||||
@@ -8271,6 +8349,7 @@ export default {
|
|||||||
this.taskCarrierRequestId += 1;
|
this.taskCarrierRequestId += 1;
|
||||||
this.taskCarrierOptions = [];
|
this.taskCarrierOptions = [];
|
||||||
this.taskCarrierLoading = false;
|
this.taskCarrierLoading = false;
|
||||||
|
this.resetWaybillCarrierContractState();
|
||||||
this.form.carrierName = '';
|
this.form.carrierName = '';
|
||||||
this.form.carrierId = '';
|
this.form.carrierId = '';
|
||||||
this.form.carrierContractId = '';
|
this.form.carrierContractId = '';
|
||||||
@@ -8282,6 +8361,9 @@ export default {
|
|||||||
this.form.projectCode = project.projectCode || '';
|
this.form.projectCode = project.projectCode || '';
|
||||||
this.form.undertakeDeptName = project.undertakeDeptName || '';
|
this.form.undertakeDeptName = project.undertakeDeptName || '';
|
||||||
if (this.isWaybillDetailLayout) {
|
if (this.isWaybillDetailLayout) {
|
||||||
|
this.taskCarrierRequestId += 1;
|
||||||
|
this.taskCarrierLoading = false;
|
||||||
|
this.resetWaybillCarrierContractState();
|
||||||
this.form.carrierName = '';
|
this.form.carrierName = '';
|
||||||
this.form.carrierId = '';
|
this.form.carrierId = '';
|
||||||
this.form.carrierContractId = '';
|
this.form.carrierContractId = '';
|
||||||
@@ -8638,15 +8720,39 @@ export default {
|
|||||||
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
parts.length > 1 ? `${parts[0]}.${parts.slice(1).join('').slice(0, 2)}` : parts[0];
|
||||||
this.syncTransportCargoJson();
|
this.syncTransportCargoJson();
|
||||||
},
|
},
|
||||||
|
handleTaskFullFreightAmountInput(cargo, value) {
|
||||||
|
this.handleTaskFullFreightNumberInput(cargo, 'freightAmount', value);
|
||||||
|
},
|
||||||
|
taskFullFreightAutoCalculable() {
|
||||||
|
const rows = this.transportCargoRows || [];
|
||||||
|
if (!rows.length) return false;
|
||||||
|
const quantityUnits = rows.map(row => String(row.quantityUnit || '').trim());
|
||||||
|
if (quantityUnits.some(unit => !unit) || new Set(quantityUnits).size !== 1) return false;
|
||||||
|
const quantityUnit = quantityUnits[0];
|
||||||
|
return rows.every(row => {
|
||||||
|
const priceUnit = String(row.priceUnit || '').trim();
|
||||||
|
const separatorIndex = Math.max(priceUnit.lastIndexOf('/'), priceUnit.lastIndexOf('/'));
|
||||||
|
const priceQuantityUnit =
|
||||||
|
separatorIndex >= 0 ? priceUnit.slice(separatorIndex + 1).trim() : priceUnit;
|
||||||
|
return !this.isBillingFieldEmpty(row.unitPrice) && priceQuantityUnit === quantityUnit;
|
||||||
|
});
|
||||||
|
},
|
||||||
taskFullFreightQuantity(index) {
|
taskFullFreightQuantity(index) {
|
||||||
return this.formatTaskFullFreightNumber(this.transportCargoRows[index]?.quantity || 0);
|
return this.formatTaskFullFreightNumber(this.transportCargoRows[index]?.quantity || 0);
|
||||||
},
|
},
|
||||||
taskFullFreightAmount(cargo = {}) {
|
taskFullFreightAmount(cargo = {}) {
|
||||||
|
if (!this.taskFullFreightAutoCalculable()) {
|
||||||
|
return this.formatTaskFullFreightNumber(cargo.freightAmount);
|
||||||
|
}
|
||||||
|
if (!this.isBillingFieldEmpty(cargo.freightAmount)) {
|
||||||
|
return this.formatTaskFullFreightNumber(cargo.freightAmount);
|
||||||
|
}
|
||||||
return this.formatTaskFullFreightNumber(
|
return this.formatTaskFullFreightNumber(
|
||||||
Number(cargo.unitPrice || 0) * Number(cargo.quantity || 0)
|
Number(cargo.unitPrice || 0) * Number(cargo.quantity || 0)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
formatTaskFullFreightNumber(value) {
|
formatTaskFullFreightNumber(value) {
|
||||||
|
if (value === undefined || value === null || String(value).trim() === '') return '';
|
||||||
const number = Number(value || 0);
|
const number = Number(value || 0);
|
||||||
if (!Number.isFinite(number)) return '';
|
if (!Number.isFinite(number)) return '';
|
||||||
return Number(number.toFixed(2)).toString();
|
return Number(number.toFixed(2)).toString();
|
||||||
@@ -8668,30 +8774,74 @@ export default {
|
|||||||
if (this.isWaybillDetailLayout) {
|
if (this.isWaybillDetailLayout) {
|
||||||
this.taskCarrierRequestId += 1;
|
this.taskCarrierRequestId += 1;
|
||||||
this.taskCarrierOptions = [];
|
this.taskCarrierOptions = [];
|
||||||
|
this.taskCarrierLoading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isTaskCarrierRequired(carrierType) {
|
isTaskCarrierRequired(carrierType) {
|
||||||
return ['承运商', '网货平台'].includes(String(carrierType || '').trim());
|
return ['承运商', '网货平台'].includes(String(carrierType || '').trim());
|
||||||
},
|
},
|
||||||
|
resetWaybillCarrierContractState() {
|
||||||
|
if (!this.isWaybillDetailLayout) return;
|
||||||
|
this.waybillCarrierContractRequestId += 1;
|
||||||
|
this.waybillCarrierContractsLoaded = false;
|
||||||
|
this.waybillHasCarrierContracts = null;
|
||||||
|
},
|
||||||
|
loadWaybillCarrierContractState(project = {}) {
|
||||||
|
if (!this.isWaybillDetailLayout) return Promise.resolve(null);
|
||||||
|
const projectId = project.id || project.projectId || this.form.projectId;
|
||||||
|
const requestId = ++this.waybillCarrierContractRequestId;
|
||||||
|
if (!projectId) {
|
||||||
|
this.waybillCarrierContractsLoaded = false;
|
||||||
|
this.waybillHasCarrierContracts = null;
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
return getContractList(1, 9999, {
|
||||||
|
projectId,
|
||||||
|
projectName: project.projectName || this.form.projectName,
|
||||||
|
contractCategory: '承运商合同',
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (requestId !== this.waybillCarrierContractRequestId) return null;
|
||||||
|
const options = this.getWaybillCarrierContractOptions(extractRecords(res));
|
||||||
|
this.waybillCarrierContractsLoaded = true;
|
||||||
|
this.waybillHasCarrierContracts = options.length > 0;
|
||||||
|
if (!options.length && this.form.carrierType !== '自运') {
|
||||||
|
this.form.carrierType = '自运';
|
||||||
|
this.form.carrierName = '';
|
||||||
|
this.form.carrierId = '';
|
||||||
|
this.form.carrierContractId = '';
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (requestId === this.waybillCarrierContractRequestId) {
|
||||||
|
this.waybillCarrierContractsLoaded = false;
|
||||||
|
this.waybillHasCarrierContracts = null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
},
|
||||||
loadTaskCarrierOptions(project = {}) {
|
loadTaskCarrierOptions(project = {}) {
|
||||||
if (!this.taskInfoFormEnabled || this.taskCarrierLoading) return Promise.resolve([]);
|
if (!this.taskInfoFormEnabled || this.taskCarrierLoading) return Promise.resolve([]);
|
||||||
if (this.isWaybillDetailLayout) {
|
if (this.isWaybillDetailLayout) {
|
||||||
const projectId = project.id || project.projectId || this.form.projectId;
|
const projectId = project.id || project.projectId || this.form.projectId;
|
||||||
const requestId = ++this.taskCarrierRequestId;
|
const requestId = ++this.taskCarrierRequestId;
|
||||||
if (this.form.carrierType === '承运商') {
|
const carrierTypeAtRequest = this.form.carrierType;
|
||||||
|
const contractPromise = this.loadWaybillCarrierContractState({
|
||||||
|
...project,
|
||||||
|
id: projectId,
|
||||||
|
});
|
||||||
|
if (carrierTypeAtRequest === '承运商') {
|
||||||
if (!projectId) {
|
if (!projectId) {
|
||||||
this.taskCarrierOptions = [];
|
this.taskCarrierOptions = [];
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
this.taskCarrierLoading = true;
|
this.taskCarrierLoading = true;
|
||||||
return getContractList(1, 9999, {
|
return contractPromise
|
||||||
projectId,
|
.then(options => {
|
||||||
projectName: project.projectName || this.form.projectName,
|
|
||||||
contractCategory: '承运商合同',
|
|
||||||
})
|
|
||||||
.then(res => {
|
|
||||||
if (requestId !== this.taskCarrierRequestId) return this.taskCarrierOptions;
|
if (requestId !== this.taskCarrierRequestId) return this.taskCarrierOptions;
|
||||||
this.taskCarrierOptions = this.getWaybillCarrierContractOptions(extractRecords(res));
|
if (options === null) return this.taskCarrierOptions;
|
||||||
|
this.taskCarrierOptions = options || [];
|
||||||
return this.taskCarrierOptions;
|
return this.taskCarrierOptions;
|
||||||
})
|
})
|
||||||
.catch(() => this.taskCarrierOptions)
|
.catch(() => this.taskCarrierOptions)
|
||||||
@@ -8708,7 +8858,7 @@ export default {
|
|||||||
});
|
});
|
||||||
if (!projectId) return Promise.resolve(this.taskCarrierOptions);
|
if (!projectId) return Promise.resolve(this.taskCarrierOptions);
|
||||||
this.taskCarrierLoading = true;
|
this.taskCarrierLoading = true;
|
||||||
return getProjectDetail(projectId)
|
const detailPromise = getProjectDetail(projectId)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (requestId !== this.taskCarrierRequestId) return this.taskCarrierOptions;
|
if (requestId !== this.taskCarrierRequestId) return this.taskCarrierOptions;
|
||||||
const detail = res?.data?.data || {};
|
const detail = res?.data?.data || {};
|
||||||
@@ -8719,7 +8869,9 @@ export default {
|
|||||||
});
|
});
|
||||||
return this.taskCarrierOptions;
|
return this.taskCarrierOptions;
|
||||||
})
|
})
|
||||||
.catch(() => this.taskCarrierOptions)
|
.catch(() => this.taskCarrierOptions);
|
||||||
|
return Promise.all([contractPromise, detailPromise])
|
||||||
|
.then(([, options]) => options)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
if (requestId === this.taskCarrierRequestId) this.taskCarrierLoading = false;
|
if (requestId === this.taskCarrierRequestId) this.taskCarrierLoading = false;
|
||||||
});
|
});
|
||||||
@@ -8974,12 +9126,16 @@ export default {
|
|||||||
},
|
},
|
||||||
initTaskFullCargoRows(goodsRows = []) {
|
initTaskFullCargoRows(goodsRows = []) {
|
||||||
if (!this.taskInfoFormEnabled) return;
|
if (!this.taskInfoFormEnabled) return;
|
||||||
|
const freightItems = this.parseJsonObject(this.form.freightJson).freightItems || [];
|
||||||
const rows = (goodsRows.length ? goodsRows : this.transportCargoRows).map((row, index) => {
|
const rows = (goodsRows.length ? goodsRows : this.transportCargoRows).map((row, index) => {
|
||||||
const cargo = this.normalizeTransportCargoRow(row);
|
const cargo = this.normalizeTransportCargoRow(row);
|
||||||
if (index === 0 && !cargo.unitPrice && this.form.unitPrice) {
|
if (index === 0 && !cargo.unitPrice && this.form.unitPrice) {
|
||||||
cargo.unitPrice = this.form.unitPrice;
|
cargo.unitPrice = this.form.unitPrice;
|
||||||
}
|
}
|
||||||
cargo.priceUnit = cargo.priceUnit || this.form.priceUnit || '元/吨';
|
cargo.priceUnit = cargo.priceUnit || this.form.priceUnit || '元/吨';
|
||||||
|
if (this.isBillingFieldEmpty(cargo.freightAmount)) {
|
||||||
|
cargo.freightAmount = freightItems[index]?.freightAmount ?? '';
|
||||||
|
}
|
||||||
return cargo;
|
return cargo;
|
||||||
});
|
});
|
||||||
while (rows.length < 1) {
|
while (rows.length < 1) {
|
||||||
@@ -9076,6 +9232,10 @@ export default {
|
|||||||
this.$message.warning(`第${index + 1}行单价不能小于0`);
|
this.$message.warning(`第${index + 1}行单价不能小于0`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!this.isBillingFieldEmpty(cargo.freightAmount) && Number(cargo.freightAmount) < 0) {
|
||||||
|
this.$message.warning(`第${index + 1}行运费不能小于0`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@@ -9328,6 +9488,7 @@ export default {
|
|||||||
cargoName: row.cargoName || row.goodsName || '',
|
cargoName: row.cargoName || row.goodsName || '',
|
||||||
packageType: row.packageType || row.package || '',
|
packageType: row.packageType || row.package || '',
|
||||||
specification: row.specification || row.spec || '',
|
specification: row.specification || row.spec || '',
|
||||||
|
freightAmount: row.freightAmount ?? row.amount ?? row.totalAmount ?? '',
|
||||||
};
|
};
|
||||||
['quantity', 'mileage', 'unitPrice'].forEach(prop => {
|
['quantity', 'mileage', 'unitPrice'].forEach(prop => {
|
||||||
if (Number(cargo[prop]) === -1) cargo[prop] = '';
|
if (Number(cargo[prop]) === -1) cargo[prop] = '';
|
||||||
@@ -9470,8 +9631,11 @@ export default {
|
|||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
getTransportTypeLabel(value) {
|
getTransportTypeLabel(value) {
|
||||||
|
if (value && typeof value === 'object') {
|
||||||
|
return value.label || value.dictValue || value.name || value.value || value.dictKey || '';
|
||||||
|
}
|
||||||
const column = (this.option.column || []).find(item => item.prop === 'transportType');
|
const column = (this.option.column || []).find(item => item.prop === 'transportType');
|
||||||
const dictionary = column?.dicData || [];
|
const dictionary = [...(column?.dicData || []), ...this.transportTypeOptions];
|
||||||
const item = dictionary.find(
|
const item = dictionary.find(
|
||||||
dic =>
|
dic =>
|
||||||
String(dic.value ?? dic.dictKey ?? '') === String(value ?? '') ||
|
String(dic.value ?? dic.dictKey ?? '') === String(value ?? '') ||
|
||||||
@@ -9479,11 +9643,28 @@ export default {
|
|||||||
);
|
);
|
||||||
return item?.label || item?.dictValue || '';
|
return item?.label || item?.dictValue || '';
|
||||||
},
|
},
|
||||||
|
loadTransportTypeOptions() {
|
||||||
|
if (this.transportTypeOptions.length || this.transportTypeOptionsLoading) {
|
||||||
|
return Promise.resolve(this.transportTypeOptions);
|
||||||
|
}
|
||||||
|
this.transportTypeOptionsLoading = true;
|
||||||
|
return getDictionary({ code: 'transport_type' })
|
||||||
|
.then(res => {
|
||||||
|
this.transportTypeOptions = this.normalizeDictOptions(res.data?.data || []);
|
||||||
|
return this.transportTypeOptions;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.transportTypeOptionsLoading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
resolveTransportMode(value) {
|
resolveTransportMode(value) {
|
||||||
const text = [value, this.form.transportTypeName, this.getTransportTypeLabel(value)]
|
const values = [value, this.form.transportTypeName, this.getTransportTypeLabel(value)]
|
||||||
.filter(Boolean)
|
.flatMap(item => {
|
||||||
.join(' ')
|
if (!item || typeof item !== 'object') return [item];
|
||||||
.toLowerCase();
|
return [item.label, item.dictValue, item.name, item.value, item.dictKey];
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
const text = values.join(' ').toLowerCase();
|
||||||
if (!text) return '';
|
if (!text) return '';
|
||||||
if (
|
if (
|
||||||
text.includes('公路') ||
|
text.includes('公路') ||
|
||||||
@@ -9715,17 +9896,28 @@ export default {
|
|||||||
},
|
},
|
||||||
openTransportAddressDialog(target) {
|
openTransportAddressDialog(target) {
|
||||||
if (!this.ensureTransportTypeBeforeAddress()) return;
|
if (!this.ensureTransportTypeBeforeAddress()) return;
|
||||||
this.transportAddressTarget = target;
|
const open = () => {
|
||||||
this.transportAddressSelected = null;
|
this.transportAddressTarget = target;
|
||||||
this.resetTransportAddressQuery();
|
this.transportAddressSelected = null;
|
||||||
this.transportAddressPage.currentPage = 1;
|
this.resetTransportAddressQuery();
|
||||||
this.transportAddressBox = true;
|
this.transportAddressPage.currentPage = 1;
|
||||||
|
this.transportAddressBox = true;
|
||||||
|
};
|
||||||
|
// Avue 远程字典可能尚未写入 option,先加载后再解析运输方式,避免按空类型查询全部地址。
|
||||||
|
if (this.isTransportPlanPage && !this.getTransportFixedAddressType()) {
|
||||||
|
this.loadTransportTypeOptions().finally(open);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
open();
|
||||||
},
|
},
|
||||||
openTransportAddressPicker(target) {
|
openTransportAddressPicker(target) {
|
||||||
if (
|
// 运输计划的常用地址按钮统一使用地址库弹窗,按运输方式筛选并锁定地址类型;
|
||||||
this.transportStationMode &&
|
// 站点输入框仍保留铁路/航空专用站点选择弹窗。
|
||||||
!(this.isTransportPlanPage && this.transportMode === 'water')
|
if (this.isTransportPlanPage) {
|
||||||
) {
|
this.openTransportAddressDialog(target);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.transportStationMode) {
|
||||||
this.openTransportStationDialog(target);
|
this.openTransportStationDialog(target);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -9808,7 +10000,10 @@ export default {
|
|||||||
this.form[`${prefix}Latitude`] = address.latitude || '';
|
this.form[`${prefix}Latitude`] = address.latitude || '';
|
||||||
this.form[`${prefix}Contact`] = address.contactName || this.form[`${prefix}Contact`] || '';
|
this.form[`${prefix}Contact`] = address.contactName || this.form[`${prefix}Contact`] || '';
|
||||||
this.form[`${prefix}Phone`] = address.contactPhone || this.form[`${prefix}Phone`] || '';
|
this.form[`${prefix}Phone`] = address.contactPhone || this.form[`${prefix}Phone`] || '';
|
||||||
this.$refs.crud?.validateField?.(`${prefix}Address`);
|
// 地址值在本轮响应式更新后再清理校验状态,避免 Avue 以旧空值重新触发必填提示。
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.crud?.clearValidate?.([`${prefix}Address`]);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
openTransportMapDialog(target) {
|
openTransportMapDialog(target) {
|
||||||
if (this.dialogReadonly) return;
|
if (this.dialogReadonly) return;
|
||||||
@@ -9974,7 +10169,9 @@ export default {
|
|||||||
this.form[`${prefix}Longitude`] = this.transportMapSelected.longitude;
|
this.form[`${prefix}Longitude`] = this.transportMapSelected.longitude;
|
||||||
this.form[`${prefix}Latitude`] = this.transportMapSelected.latitude;
|
this.form[`${prefix}Latitude`] = this.transportMapSelected.latitude;
|
||||||
this.transportMapBox = false;
|
this.transportMapBox = false;
|
||||||
this.$refs.crud?.validateField?.(`${prefix}Address`);
|
this.$nextTick(() => {
|
||||||
|
this.$refs.crud?.clearValidate?.([`${prefix}Address`]);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
buildTransportMapSelection({ lng, lat, address, regionName, regionCode }) {
|
buildTransportMapSelection({ lng, lat, address, regionName, regionCode }) {
|
||||||
const longitude = this.formatCoordinate(lng);
|
const longitude = this.formatCoordinate(lng);
|
||||||
@@ -11721,13 +11918,17 @@ export default {
|
|||||||
this.api.getDetail(row.id).then(res => {
|
this.api.getDetail(row.id).then(res => {
|
||||||
const template = res.data?.data || row;
|
const template = res.data?.data || row;
|
||||||
const templateType = template.templateType || template.templateTypeName;
|
const templateType = template.templateType || template.templateTypeName;
|
||||||
const target =
|
const listTarget =
|
||||||
templateType === '运单' ? '/business/waybill-manage' : '/business/transport-plan';
|
templateType === '运单' ? '/business/waybill-manage' : '/business/transport-plan';
|
||||||
|
const target = standaloneBusinessFormRoutes[listTarget] || listTarget;
|
||||||
sessionStorage.setItem(
|
sessionStorage.setItem(
|
||||||
'business-template-create',
|
'business-template-create',
|
||||||
JSON.stringify({ target: target.slice('/business/'.length), data: template })
|
JSON.stringify({ target: listTarget.slice('/business/'.length), data: template })
|
||||||
);
|
);
|
||||||
this.$router.push({ path: target, query: { fromTemplate: Date.now().toString() } });
|
this.$router.push({
|
||||||
|
path: target,
|
||||||
|
query: { mode: 'add', fromTemplate: Date.now().toString() },
|
||||||
|
});
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -15535,10 +15736,21 @@ export default {
|
|||||||
|
|
||||||
:global(.business-crud-form-page-dialog .business-crud-page__shipping-template-footer-summary) {
|
:global(.business-crud-form-page-dialog .business-crud-page__shipping-template-footer-summary) {
|
||||||
position: static;
|
position: static;
|
||||||
|
order: 1;
|
||||||
height: auto;
|
height: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(.business-crud-form-page-dialog .avue-form__menu > .business-crud-page__standalone-menu-actions) {
|
||||||
|
display: flex;
|
||||||
|
order: 2;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.business-crud-form-page-dialog .avue-form__menu > .el-button) {
|
||||||
|
order: 3;
|
||||||
|
}
|
||||||
|
|
||||||
:global(.avue--collapse .el-overlay:has(.business-crud-form-page-dialog)) {
|
:global(.avue--collapse .el-overlay:has(.business-crud-form-page-dialog)) {
|
||||||
left: 60px !important;
|
left: 60px !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,10 +154,10 @@
|
|||||||
<template v-else>
|
<template v-else>
|
||||||
<el-col v-if="route.carrierType === '承运商'" :span="6"><el-form-item label="承运商" required><el-select v-model="route.carrierContractId" filterable clearable placeholder="请选择" :loading="carrierLoading" @change="value => handleCarrierContractChange(route, value)"><el-option v-for="item in carrierOptions" :key="item.contractId" :label="carrierOptionLabel(item)" :value="item.contractId" /></el-select></el-form-item></el-col>
|
<el-col v-if="route.carrierType === '承运商'" :span="6"><el-form-item label="承运商" required><el-select v-model="route.carrierContractId" filterable clearable placeholder="请选择" :loading="carrierLoading" @change="value => handleCarrierContractChange(route, value)"><el-option v-for="item in carrierOptions" :key="item.contractId" :label="carrierOptionLabel(item)" :value="item.contractId" /></el-select></el-form-item></el-col>
|
||||||
<el-col :span="6"><el-form-item label="船/航/班列号" required><el-input v-model="route.vehicleNo" placeholder="请输入" /></el-form-item></el-col>
|
<el-col :span="6"><el-form-item label="船/航/班列号" required><el-input v-model="route.vehicleNo" placeholder="请输入" /></el-form-item></el-col>
|
||||||
<el-col :span="6"><el-form-item label="船长" required><el-input v-model="route.captainName" placeholder="请输入" /></el-form-item></el-col>
|
<el-col :span="6"><el-form-item label="船长"><el-input v-model="route.captainName" placeholder="请输入" /></el-form-item></el-col>
|
||||||
<el-col :span="6"><el-form-item label="联系电话" required><el-input v-model="route.driverPhone" placeholder="请输入" /></el-form-item></el-col>
|
<el-col :span="6"><el-form-item label="联系电话" required><el-input v-model="route.driverPhone" placeholder="请输入" /></el-form-item></el-col>
|
||||||
<el-col :span="6"><el-form-item label="箱号" required><el-input v-model="route.containerNo" placeholder="请输入" /></el-form-item></el-col>
|
<el-col :span="6"><el-form-item label="箱号"><el-input v-model="route.containerNo" placeholder="请输入" /></el-form-item></el-col>
|
||||||
<el-col :span="6"><el-form-item label="舱位" required><el-input v-model="route.cabinNo" placeholder="请输入" /></el-form-item></el-col>
|
<el-col :span="6"><el-form-item label="舱位"><el-input v-model="route.cabinNo" placeholder="请输入" /></el-form-item></el-col>
|
||||||
</template>
|
</template>
|
||||||
<el-col :span="6"><el-form-item label="里程(km)"><el-input :model-value="route.mileage" inputmode="numeric" maxlength="10" placeholder="请输入" @input="value => handleMileageInput(route, value)" /></el-form-item></el-col>
|
<el-col :span="6"><el-form-item label="里程(km)"><el-input :model-value="route.mileage" inputmode="numeric" maxlength="10" placeholder="请输入" @input="value => handleMileageInput(route, value)" /></el-form-item></el-col>
|
||||||
<el-col :span="6"><el-form-item label="备注"><el-input v-model="route.remark" maxlength="200" show-word-limit placeholder="请输入" /></el-form-item></el-col>
|
<el-col :span="6"><el-form-item label="备注"><el-input v-model="route.remark" maxlength="200" show-word-limit placeholder="请输入" /></el-form-item></el-col>
|
||||||
@@ -316,6 +316,10 @@ export default {
|
|||||||
this.syncFreightItems(route);
|
this.syncFreightItems(route);
|
||||||
},
|
},
|
||||||
isRoad(route) { const type = String(route.transportType || '').toLowerCase(); return type.includes('公路') || type === 'road'; },
|
isRoad(route) { const type = String(route.transportType || '').toLowerCase(); return type.includes('公路') || type === 'road'; },
|
||||||
|
isWater(route) {
|
||||||
|
const type = String(route.transportType || '').trim().toLowerCase();
|
||||||
|
return type === 'river' || type === 'water' || type === 'sl' || type.includes('水路') || type.includes('水运');
|
||||||
|
},
|
||||||
dispatchedQuantity(route) {
|
dispatchedQuantity(route) {
|
||||||
return this.quantityNumber(route.dispatchedQuantity) + this.pendingSegmentQuantity(route);
|
return this.quantityNumber(route.dispatchedQuantity) + this.pendingSegmentQuantity(route);
|
||||||
},
|
},
|
||||||
@@ -619,7 +623,7 @@ export default {
|
|||||||
if (this.isRoad(route)) {
|
if (this.isRoad(route)) {
|
||||||
if (route.carrierType === '承运商' && (!route.carrierContractId || !route.carrierName || !route.vehicleNo)) return this.$message.warning('请选择承运商并填写车牌号');
|
if (route.carrierType === '承运商' && (!route.carrierContractId || !route.carrierName || !route.vehicleNo)) return this.$message.warning('请选择承运商并填写车牌号');
|
||||||
if (route.carrierType !== '承运商' && (!route.driverName || !route.driverPhone || !route.vehicleNo || !route.trailerVehicleNo || !route.escortName || !route.escortPhone)) return this.$message.warning('请补全自运或网货平台的车辆与人员信息');
|
if (route.carrierType !== '承运商' && (!route.driverName || !route.driverPhone || !route.vehicleNo || !route.trailerVehicleNo || !route.escortName || !route.escortPhone)) return this.$message.warning('请补全自运或网货平台的车辆与人员信息');
|
||||||
} else if (!route.vehicleNo || !route.captainName || !route.driverPhone || !route.containerNo || !route.cabinNo || (route.carrierType === '承运商' && (!route.carrierContractId || !route.carrierName))) {
|
} else if (!route.vehicleNo || !route.driverPhone || (route.carrierType === '承运商' && (!route.carrierContractId || !route.carrierName))) {
|
||||||
return this.$message.warning('请补全非公路运输的承运信息');
|
return this.$message.warning('请补全非公路运输的承运信息');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,12 +29,12 @@
|
|||||||
:label="contract.contractName"
|
:label="contract.contractName"
|
||||||
:value="contract.id" /></el-select></el-form-item></el-col
|
:value="contract.id" /></el-select></el-form-item></el-col
|
||||||
><el-col :span="6"
|
><el-col :span="6"
|
||||||
><el-form-item label="总单号"
|
><el-form-item label="总单号" required
|
||||||
><el-input
|
><el-input
|
||||||
:model-value="form.masterNo || '系统自动生成'"
|
:model-value="form.masterNo || '系统自动生成'"
|
||||||
readonly /></el-form-item></el-col
|
readonly /></el-form-item></el-col
|
||||||
><el-col :span="6"
|
><el-col :span="6"
|
||||||
><el-form-item label="运输组织类型"
|
><el-form-item label="运输组织类型" required
|
||||||
><el-select v-model="form.transportOrganizationType" disabled
|
><el-select v-model="form.transportOrganizationType" disabled
|
||||||
><el-option label="多式联运" value="多式联运" /></el-select></el-form-item></el-col
|
><el-option label="多式联运" value="多式联运" /></el-select></el-form-item></el-col
|
||||||
><el-col :span="24"
|
><el-col :span="24"
|
||||||
@@ -1766,6 +1766,10 @@ export default {
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.master-editor {
|
||||||
|
padding-bottom: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
.master-editor section {
|
.master-editor section {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
|
|||||||
@@ -247,13 +247,14 @@
|
|||||||
:min="0"
|
:min="0"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
:controls="false"
|
:controls="false"
|
||||||
|
@change="recalculateLine(row)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="含税金额" min-width="130">
|
<el-table-column label="不含税单价" min-width="130">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-model="row.amountWithTax"
|
v-model="row.unitPriceNoTax"
|
||||||
:disabled="readonly"
|
:disabled="readonly"
|
||||||
:min="0"
|
:min="0"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
@@ -262,6 +263,9 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="不含税金额" min-width="130" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.amountNoTax) }}</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="税率" min-width="110">
|
<el-table-column label="税率" min-width="110">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-select
|
<el-select
|
||||||
@@ -281,6 +285,9 @@
|
|||||||
<el-table-column label="税额" min-width="120">
|
<el-table-column label="税额" min-width="120">
|
||||||
<template #default="{ row }">{{ formatMoney(row.taxAmount) }}</template>
|
<template #default="{ row }">{{ formatMoney(row.taxAmount) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="合计" min-width="130" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.totalAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="备注" min-width="170">
|
<el-table-column label="备注" min-width="170">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-input v-model="row.remark" :disabled="readonly" maxlength="200" />
|
<el-input v-model="row.remark" :disabled="readonly" maxlength="200" />
|
||||||
@@ -374,6 +381,7 @@
|
|||||||
<span v-else-if="column.prop === 'transportType'">{{
|
<span v-else-if="column.prop === 'transportType'">{{
|
||||||
transportTypeLabel(row.transportType)
|
transportTypeLabel(row.transportType)
|
||||||
}}</span>
|
}}</span>
|
||||||
|
<span v-else-if="column.dateTime">{{ formatDateTime(row[column.prop]) }}</span>
|
||||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -492,7 +500,9 @@ const emptyLine = () => ({
|
|||||||
unit: '吨',
|
unit: '吨',
|
||||||
quantity: 0,
|
quantity: 0,
|
||||||
unitPriceNoTax: 0,
|
unitPriceNoTax: 0,
|
||||||
|
amountNoTax: 0,
|
||||||
amountWithTax: 0,
|
amountWithTax: 0,
|
||||||
|
totalAmount: 0,
|
||||||
taxRate: 0,
|
taxRate: 0,
|
||||||
taxAmount: 0,
|
taxAmount: 0,
|
||||||
remark: '',
|
remark: '',
|
||||||
@@ -598,7 +608,7 @@ export default {
|
|||||||
return this.form.sheets.reduce(
|
return this.form.sheets.reduce(
|
||||||
(sheetTotal, sheet) =>
|
(sheetTotal, sheet) =>
|
||||||
sheetTotal +
|
sheetTotal +
|
||||||
sheet.lines.reduce((lineTotal, line) => lineTotal + Number(line.amountWithTax || 0), 0),
|
sheet.lines.reduce((lineTotal, line) => lineTotal + Number(line.totalAmount || 0), 0),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -714,11 +724,11 @@ export default {
|
|||||||
const item = this.findInvoiceItem(line);
|
const item = this.findInvoiceItem(line);
|
||||||
if (item) {
|
if (item) {
|
||||||
line.taxRate = Number(item.defaultTaxRate || 0);
|
line.taxRate = Number(item.defaultTaxRate || 0);
|
||||||
this.recalculateLine(line);
|
|
||||||
} else if (clearInvalid) {
|
} else if (clearInvalid) {
|
||||||
line.goodsName = '';
|
line.goodsName = '';
|
||||||
line.taxRate = 0;
|
line.taxRate = 0;
|
||||||
}
|
}
|
||||||
|
this.recalculateLine(line);
|
||||||
},
|
},
|
||||||
handleGoodsCategoryChange(line) {
|
handleGoodsCategoryChange(line) {
|
||||||
const names = this.invoiceItemNames(line.goodsCategory);
|
const names = this.invoiceItemNames(line.goodsCategory);
|
||||||
@@ -1067,9 +1077,14 @@ export default {
|
|||||||
sheet.lines.push(emptyLine());
|
sheet.lines.push(emptyLine());
|
||||||
},
|
},
|
||||||
recalculateLine(row) {
|
recalculateLine(row) {
|
||||||
const amount = Number(row.amountWithTax || 0);
|
const quantity = Number(row.quantity || 0);
|
||||||
|
const unitPrice = Number(row.unitPriceNoTax || 0);
|
||||||
|
const amountNoTax = Number((quantity * unitPrice).toFixed(2));
|
||||||
const rate = Number(row.taxRate || 0) / 100;
|
const rate = Number(row.taxRate || 0) / 100;
|
||||||
row.taxAmount = rate ? Number((amount - amount / (1 + rate)).toFixed(2)) : 0;
|
row.amountNoTax = amountNoTax;
|
||||||
|
row.taxAmount = rate ? Number((amountNoTax * rate).toFixed(2)) : 0;
|
||||||
|
row.totalAmount = Number((amountNoTax + row.taxAmount).toFixed(2));
|
||||||
|
row.amountWithTax = row.totalAmount;
|
||||||
},
|
},
|
||||||
validateBusiness() {
|
validateBusiness() {
|
||||||
if (!this.selectedDetailIds.length) throw new Error('请至少选择一条开票明细');
|
if (!this.selectedDetailIds.length) throw new Error('请至少选择一条开票明细');
|
||||||
@@ -1082,7 +1097,7 @@ export default {
|
|||||||
for (const line of sheet.lines) {
|
for (const line of sheet.lines) {
|
||||||
if (!line.goodsCategory || !line.goodsName) throw new Error('请完整填写商品和服务信息');
|
if (!line.goodsCategory || !line.goodsName) throw new Error('请完整填写商品和服务信息');
|
||||||
if (
|
if (
|
||||||
[line.quantity, line.unitPriceNoTax, line.amountWithTax, line.taxRate].some(
|
[line.quantity, line.unitPriceNoTax, line.amountNoTax, line.taxRate].some(
|
||||||
Number.isNaN
|
Number.isNaN
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
@@ -1091,6 +1106,13 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
validateAvailableInvoiceAmount() {
|
||||||
|
const availableAmountCents = Math.round(Number(this.availableInvoiceAmount || 0) * 100);
|
||||||
|
const invoiceAmountCents = Math.round(Number(this.invoiceAmount || 0) * 100);
|
||||||
|
if (invoiceAmountCents > availableAmountCents) {
|
||||||
|
throw new Error('本次开票金额不能超过可开票金额');
|
||||||
|
}
|
||||||
|
},
|
||||||
payload() {
|
payload() {
|
||||||
return {
|
return {
|
||||||
id: this.form.id,
|
id: this.form.id,
|
||||||
@@ -1114,7 +1136,9 @@ export default {
|
|||||||
unit: line.unit,
|
unit: line.unit,
|
||||||
quantity: line.quantity,
|
quantity: line.quantity,
|
||||||
unitPriceNoTax: line.unitPriceNoTax,
|
unitPriceNoTax: line.unitPriceNoTax,
|
||||||
amountWithTax: line.amountWithTax,
|
amountNoTax: line.amountNoTax,
|
||||||
|
totalAmount: line.totalAmount,
|
||||||
|
amountWithTax: line.totalAmount,
|
||||||
taxRate: line.taxRate,
|
taxRate: line.taxRate,
|
||||||
taxAmount: line.taxAmount,
|
taxAmount: line.taxAmount,
|
||||||
remark: line.remark,
|
remark: line.remark,
|
||||||
@@ -1136,6 +1160,12 @@ export default {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
async submitForm() {
|
async submitForm() {
|
||||||
|
try {
|
||||||
|
this.validateAvailableInvoiceAmount();
|
||||||
|
} catch (error) {
|
||||||
|
this.$message.warning(error.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const saved = await this.saveDraft();
|
const saved = await this.saveDraft();
|
||||||
if (!saved) return;
|
if (!saved) return;
|
||||||
await api.submit({ id: this.form.id });
|
await api.submit({ id: this.form.id });
|
||||||
@@ -1156,6 +1186,10 @@ export default {
|
|||||||
displayValue(value) {
|
displayValue(value) {
|
||||||
return value === null || value === undefined || value === '' ? '-' : value;
|
return value === null || value === undefined || value === '' ? '-' : value;
|
||||||
},
|
},
|
||||||
|
formatDateTime(value) {
|
||||||
|
if (!value) return '-';
|
||||||
|
return this.$dayjs(value).format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
},
|
||||||
formatMoney(value) {
|
formatMoney(value) {
|
||||||
return Number(value || 0).toFixed(2);
|
return Number(value || 0).toFixed(2);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -654,7 +654,7 @@ export default {
|
|||||||
...item,
|
...item,
|
||||||
formalSettlementId: item.id,
|
formalSettlementId: item.id,
|
||||||
settlementId: item.id,
|
settlementId: item.id,
|
||||||
allocatedInvoiceAmount: 0,
|
allocatedInvoiceAmount: rows.length === 1 ? Number(item.settlementAmount || 0) : 0,
|
||||||
}));
|
}));
|
||||||
this.form.projectName = first.projectName;
|
this.form.projectName = first.projectName;
|
||||||
this.form.deptName = first.deptName;
|
this.form.deptName = first.deptName;
|
||||||
|
|||||||
@@ -140,32 +140,23 @@
|
|||||||
></el-col>
|
></el-col>
|
||||||
<el-col v-if="billPayment" :span="6"
|
<el-col v-if="billPayment" :span="6"
|
||||||
><el-form-item label="汇票单号" prop="billLedgerId"
|
><el-form-item label="汇票单号" prop="billLedgerId"
|
||||||
><el-select
|
><el-input
|
||||||
v-model="form.billLedgerId"
|
v-model="form.billNo"
|
||||||
|
readonly
|
||||||
:disabled="readonly"
|
:disabled="readonly"
|
||||||
filterable
|
|
||||||
remote
|
|
||||||
reserve-keyword
|
|
||||||
:remote-method="loadBillOptions"
|
|
||||||
:loading="billLoading"
|
|
||||||
placeholder="请选择可用汇票"
|
placeholder="请选择可用汇票"
|
||||||
@change="handleBillChange"
|
><template #append
|
||||||
><el-option
|
><el-button :disabled="readonly" @click="openBillDialog">选择</el-button></template
|
||||||
v-for="item in billOptions"
|
></el-input></el-form-item
|
||||||
:key="item.id"
|
|
||||||
:label="`${item.billNo}|余额${formatMoney(item.availableBalance)}|${
|
|
||||||
item.maturityDate
|
|
||||||
}`"
|
|
||||||
:value="item.id" /></el-select></el-form-item
|
|
||||||
></el-col>
|
></el-col>
|
||||||
<el-col :span="6"
|
<el-col :span="6"
|
||||||
><el-form-item label="收款方"
|
><el-form-item label="收款方"
|
||||||
><el-input v-model="form.payeeName" disabled /></el-form-item
|
><el-input v-model="form.payeeName" disabled /></el-form-item
|
||||||
></el-col>
|
></el-col>
|
||||||
<el-col :span="6"
|
<el-col :span="6">
|
||||||
><el-form-item label="收款账号" prop="receiptAccountId"
|
<el-form-item label="收款账号" prop="receiptAccountId">
|
||||||
><span v-if="readonly">{{ form.bankAccount || '-' }}</span
|
<el-input v-if="readonly" v-model="form.bankAccount" disabled></el-input>
|
||||||
><el-select
|
<el-select
|
||||||
v-else
|
v-else
|
||||||
v-model="form.receiptAccountId"
|
v-model="form.receiptAccountId"
|
||||||
:disabled="!form.payeeName"
|
:disabled="!form.payeeName"
|
||||||
@@ -179,8 +170,11 @@
|
|||||||
v-for="item in receiptAccountOptions"
|
v-for="item in receiptAccountOptions"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:label="receiptAccountLabel(item)"
|
:label="receiptAccountLabel(item)"
|
||||||
:value="item.id" /></el-select></el-form-item
|
:value="item.id"
|
||||||
></el-col>
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
<el-col :span="6"
|
<el-col :span="6"
|
||||||
><el-form-item label="开户银行"
|
><el-form-item label="开户银行"
|
||||||
><el-input v-model="form.bankName" disabled /></el-form-item
|
><el-input v-model="form.bankName" disabled /></el-form-item
|
||||||
@@ -358,7 +352,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</section-card>
|
</section-card>
|
||||||
<div class="payment-form-page__actions">
|
<div class="payment-form-page__actions">
|
||||||
<el-button @click="goBack">取消</el-button>
|
<el-button v-if="!readonly" type="primary" plain @click="syncPaymentRecords"
|
||||||
|
>同步</el-button
|
||||||
|
>
|
||||||
<el-button v-if="!readonly && canSave" type="primary" plain @click="saveDraft(false)"
|
<el-button v-if="!readonly && canSave" type="primary" plain @click="saveDraft(false)"
|
||||||
>保存</el-button
|
>保存</el-button
|
||||||
>
|
>
|
||||||
@@ -368,6 +364,7 @@
|
|||||||
@click="submitForm"
|
@click="submitForm"
|
||||||
>提交</el-button
|
>提交</el-button
|
||||||
>
|
>
|
||||||
|
<el-button @click="goBack">返回</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
@@ -495,6 +492,54 @@
|
|||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs></div
|
</el-tabs></div
|
||||||
></el-dialog>
|
></el-dialog>
|
||||||
|
<el-dialog v-model="billDialogVisible" title="选择汇票" width="80%" append-to-body>
|
||||||
|
<div class="payment-form-page__reference-search">
|
||||||
|
<el-form :model="billQuery" label-position="right" label-width="160px" @submit.prevent>
|
||||||
|
<div class="payment-form-page__reference-search-fields">
|
||||||
|
<el-form-item label="汇票筛选">
|
||||||
|
<el-input
|
||||||
|
v-model="billQuery.keyword"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入汇票号、出票单位或收票单位"
|
||||||
|
@keyup.enter="handleBillSearch"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="payment-form-page__reference-search-actions">
|
||||||
|
<el-button type="primary" @click="handleBillSearch">搜索</el-button>
|
||||||
|
<el-button @click="resetBillSearch">清空</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<el-table v-loading="billPage.loading" :data="billRows" border>
|
||||||
|
<el-table-column prop="billNo" label="汇票单号" min-width="180" />
|
||||||
|
<el-table-column prop="issuerName" label="出票单位" min-width="160" />
|
||||||
|
<el-table-column prop="receiverName" label="收票单位" min-width="160" />
|
||||||
|
<el-table-column prop="faceAmount" label="票面金额" min-width="120">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.faceAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="availableBalance" label="可用余额" min-width="120">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.availableBalance) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="maturityDate" label="到期日期" min-width="120" />
|
||||||
|
<el-table-column label="操作" width="100" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-link type="primary" @click="selectBill(row)">选择</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<div class="payment-form-page__reference-pagination">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="billPage.current"
|
||||||
|
v-model:page-size="billPage.size"
|
||||||
|
:total="billPage.total"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@current-change="loadBillPage"
|
||||||
|
@size-change="handleBillSizeChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="attachmentDocumentPreviewVisible"
|
v-model="attachmentDocumentPreviewVisible"
|
||||||
:title="attachmentPreviewFile.name || '附件预览'"
|
:title="attachmentPreviewFile.name || '附件预览'"
|
||||||
@@ -546,6 +591,7 @@ import pdfWorkerSrc from 'pdfjs-dist/build/pdf.worker.mjs?url';
|
|||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import * as api from '@/api/payment/paymentApplication';
|
import * as api from '@/api/payment/paymentApplication';
|
||||||
import * as billLedgerApi from '@/api/payment/billLedger';
|
import * as billLedgerApi from '@/api/payment/billLedger';
|
||||||
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
import { getList as getProjectList } from '@/api/business/project-apply';
|
import { getList as getProjectList } from '@/api/business/project-apply';
|
||||||
import { getDetail as getContractDetail } from '@/api/business/contract-manage';
|
import { getDetail as getContractDetail } from '@/api/business/contract-manage';
|
||||||
import * as formalApi from '@/api/settlement/formalSettlement';
|
import * as formalApi from '@/api/settlement/formalSettlement';
|
||||||
@@ -668,6 +714,17 @@ export default {
|
|||||||
contractRows: [],
|
contractRows: [],
|
||||||
billOptions: [],
|
billOptions: [],
|
||||||
billLoading: false,
|
billLoading: false,
|
||||||
|
billDialogVisible: false,
|
||||||
|
billQuery: {
|
||||||
|
keyword: '',
|
||||||
|
},
|
||||||
|
billRows: [],
|
||||||
|
billPage: {
|
||||||
|
current: 1,
|
||||||
|
size: 10,
|
||||||
|
total: 0,
|
||||||
|
loading: false,
|
||||||
|
},
|
||||||
receiptAccountOptions: [],
|
receiptAccountOptions: [],
|
||||||
receiptAccountLoading: false,
|
receiptAccountLoading: false,
|
||||||
firstContractPayment: false,
|
firstContractPayment: false,
|
||||||
@@ -690,7 +747,7 @@ export default {
|
|||||||
zoom: true,
|
zoom: true,
|
||||||
},
|
},
|
||||||
paymentTypeOptions: api.paymentTypeOptions,
|
paymentTypeOptions: api.paymentTypeOptions,
|
||||||
paymentMethodOptions: api.paymentMethodOptions,
|
paymentMethodOptions: [],
|
||||||
attachmentFileTypes: [
|
attachmentFileTypes: [
|
||||||
'pdf',
|
'pdf',
|
||||||
'bmp',
|
'bmp',
|
||||||
@@ -734,7 +791,7 @@ export default {
|
|||||||
return this.hasPermission(code);
|
return this.hasPermission(code);
|
||||||
},
|
},
|
||||||
billPayment() {
|
billPayment() {
|
||||||
return this.form.paymentMethod !== 'bank_transfer';
|
return this.isBillPaymentMethod();
|
||||||
},
|
},
|
||||||
paymentAmountBase() {
|
paymentAmountBase() {
|
||||||
if (this.form.paymentType === 'project_advance') return 0;
|
if (this.form.paymentType === 'project_advance') return 0;
|
||||||
@@ -970,6 +1027,7 @@ export default {
|
|||||||
return body?.data || body;
|
return body?.data || body;
|
||||||
},
|
},
|
||||||
async initialize() {
|
async initialize() {
|
||||||
|
await this.loadPaymentMethodOptions();
|
||||||
if (this.recordId) {
|
if (this.recordId) {
|
||||||
const data = this.unwrapData(await api.getDetail(this.recordId));
|
const data = this.unwrapData(await api.getDetail(this.recordId));
|
||||||
this.form = {
|
this.form = {
|
||||||
@@ -1014,6 +1072,37 @@ export default {
|
|||||||
? paymentType
|
? paymentType
|
||||||
: 'project_advance';
|
: 'project_advance';
|
||||||
},
|
},
|
||||||
|
async loadPaymentMethodOptions() {
|
||||||
|
try {
|
||||||
|
const data = this.unwrapData(await getDictionary({ code: 'pay_method' }));
|
||||||
|
const records = Array.isArray(data) ? data : data?.records || [];
|
||||||
|
this.paymentMethodOptions = records
|
||||||
|
.map(item => ({
|
||||||
|
...item,
|
||||||
|
label: item.dictValue || item.label || item.name || item.dictKey || item.value,
|
||||||
|
value: item.dictKey || item.value || item.dictValue || item.name,
|
||||||
|
}))
|
||||||
|
.filter(item => item.label && item.value);
|
||||||
|
} catch (error) {
|
||||||
|
this.paymentMethodOptions = [];
|
||||||
|
this.$message.warning('付款方式字典加载失败,请稍后重试');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isBillPaymentMethod(value = this.form.paymentMethod) {
|
||||||
|
const selected = this.paymentMethodOptions.find(
|
||||||
|
item => String(item.value) === String(value)
|
||||||
|
);
|
||||||
|
const searchableText = [
|
||||||
|
selected?.dictKey,
|
||||||
|
selected?.dictValue,
|
||||||
|
selected?.label,
|
||||||
|
selected?.name,
|
||||||
|
selected?.value,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ');
|
||||||
|
return searchableText.includes('汇票');
|
||||||
|
},
|
||||||
getTransferredPreSettlementIds() {
|
getTransferredPreSettlementIds() {
|
||||||
const payloadIds = (this.transferPayload?.sourcePreSettlements || []).map(
|
const payloadIds = (this.transferPayload?.sourcePreSettlements || []).map(
|
||||||
item => item.preSettlementId || item.id
|
item => item.preSettlementId || item.id
|
||||||
@@ -1180,27 +1269,38 @@ export default {
|
|||||||
attachmentJson: invoice.attachmentJson || '',
|
attachmentJson: invoice.attachmentJson || '',
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
createMockPaymentRecords(settlementNo, amount = this.form.appliedAmount) {
|
syncPaymentRecords() {
|
||||||
const totalCents = Math.max(0, Math.round(Number(amount || 0) * 100));
|
const appliedCents = Math.max(0, Math.round(Number(this.form.appliedAmount || 0) * 100));
|
||||||
const paidCents = [Math.round(totalCents * 0.2), Math.round(totalCents * 0.3)];
|
if (!appliedCents) {
|
||||||
paidCents.push(totalCents - paidCents[0] - paidCents[1]);
|
this.$message.warning('请先填写申请付款金额');
|
||||||
const normalizedSettlementNo = String(settlementNo || 'SETTLEMENT').slice(0, 60);
|
return;
|
||||||
this.paymentRecords = paidCents.map((value, index) => {
|
}
|
||||||
const paidDate = this.$dayjs().subtract(index, 'day').format('YYYY-MM-DD');
|
const maxCount = Math.min(5, appliedCents);
|
||||||
|
const minCount = Math.min(2, maxCount);
|
||||||
|
const recordCount = Math.floor(Math.random() * (maxCount - minCount + 1)) + minCount;
|
||||||
|
const minimumTotal = recordCount;
|
||||||
|
const randomTotal = Math.round(appliedCents * (0.5 + Math.random() * 0.5));
|
||||||
|
let remainingCents = Math.max(minimumTotal, Math.min(appliedCents, randomTotal));
|
||||||
|
const timestamp = this.$dayjs().format('YYYYMMDDHHmmss');
|
||||||
|
this.paymentRecords = Array.from({ length: recordCount }, (_unusedItem, index) => {
|
||||||
|
const remainingCount = recordCount - index - 1;
|
||||||
|
const maxCurrentCents = remainingCents - remainingCount;
|
||||||
|
const paidCents =
|
||||||
|
remainingCount === 0 ? remainingCents : Math.floor(Math.random() * maxCurrentCents) + 1;
|
||||||
|
remainingCents -= paidCents;
|
||||||
const sequence = String(index + 1).padStart(2, '0');
|
const sequence = String(index + 1).padStart(2, '0');
|
||||||
|
const paidDate = this.$dayjs()
|
||||||
|
.subtract(Math.floor(Math.random() * 30), 'day')
|
||||||
|
.format('YYYY-MM-DD');
|
||||||
return {
|
return {
|
||||||
paidAmount: Number((value / 100).toFixed(2)),
|
paidAmount: Number((paidCents / 100).toFixed(2)),
|
||||||
paidDate,
|
paidDate,
|
||||||
paymentNo: `MOCK-${normalizedSettlementNo}-${sequence}`,
|
paymentNo: `MOCK-PAY-${timestamp}-${sequence}`,
|
||||||
voucherJson: '',
|
voucherJson: '',
|
||||||
kingdeeBillNo: `MOCK-KD${paidDate.replaceAll('-', '')}${sequence}`,
|
kingdeeBillNo: `MOCK-KD-${timestamp}-${sequence}`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
this.$message.success(`同步成功,已生成${recordCount}条付款记录`);
|
||||||
referencePaymentAmount(settlementAmount) {
|
|
||||||
return Number(
|
|
||||||
((Number(settlementAmount || 0) * Number(this.form.paymentRatio || 0)) / 100).toFixed(2)
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
applyTransferredPreSettlements(rows = []) {
|
applyTransferredPreSettlements(rows = []) {
|
||||||
if (!rows.length) return;
|
if (!rows.length) return;
|
||||||
@@ -1246,7 +1346,7 @@ export default {
|
|||||||
billType: '预结算单',
|
billType: '预结算单',
|
||||||
appliedAmount: Number(((settlementAmount * paymentRatio) / 100).toFixed(2)),
|
appliedAmount: Number(((settlementAmount * paymentRatio) / 100).toFixed(2)),
|
||||||
});
|
});
|
||||||
this.createMockPaymentRecords(this.form.preSettlementNo, this.form.appliedAmount);
|
this.paymentRecords = [];
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.amountSyncing = false;
|
this.amountSyncing = false;
|
||||||
this.$refs.formRef?.clearValidate();
|
this.$refs.formRef?.clearValidate();
|
||||||
@@ -1549,14 +1649,13 @@ export default {
|
|||||||
}
|
}
|
||||||
this.contractPaymentRatioExceeded = exceeded;
|
this.contractPaymentRatioExceeded = exceeded;
|
||||||
},
|
},
|
||||||
validateRequiredAttachments() {
|
notifyMissingAttachments() {
|
||||||
if (!this.missingAttachmentMaterials.length) return true;
|
if (!this.missingAttachmentMaterials.length) return;
|
||||||
this.$message.warning(
|
this.$message.warning(
|
||||||
`请先上传付款申请所需材料:${this.missingAttachmentMaterials
|
`付款申请所需材料未上传,请核对:${this.missingAttachmentMaterials
|
||||||
.map(item => item.label)
|
.map(item => item.label)
|
||||||
.join('、')}`
|
.join('、')}`
|
||||||
);
|
);
|
||||||
return false;
|
|
||||||
},
|
},
|
||||||
handleTypeChange() {
|
handleTypeChange() {
|
||||||
this.form.invoices = [];
|
this.form.invoices = [];
|
||||||
@@ -1631,20 +1730,10 @@ export default {
|
|||||||
try {
|
try {
|
||||||
const response = await formalApi.getContractOptions('', projectId);
|
const response = await formalApi.getContractOptions('', projectId);
|
||||||
this.contractOptions = (this.unwrapData(response) || []).filter(
|
this.contractOptions = (this.unwrapData(response) || []).filter(
|
||||||
item => String(item.projectId || '') === String(projectId)
|
item =>
|
||||||
|
String(item.projectId || '') === String(projectId) &&
|
||||||
|
String(item.contractCategory || '').trim() === '承运商合同'
|
||||||
);
|
);
|
||||||
if (
|
|
||||||
this.form.contractId &&
|
|
||||||
this.form.contractName &&
|
|
||||||
!this.contractOptions.some(item => String(item.id) === String(this.form.contractId))
|
|
||||||
) {
|
|
||||||
this.contractOptions.unshift({
|
|
||||||
id: this.form.contractId,
|
|
||||||
contractNo: this.form.contractNo,
|
|
||||||
contractName: this.form.contractName,
|
|
||||||
projectId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.contractOptions = [];
|
this.contractOptions = [];
|
||||||
this.$message.warning('项目合同加载失败,请稍后重试');
|
this.$message.warning('项目合同加载失败,请稍后重试');
|
||||||
@@ -1790,6 +1879,8 @@ export default {
|
|||||||
this.form.billLedgerId = null;
|
this.form.billLedgerId = null;
|
||||||
this.form.billNo = '';
|
this.form.billNo = '';
|
||||||
this.billOptions = [];
|
this.billOptions = [];
|
||||||
|
this.billRows = [];
|
||||||
|
this.billDialogVisible = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.loadBillOptions();
|
await this.loadBillOptions();
|
||||||
@@ -1806,10 +1897,49 @@ export default {
|
|||||||
this.billLoading = false;
|
this.billLoading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleBillChange(id) {
|
async openBillDialog() {
|
||||||
const selected = this.billOptions.find(item => String(item.id) === String(id));
|
if (this.readonly || !this.billPayment) return;
|
||||||
this.form.billNo = selected?.billNo || '';
|
this.billDialogVisible = true;
|
||||||
this.$refs.formRef?.validateField('billLedgerId').catch(() => {});
|
this.billQuery.keyword = '';
|
||||||
|
this.billPage.current = 1;
|
||||||
|
await this.loadBillPage();
|
||||||
|
},
|
||||||
|
async loadBillPage() {
|
||||||
|
if (!this.billPayment) return;
|
||||||
|
this.billPage.loading = true;
|
||||||
|
try {
|
||||||
|
const response = await billLedgerApi.getAvailablePage(
|
||||||
|
this.billPage.current,
|
||||||
|
this.billPage.size,
|
||||||
|
String(this.billQuery.keyword || '').trim() || undefined,
|
||||||
|
this.form.deptId,
|
||||||
|
this.form.billLedgerId
|
||||||
|
);
|
||||||
|
const data = this.unwrapData(response) || {};
|
||||||
|
this.billRows = data.records || [];
|
||||||
|
this.billPage.total = Number(data.total || 0);
|
||||||
|
} finally {
|
||||||
|
this.billPage.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleBillSearch() {
|
||||||
|
this.billPage.current = 1;
|
||||||
|
this.loadBillPage();
|
||||||
|
},
|
||||||
|
resetBillSearch() {
|
||||||
|
this.billQuery.keyword = '';
|
||||||
|
this.handleBillSearch();
|
||||||
|
},
|
||||||
|
handleBillSizeChange() {
|
||||||
|
this.billPage.current = 1;
|
||||||
|
this.loadBillPage();
|
||||||
|
},
|
||||||
|
selectBill(row) {
|
||||||
|
this.form.billLedgerId = row.id;
|
||||||
|
this.form.billNo = row.billNo || '';
|
||||||
|
this.billOptions = [row];
|
||||||
|
this.billDialogVisible = false;
|
||||||
|
this.$nextTick(() => this.$refs.formRef?.validateField('billLedgerId').catch(() => {}));
|
||||||
},
|
},
|
||||||
referenceParams(type) {
|
referenceParams(type) {
|
||||||
const { settlementNo, projectName, contractName } = this.referenceQuery;
|
const { settlementNo, projectName, contractName } = this.referenceQuery;
|
||||||
@@ -1957,10 +2087,7 @@ export default {
|
|||||||
detail?.invoices,
|
detail?.invoices,
|
||||||
detail?.formalSettlementNo || row.formalSettlementNo
|
detail?.formalSettlementNo || row.formalSettlementNo
|
||||||
);
|
);
|
||||||
this.createMockPaymentRecords(
|
this.paymentRecords = [];
|
||||||
row.formalSettlementNo,
|
|
||||||
this.referencePaymentAmount(settlementAmount)
|
|
||||||
);
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.loadReceiptAccountOptions(),
|
this.loadReceiptAccountOptions(),
|
||||||
this.loadAttachmentRuleData(detail?.id ? [detail] : []),
|
this.loadAttachmentRuleData(detail?.id ? [detail] : []),
|
||||||
@@ -2019,10 +2146,6 @@ export default {
|
|||||||
});
|
});
|
||||||
this.referenceVisible = false;
|
this.referenceVisible = false;
|
||||||
this.refreshReferenceValidation();
|
this.refreshReferenceValidation();
|
||||||
this.createMockPaymentRecords(
|
|
||||||
row.preSettlementNo,
|
|
||||||
this.referencePaymentAmount(settlementAmount)
|
|
||||||
);
|
|
||||||
const detail = this.unwrapData(detailResponse);
|
const detail = this.unwrapData(detailResponse);
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.loadReceiptAccountOptions(),
|
this.loadReceiptAccountOptions(),
|
||||||
@@ -2190,7 +2313,7 @@ export default {
|
|||||||
this.$message.warning('请选择所属项目');
|
this.$message.warning('请选择所属项目');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (validateMaterials && !this.validateRequiredAttachments()) return false;
|
if (validateMaterials) this.notifyMissingAttachments();
|
||||||
try {
|
try {
|
||||||
this.validateInvoices();
|
this.validateInvoices();
|
||||||
this.validatePaymentRecords();
|
this.validatePaymentRecords();
|
||||||
|
|||||||
@@ -0,0 +1,447 @@
|
|||||||
|
<template>
|
||||||
|
<basic-container class="receipt-claim-batch-form-page">
|
||||||
|
<div class="archive-page-form__title">批量认领收款流水</div>
|
||||||
|
<el-form ref="formRef" :model="form" class="receipt-claim-batch-form-page__form">
|
||||||
|
<section-card title="收款信息">
|
||||||
|
<el-table v-loading="loading" :data="flows" border>
|
||||||
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
|
<el-table-column
|
||||||
|
prop="receiptNoticeNo"
|
||||||
|
label="认领通知单"
|
||||||
|
min-width="160"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column prop="payerName" label="付款人" min-width="150" show-overflow-tooltip />
|
||||||
|
<el-table-column label="收款金额" min-width="130" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.receiptAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="transactionTime" label="交易时间" min-width="170" />
|
||||||
|
<el-table-column
|
||||||
|
prop="counterpartyName"
|
||||||
|
label="对方户名"
|
||||||
|
min-width="150"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="counterpartyBank"
|
||||||
|
label="对方开户行"
|
||||||
|
min-width="170"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="counterpartyAccount"
|
||||||
|
label="对方账号"
|
||||||
|
min-width="180"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
prop="detailSerialNo"
|
||||||
|
label="明细流水号"
|
||||||
|
min-width="180"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column label="已认领金额" min-width="130" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.claimedAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="summary" label="摘要" min-width="180" show-overflow-tooltip />
|
||||||
|
</el-table>
|
||||||
|
<el-row :gutter="32" class="receipt-claim-batch-form-page__claim-info">
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item label="认领人">
|
||||||
|
<el-input v-model="claimerName" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item label="认领日期">
|
||||||
|
<el-input v-model="claimDate" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item label="认领人部门">
|
||||||
|
<el-input v-model="claimerDeptName" disabled />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</section-card>
|
||||||
|
|
||||||
|
<section-card title="结算信息">
|
||||||
|
<div class="receipt-claim-batch-form-page__settlement-toolbar">
|
||||||
|
<el-button type="primary" @click="openSettlementDialog">选择结算单</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table
|
||||||
|
:data="allocationRows"
|
||||||
|
border
|
||||||
|
show-summary
|
||||||
|
:summary-method="summaryMethod"
|
||||||
|
:span-method="settlementSpanMethod"
|
||||||
|
empty-text="请先选择应收正式结算单"
|
||||||
|
>
|
||||||
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
|
<el-table-column label="结算单号" min-width="180">
|
||||||
|
<template #default="{ row }">{{ row.settlement.formalSettlementNo || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="结算总金额" min-width="140" align="right">
|
||||||
|
<template #default="{ row }">{{
|
||||||
|
formatMoney(row.settlement.settlementAmount)
|
||||||
|
}}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="已认领收款金额" min-width="150" align="right">
|
||||||
|
<template #default="{ row }">{{
|
||||||
|
formatMoney(row.settlement.claimedReceiptAmount)
|
||||||
|
}}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="认领通知单" min-width="180">
|
||||||
|
<template #default="{ row }">{{ row.flow.receiptNoticeNo || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="收款金额" min-width="140" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.flow.receiptAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="分摊收款金额" min-width="180" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-input-number
|
||||||
|
v-model="row.settlement.allocations[row.flow.id]"
|
||||||
|
:min="0"
|
||||||
|
:max="maxAllocation(row)"
|
||||||
|
:precision="2"
|
||||||
|
:controls="false"
|
||||||
|
@change="handleAllocationChange(row.settlement)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</section-card>
|
||||||
|
|
||||||
|
<section-card title="附件信息">
|
||||||
|
<vehicle-attachment-table v-model="form.attachments" />
|
||||||
|
</section-card>
|
||||||
|
|
||||||
|
<section-card title="备注">
|
||||||
|
<el-form-item class="receipt-claim-batch-form-page__remark">
|
||||||
|
<el-input
|
||||||
|
v-model="form.remark"
|
||||||
|
type="textarea"
|
||||||
|
:rows="2"
|
||||||
|
maxlength="200"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</section-card>
|
||||||
|
|
||||||
|
<div class="receipt-claim-batch-form-page__actions">
|
||||||
|
<el-button :disabled="submitting" @click="goBack">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="submitting" @click="submitClaim">确认</el-button>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-model="settlementDialog.visible"
|
||||||
|
title="选择应收正式结算单"
|
||||||
|
width="86%"
|
||||||
|
append-to-body
|
||||||
|
>
|
||||||
|
<div class="receipt-claim-batch-form-page__dialog-search">
|
||||||
|
<el-input
|
||||||
|
v-model="settlementDialog.keyword"
|
||||||
|
clearable
|
||||||
|
placeholder="结算单号、项目或合同"
|
||||||
|
@keyup.enter="loadSettlementCandidates"
|
||||||
|
/>
|
||||||
|
<el-button type="primary" @click="loadSettlementCandidates">查询</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table v-loading="settlementDialog.loading" :data="settlementDialog.rows" border>
|
||||||
|
<el-table-column prop="formalSettlementNo" label="结算单号" min-width="170" />
|
||||||
|
<el-table-column prop="projectName" label="所属项目" min-width="150" />
|
||||||
|
<el-table-column prop="deptName" label="所属组织" min-width="150" />
|
||||||
|
<el-table-column prop="payerName" label="付款方" min-width="150" />
|
||||||
|
<el-table-column prop="payeeName" label="收款方" min-width="150" />
|
||||||
|
<el-table-column label="结算总金额" min-width="140" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.settlementAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="已认领收款金额" min-width="150" align="right">
|
||||||
|
<template #default="{ row }">{{ formatMoney(row.claimedReceiptAmount) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="100" fixed="right" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-link type="primary" @click="selectSettlement(row)">选择</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="settlementDialog.visible = false">取消</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</basic-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as api from '@/api/payment/receiptFlow';
|
||||||
|
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||||
|
|
||||||
|
const emptyForm = () => ({ attachments: [], remark: '' });
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ReceiptFlowBatchForm',
|
||||||
|
components: { VehicleAttachmentTable },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: emptyForm(),
|
||||||
|
flows: [],
|
||||||
|
settlements: [],
|
||||||
|
loading: false,
|
||||||
|
submitting: false,
|
||||||
|
claimerName: '',
|
||||||
|
claimerDeptName: '',
|
||||||
|
claimDate: '',
|
||||||
|
settlementDialog: { visible: false, loading: false, keyword: '', rows: [], selection: [] },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
const userInfo = this.$store.getters.userInfo || {};
|
||||||
|
this.claimerName = userInfo.realName || userInfo.userName || '';
|
||||||
|
this.claimerDeptName = userInfo.deptName || userInfo.dept_name || '';
|
||||||
|
this.claimDate = this.$dayjs().format('YYYY-MM-DD');
|
||||||
|
this.initialize();
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
allocationRows() {
|
||||||
|
return this.settlements.flatMap(settlement => this.flows.map(flow => ({ settlement, flow })));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
unwrapData(response) {
|
||||||
|
const body = response?.data || response || {};
|
||||||
|
return body?.data || body;
|
||||||
|
},
|
||||||
|
async initialize() {
|
||||||
|
const ids = [
|
||||||
|
...new Set(
|
||||||
|
String(this.$route.query.ids || '')
|
||||||
|
.split(',')
|
||||||
|
.filter(Boolean)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
if (!ids.length) {
|
||||||
|
this.$message.error('缺少批量认领流水');
|
||||||
|
this.goBack();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const details = await Promise.all(ids.map(id => api.getDetail(id)));
|
||||||
|
this.flows = details.map(item => this.unwrapData(item)).filter(Boolean);
|
||||||
|
if (!this.flows.length) throw new Error('没有可认领的收款流水');
|
||||||
|
const first = this.flows[0];
|
||||||
|
const sameParty = this.flows.every(
|
||||||
|
row =>
|
||||||
|
String(row.payerName || '').trim() === String(first.payerName || '').trim() &&
|
||||||
|
String(row.counterpartyAccount || '').trim() ===
|
||||||
|
String(first.counterpartyAccount || '').trim()
|
||||||
|
);
|
||||||
|
if (!sameParty) throw new Error('批量认领所选流水的付款人、对方账号必须一致');
|
||||||
|
} catch (error) {
|
||||||
|
this.$message.error(error?.message || '批量认领流水加载失败');
|
||||||
|
this.goBack();
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async openSettlementDialog() {
|
||||||
|
this.settlementDialog.visible = true;
|
||||||
|
await this.loadSettlementCandidates();
|
||||||
|
},
|
||||||
|
async loadSettlementCandidates() {
|
||||||
|
if (!this.flows.length) return;
|
||||||
|
this.settlementDialog.loading = true;
|
||||||
|
try {
|
||||||
|
this.settlementDialog.rows =
|
||||||
|
this.unwrapData(
|
||||||
|
await api.getSettlementCandidates(this.settlementDialog.keyword, this.flows[0].id)
|
||||||
|
) || [];
|
||||||
|
} finally {
|
||||||
|
this.settlementDialog.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectSettlement(row) {
|
||||||
|
this.settlementDialog.selection = [row];
|
||||||
|
this.confirmSettlements();
|
||||||
|
},
|
||||||
|
confirmSettlements() {
|
||||||
|
const rows = this.settlementDialog.selection;
|
||||||
|
if (!rows.length) {
|
||||||
|
this.$message.warning('请至少选择一张应收正式结算单');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const item = rows[0];
|
||||||
|
this.settlements = [
|
||||||
|
{
|
||||||
|
...item,
|
||||||
|
allocations: this.flows.reduce((result, flow) => {
|
||||||
|
result[flow.id] = 0;
|
||||||
|
return result;
|
||||||
|
}, {}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
this.settlementDialog.visible = false;
|
||||||
|
},
|
||||||
|
settlementRemaining(row) {
|
||||||
|
return Math.max(Number(row.settlementAmount || 0) - Number(row.claimedReceiptAmount || 0), 0);
|
||||||
|
},
|
||||||
|
settlementAllocatedTotal(row) {
|
||||||
|
return this.flows.reduce((total, flow) => total + Number(row.allocations?.[flow.id] || 0), 0);
|
||||||
|
},
|
||||||
|
flowAllocatedTotal(flowId) {
|
||||||
|
return this.settlements.reduce(
|
||||||
|
(total, row) => total + Number(row.allocations?.[flowId] || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
},
|
||||||
|
maxAllocation(row) {
|
||||||
|
const current = Number(row.settlement.allocations?.[row.flow.id] || 0);
|
||||||
|
const otherTotal = this.settlementAllocatedTotal(row.settlement) - current;
|
||||||
|
return Math.max(this.settlementRemaining(row.settlement) - otherTotal, 0);
|
||||||
|
},
|
||||||
|
handleAllocationChange(settlement) {
|
||||||
|
const total = this.settlementAllocatedTotal(settlement);
|
||||||
|
if (total > this.settlementRemaining(settlement) + 0.001) {
|
||||||
|
this.$message.warning(`结算单${settlement.formalSettlementNo}的分摊金额不能超过可认领金额`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
summaryMethod({ columns }) {
|
||||||
|
return columns.map((column, index) => {
|
||||||
|
if (index === 0) return '合计';
|
||||||
|
if (index === 6) return this.formatMoney(this.allocatedTotal());
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
settlementSpanMethod({ rowIndex, columnIndex }) {
|
||||||
|
if (![1, 2, 3].includes(columnIndex)) return [1, 1];
|
||||||
|
if (!this.flows.length) return [1, 1];
|
||||||
|
if (rowIndex % this.flows.length === 0) return [this.flows.length, 1];
|
||||||
|
return [0, 0];
|
||||||
|
},
|
||||||
|
allocatedTotal() {
|
||||||
|
return this.settlements.reduce((total, row) => total + this.settlementAllocatedTotal(row), 0);
|
||||||
|
},
|
||||||
|
async submitClaim() {
|
||||||
|
if (!this.settlements.length) {
|
||||||
|
this.$message.warning('请至少选择一张应收正式结算单');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const row of this.settlements) {
|
||||||
|
if (this.settlementAllocatedTotal(row) > this.settlementRemaining(row) + 0.001) {
|
||||||
|
this.$message.warning(`结算单${row.formalSettlementNo}的累计认领金额不能超过结算总金额`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const flow of this.flows) {
|
||||||
|
const total = this.flowAllocatedTotal(flow.id);
|
||||||
|
const remaining = Math.max(
|
||||||
|
Number(flow.receiptAmount || 0) - Number(flow.claimedAmount || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
if (total > remaining + 0.001) {
|
||||||
|
this.$message.warning(`流水${flow.receiptNoticeNo}的分摊金额不能超过剩余可认领金额`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const requests = this.flows
|
||||||
|
.map(flow => ({
|
||||||
|
flow,
|
||||||
|
settlements: this.settlements
|
||||||
|
.map(row => ({
|
||||||
|
settlementId: row.id,
|
||||||
|
allocatedReceiptAmount: Number(row.allocations?.[flow.id] || 0),
|
||||||
|
}))
|
||||||
|
.filter(row => row.allocatedReceiptAmount > 0),
|
||||||
|
}))
|
||||||
|
.filter(item => item.settlements.length);
|
||||||
|
if (!requests.length) {
|
||||||
|
this.$message.warning('没有需要入库的分摊收款金额');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.submitting = true;
|
||||||
|
try {
|
||||||
|
for (const item of requests) {
|
||||||
|
await api.claim({
|
||||||
|
flowId: item.flow.id,
|
||||||
|
attachmentsJson: JSON.stringify(this.form.attachments || []),
|
||||||
|
remark: this.form.remark,
|
||||||
|
settlements: item.settlements,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.$message.success(`批量认领成功,共${requests.length}条流水`);
|
||||||
|
this.goBack();
|
||||||
|
} finally {
|
||||||
|
this.submitting = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
goBack() {
|
||||||
|
this.$router.push('/payment/receipt-flow');
|
||||||
|
},
|
||||||
|
formatMoney(value) {
|
||||||
|
return Number(value || 0).toFixed(2);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.receipt-claim-batch-form-page__form {
|
||||||
|
padding-bottom: 72px;
|
||||||
|
}
|
||||||
|
.receipt-claim-batch-form-page__claim-info {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
.receipt-claim-batch-form-page__form :deep(.el-input-number) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.receipt-claim-batch-form-page__settlement-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
.receipt-claim-batch-form-page__notice-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px 8px;
|
||||||
|
}
|
||||||
|
.receipt-claim-batch-form-page__actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px 24px;
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
left: 230px;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 10;
|
||||||
|
background: #fff;
|
||||||
|
border-top: 1px solid #eff1f7;
|
||||||
|
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.receipt-claim-batch-form-page__remark :deep(.el-form-item__content) {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
}
|
||||||
|
.receipt-claim-batch-form-page__dialog-search {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.receipt-claim-batch-form-page__dialog-search .el-input {
|
||||||
|
width: 360px;
|
||||||
|
}
|
||||||
|
.receipt-claim-batch-form-page :deep(.el-table) {
|
||||||
|
--el-table-border-color: #eff1f7;
|
||||||
|
}
|
||||||
|
.receipt-claim-batch-form-page :deep(.el-table__row:nth-child(even) > td.el-table__cell) {
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.receipt-claim-batch-form-page__actions {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<basic-container class="receipt-claim-form-page">
|
<receipt-flow-batch-form v-if="batchMode" />
|
||||||
|
<basic-container v-else class="receipt-claim-form-page">
|
||||||
<el-form
|
<el-form
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
:model="form"
|
:model="form"
|
||||||
@@ -182,6 +183,7 @@
|
|||||||
import { Search } from '@element-plus/icons-vue';
|
import { Search } from '@element-plus/icons-vue';
|
||||||
import * as api from '@/api/payment/receiptFlow';
|
import * as api from '@/api/payment/receiptFlow';
|
||||||
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue';
|
||||||
|
import ReceiptFlowBatchForm from './receipt-flow-batch-form.vue';
|
||||||
|
|
||||||
const emptyForm = () => ({
|
const emptyForm = () => ({
|
||||||
id: null,
|
id: null,
|
||||||
@@ -206,7 +208,7 @@ const emptyForm = () => ({
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ReceiptFlowForm',
|
name: 'ReceiptFlowForm',
|
||||||
components: { VehicleAttachmentTable },
|
components: { VehicleAttachmentTable, ReceiptFlowBatchForm },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
Search,
|
Search,
|
||||||
@@ -222,6 +224,9 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
batchMode() {
|
||||||
|
return String(this.$route.query.batch || '') === '1';
|
||||||
|
},
|
||||||
flowId() {
|
flowId() {
|
||||||
return this.$route.query.id || '';
|
return this.$route.query.id || '';
|
||||||
},
|
},
|
||||||
@@ -239,7 +244,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.initialize();
|
if (!this.batchMode) this.initialize();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
unwrapData(response) {
|
unwrapData(response) {
|
||||||
|
|||||||
@@ -60,8 +60,22 @@
|
|||||||
>
|
>
|
||||||
手动同步流水
|
手动同步流水
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="hasPermission('receipt_flow_claim')"
|
||||||
|
type="primary"
|
||||||
|
:disabled="!selectedRows.length"
|
||||||
|
@click="openBatchClaim"
|
||||||
|
>
|
||||||
|
批量认领
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<el-table v-loading="loading" :data="rows" border>
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="rows"
|
||||||
|
border
|
||||||
|
@selection-change="selectedRows = $event"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="52" align="center" />
|
||||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-for="column in columns"
|
v-for="column in columns"
|
||||||
@@ -256,6 +270,7 @@ export default {
|
|||||||
claimStatusOptions: api.claimStatusOptions,
|
claimStatusOptions: api.claimStatusOptions,
|
||||||
columns: receiptFlowTableColumns,
|
columns: receiptFlowTableColumns,
|
||||||
rows: [],
|
rows: [],
|
||||||
|
selectedRows: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
syncing: false,
|
syncing: false,
|
||||||
searchExpanded: false,
|
searchExpanded: false,
|
||||||
@@ -293,6 +308,7 @@ export default {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
this.rows = data.records || [];
|
this.rows = data.records || [];
|
||||||
|
this.selectedRows = [];
|
||||||
this.page.total = Number(data.total || 0);
|
this.page.total = Number(data.total || 0);
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
@@ -342,6 +358,32 @@ export default {
|
|||||||
query: { mode: 'add', id: row.id },
|
query: { mode: 'add', id: row.id },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
openBatchClaim() {
|
||||||
|
if (!this.selectedRows.length) {
|
||||||
|
this.$message.warning('请先勾选收款流水');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const first = this.selectedRows[0];
|
||||||
|
const payerName = String(first.payerName || '').trim();
|
||||||
|
const counterpartyAccount = String(first.counterpartyAccount || '').trim();
|
||||||
|
const sameParty = this.selectedRows.every(
|
||||||
|
row =>
|
||||||
|
String(row.payerName || '').trim() === payerName &&
|
||||||
|
String(row.counterpartyAccount || '').trim() === counterpartyAccount
|
||||||
|
);
|
||||||
|
if (!sameParty) {
|
||||||
|
this.$message.warning('批量认领所选流水的付款人、对方账号必须一致');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$router.push({
|
||||||
|
path: '/payment/receipt-flow/form',
|
||||||
|
query: {
|
||||||
|
mode: 'add',
|
||||||
|
batch: '1',
|
||||||
|
ids: this.selectedRows.map(row => row.id).join(','),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
hasPermission(code) {
|
hasPermission(code) {
|
||||||
return this.isAdmin || this.validData(this.permission?.[code], false);
|
return this.isAdmin || this.validData(this.permission?.[code], false);
|
||||||
},
|
},
|
||||||
@@ -398,6 +440,7 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
padding: 12px 0;
|
padding: 12px 0;
|
||||||
}
|
}
|
||||||
.receipt-flow-page__pagination {
|
.receipt-flow-page__pagination {
|
||||||
|
|||||||
@@ -400,7 +400,7 @@
|
|||||||
<div class="formal-editor__links">
|
<div class="formal-editor__links">
|
||||||
<el-link type="primary" @click="viewInvoiceAttachment(row)">查看</el-link>
|
<el-link type="primary" @click="viewInvoiceAttachment(row)">查看</el-link>
|
||||||
<el-link type="primary" @click="downloadInvoiceAttachment(row)">下载</el-link>
|
<el-link type="primary" @click="downloadInvoiceAttachment(row)">下载</el-link>
|
||||||
<el-link type="danger" @click="removeInvoice($index)">删除</el-link>
|
<el-link v-if="editable" type="danger" @click="removeInvoice($index)">删除</el-link>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -456,6 +456,9 @@
|
|||||||
<el-table-column prop="paymentTypeName" label="付款类型" min-width="120" align="center">
|
<el-table-column prop="paymentTypeName" label="付款类型" min-width="120" align="center">
|
||||||
<template #default="{ row }">{{ displayValue(row.paymentTypeName) }}</template>
|
<template #default="{ row }">{{ displayValue(row.paymentTypeName) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="付款方式" min-width="140" align="center">
|
||||||
|
<template #default="{ row }">{{ paymentMethodName(row.paymentMethod) }}</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="paymentNo" label="单据号" min-width="160" align="center" />
|
<el-table-column prop="paymentNo" label="单据号" min-width="160" align="center" />
|
||||||
<el-table-column prop="appliedAmount" label="申请付款金额" min-width="145" align="center">
|
<el-table-column prop="appliedAmount" label="申请付款金额" min-width="145" align="center">
|
||||||
<template #default="{ row }">{{ formatMoney(row.appliedAmount) }}</template>
|
<template #default="{ row }">{{ formatMoney(row.appliedAmount) }}</template>
|
||||||
@@ -1784,6 +1787,14 @@ export default {
|
|||||||
displayValue(value) {
|
displayValue(value) {
|
||||||
return value === null || value === undefined || value === '' ? '-' : value;
|
return value === null || value === undefined || value === '' ? '-' : value;
|
||||||
},
|
},
|
||||||
|
paymentMethodName(value) {
|
||||||
|
const labels = {
|
||||||
|
bank_transfer: '银行转账',
|
||||||
|
bank_draft: '银行承兑汇票',
|
||||||
|
commercial_draft: '商业承兑汇票',
|
||||||
|
};
|
||||||
|
return this.displayValue(labels[value] || value);
|
||||||
|
},
|
||||||
unwrapData(response) {
|
unwrapData(response) {
|
||||||
const body = response?.data || response || {};
|
const body = response?.data || response || {};
|
||||||
return body?.data || body;
|
return body?.data || body;
|
||||||
|
|||||||
@@ -18,25 +18,22 @@
|
|||||||
<el-row :gutter="24">
|
<el-row :gutter="24">
|
||||||
<el-col v-for="field in formFields" :key="field.prop" :span="field.span || 6">
|
<el-col v-for="field in formFields" :key="field.prop" :span="field.span || 6">
|
||||||
<el-form-item :label="field.label" :prop="field.prop">
|
<el-form-item :label="field.label" :prop="field.prop">
|
||||||
<el-select
|
<el-input
|
||||||
v-if="field.type === 'contract'"
|
v-if="field.type === 'contract'"
|
||||||
v-model="form.contractId"
|
v-model="form.contractName"
|
||||||
filterable
|
readonly
|
||||||
remote
|
|
||||||
clearable
|
|
||||||
:remote-method="loadContractOptions"
|
|
||||||
:loading="contractLoading"
|
|
||||||
:disabled="!editable || details.length > 0"
|
:disabled="!editable || details.length > 0"
|
||||||
placeholder="请选择合同"
|
placeholder="请选择合同"
|
||||||
@change="handleContractChange"
|
|
||||||
>
|
>
|
||||||
<el-option
|
<template #append>
|
||||||
v-for="item in contractOptions"
|
<el-button
|
||||||
:key="item.id"
|
:disabled="!editable || details.length > 0"
|
||||||
:label="`${item.contractName}(${item.contractNo || '-'})`"
|
@click="openContractDialog"
|
||||||
:value="item.id"
|
>
|
||||||
/>
|
选择
|
||||||
</el-select>
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-else-if="field.type === 'date'"
|
v-else-if="field.type === 'date'"
|
||||||
v-model="form[field.prop]"
|
v-model="form[field.prop]"
|
||||||
@@ -124,8 +121,13 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-else-if="editable && column.prop === 'adjustAmount'"
|
v-else-if="
|
||||||
|
editable && column.prop === 'adjustAmount' && Number(row.manualFlag) === 1
|
||||||
|
"
|
||||||
v-model="row.adjustAmount"
|
v-model="row.adjustAmount"
|
||||||
|
:class="{
|
||||||
|
'pre-settlement-editor__negative-amount': Number(row.adjustAmount || 0) < 0,
|
||||||
|
}"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
:controls="false"
|
:controls="false"
|
||||||
@change="recalculateSummaryRow(row)"
|
@change="recalculateSummaryRow(row)"
|
||||||
@@ -136,7 +138,13 @@
|
|||||||
maxlength="50"
|
maxlength="50"
|
||||||
show-word-limit
|
show-word-limit
|
||||||
/>
|
/>
|
||||||
<span v-else-if="isSummaryMoney(column.prop)">
|
<span
|
||||||
|
v-else-if="isSummaryMoney(column.prop)"
|
||||||
|
:class="{
|
||||||
|
'pre-settlement-editor__negative-amount':
|
||||||
|
column.prop === 'adjustAmount' && Number(row.adjustAmount || 0) < 0,
|
||||||
|
}"
|
||||||
|
>
|
||||||
{{ formatMoney(row[column.prop], form.currency) }}
|
{{ formatMoney(row[column.prop], form.currency) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="column.prop === 'feeType'">
|
<span v-else-if="column.prop === 'feeType'">
|
||||||
@@ -393,7 +401,9 @@
|
|||||||
|
|
||||||
<template v-if="!pageMode" #footer>
|
<template v-if="!pageMode" #footer>
|
||||||
<el-button @click="visible = false">取消</el-button>
|
<el-button @click="visible = false">取消</el-button>
|
||||||
<el-button v-if="editable" type="primary" plain :loading="saving" @click="saveDraft(false)">保存</el-button>
|
<el-button v-if="editable" type="primary" plain :loading="saving" @click="saveDraft(false)"
|
||||||
|
>保存</el-button
|
||||||
|
>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="editable && hasPermission('pre_settlement_submit')"
|
v-if="editable && hasPermission('pre_settlement_submit')"
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -405,7 +415,9 @@
|
|||||||
</template>
|
</template>
|
||||||
<div v-if="pageMode" class="pre-settlement-editor__page-actions">
|
<div v-if="pageMode" class="pre-settlement-editor__page-actions">
|
||||||
<el-button @click="visible = false">取消</el-button>
|
<el-button @click="visible = false">取消</el-button>
|
||||||
<el-button v-if="editable" type="primary" plain :loading="saving" @click="saveDraft(false)">保存</el-button>
|
<el-button v-if="editable" type="primary" plain :loading="saving" @click="saveDraft(false)"
|
||||||
|
>保存</el-button
|
||||||
|
>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="editable && hasPermission('pre_settlement_submit')"
|
v-if="editable && hasPermission('pre_settlement_submit')"
|
||||||
type="primary"
|
type="primary"
|
||||||
@@ -492,6 +504,164 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-model="contractDialog.visible"
|
||||||
|
title="选择合同"
|
||||||
|
width="92%"
|
||||||
|
append-to-body
|
||||||
|
destroy-on-close
|
||||||
|
class="pre-settlement-contract-dialog"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
:model="contractDialog.query"
|
||||||
|
inline
|
||||||
|
label-position="right"
|
||||||
|
label-width="auto"
|
||||||
|
class="pre-settlement-editor__contract-filter"
|
||||||
|
@submit.prevent
|
||||||
|
>
|
||||||
|
<el-form-item label="合同编号">
|
||||||
|
<el-input
|
||||||
|
v-model="contractDialog.query.contractNo"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入"
|
||||||
|
@keyup.enter="loadContractRows"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="合同名称">
|
||||||
|
<el-input
|
||||||
|
v-model="contractDialog.query.contractName"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入"
|
||||||
|
@keyup.enter="loadContractRows"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="所属项目">
|
||||||
|
<el-input
|
||||||
|
v-model="contractDialog.query.projectName"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入"
|
||||||
|
@keyup.enter="loadContractRows"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="所属组织">
|
||||||
|
<el-input
|
||||||
|
v-model="contractDialog.query.organizationName"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入"
|
||||||
|
@keyup.enter="loadContractRows"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="合同类别">
|
||||||
|
<el-select v-model="contractDialog.query.contractCategory" clearable placeholder="请选择">
|
||||||
|
<el-option
|
||||||
|
v-for="item in contractCategoryOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="签约类型">
|
||||||
|
<el-select v-model="contractDialog.query.signType" clearable placeholder="请选择">
|
||||||
|
<el-option
|
||||||
|
v-for="item in signTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="生效类型">
|
||||||
|
<el-select v-model="contractDialog.query.effectiveType" clearable placeholder="请选择">
|
||||||
|
<el-option
|
||||||
|
v-for="item in effectiveTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="合同阶段">
|
||||||
|
<el-select v-model="contractDialog.query.contractStage" clearable placeholder="请选择">
|
||||||
|
<el-option
|
||||||
|
v-for="item in contractStageOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="contractDialog.expanded" label="审核状态">
|
||||||
|
<el-select v-model="contractDialog.query.approvalStatus" clearable placeholder="请选择">
|
||||||
|
<el-option
|
||||||
|
v-for="item in contractApprovalStatusOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item class="pre-settlement-editor__contract-filter-actions">
|
||||||
|
<el-button type="primary" @click="searchContractRows">查询</el-button>
|
||||||
|
<el-button @click="resetContractQuery">重置</el-button>
|
||||||
|
<el-button text @click="contractDialog.expanded = !contractDialog.expanded">
|
||||||
|
{{ contractDialog.expanded ? '收起' : '展开' }}
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-table
|
||||||
|
v-loading="contractDialog.loading"
|
||||||
|
:data="contractDialog.rows"
|
||||||
|
border
|
||||||
|
highlight-current-row
|
||||||
|
@current-change="contractDialog.current = $event"
|
||||||
|
@row-dblclick="selectContract"
|
||||||
|
>
|
||||||
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
|
<el-table-column
|
||||||
|
v-for="column in contractSelectionColumns"
|
||||||
|
:key="column.prop"
|
||||||
|
:prop="column.prop"
|
||||||
|
:label="column.label"
|
||||||
|
:min-width="column.minWidth"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="column.prop === 'organizationName'">{{
|
||||||
|
row.organizationName || row.deptName || '-'
|
||||||
|
}}</span>
|
||||||
|
<span v-else-if="column.prop === 'contractCategory'">{{
|
||||||
|
contractCategoryName(row.contractCategory)
|
||||||
|
}}</span>
|
||||||
|
<span v-else-if="column.prop === 'signType'">{{ signTypeName(row.signType) }}</span>
|
||||||
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="90" fixed="right" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-link type="primary" @click.stop="selectContract(row)">选择</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<div class="pre-settlement-editor__pagination">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="contractDialog.page.current"
|
||||||
|
v-model:page-size="contractDialog.page.size"
|
||||||
|
:total="contractDialog.page.total"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@current-change="loadContractRows"
|
||||||
|
@size-change="handleContractSizeChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="contractDialog.visible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="selectContract(contractDialog.current)">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="adjustDialog.visible"
|
v-model="adjustDialog.visible"
|
||||||
title="结算明细调整"
|
title="结算明细调整"
|
||||||
@@ -503,6 +673,20 @@
|
|||||||
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
|
||||||
<el-table-column prop="cargoName" label="货物名称" min-width="140" align="center" />
|
<el-table-column prop="cargoName" label="货物名称" min-width="140" align="center" />
|
||||||
<el-table-column prop="cargoType" label="货物类型" min-width="130" align="center" />
|
<el-table-column prop="cargoType" label="货物类型" min-width="130" align="center" />
|
||||||
|
<el-table-column label="计费规则" min-width="190" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="pre-settlement-editor__billing-rule-cell">
|
||||||
|
<span>{{ displayValue(billingRuleName(row)) }}</span>
|
||||||
|
<el-icon
|
||||||
|
class="pre-settlement-editor__billing-rule-icon"
|
||||||
|
title="查看计费规则详情"
|
||||||
|
@click="openBillingRuleInfo(row)"
|
||||||
|
>
|
||||||
|
<InfoFilled />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="运输总量" min-width="150" align="center">
|
<el-table-column label="运输总量" min-width="150" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span v-if="adjustDialog.readonly">{{ formatQuantity(row) }}</span>
|
<span v-if="adjustDialog.readonly">{{ formatQuantity(row) }}</span>
|
||||||
@@ -537,18 +721,6 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="运费" min-width="140" align="center">
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-input-number
|
|
||||||
v-model="row.freightAmount"
|
|
||||||
:min="0"
|
|
||||||
:precision="2"
|
|
||||||
:controls="false"
|
|
||||||
:disabled="adjustDialog.readonly"
|
|
||||||
@change="recalculateAdjustRow(row, 'freight')"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-for="feeItem in adjustFeeItemNames"
|
v-for="feeItem in adjustFeeItemNames"
|
||||||
:key="feeItem"
|
:key="feeItem"
|
||||||
@@ -606,14 +778,80 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-model="billingRuleDialog.visible"
|
||||||
|
title="计费规则详情"
|
||||||
|
width="720px"
|
||||||
|
append-to-body
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<template v-if="billingRuleDialog.rule">
|
||||||
|
<div class="dialog-section-title">计费规则</div>
|
||||||
|
<el-descriptions :column="2" border class="pre-settlement-editor__billing-rule-detail">
|
||||||
|
<el-descriptions-item label="规则名称">
|
||||||
|
{{ displayValue(billingRuleDialog.rule.name) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="费用类型">
|
||||||
|
{{ displayValue(billingRuleDialog.rule.feeType) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="费用项">
|
||||||
|
{{ displayValue(billingRuleDialog.rule.feeItem) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="计费要素">
|
||||||
|
{{ displayValue(billingRuleDialog.rule.billingElement) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="计费类型">
|
||||||
|
{{ displayValue(billingRuleDialog.rule.billingType) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="计费单位">
|
||||||
|
{{ displayValue(billingRuleDialog.rule.billingUnit) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="单价">
|
||||||
|
{{ displayValue(billingRuleDialog.rule.unitPrice) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="最低计费量">
|
||||||
|
{{ displayValue(billingRuleDialog.rule.minimumBillingWeight) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="计费区间">
|
||||||
|
{{ billingRuleRanges(billingRuleDialog.rule) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="备注" :span="2">
|
||||||
|
{{ displayValue(billingRuleDialog.rule.remark) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
<div class="dialog-section-title pre-settlement-editor__billing-rule-match-title">
|
||||||
|
匹配条件
|
||||||
|
</div>
|
||||||
|
<el-descriptions :column="2" border class="pre-settlement-editor__billing-rule-detail">
|
||||||
|
<el-descriptions-item label="起运地">
|
||||||
|
{{ displayValue(billingRuleDialog.rule.matchCondition.origin) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="目的地">
|
||||||
|
{{ displayValue(billingRuleDialog.rule.matchCondition.destination) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="运输方式">
|
||||||
|
{{ displayValue(billingRuleDialog.rule.matchCondition.transportMode) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="货物类型">
|
||||||
|
{{ displayValue(billingRuleDialog.rule.matchCondition.cargoType) }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</template>
|
||||||
|
<el-empty v-else description="暂无计费规则详情" />
|
||||||
|
<template #footer>
|
||||||
|
<el-button type="primary" @click="billingRuleDialog.visible = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
import { InfoFilled } from '@element-plus/icons-vue';
|
||||||
import {
|
import {
|
||||||
adjustDetail,
|
adjustDetail,
|
||||||
getCandidateDetails,
|
getCandidateDetails,
|
||||||
getContractOptions,
|
getContractList,
|
||||||
getDetail,
|
getDetail,
|
||||||
getDetailFees,
|
getDetailFees,
|
||||||
getFeeOptions,
|
getFeeOptions,
|
||||||
@@ -627,6 +865,13 @@ import {
|
|||||||
preSettlementFormFields,
|
preSettlementFormFields,
|
||||||
preSettlementFormRules,
|
preSettlementFormRules,
|
||||||
} from '@/option/settlement/preSettlementForm';
|
} from '@/option/settlement/preSettlementForm';
|
||||||
|
import {
|
||||||
|
contractApprovalStatusOptions,
|
||||||
|
contractCategoryOptions,
|
||||||
|
contractStageOptions,
|
||||||
|
effectiveTypeOptions,
|
||||||
|
signTypeOptions,
|
||||||
|
} from '@/option/business/common';
|
||||||
import {
|
import {
|
||||||
advanceColumns,
|
advanceColumns,
|
||||||
candidateDetailColumns,
|
candidateDetailColumns,
|
||||||
@@ -639,6 +884,7 @@ import * as XLSX from 'xlsx';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PreSettlementEditor',
|
name: 'PreSettlementEditor',
|
||||||
|
components: { InfoFilled },
|
||||||
props: {
|
props: {
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -660,6 +906,10 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
deferSave: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
emits: ['update:modelValue', 'success'],
|
emits: ['update:modelValue', 'success'],
|
||||||
data() {
|
data() {
|
||||||
@@ -675,8 +925,45 @@ export default {
|
|||||||
advanceTableColumns: advanceColumns,
|
advanceTableColumns: advanceColumns,
|
||||||
changeTableColumns: changeRecordColumns,
|
changeTableColumns: changeRecordColumns,
|
||||||
candidateColumns: candidateDetailColumns,
|
candidateColumns: candidateDetailColumns,
|
||||||
contractLoading: false,
|
|
||||||
contractOptions: [],
|
contractOptions: [],
|
||||||
|
contractCategoryOptions,
|
||||||
|
signTypeOptions,
|
||||||
|
effectiveTypeOptions,
|
||||||
|
contractStageOptions,
|
||||||
|
contractApprovalStatusOptions,
|
||||||
|
contractSelectionColumns: [
|
||||||
|
{ prop: 'contractNo', label: '合同编号', minWidth: 180 },
|
||||||
|
{ prop: 'contractName', label: '合同名称', minWidth: 220 },
|
||||||
|
{ prop: 'projectName', label: '所属项目', minWidth: 170 },
|
||||||
|
{ prop: 'organizationName', label: '所属组织', minWidth: 170 },
|
||||||
|
{ prop: 'contractCategory', label: '合同类别', minWidth: 130 },
|
||||||
|
{ prop: 'signType', label: '签约类型', minWidth: 120 },
|
||||||
|
{ prop: 'partyA', label: '甲方', minWidth: 170 },
|
||||||
|
{ prop: 'partyB', label: '乙方', minWidth: 170 },
|
||||||
|
{ prop: 'startDate', label: '开始日期', minWidth: 130 },
|
||||||
|
{ prop: 'endDate', label: '结束日期', minWidth: 130 },
|
||||||
|
{ prop: 'temporaryStartDate', label: '临时效力起', minWidth: 130 },
|
||||||
|
{ prop: 'temporaryEndDate', label: '临时效力止', minWidth: 130 },
|
||||||
|
],
|
||||||
|
contractDialog: {
|
||||||
|
visible: false,
|
||||||
|
loading: false,
|
||||||
|
expanded: false,
|
||||||
|
rows: [],
|
||||||
|
current: null,
|
||||||
|
query: {
|
||||||
|
contractNo: '',
|
||||||
|
contractName: '',
|
||||||
|
projectName: '',
|
||||||
|
organizationName: '',
|
||||||
|
contractCategory: '',
|
||||||
|
signType: '',
|
||||||
|
effectiveType: '',
|
||||||
|
contractStage: '',
|
||||||
|
approvalStatus: '',
|
||||||
|
},
|
||||||
|
page: { current: 1, size: 10, total: 0 },
|
||||||
|
},
|
||||||
feeOptions: [],
|
feeOptions: [],
|
||||||
feeCategoryOptions: [],
|
feeCategoryOptions: [],
|
||||||
transportTypeOptions: [],
|
transportTypeOptions: [],
|
||||||
@@ -724,6 +1011,10 @@ export default {
|
|||||||
reason: '',
|
reason: '',
|
||||||
},
|
},
|
||||||
adjustRows: [],
|
adjustRows: [],
|
||||||
|
billingRuleDialog: {
|
||||||
|
visible: false,
|
||||||
|
rule: null,
|
||||||
|
},
|
||||||
attachmentFileTypes: [
|
attachmentFileTypes: [
|
||||||
'pdf',
|
'pdf',
|
||||||
'bmp',
|
'bmp',
|
||||||
@@ -857,7 +1148,6 @@ export default {
|
|||||||
},
|
},
|
||||||
async initialize() {
|
async initialize() {
|
||||||
this.resetEditor();
|
this.resetEditor();
|
||||||
await this.loadContractOptions('');
|
|
||||||
await this.loadFeeOptions();
|
await this.loadFeeOptions();
|
||||||
await this.loadFeeCategoryOptions();
|
await this.loadFeeCategoryOptions();
|
||||||
if (this.pageMode) await this.loadTransportTypeOptions();
|
if (this.pageMode) await this.loadTransportTypeOptions();
|
||||||
@@ -935,6 +1225,15 @@ export default {
|
|||||||
this.changeRecords = [];
|
this.changeRecords = [];
|
||||||
this.attachments = [];
|
this.attachments = [];
|
||||||
this.selectedAttachmentRows = [];
|
this.selectedAttachmentRows = [];
|
||||||
|
this.adjustRows = [];
|
||||||
|
this.billingRuleDialog.visible = false;
|
||||||
|
this.billingRuleDialog.rule = null;
|
||||||
|
this.contractOptions = [];
|
||||||
|
this.contractDialog.visible = false;
|
||||||
|
this.contractDialog.expanded = false;
|
||||||
|
this.contractDialog.rows = [];
|
||||||
|
this.contractDialog.current = null;
|
||||||
|
this.contractDialog.page = { current: 1, size: 10, total: 0 };
|
||||||
this.$nextTick(() => this.$refs.formRef?.clearValidate());
|
this.$nextTick(() => this.$refs.formRef?.clearValidate());
|
||||||
},
|
},
|
||||||
async loadDetail() {
|
async loadDetail() {
|
||||||
@@ -969,15 +1268,85 @@ export default {
|
|||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async loadContractOptions(keyword = '') {
|
openContractDialog() {
|
||||||
this.contractLoading = true;
|
if (!this.editable || this.details.length > 0) return;
|
||||||
|
this.contractDialog.visible = true;
|
||||||
|
this.contractDialog.expanded = false;
|
||||||
|
this.contractDialog.current = null;
|
||||||
|
this.contractDialog.page.current = 1;
|
||||||
|
this.loadContractRows();
|
||||||
|
},
|
||||||
|
async loadContractRows() {
|
||||||
|
this.contractDialog.loading = true;
|
||||||
try {
|
try {
|
||||||
const { data } = await getContractOptions(keyword);
|
const { data } = await getContractList(
|
||||||
this.contractOptions = data?.data || [];
|
this.contractDialog.page.current,
|
||||||
|
this.contractDialog.page.size,
|
||||||
|
{ ...this.contractDialog.query }
|
||||||
|
);
|
||||||
|
const page = data?.data || {};
|
||||||
|
this.contractDialog.rows = page.records || [];
|
||||||
|
this.contractDialog.page.total = Number(page.total || 0);
|
||||||
} finally {
|
} finally {
|
||||||
this.contractLoading = false;
|
this.contractDialog.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
searchContractRows() {
|
||||||
|
this.contractDialog.page.current = 1;
|
||||||
|
this.loadContractRows();
|
||||||
|
},
|
||||||
|
resetContractQuery() {
|
||||||
|
this.contractDialog.query = {
|
||||||
|
contractNo: '',
|
||||||
|
contractName: '',
|
||||||
|
projectName: '',
|
||||||
|
organizationName: '',
|
||||||
|
contractCategory: '',
|
||||||
|
signType: '',
|
||||||
|
effectiveType: '',
|
||||||
|
contractStage: '',
|
||||||
|
approvalStatus: '',
|
||||||
|
};
|
||||||
|
this.searchContractRows();
|
||||||
|
},
|
||||||
|
handleContractSizeChange() {
|
||||||
|
this.contractDialog.page.current = 1;
|
||||||
|
this.loadContractRows();
|
||||||
|
},
|
||||||
|
selectContract(row) {
|
||||||
|
if (!row) {
|
||||||
|
this.$message.warning('请选择合同');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const contract = {
|
||||||
|
...row,
|
||||||
|
deptId: row.deptId || row.organizationId,
|
||||||
|
deptName: row.deptName || row.organizationName,
|
||||||
|
partyA: row.partyA || row.payerName,
|
||||||
|
partyB: row.partyB || row.payeeName,
|
||||||
|
settlementType:
|
||||||
|
row.settlementType || (row.contractCategory === '客户合同' ? 'receivable' : 'payable'),
|
||||||
|
};
|
||||||
|
const index = this.contractOptions.findIndex(item => String(item.id) === String(contract.id));
|
||||||
|
if (index >= 0) this.contractOptions.splice(index, 1, contract);
|
||||||
|
else this.contractOptions.push(contract);
|
||||||
|
this.handleContractChange(contract.id);
|
||||||
|
this.contractDialog.visible = false;
|
||||||
|
this.contractDialog.current = null;
|
||||||
|
this.$refs.formRef?.clearValidate('contractId');
|
||||||
|
},
|
||||||
|
contractCategoryName(value) {
|
||||||
|
return (
|
||||||
|
this.contractCategoryOptions.find(item => String(item.value) === String(value))?.label ||
|
||||||
|
this.displayValue(value)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
signTypeName(value) {
|
||||||
|
return (
|
||||||
|
this.signTypeOptions.find(item => String(item.value) === String(value))?.label ||
|
||||||
|
this.displayValue(value)
|
||||||
|
);
|
||||||
|
},
|
||||||
async loadFeeOptions() {
|
async loadFeeOptions() {
|
||||||
const { data } = await getFeeOptions();
|
const { data } = await getFeeOptions();
|
||||||
this.feeOptions = data?.data || [];
|
this.feeOptions = data?.data || [];
|
||||||
@@ -993,6 +1362,7 @@ export default {
|
|||||||
handleContractChange(id) {
|
handleContractChange(id) {
|
||||||
const contract = this.contractOptions.find(item => String(item.id) === String(id));
|
const contract = this.contractOptions.find(item => String(item.id) === String(id));
|
||||||
if (!contract) {
|
if (!contract) {
|
||||||
|
this.form.contractId = '';
|
||||||
this.form.contractNo = '';
|
this.form.contractNo = '';
|
||||||
this.form.contractName = '';
|
this.form.contractName = '';
|
||||||
this.form.projectId = '';
|
this.form.projectId = '';
|
||||||
@@ -1004,6 +1374,7 @@ export default {
|
|||||||
this.summaryFees = [];
|
this.summaryFees = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.form.contractId = contract.id;
|
||||||
this.form.contractNo = contract.contractNo;
|
this.form.contractNo = contract.contractNo;
|
||||||
this.form.contractName = contract.contractName;
|
this.form.contractName = contract.contractName;
|
||||||
this.form.projectId = contract.projectId;
|
this.form.projectId = contract.projectId;
|
||||||
@@ -1132,6 +1503,7 @@ export default {
|
|||||||
this.summaryFees.splice(index, 1);
|
this.summaryFees.splice(index, 1);
|
||||||
},
|
},
|
||||||
recalculateSummaryRow(row) {
|
recalculateSummaryRow(row) {
|
||||||
|
if (Number(row.manualFlag) !== 1) return;
|
||||||
row.settlementAmount = Number(row.originalAmount || 0) + Number(row.adjustAmount || 0);
|
row.settlementAmount = Number(row.originalAmount || 0) + Number(row.adjustAmount || 0);
|
||||||
},
|
},
|
||||||
buildSummaryFeesFromDetails() {
|
buildSummaryFeesFromDetails() {
|
||||||
@@ -1166,9 +1538,7 @@ export default {
|
|||||||
this.details.forEach(row => {
|
this.details.forEach(row => {
|
||||||
const feeItems = this.parseFeeItems(row.feeItemsJson || row.feeItems);
|
const feeItems = this.parseFeeItems(row.feeItemsJson || row.feeItems);
|
||||||
const feeItemEntries = Object.entries(feeItems);
|
const feeItemEntries = Object.entries(feeItems);
|
||||||
feeItemEntries.forEach(([feeItem, amount]) =>
|
feeItemEntries.forEach(([feeItem, amount]) => appendSummary(feeItem, amount, row.feeType));
|
||||||
appendSummary(feeItem, amount, row.feeType)
|
|
||||||
);
|
|
||||||
|
|
||||||
let knownAmount = feeItemEntries.reduce(
|
let knownAmount = feeItemEntries.reduce(
|
||||||
(total, [, amount]) => total + Number(amount || 0),
|
(total, [, amount]) => total + Number(amount || 0),
|
||||||
@@ -1186,11 +1556,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const totalAmount = Number(
|
const totalAmount = Number(
|
||||||
row.settlementAmountTax ??
|
row.settlementAmountTax ?? row.totalAmount ?? row.afterAmount ?? row.settlementAmount ?? 0
|
||||||
row.totalAmount ??
|
|
||||||
row.afterAmount ??
|
|
||||||
row.settlementAmount ??
|
|
||||||
0
|
|
||||||
);
|
);
|
||||||
const residualAmount = Number((totalAmount - knownAmount).toFixed(2));
|
const residualAmount = Number((totalAmount - knownAmount).toFixed(2));
|
||||||
if (Math.abs(residualAmount) >= 0.005) {
|
if (Math.abs(residualAmount) >= 0.005) {
|
||||||
@@ -1265,6 +1631,13 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.candidateDialog.confirming = true;
|
this.candidateDialog.confirming = true;
|
||||||
|
if (this.deferSave) {
|
||||||
|
this.buildSummaryFeesFromDetails();
|
||||||
|
this.candidateDialog.visible = false;
|
||||||
|
this.candidateDialog.confirming = false;
|
||||||
|
this.$message.success('结算明细添加成功,请点击保存提交');
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const { data } = await save(this.buildSavePayload());
|
const { data } = await save(this.buildSavePayload());
|
||||||
this.form.id = data?.data || this.form.id;
|
this.form.id = data?.data || this.form.id;
|
||||||
@@ -1300,26 +1673,139 @@ export default {
|
|||||||
};
|
};
|
||||||
this.appliedDetailQuery = { ...this.detailQuery };
|
this.appliedDetailQuery = { ...this.detailQuery };
|
||||||
},
|
},
|
||||||
async openAdjustDialog(row, readonly) {
|
async persistDetailForAdjustment(row) {
|
||||||
if (!row.id || row.id === row.sourceDetailId) {
|
const sourceDetailId = row.sourceDetailId || row.id;
|
||||||
this.$message.warning('请先保存预结算单');
|
const isPersistedDetail =
|
||||||
return;
|
row.id && sourceDetailId && String(row.id) !== String(sourceDetailId);
|
||||||
|
if (isPersistedDetail) return row;
|
||||||
|
if (this.deferSave) {
|
||||||
|
this.$message.warning('请先保存预结算单后再调整结算明细');
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.$refs.formRef?.validate();
|
||||||
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
const { data } = await save(this.buildSavePayload());
|
||||||
|
this.form.id = data?.data || this.form.id;
|
||||||
|
await this.loadDetail();
|
||||||
|
const savedRow = this.details.find(
|
||||||
|
item => String(item.sourceDetailId || '') === String(sourceDetailId || '')
|
||||||
|
);
|
||||||
|
if (!savedRow) {
|
||||||
|
this.$message.warning('结算明细保存失败,请重试');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
this.$emit('success', this.form.id);
|
||||||
|
return savedRow;
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async openAdjustDialog(row, readonly) {
|
||||||
|
const detailRow = await this.persistDetailForAdjustment(row);
|
||||||
|
if (!detailRow) return;
|
||||||
this.adjustDialog.visible = true;
|
this.adjustDialog.visible = true;
|
||||||
this.adjustDialog.loading = true;
|
this.adjustDialog.loading = true;
|
||||||
this.adjustDialog.readonly = readonly;
|
this.adjustDialog.readonly = readonly;
|
||||||
this.adjustDialog.detailId = row.id;
|
this.adjustDialog.detailId = detailRow.id;
|
||||||
this.adjustDialog.reason = '';
|
this.adjustDialog.reason = '';
|
||||||
try {
|
try {
|
||||||
const { data } = await getDetailFees(row.id);
|
const { data } = await getDetailFees(detailRow.id);
|
||||||
this.adjustRows = (data?.data || []).map(item => ({
|
this.adjustRows = (data?.data || []).map(item => ({
|
||||||
...item,
|
...item,
|
||||||
feeItems: this.parseFeeItems(item.feeItemsJson),
|
feeItems: this.parseFeeItems(item.feeItemsJson),
|
||||||
|
billingRule: this.normalizeBillingRule(item),
|
||||||
}));
|
}));
|
||||||
} finally {
|
} finally {
|
||||||
this.adjustDialog.loading = false;
|
this.adjustDialog.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
billingRuleName(row) {
|
||||||
|
return this.normalizeBillingRule(row).name;
|
||||||
|
},
|
||||||
|
openBillingRuleInfo(row) {
|
||||||
|
this.billingRuleDialog.rule = this.normalizeBillingRule(row);
|
||||||
|
this.billingRuleDialog.visible = true;
|
||||||
|
},
|
||||||
|
normalizeBillingRule(row) {
|
||||||
|
const source = row || {};
|
||||||
|
const nested = [
|
||||||
|
source.billingRule,
|
||||||
|
source.billingRuleInfo,
|
||||||
|
source.billingRuleJson,
|
||||||
|
source.billingRulesJson,
|
||||||
|
source.billingRules,
|
||||||
|
source.billingPlanRule,
|
||||||
|
source.feeRule,
|
||||||
|
source.rule,
|
||||||
|
].find(value => value !== undefined && value !== null && value !== '');
|
||||||
|
const parsedNested = this.parseJsonValue(nested);
|
||||||
|
const matchedRules = Array.isArray(parsedNested)
|
||||||
|
? parsedNested.filter(item => item && typeof item === 'object')
|
||||||
|
: [];
|
||||||
|
let rule = this.parseObject(matchedRules[0] || parsedNested || nested);
|
||||||
|
if (!Object.keys(rule).length && typeof nested === 'string') rule = { name: nested };
|
||||||
|
const matchCondition = this.parseObject(
|
||||||
|
source.matchCondition ?? source.billingMatchCondition ?? rule.matchCondition
|
||||||
|
);
|
||||||
|
const ruleNames = matchedRules
|
||||||
|
.map(item => item.ruleName || item.name || item.billingRuleName || item.feeItem)
|
||||||
|
.filter(Boolean);
|
||||||
|
const name =
|
||||||
|
source.billingRuleName ||
|
||||||
|
source.feeRuleName ||
|
||||||
|
source.ruleName ||
|
||||||
|
source.billingPlanRuleName ||
|
||||||
|
source.billingPlanName ||
|
||||||
|
rule.name ||
|
||||||
|
rule.ruleName ||
|
||||||
|
rule.billingRuleName ||
|
||||||
|
rule.planName ||
|
||||||
|
rule.billingPlanName ||
|
||||||
|
ruleNames.join('、') ||
|
||||||
|
rule.feeItem ||
|
||||||
|
'';
|
||||||
|
return {
|
||||||
|
...rule,
|
||||||
|
name,
|
||||||
|
feeType: source.billingFeeType || source.feeType || rule.feeType,
|
||||||
|
feeItem: source.billingFeeItem || source.feeItem || rule.feeItem,
|
||||||
|
billingElement:
|
||||||
|
source.billingElement ||
|
||||||
|
source.billingFactor ||
|
||||||
|
rule.billingElement ||
|
||||||
|
rule.billingFactor,
|
||||||
|
billingType: source.billingType || rule.billingType,
|
||||||
|
billingUnit: source.billingUnit || source.priceUnit || rule.billingUnit || rule.priceUnit,
|
||||||
|
unitPrice: source.billingUnitPrice ?? source.unitPrice ?? rule.unitPrice,
|
||||||
|
minimumBillingWeight:
|
||||||
|
source.minimumBillingWeight ?? rule.minimumBillingWeight ?? rule.lowerLimit,
|
||||||
|
remark: source.billingRuleRemark || source.ruleRemark || rule.remark,
|
||||||
|
rules: matchedRules,
|
||||||
|
matchCondition: {
|
||||||
|
...matchCondition,
|
||||||
|
origin: matchCondition.origin || matchCondition.originName,
|
||||||
|
destination: matchCondition.destination || matchCondition.destinationName,
|
||||||
|
transportMode: matchCondition.transportMode || matchCondition.transportType,
|
||||||
|
cargoType: matchCondition.cargoType || matchCondition.cargoTypeName,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
billingRuleRanges(rule) {
|
||||||
|
const ranges = Array.isArray(rule?.limitRanges) ? rule.limitRanges : [];
|
||||||
|
if (ranges.length) {
|
||||||
|
return ranges
|
||||||
|
.map(
|
||||||
|
item => `${item.lowerLimit ?? '-'}~${item.upperLimit ?? '-'}:${item.unitPrice ?? '-'}`
|
||||||
|
)
|
||||||
|
.join(';');
|
||||||
|
}
|
||||||
|
if (rule?.lowerLimit !== undefined || rule?.upperLimit !== undefined) {
|
||||||
|
return `${rule.lowerLimit ?? '-'}~${rule.upperLimit ?? '-'}`;
|
||||||
|
}
|
||||||
|
return '-';
|
||||||
|
},
|
||||||
recalculateAdjustRow(row, changedField) {
|
recalculateAdjustRow(row, changedField) {
|
||||||
if (changedField && changedField !== 'freight' && this.isFreightFeeItem(changedField)) {
|
if (changedField && changedField !== 'freight' && this.isFreightFeeItem(changedField)) {
|
||||||
row.freightAmount = Number(row.feeItems[changedField] || 0);
|
row.freightAmount = Number(row.feeItems[changedField] || 0);
|
||||||
@@ -1491,6 +1977,25 @@ export default {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
parseObject(value) {
|
||||||
|
if (!value) return {};
|
||||||
|
if (typeof value === 'object' && !Array.isArray(value)) return { ...value };
|
||||||
|
if (typeof value !== 'string') return {};
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value);
|
||||||
|
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
|
||||||
|
} catch (error) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parseJsonValue(value) {
|
||||||
|
if (typeof value !== 'string') return value;
|
||||||
|
try {
|
||||||
|
return JSON.parse(value);
|
||||||
|
} catch (error) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
},
|
||||||
formatMoney(value, currency = 'RMB') {
|
formatMoney(value, currency = 'RMB') {
|
||||||
if (value === undefined || value === null || value === '') return '-';
|
if (value === undefined || value === null || value === '') return '-';
|
||||||
const amount = Number(value);
|
const amount = Number(value);
|
||||||
@@ -1545,6 +2050,7 @@ export default {
|
|||||||
|
|
||||||
&--page &__content {
|
&--page &__content {
|
||||||
max-height: none;
|
max-height: none;
|
||||||
|
padding-bottom: 72px;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1597,6 +2103,39 @@ export default {
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__contract-filter {
|
||||||
|
display: grid !important;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 8px 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
:deep(.el-form-item) {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item__content) {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input),
|
||||||
|
:deep(.el-select) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__contract-filter-actions {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
:deep(.el-form-item__content) {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&__attachment-actions {
|
&__attachment-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -1625,6 +2164,14 @@ export default {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__negative-amount {
|
||||||
|
color: var(--el-color-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.pre-settlement-editor__negative-amount .el-input__inner) {
|
||||||
|
color: var(--el-color-danger);
|
||||||
|
}
|
||||||
|
|
||||||
&__pagination {
|
&__pagination {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
@@ -1635,6 +2182,28 @@ export default {
|
|||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__billing-rule-cell {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__billing-rule-icon {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__billing-rule-detail {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__billing-rule-match-title {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.form-readonly {
|
.form-readonly {
|
||||||
min-height: 32px;
|
min-height: 32px;
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
page-mode
|
page-mode
|
||||||
:record-id="recordId"
|
:record-id="recordId"
|
||||||
:initial-data="transferPayload"
|
:initial-data="transferPayload"
|
||||||
|
:defer-save="Boolean(transferPayload)"
|
||||||
@success="handleSuccess"
|
@success="handleSuccess"
|
||||||
/>
|
/>
|
||||||
</basic-container>
|
</basic-container>
|
||||||
|
|||||||
@@ -150,7 +150,7 @@
|
|||||||
</el-table>
|
</el-table>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="变更记录" name="change">
|
<el-tab-pane label="变更记录" name="change">
|
||||||
<el-table v-loading="changeDialog.loading" :data="changeRows" border>
|
<el-table v-loading="latestChangeLoading" :data="latestChangeRows" border>
|
||||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-for="column in changeRecordColumns"
|
v-for="column in changeRecordColumns"
|
||||||
@@ -161,17 +161,17 @@
|
|||||||
align="center"
|
align="center"
|
||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
/>
|
/>
|
||||||
|
<el-table-column label="操作" width="120" fixed="right" align="center">
|
||||||
|
<template #default>
|
||||||
|
<el-link type="primary" @click="openChangeRecordsDialog">查看详情</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<div class="settlement-detail-page__pagination">
|
<div
|
||||||
<el-pagination
|
v-if="!latestChangeLoading && !latestChangeRows.length"
|
||||||
v-model:current-page="changePage.current"
|
class="settlement-detail-page__empty"
|
||||||
v-model:page-size="changePage.size"
|
>
|
||||||
:total="changePage.total"
|
暂无变更记录
|
||||||
:page-sizes="[10, 20, 50]"
|
|
||||||
layout="total, sizes, prev, pager, next, jumper"
|
|
||||||
@current-change="loadChangeRecords"
|
|
||||||
@size-change="handleChangeSizeChange"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
@@ -182,6 +182,37 @@
|
|||||||
</section>
|
</section>
|
||||||
</teleport>
|
</teleport>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-model="changeRecordsDialog.visible"
|
||||||
|
title="变更记录详情"
|
||||||
|
width="88%"
|
||||||
|
append-to-body
|
||||||
|
>
|
||||||
|
<el-table v-loading="changeRecordsDialog.loading" :data="changeRows" border>
|
||||||
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
|
<el-table-column
|
||||||
|
v-for="column in changeRecordColumns"
|
||||||
|
:key="column.prop"
|
||||||
|
:prop="column.prop"
|
||||||
|
:label="column.label"
|
||||||
|
:min-width="column.minWidth"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
</el-table>
|
||||||
|
<div class="settlement-detail-page__pagination">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="changePage.current"
|
||||||
|
v-model:page-size="changePage.size"
|
||||||
|
:total="changePage.total"
|
||||||
|
:page-sizes="[10, 20, 50]"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@current-change="loadChangeRecords"
|
||||||
|
@size-change="handleChangeSizeChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<teleport to="body">
|
<teleport to="body">
|
||||||
<section
|
<section
|
||||||
v-if="adjustDialog.visible"
|
v-if="adjustDialog.visible"
|
||||||
@@ -365,19 +396,12 @@
|
|||||||
inputmode="decimal"
|
inputmode="decimal"
|
||||||
@input="value => handleAdjustFeeItemInput(row, column.feeItemName, value)"
|
@input="value => handleAdjustFeeItemInput(row, column.feeItemName, value)"
|
||||||
/>
|
/>
|
||||||
<el-input
|
|
||||||
v-else-if="column.prop === 'remark'"
|
|
||||||
v-model="row.remark"
|
|
||||||
clearable
|
|
||||||
maxlength="200"
|
|
||||||
placeholder="请输入备注"
|
|
||||||
/>
|
|
||||||
<el-input
|
<el-input
|
||||||
v-else-if="column.prop === 'changeReason'"
|
v-else-if="column.prop === 'changeReason'"
|
||||||
v-model="row.changeReason"
|
v-model="row.changeReason"
|
||||||
clearable
|
clearable
|
||||||
maxlength="300"
|
maxlength="300"
|
||||||
placeholder="请输入变更原因"
|
placeholder="请输入调整原因"
|
||||||
/>
|
/>
|
||||||
<span v-else-if="column.prop === 'adjustAmountText'">
|
<span v-else-if="column.prop === 'adjustAmountText'">
|
||||||
{{ fixedTwoDecimals(row.adjustAmount) }}
|
{{ fixedTwoDecimals(row.adjustAmount) }}
|
||||||
@@ -545,22 +569,16 @@
|
|||||||
label-width="120px"
|
label-width="120px"
|
||||||
>
|
>
|
||||||
<el-form-item label="运单合同" required>
|
<el-form-item label="运单合同" required>
|
||||||
<el-select
|
<el-input
|
||||||
v-model="generateQuery.contractId"
|
:model-value="generateContractName"
|
||||||
class="settlement-detail-page__generate-select"
|
class="settlement-detail-page__generate-select"
|
||||||
filterable
|
readonly
|
||||||
clearable
|
|
||||||
:loading="contractLoading"
|
|
||||||
placeholder="请选择合同"
|
placeholder="请选择合同"
|
||||||
@change="handleGenerateContractChange"
|
|
||||||
>
|
>
|
||||||
<el-option
|
<template #append>
|
||||||
v-for="item in contractOptions"
|
<el-button @click="openGenerateContractDialog">选择</el-button>
|
||||||
:key="item.id"
|
</template>
|
||||||
:label="item.contractName"
|
</el-input>
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="计费方案" required>
|
<el-form-item label="计费方案" required>
|
||||||
<el-select
|
<el-select
|
||||||
@@ -673,6 +691,182 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
v-model="generateContractDialog.visible"
|
||||||
|
title="选择合同"
|
||||||
|
width="92%"
|
||||||
|
append-to-body
|
||||||
|
destroy-on-close
|
||||||
|
class="settlement-detail-page__contract-dialog"
|
||||||
|
@opened="layoutGenerateContractTable"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
:model="generateContractDialog.query"
|
||||||
|
label-position="right"
|
||||||
|
label-width="auto"
|
||||||
|
class="settlement-detail-page__contract-filter"
|
||||||
|
@submit.prevent
|
||||||
|
>
|
||||||
|
<el-form-item label="合同编号">
|
||||||
|
<el-input
|
||||||
|
v-model="generateContractDialog.query.contractNo"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入"
|
||||||
|
@keyup.enter="loadGenerateContracts"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="合同名称">
|
||||||
|
<el-input
|
||||||
|
v-model="generateContractDialog.query.contractName"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入"
|
||||||
|
@keyup.enter="loadGenerateContracts"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="所属项目">
|
||||||
|
<el-input
|
||||||
|
v-model="generateContractDialog.query.projectName"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入"
|
||||||
|
@keyup.enter="loadGenerateContracts"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="所属组织">
|
||||||
|
<el-input
|
||||||
|
v-model="generateContractDialog.query.organizationName"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入"
|
||||||
|
@keyup.enter="loadGenerateContracts"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="合同类别">
|
||||||
|
<el-select :model-value="generateContractCategory" disabled placeholder="请选择">
|
||||||
|
<el-option
|
||||||
|
v-for="item in contractCategoryOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="签约类型">
|
||||||
|
<el-select v-model="generateContractDialog.query.signType" clearable placeholder="请选择">
|
||||||
|
<el-option
|
||||||
|
v-for="item in signTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="生效类型">
|
||||||
|
<el-select
|
||||||
|
v-model="generateContractDialog.query.effectiveType"
|
||||||
|
clearable
|
||||||
|
placeholder="请选择"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in effectiveTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="合同阶段">
|
||||||
|
<el-select
|
||||||
|
v-model="generateContractDialog.query.contractStage"
|
||||||
|
clearable
|
||||||
|
placeholder="请选择"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in contractStageOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="generateContractDialog.expanded" label="审核状态">
|
||||||
|
<el-select
|
||||||
|
v-model="generateContractDialog.query.approvalStatus"
|
||||||
|
clearable
|
||||||
|
placeholder="请选择"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in contractApprovalStatusOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item class="settlement-detail-page__contract-filter-actions">
|
||||||
|
<el-button type="primary" @click="searchGenerateContracts">查询</el-button>
|
||||||
|
<el-button @click="resetGenerateContractQuery">重置</el-button>
|
||||||
|
<el-button
|
||||||
|
text
|
||||||
|
@click="generateContractDialog.expanded = !generateContractDialog.expanded"
|
||||||
|
>
|
||||||
|
{{ generateContractDialog.expanded ? '收起' : '展开' }}
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-table
|
||||||
|
ref="generateContractTable"
|
||||||
|
v-loading="generateContractDialog.loading"
|
||||||
|
:data="generateContractDialog.rows"
|
||||||
|
border
|
||||||
|
highlight-current-row
|
||||||
|
@current-change="generateContractDialog.current = $event"
|
||||||
|
@row-dblclick="selectGenerateContract"
|
||||||
|
>
|
||||||
|
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||||
|
<el-table-column
|
||||||
|
v-for="column in contractSelectionColumns"
|
||||||
|
:key="column.prop"
|
||||||
|
:prop="column.prop"
|
||||||
|
:label="column.label"
|
||||||
|
:min-width="column.minWidth"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="column.prop === 'organizationName'">{{
|
||||||
|
row.organizationName || row.deptName || '-'
|
||||||
|
}}</span>
|
||||||
|
<span v-else-if="column.prop === 'contractCategory'">{{
|
||||||
|
contractCategoryName(row.contractCategory)
|
||||||
|
}}</span>
|
||||||
|
<span v-else-if="column.prop === 'signType'">{{ signTypeName(row.signType) }}</span>
|
||||||
|
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" width="90" fixed="right" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-link type="primary" @click.stop="selectGenerateContract(row)">选择</el-link>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<div class="settlement-detail-page__pagination">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="generateContractDialog.page.current"
|
||||||
|
v-model:page-size="generateContractDialog.page.size"
|
||||||
|
:total="generateContractDialog.page.total"
|
||||||
|
:page-sizes="[10, 20, 50, 100]"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
@current-change="loadGenerateContracts"
|
||||||
|
@size-change="handleGenerateContractSizeChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="generateContractDialog.visible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="selectGenerateContract(generateContractDialog.current)">
|
||||||
|
确定
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</basic-container>
|
</basic-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -698,6 +892,28 @@ import { exportBlob } from '@/api/common';
|
|||||||
import { getDictionary } from '@/api/system/dictbiz';
|
import { getDictionary } from '@/api/system/dictbiz';
|
||||||
import { downloadXls } from '@/utils/util';
|
import { downloadXls } from '@/utils/util';
|
||||||
import { createSettlementTransfer } from '@/utils/settlement-transfer';
|
import { createSettlementTransfer } from '@/utils/settlement-transfer';
|
||||||
|
import {
|
||||||
|
contractApprovalStatusOptions,
|
||||||
|
contractCategoryOptions,
|
||||||
|
contractStageOptions,
|
||||||
|
effectiveTypeOptions,
|
||||||
|
signTypeOptions,
|
||||||
|
} from '@/option/business/common';
|
||||||
|
|
||||||
|
const contractSelectionColumns = [
|
||||||
|
{ prop: 'contractNo', label: '合同编号', minWidth: 180 },
|
||||||
|
{ prop: 'contractName', label: '合同名称', minWidth: 220 },
|
||||||
|
{ prop: 'projectName', label: '所属项目', minWidth: 170 },
|
||||||
|
{ prop: 'organizationName', label: '所属组织', minWidth: 170 },
|
||||||
|
{ prop: 'contractCategory', label: '合同类别', minWidth: 130 },
|
||||||
|
{ prop: 'signType', label: '签约类型', minWidth: 120 },
|
||||||
|
{ prop: 'partyA', label: '甲方', minWidth: 170 },
|
||||||
|
{ prop: 'partyB', label: '乙方', minWidth: 170 },
|
||||||
|
{ prop: 'startDate', label: '开始日期', minWidth: 130 },
|
||||||
|
{ prop: 'endDate', label: '结束日期', minWidth: 130 },
|
||||||
|
{ prop: 'temporaryStartDate', label: '临时效力起', minWidth: 130 },
|
||||||
|
{ prop: 'temporaryEndDate', label: '临时效力止', minWidth: 130 },
|
||||||
|
];
|
||||||
|
|
||||||
const ADJUST_BILLING_ELEMENTS = [
|
const ADJUST_BILLING_ELEMENTS = [
|
||||||
'按重量',
|
'按重量',
|
||||||
@@ -777,15 +993,22 @@ export default {
|
|||||||
priceUnitRequest: null,
|
priceUnitRequest: null,
|
||||||
changeRows: [],
|
changeRows: [],
|
||||||
changePage: { current: 1, size: 10, total: 0 },
|
changePage: { current: 1, size: 10, total: 0 },
|
||||||
changeDialog: { loading: false },
|
latestChangeRows: [],
|
||||||
|
latestChangeLoading: false,
|
||||||
|
changeRecordsDialog: { visible: false, loading: false },
|
||||||
updateFeeDialog: { visible: false, submitting: false, row: null },
|
updateFeeDialog: { visible: false, submitting: false, row: null },
|
||||||
updateFeeForm: { contractId: '', billingPlanId: '' },
|
updateFeeForm: { contractId: '', billingPlanId: '' },
|
||||||
updateFeeRules: {
|
updateFeeRules: {
|
||||||
contractId: [{ required: true, message: '请选择更新范围', trigger: 'change' }],
|
contractId: [{ required: true, message: '请选择更新范围', trigger: 'change' }],
|
||||||
},
|
},
|
||||||
contractOptions: [],
|
contractOptions: [],
|
||||||
contractLoading: false,
|
|
||||||
updateContractOptions: [],
|
updateContractOptions: [],
|
||||||
|
contractCategoryOptions,
|
||||||
|
signTypeOptions,
|
||||||
|
effectiveTypeOptions,
|
||||||
|
contractStageOptions,
|
||||||
|
contractApprovalStatusOptions,
|
||||||
|
contractSelectionColumns,
|
||||||
transportTypeOptions: [],
|
transportTypeOptions: [],
|
||||||
billingPlanOptions: [],
|
billingPlanOptions: [],
|
||||||
transferDialog: { visible: false, loading: false, submitting: false },
|
transferDialog: { visible: false, loading: false, submitting: false },
|
||||||
@@ -796,6 +1019,25 @@ export default {
|
|||||||
transferPage: { current: 1, size: 10, total: 0 },
|
transferPage: { current: 1, size: 10, total: 0 },
|
||||||
generateDialog: { visible: false, loading: false },
|
generateDialog: { visible: false, loading: false },
|
||||||
generateQuery: {},
|
generateQuery: {},
|
||||||
|
generateContractDialog: {
|
||||||
|
visible: false,
|
||||||
|
loading: false,
|
||||||
|
expanded: false,
|
||||||
|
rows: [],
|
||||||
|
current: null,
|
||||||
|
query: {
|
||||||
|
contractNo: '',
|
||||||
|
contractName: '',
|
||||||
|
projectName: '',
|
||||||
|
organizationName: '',
|
||||||
|
contractCategory: '',
|
||||||
|
signType: '',
|
||||||
|
effectiveType: '',
|
||||||
|
contractStage: '',
|
||||||
|
approvalStatus: '',
|
||||||
|
},
|
||||||
|
page: { current: 1, size: 10, total: 0 },
|
||||||
|
},
|
||||||
generateRows: [],
|
generateRows: [],
|
||||||
generateSelection: [],
|
generateSelection: [],
|
||||||
generatePage: { current: 1, size: 10, total: 0 },
|
generatePage: { current: 1, size: 10, total: 0 },
|
||||||
@@ -805,6 +1047,30 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
generateContractName() {
|
||||||
|
const contract = this.contractOptions.find(
|
||||||
|
item => String(item.id) === String(this.generateQuery.contractId)
|
||||||
|
);
|
||||||
|
return contract?.contractName || '';
|
||||||
|
},
|
||||||
|
generateContractCategory() {
|
||||||
|
return this.settlementType === 'payable' ? '承运商合同' : '客户合同';
|
||||||
|
},
|
||||||
|
contractCategoryName(value) {
|
||||||
|
return (
|
||||||
|
this.contractCategoryOptions.find(item => String(item.value) === String(value))?.label ||
|
||||||
|
this.formatCell(value)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
signTypeName(value) {
|
||||||
|
return (
|
||||||
|
this.signTypeOptions.find(item => String(item.value) === String(value))?.label ||
|
||||||
|
this.formatCell(value)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
displayValue(value) {
|
||||||
|
return this.formatCell(value);
|
||||||
|
},
|
||||||
settlementTypeLabel() {
|
settlementTypeLabel() {
|
||||||
if (this.settlementType === 'receivable') return '应收';
|
if (this.settlementType === 'receivable') return '应收';
|
||||||
if (this.settlementType === 'payable') return '应付';
|
if (this.settlementType === 'payable') return '应付';
|
||||||
@@ -830,14 +1096,31 @@ export default {
|
|||||||
column => this.settlementType !== 'receivable' || column.prop !== 'preSettlementNo'
|
column => this.settlementType !== 'receivable' || column.prop !== 'preSettlementNo'
|
||||||
);
|
);
|
||||||
const totalIndex = columns.findIndex(column => column.prop === 'totalAmountText');
|
const totalIndex = columns.findIndex(column => column.prop === 'totalAmountText');
|
||||||
const dynamicColumns = this.tableFeeItemNames.map((name, index) => ({
|
const dynamicColumns = this.isPayable
|
||||||
label: name,
|
? [
|
||||||
prop: `tableFeeItem${index}`,
|
{
|
||||||
feeItemName: name,
|
label: '运输费',
|
||||||
dynamic: true,
|
prop: 'tableFreightAmount',
|
||||||
minWidth: 130,
|
feeSummaryType: 'freight',
|
||||||
align: 'right',
|
minWidth: 130,
|
||||||
}));
|
align: 'right',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '其他费用',
|
||||||
|
prop: 'tableOtherFeeAmount',
|
||||||
|
feeSummaryType: 'other',
|
||||||
|
minWidth: 130,
|
||||||
|
align: 'right',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: this.tableFeeItemNames.map((name, index) => ({
|
||||||
|
label: name,
|
||||||
|
prop: `tableFeeItem${index}`,
|
||||||
|
feeItemName: name,
|
||||||
|
dynamic: true,
|
||||||
|
minWidth: 130,
|
||||||
|
align: 'right',
|
||||||
|
}));
|
||||||
if (totalIndex < 0) return [...columns, ...dynamicColumns];
|
if (totalIndex < 0) return [...columns, ...dynamicColumns];
|
||||||
columns.splice(totalIndex, 0, ...dynamicColumns);
|
columns.splice(totalIndex, 0, ...dynamicColumns);
|
||||||
return columns;
|
return columns;
|
||||||
@@ -846,12 +1129,14 @@ export default {
|
|||||||
return [...generatePreviewColumns, ...this.dynamicFeeColumns, ...feeDetailTailColumns];
|
return [...generatePreviewColumns, ...this.dynamicFeeColumns, ...feeDetailTailColumns];
|
||||||
},
|
},
|
||||||
adjustFeeColumns() {
|
adjustFeeColumns() {
|
||||||
return [
|
const tailColumns = feeDetailTailColumns.filter(column => column.prop !== 'remark');
|
||||||
...feeDetailBaseColumns,
|
const afterAmountIndex = tailColumns.findIndex(column => column.prop === 'afterAmountText');
|
||||||
...this.adjustDynamicFeeColumns,
|
tailColumns.splice(afterAmountIndex + 1, 0, {
|
||||||
...feeDetailTailColumns,
|
label: '调整原因',
|
||||||
{ label: '变更原因', prop: 'changeReason', minWidth: 220 },
|
prop: 'changeReason',
|
||||||
];
|
minWidth: 220,
|
||||||
|
});
|
||||||
|
return [...feeDetailBaseColumns, ...this.adjustDynamicFeeColumns, ...tailColumns];
|
||||||
},
|
},
|
||||||
transportCargoTypeOptions() {
|
transportCargoTypeOptions() {
|
||||||
return this.cargoTypeOptions.filter(item => item.children?.length);
|
return this.cargoTypeOptions.filter(item => item.children?.length);
|
||||||
@@ -1114,6 +1399,12 @@ export default {
|
|||||||
formatColumnValue(row, column) {
|
formatColumnValue(row, column) {
|
||||||
if (column.prop === 'transportType') return this.transportTypeLabel(row[column.prop]);
|
if (column.prop === 'transportType') return this.transportTypeLabel(row[column.prop]);
|
||||||
if (column.prop === 'transportQuantity') return this.fixedTwoDecimals(row[column.prop]);
|
if (column.prop === 'transportQuantity') return this.fixedTwoDecimals(row[column.prop]);
|
||||||
|
if (column.feeSummaryType) {
|
||||||
|
return this.money(
|
||||||
|
this.summarizeTableFee(row, column.feeSummaryType),
|
||||||
|
row.currency || 'RMB'
|
||||||
|
);
|
||||||
|
}
|
||||||
if (column.dynamic) {
|
if (column.dynamic) {
|
||||||
return this.money(
|
return this.money(
|
||||||
this.normalizeFeeItems(row.feeItems)[column.feeItemName],
|
this.normalizeFeeItems(row.feeItems)[column.feeItemName],
|
||||||
@@ -1137,25 +1428,86 @@ export default {
|
|||||||
const res = await api.getList(this.page.current, this.page.size, params);
|
const res = await api.getList(this.page.current, this.page.size, params);
|
||||||
const data = this.unwrapPage(res);
|
const data = this.unwrapPage(res);
|
||||||
this.rows = (data.records || []).map(this.decorateRow);
|
this.rows = (data.records || []).map(this.decorateRow);
|
||||||
this.tableFeeItemNames = this.collectFeeItemNames(this.rows);
|
this.tableFeeItemNames = this.isPayable ? [] : this.collectFeeItemNames(this.rows);
|
||||||
this.page.total = data.total || 0;
|
this.page.total = data.total || 0;
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async loadContracts() {
|
openGenerateContractDialog() {
|
||||||
const contractCategory = this.settlementType === 'payable' ? '承运商合同' : '客户合同';
|
this.generateContractDialog.visible = true;
|
||||||
this.contractLoading = true;
|
this.generateContractDialog.expanded = false;
|
||||||
|
this.generateContractDialog.current = null;
|
||||||
|
this.generateContractDialog.page.current = 1;
|
||||||
|
this.loadGenerateContracts();
|
||||||
|
},
|
||||||
|
async loadGenerateContracts() {
|
||||||
|
this.generateContractDialog.loading = true;
|
||||||
try {
|
try {
|
||||||
const res = await getContractList(1, 999, {
|
const res = await getContractList(
|
||||||
contractCategory,
|
this.generateContractDialog.page.current,
|
||||||
});
|
this.generateContractDialog.page.size,
|
||||||
|
{
|
||||||
|
...this.generateContractDialog.query,
|
||||||
|
contractCategory: this.generateContractCategory,
|
||||||
|
approvalStatuses: 'approved,change_approved',
|
||||||
|
}
|
||||||
|
);
|
||||||
const data = this.unwrapPage(res);
|
const data = this.unwrapPage(res);
|
||||||
this.contractOptions = data.records || [];
|
this.generateContractDialog.rows = data.records || [];
|
||||||
|
this.generateContractDialog.page.total = Number(data.total || 0);
|
||||||
|
this.layoutGenerateContractTable();
|
||||||
} finally {
|
} finally {
|
||||||
this.contractLoading = false;
|
this.generateContractDialog.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
layoutGenerateContractTable() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.generateContractTable?.doLayout?.();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
searchGenerateContracts() {
|
||||||
|
this.generateContractDialog.page.current = 1;
|
||||||
|
this.loadGenerateContracts();
|
||||||
|
},
|
||||||
|
resetGenerateContractQuery() {
|
||||||
|
this.generateContractDialog.query = {
|
||||||
|
contractNo: '',
|
||||||
|
contractName: '',
|
||||||
|
projectName: '',
|
||||||
|
organizationName: '',
|
||||||
|
contractCategory: '',
|
||||||
|
signType: '',
|
||||||
|
effectiveType: '',
|
||||||
|
contractStage: '',
|
||||||
|
approvalStatus: '',
|
||||||
|
};
|
||||||
|
this.searchGenerateContracts();
|
||||||
|
},
|
||||||
|
handleGenerateContractSizeChange() {
|
||||||
|
this.generateContractDialog.page.current = 1;
|
||||||
|
this.loadGenerateContracts();
|
||||||
|
},
|
||||||
|
selectGenerateContract(row) {
|
||||||
|
if (!row) {
|
||||||
|
this.$message.warning('请选择合同');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const contract = {
|
||||||
|
...row,
|
||||||
|
deptId: row.deptId || row.organizationId,
|
||||||
|
deptName: row.deptName || row.organizationName,
|
||||||
|
partyA: row.partyA || row.payerName,
|
||||||
|
partyB: row.partyB || row.payeeName,
|
||||||
|
};
|
||||||
|
const index = this.contractOptions.findIndex(item => String(item.id) === String(contract.id));
|
||||||
|
if (index >= 0) this.contractOptions.splice(index, 1, contract);
|
||||||
|
else this.contractOptions.push(contract);
|
||||||
|
this.generateQuery.contractId = contract.id;
|
||||||
|
this.handleGenerateContractChange(contract.id);
|
||||||
|
this.generateContractDialog.visible = false;
|
||||||
|
this.generateContractDialog.current = null;
|
||||||
|
},
|
||||||
handleSearch() {
|
handleSearch() {
|
||||||
this.page.current = 1;
|
this.page.current = 1;
|
||||||
this.loadTable();
|
this.loadTable();
|
||||||
@@ -1177,6 +1529,7 @@ export default {
|
|||||||
closeDetailPanel() {
|
closeDetailPanel() {
|
||||||
this.detailDialog.visible = false;
|
this.detailDialog.visible = false;
|
||||||
this.detailDialog.row = null;
|
this.detailDialog.row = null;
|
||||||
|
this.changeRecordsDialog.visible = false;
|
||||||
},
|
},
|
||||||
closeAdjustPanel() {
|
closeAdjustPanel() {
|
||||||
this.clearAdjustCalculations();
|
this.clearAdjustCalculations();
|
||||||
@@ -1525,11 +1878,31 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleDetailTabChange(name) {
|
handleDetailTabChange(name) {
|
||||||
if (name === 'change') this.loadChangeRecords();
|
if (name === 'change') this.loadLatestChangeRecord();
|
||||||
|
},
|
||||||
|
async loadLatestChangeRecord() {
|
||||||
|
if (!this.detailDialog.row) return;
|
||||||
|
this.latestChangeLoading = true;
|
||||||
|
this.latestChangeRows = [];
|
||||||
|
try {
|
||||||
|
const res = await api.getChangeRecords(1, 1, {
|
||||||
|
detailId: this.detailDialog.row.id,
|
||||||
|
});
|
||||||
|
const data = this.unwrapPage(res);
|
||||||
|
this.latestChangeRows = (data.records || []).slice(0, 1);
|
||||||
|
} finally {
|
||||||
|
this.latestChangeLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openChangeRecordsDialog() {
|
||||||
|
if (!this.detailDialog.row) return;
|
||||||
|
this.changeRecordsDialog.visible = true;
|
||||||
|
this.changePage.current = 1;
|
||||||
|
this.loadChangeRecords();
|
||||||
},
|
},
|
||||||
async loadChangeRecords() {
|
async loadChangeRecords() {
|
||||||
if (!this.detailDialog.row) return;
|
if (!this.detailDialog.row) return;
|
||||||
this.changeDialog.loading = true;
|
this.changeRecordsDialog.loading = true;
|
||||||
try {
|
try {
|
||||||
const res = await api.getChangeRecords(this.changePage.current, this.changePage.size, {
|
const res = await api.getChangeRecords(this.changePage.current, this.changePage.size, {
|
||||||
detailId: this.detailDialog.row.id,
|
detailId: this.detailDialog.row.id,
|
||||||
@@ -1538,7 +1911,7 @@ export default {
|
|||||||
this.changeRows = data.records || [];
|
this.changeRows = data.records || [];
|
||||||
this.changePage.total = data.total || 0;
|
this.changePage.total = data.total || 0;
|
||||||
} finally {
|
} finally {
|
||||||
this.changeDialog.loading = false;
|
this.changeRecordsDialog.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleChangeSizeChange() {
|
handleChangeSizeChange() {
|
||||||
@@ -1792,11 +2165,26 @@ export default {
|
|||||||
this.generateDialog.visible = true;
|
this.generateDialog.visible = true;
|
||||||
this.generateQuery = {};
|
this.generateQuery = {};
|
||||||
this.contractOptions = [];
|
this.contractOptions = [];
|
||||||
|
this.generateContractDialog.query = {
|
||||||
|
contractNo: '',
|
||||||
|
contractName: '',
|
||||||
|
projectName: '',
|
||||||
|
organizationName: '',
|
||||||
|
contractCategory: '',
|
||||||
|
signType: '',
|
||||||
|
effectiveType: '',
|
||||||
|
contractStage: '',
|
||||||
|
approvalStatus: '',
|
||||||
|
};
|
||||||
|
this.generateContractDialog.visible = false;
|
||||||
|
this.generateContractDialog.expanded = false;
|
||||||
|
this.generateContractDialog.rows = [];
|
||||||
|
this.generateContractDialog.current = null;
|
||||||
|
this.generateContractDialog.page = { current: 1, size: 10, total: 0 };
|
||||||
this.billingPlanOptions = [];
|
this.billingPlanOptions = [];
|
||||||
this.generateRows = [];
|
this.generateRows = [];
|
||||||
this.generateSelection = [];
|
this.generateSelection = [];
|
||||||
this.generatePage = { current: 1, size: 10, total: 0 };
|
this.generatePage = { current: 1, size: 10, total: 0 };
|
||||||
this.loadContracts();
|
|
||||||
},
|
},
|
||||||
handleGenerateContractChange(contractId) {
|
handleGenerateContractChange(contractId) {
|
||||||
const options = this.syncBillingPlanOptions(contractId);
|
const options = this.syncBillingPlanOptions(contractId);
|
||||||
@@ -1911,6 +2299,7 @@ export default {
|
|||||||
row.vehicleNumber ||
|
row.vehicleNumber ||
|
||||||
sourceWaybill?.vehicleNo ||
|
sourceWaybill?.vehicleNo ||
|
||||||
'',
|
'',
|
||||||
|
transportQuantityText: row.transportQuantityText ?? row.transportQuantity ?? '',
|
||||||
...dynamic,
|
...dynamic,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -2032,6 +2421,16 @@ export default {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
summarizeTableFee(row, summaryType) {
|
||||||
|
const feeItemEntries = Object.entries(this.normalizeFeeItems(row.feeItems));
|
||||||
|
const matchedEntries = feeItemEntries.filter(([name]) =>
|
||||||
|
summaryType === 'freight' ? this.isFreightFeeItem(name) : !this.isFreightFeeItem(name)
|
||||||
|
);
|
||||||
|
if (matchedEntries.length) {
|
||||||
|
return matchedEntries.reduce((total, [, amount]) => total + Number(amount || 0), 0);
|
||||||
|
}
|
||||||
|
return summaryType === 'freight' ? row.freightAmount : row.otherFeeAmount;
|
||||||
|
},
|
||||||
money(value, currency) {
|
money(value, currency) {
|
||||||
if (value === null || value === undefined || value === '') return '-';
|
if (value === null || value === undefined || value === '') return '-';
|
||||||
return `${Number(value).toFixed(2)} ${currency}`;
|
return `${Number(value).toFixed(2)} ${currency}`;
|
||||||
@@ -2160,6 +2559,12 @@ export default {
|
|||||||
padding: 16px 0;
|
padding: 16px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settlement-detail-page__empty {
|
||||||
|
padding: 24px 0;
|
||||||
|
color: #909399;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.settlement-detail-page__dialog-form {
|
.settlement-detail-page__dialog-form {
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
@@ -2212,6 +2617,39 @@ export default {
|
|||||||
width: 220px;
|
width: 220px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settlement-detail-page__contract-filter {
|
||||||
|
display: grid !important;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 8px 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
:deep(.el-form-item) {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item__content) {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input),
|
||||||
|
:deep(.el-select) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-detail-page__contract-filter-actions {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
:deep(.el-form-item__content) {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.settlement-detail-page__transfer-form {
|
.settlement-detail-page__transfer-form {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
padding: 12px 12px 4px;
|
padding: 12px 12px 4px;
|
||||||
|
|||||||
+13
-13
@@ -47,26 +47,26 @@ export default ({ mode, command }) => {
|
|||||||
__VUE_I18N_LEGACY_API__: true,
|
__VUE_I18N_LEGACY_API__: true,
|
||||||
__INTLIFY_PROD_DEVTOOLS__: false,
|
__INTLIFY_PROD_DEVTOOLS__: false,
|
||||||
},
|
},
|
||||||
// server: {
|
|
||||||
// port: 2888,
|
|
||||||
// proxy: {
|
|
||||||
// '/api': {
|
|
||||||
// target: 'http://localhost',
|
|
||||||
// //target: 'https://saber3.bladex.cn/api',
|
|
||||||
// changeOrigin: true,
|
|
||||||
// rewrite: path => path.replace(/^\/api/, ''),
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
server: {
|
server: {
|
||||||
port: 2889,
|
port: 2888,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://172.16.203.228:8000',
|
target: 'http://localhost',
|
||||||
|
//target: 'https://saber3.bladex.cn/api',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
rewrite: path => path.replace(/^\/api/, ''),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// server: {
|
||||||
|
// port: 2889,
|
||||||
|
// proxy: {
|
||||||
|
// '/api': {
|
||||||
|
// target: 'http://172.16.203.228:8000',
|
||||||
|
// changeOrigin: true,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'~': resolve(__dirname, './'),
|
'~': resolve(__dirname, './'),
|
||||||
|
|||||||
Reference in New Issue
Block a user