调整结算

This commit is contained in:
2026-09-05 00:04:46 +08:00
parent 96f2fa0eb5
commit 3bac9b6472
11 changed files with 688 additions and 352 deletions
+2
View File
@@ -18,6 +18,8 @@ export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'p
export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data }); export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data });
export const syncKingdee = id => export const syncKingdee = id =>
request({ url: `${baseUrl}/sync-kingdee`, method: 'post', params: { id } }); request({ url: `${baseUrl}/sync-kingdee`, method: 'post', params: { id } });
export const syncKingdeeBatch = ids =>
request({ url: `${baseUrl}/sync-kingdee-batch`, method: 'post', data: ids });
export const paymentTypeOptions = [ export const paymentTypeOptions = [
{ label: '项目预付', value: 'project_advance' }, { label: '项目预付', value: 'project_advance' },
@@ -691,7 +691,7 @@ export default {
return value !== undefined && value !== null && String(value).trim() !== ''; return value !== undefined && value !== null && String(value).trim() !== '';
}, },
taxRateInput(row, value) { taxRateInput(row, value) {
const text = String(value || '').replace(/[^\d.]/g, ''); const text = String(value ?? '').replace(/[^\d.]/g, '');
const parts = text.split('.'); const parts = text.split('.');
row.taxRate = row.taxRate =
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];
@@ -71,12 +71,12 @@
<el-form :model="route" label-position="right" label-width="auto" class="dispatch-form"> <el-form :model="route" label-position="right" label-width="auto" class="dispatch-form">
<el-row :gutter="16"> <el-row :gutter="16">
<el-col :span="8"> <el-col :span="8">
<el-form-item label="运输方式" required class="transport-type-field"><el-input :model-value="route.transportType" readonly /></el-form-item> <el-form-item label="运输方式" required class="transport-type-field"><el-input :model-value="route.transportType" disabled /></el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="16"> <el-row :gutter="16">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="发货地址" required><el-input :model-value="addressText(route.departureName, route.departureAddress)" readonly /></el-form-item> <el-form-item label="发货地址" required><el-input :model-value="addressText(route.departureName, route.departureAddress)" disabled /></el-form-item>
</el-col> </el-col>
<el-col :span="4"><el-form-item label="联系人"><el-input v-model="route.departureContact" placeholder="请输入" /></el-form-item></el-col> <el-col :span="4"><el-form-item label="联系人"><el-input v-model="route.departureContact" placeholder="请输入" /></el-form-item></el-col>
<el-col :span="4"><el-form-item label="联系方式"><el-input v-model="route.departurePhone" placeholder="请输入" /></el-form-item></el-col> <el-col :span="4"><el-form-item label="联系方式"><el-input v-model="route.departurePhone" placeholder="请输入" /></el-form-item></el-col>
@@ -84,7 +84,7 @@
</el-row> </el-row>
<el-row :gutter="16"> <el-row :gutter="16">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="收货地址" required><el-input :model-value="addressText(route.arrivalName, route.arrivalAddress)" readonly /></el-form-item> <el-form-item label="收货地址" required><el-input :model-value="addressText(route.arrivalName, route.arrivalAddress)" disabled /></el-form-item>
</el-col> </el-col>
<el-col :span="4"><el-form-item label="联系人"><el-input v-model="route.arrivalContact" placeholder="请输入" /></el-form-item></el-col> <el-col :span="4"><el-form-item label="联系人"><el-input v-model="route.arrivalContact" placeholder="请输入" /></el-form-item></el-col>
<el-col :span="4"><el-form-item label="联系方式"><el-input v-model="route.arrivalPhone" placeholder="请输入" /></el-form-item></el-col> <el-col :span="4"><el-form-item label="联系方式"><el-input v-model="route.arrivalPhone" placeholder="请输入" /></el-form-item></el-col>
@@ -109,6 +109,7 @@
</el-table> </el-table>
<template v-if="route.documentType === '运单'"> <template v-if="route.documentType === '运单'">
<div class="goods-heading"><h3>任务信息</h3></div>
<el-form :model="route" label-position="right" label-width="auto" class="dispatch-form carrier-type-form"><el-form-item label="承运类型" required><el-radio-group v-model="route.carrierType" @change="value => handleCarrierTypeChange(route, value)"><el-radio-button label="承运商" /><el-radio-button label="自运" /><el-radio-button label="网货平台" /></el-radio-group></el-form-item></el-form> <el-form :model="route" label-position="right" label-width="auto" class="dispatch-form carrier-type-form"><el-form-item label="承运类型" required><el-radio-group v-model="route.carrierType" @change="value => handleCarrierTypeChange(route, value)"><el-radio-button label="承运商" /><el-radio-button label="自运" /><el-radio-button label="网货平台" /></el-radio-group></el-form-item></el-form>
<el-form :model="route" label-position="right" label-width="auto" class="dispatch-form carrier-form"> <el-form :model="route" label-position="right" label-width="auto" class="dispatch-form carrier-form">
<el-row :gutter="16"> <el-row :gutter="16">
@@ -125,7 +126,7 @@
<el-col :span="6"><el-form-item label="承运商" :required="route.carrierType === '承运商'"><el-select :model-value="route.carrierType === '承运商' ? route.carrierContractId : route.carrierName" filterable clearable placeholder="请选择" :loading="carrierLoading" @change="value => handleCarrierContractChange(route, value)"><el-option v-for="item in carrierOptions" :key="carrierOptionValue(item)" :label="carrierOptionLabel(item)" :value="route.carrierType === '承运商' ? carrierOptionValue(item) : item.carrierName" /></el-select></el-form-item></el-col> <el-col :span="6"><el-form-item label="承运商" :required="route.carrierType === '承运商'"><el-select :model-value="route.carrierType === '承运商' ? route.carrierContractId : route.carrierName" filterable clearable placeholder="请选择" :loading="carrierLoading" @change="value => handleCarrierContractChange(route, value)"><el-option v-for="item in carrierOptions" :key="carrierOptionValue(item)" :label="carrierOptionLabel(item)" :value="route.carrierType === '承运商' ? carrierOptionValue(item) : item.carrierName" /></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="船长"><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="联系电话"><el-input v-model="route.driverPhone" 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="箱号"><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.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>
@@ -171,7 +172,7 @@
<section class="pending-panel"> <section class="pending-panel">
<div class="pending-bar"> <div class="pending-bar">
<div class="pending-summary"> <div class="pending-summary">
<strong>待提交 <em>{{ pending.length }}</em> <span class="pending-summary-text">{{ pendingSegmentSummary }}</span></strong> <strong>待提交 <em>{{ pendingBatchCount }}</em> <span class="pending-summary-text">{{ pendingSegmentSummary }}</span></strong>
<el-link type="primary" @click="pendingExpanded = !pendingExpanded">{{ pendingExpanded ? '收起待调度列表' : '展开待调度列表' }}</el-link> <el-link type="primary" @click="pendingExpanded = !pendingExpanded">{{ pendingExpanded ? '收起待调度列表' : '展开待调度列表' }}</el-link>
</div> </div>
<div class="pending-actions"><el-button @click="$emit('back')">取消</el-button><el-button type="primary" :loading="submitting" @click="submit">确认调度</el-button></div> <div class="pending-actions"><el-button @click="$emit('back')">取消</el-button><el-button type="primary" :loading="submitting" @click="submit">确认调度</el-button></div>
@@ -179,7 +180,7 @@
<div v-show="pendingExpanded" class="pending-list"> <div v-show="pendingExpanded" class="pending-list">
<template v-for="(items, segmentNo) in pendingGroups" :key="segmentNo"> <template v-for="(items, segmentNo) in pendingGroups" :key="segmentNo">
<h3>{{ segmentNo }}{{ dispatchLocationName(items[0], 'departure') }} {{ dispatchLocationName(items[0], 'arrival') }}</h3> <h3>{{ segmentNo }}{{ dispatchLocationName(items[0], 'departure') }} {{ dispatchLocationName(items[0], 'arrival') }}</h3>
<el-table :data="items" border><el-table-column type="index" label="序号" width="70" /><el-table-column label="单据类型" prop="documentType" width="100" /><el-table-column label="货物" min-width="220"><template #default="{ row }">{{ row.cargoName }}{{ row.cargoType ? `${row.cargoType}` : '' }}</template></el-table-column><el-table-column label="本次数量" width="150"><template #default="{ row }">{{ formatQuantity(row.quantity) }} {{ row.quantityUnit }}</template></el-table-column><el-table-column prop="carrierName" label="承运商" min-width="180" /><el-table-column prop="driverName" label="司机" width="130" /><el-table-column prop="vehicleNo" label="车牌" width="130" /><el-table-column label="操作" width="120" fixed="right"><template #default="{ row }"><div class="table-actions"><el-link type="primary" @click="editPending(row)">编辑</el-link><el-link type="danger" @click="removePending(row.id)">删除</el-link></div></template></el-table-column></el-table> <el-table :data="items" border><el-table-column type="index" label="序号" width="70" /><el-table-column label="货物" min-width="220"><template #default="{ row }">{{ pendingGoodsText(row) }}</template></el-table-column><el-table-column label="本次数量" min-width="150"><template #default="{ row }">{{ pendingQuantityText(row) }}</template></el-table-column><el-table-column prop="carrierName" label="承运商" min-width="180" /><el-table-column prop="driverName" label="司机" width="130" /><el-table-column prop="vehicleNo" label="车牌" width="130" /><el-table-column label="操作" width="120" fixed="right"><template #default="{ row }"><div class="table-actions"><el-link type="primary" @click="editPending(row)">编辑</el-link><el-link type="danger" @click="removePendingBatch(row)">删除</el-link></div></template></el-table-column></el-table>
</template> </template>
<el-empty v-if="!pending.length" description="暂无待提交调度条目" :image-size="60" /> <el-empty v-if="!pending.length" description="暂无待提交调度条目" :image-size="60" />
</div> </div>
@@ -216,13 +217,28 @@ export default {
goodsTypes() { return [...new Set((this.master?.goods || []).map(item => item.cargoType).filter(Boolean))].join('、') || '-'; }, goodsTypes() { return [...new Set((this.master?.goods || []).map(item => item.cargoType).filter(Boolean))].join('、') || '-'; },
masterGoods() { return (this.master?.goods || []).map((item, sourceIndex) => ({ ...item, sourceIndex, label: [item.cargoName, item.cargoType].filter(Boolean).join(' / ') })); }, masterGoods() { return (this.master?.goods || []).map((item, sourceIndex) => ({ ...item, sourceIndex, label: [item.cargoName, item.cargoType].filter(Boolean).join(' / ') })); },
dateRange() { return this.master?.planStartTime && this.master?.planEndTime ? `${String(this.master.planStartTime).slice(0, 10)} ~ ${String(this.master.planEndTime).slice(0, 10)}` : '-'; }, dateRange() { return this.master?.planStartTime && this.master?.planEndTime ? `${String(this.master.planStartTime).slice(0, 10)} ~ ${String(this.master.planEndTime).slice(0, 10)}` : '-'; },
pendingGroups() { return this.pending.reduce((groups, item) => { (groups[item.segmentNo] ||= []).push(item); return groups; }, {}); }, pendingGroups() {
return this.pending.reduce((groups, item, index) => {
const segmentRows = (groups[item.segmentNo] ||= []);
const batchKey = item.batchNo || item.id || `${item.segmentNo}-${index}`;
let batch = segmentRows.find(row => row.batchKey === batchKey);
if (!batch) {
batch = { ...item, batchKey, items: [] };
segmentRows.push(batch);
}
batch.items.push(item);
return groups;
}, {});
},
pendingBatchCount() {
return Object.values(this.pendingGroups).reduce((count, rows) => count + rows.length, 0);
},
pendingSegmentSummary() { pendingSegmentSummary() {
return this.routes.map(route => { return this.routes.map(route => {
const items = this.pending.filter(item => item.segmentNo === route.segmentNo); const batches = this.pendingGroups[route.segmentNo] || [];
if (!items.length) return `${route.segmentNo}:无`; if (!batches.length) return `${route.segmentNo}:无`;
const documentSummary = items.reduce((result, item) => { const documentSummary = batches.reduce((result, batch) => {
result[item.documentType] = (result[item.documentType] || 0) + 1; result[batch.documentType] = (result[batch.documentType] || 0) + 1;
return result; return result;
}, {}); }, {});
return `${route.segmentNo}${Object.entries(documentSummary).map(([type, count]) => `${type} x ${count}`).join('、')}`; return `${route.segmentNo}${Object.entries(documentSummary).map(([type, count]) => `${type} x ${count}`).join('、')}`;
@@ -506,6 +522,26 @@ export default {
return sum + this.quantityNumber(item.quantity); return sum + this.quantityNumber(item.quantity);
}, 0); }, 0);
}, },
validatePreviousSegmentCompletedQuantity(route, goods) {
const routeIndex = this.routes.indexOf(route);
if (routeIndex <= 0) return true;
const previousCompletedQuantity = this.quantityNumber(
this.routes[routeIndex - 1]?.arrivedQuantity
);
const currentDispatchQuantity = (goods || []).reduce(
(sum, item) => sum + this.quantityNumber(item.dispatchQuantity),
0
);
const dispatchTotal =
this.quantityNumber(route.dispatchedQuantity) +
this.pendingSegmentQuantity(route) +
currentDispatchQuantity;
if (dispatchTotal <= previousCompletedQuantity) return true;
this.$message.warning(
`${route.segmentNo}调度总量不能大于上一段已完成数量(${this.formatQuantity(previousCompletedQuantity)} ${this.quantityUnitLabel}`
);
return false;
},
pendingGoodsQuantity(route, row) { pendingGoodsQuantity(route, row) {
const goodsIndex = this.masterGoodsIndex(row); const goodsIndex = this.masterGoodsIndex(row);
return this.pending.reduce((sum, item) => { return this.pending.reduce((sum, item) => {
@@ -798,6 +834,7 @@ export default {
if (!goods.length) return this.$message.warning('请填写本次数量'); if (!goods.length) return this.$message.warning('请填写本次数量');
if (!this.validateDispatchGoods(route, goods)) return; if (!this.validateDispatchGoods(route, goods)) return;
if (goods.some(item => Number(item.dispatchQuantity) > this.availableDispatchQuantity(route, item))) return this.$message.warning('本次数量不能超过剩余数量'); if (goods.some(item => Number(item.dispatchQuantity) > this.availableDispatchQuantity(route, item))) return this.$message.warning('本次数量不能超过剩余数量');
if (!this.validatePreviousSegmentCompletedQuantity(route, goods)) return;
this.syncFreightItems(route); this.syncFreightItems(route);
if (!this.validateFreightItems(route)) return; if (!this.validateFreightItems(route)) return;
if (!this.validateRoutePhones(route)) return; if (!this.validateRoutePhones(route)) return;
@@ -805,7 +842,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)) return this.$message.warning('请补全自运或网货平台的车辆与人员信息'); if (route.carrierType !== '承运商' && (!route.driverName || !route.driverPhone || !route.vehicleNo)) return this.$message.warning('请补全自运或网货平台的车辆与人员信息');
} else if (!route.vehicleNo || !route.driverPhone || (route.carrierType === '承运商' && (!route.carrierContractId || !route.carrierName))) { } else if (!route.vehicleNo || (route.carrierType === '承运商' && (!route.carrierContractId || !route.carrierName))) {
return this.$message.warning('请补全非公路运输的承运信息'); return this.$message.warning('请补全非公路运输的承运信息');
} }
} }
@@ -829,9 +866,23 @@ export default {
} }
this.$message.success('已加入调度清单'); this.$message.success('已加入调度清单');
}, },
editPending(item) { pendingGoodsText(batch = {}) {
const route = this.routes.find(routeItem => routeItem.segmentNo === item.segmentNo); return (batch.items || [batch])
.map(item => [item.cargoName, item.cargoType].filter(Boolean).join(''))
.join(' | ');
},
pendingQuantityText(batch = {}) {
return (batch.items || [batch])
.map(item => `${this.formatQuantity(item.quantity)} ${item.quantityUnit || ''}`.trim())
.join(' | ');
},
editPending(batch) {
const items = batch.items || [batch];
const first = items[0];
const route = this.routes.find(routeItem => routeItem.segmentNo === first.segmentNo);
if (!route) return; if (!route) return;
Object.assign(route, first);
const editedGoods = items.map(item => {
const itemSourceIndex = Number(item.sourceIndex); const itemSourceIndex = Number(item.sourceIndex);
const sourceIndex = Number.isInteger(itemSourceIndex) && itemSourceIndex >= 0 const sourceIndex = Number.isInteger(itemSourceIndex) && itemSourceIndex >= 0
? itemSourceIndex ? itemSourceIndex
@@ -842,22 +893,32 @@ export default {
route.goods.push(goods); route.goods.push(goods);
} }
if (goods) goods.dispatchQuantity = item.quantity; if (goods) goods.dispatchQuantity = item.quantity;
Object.assign(route, item); return { goods, item };
});
route.currency = this.normalizeCurrencyValue( route.currency = this.normalizeCurrencyValue(
this.master?.settlementCurrency || route.currency || 'RMB' this.master?.settlementCurrency || route.currency || 'RMB'
); );
this.syncFreightItems(route); this.syncFreightItems(route);
editedGoods.forEach(({ goods, item }) => {
const freightItem = this.freightItemsForGoods(route, goods); const freightItem = this.freightItemsForGoods(route, goods);
if (freightItem) { freightItem.unitPrice = item.unitPrice || ''; freightItem.priceUnit = item.priceUnit || freightItem.priceUnit; } if (freightItem) {
this.removePending(item.id); freightItem.unitPrice = item.unitPrice || '';
this.editingId = item.id; freightItem.priceUnit = item.priceUnit || freightItem.priceUnit;
}
});
this.removePendingBatch(batch);
this.editingId = batch.batchKey;
route.selected = true; route.selected = true;
this.$nextTick(() => document.querySelector(`[data-segment="${item.segmentNo}"]`)?.scrollIntoView({ behavior: 'smooth', block: 'start' })); this.$nextTick(() => document.querySelector(`[data-segment="${first.segmentNo}"]`)?.scrollIntoView({ behavior: 'smooth', block: 'start' }));
}, },
freightItemsForGoods(route, goods) { freightItemsForGoods(route, goods) {
return (route.freightItems || []).find(item => String(item.sourceIndex) === String(goods?.sourceIndex)) || {}; return (route.freightItems || []).find(item => String(item.sourceIndex) === String(goods?.sourceIndex)) || {};
}, },
removePending(id) { this.pending = this.pending.filter(item => item.id !== id); if (this.editingId === id) this.editingId = null; }, removePendingBatch(batch = {}) {
const ids = new Set((batch.items || [batch]).map(item => item.id));
this.pending = this.pending.filter(item => !ids.has(item.id));
if (this.editingId === batch.batchKey) this.editingId = null;
},
validateRoutePhones(route = {}) { validateRoutePhones(route = {}) {
const invalidPhone = [ const invalidPhone = [
[route.departurePhone, '发货联系方式'], [route.departurePhone, '发货联系方式'],
@@ -228,9 +228,9 @@
> >
</section> </section>
<section> <section>
<div class="section-head">
<h3>附件</h3> <h3>附件</h3>
<div class="master-editor__attachment"> <div class="section-head__actions">
<div class="master-editor__attachment-head">
<el-button <el-button
type="primary" type="primary"
:disabled="!attachmentRows.length" :disabled="!attachmentRows.length"
@@ -239,6 +239,8 @@
批量下载 批量下载
</el-button> </el-button>
</div> </div>
</div>
<div class="master-editor__attachment">
<el-table <el-table
:data="attachmentRows" :data="attachmentRows"
border border
@@ -254,6 +256,17 @@
</el-link> </el-link>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="附件描述" min-width="220" align="center">
<template #default="{ row }">
<el-input
v-model="row.description"
maxlength="200"
show-word-limit
placeholder="请输入附件描述"
@input="syncAttachmentsJson"
/>
</template>
</el-table-column>
<el-table-column label="文件大小" width="120" align="center"> <el-table-column label="文件大小" width="120" align="center">
<template #default="{ row }">{{ formatFileSize(row.size) }}</template> <template #default="{ row }">{{ formatFileSize(row.size) }}</template>
</el-table-column> </el-table-column>
@@ -1006,6 +1019,7 @@ export default {
const first = this.contracts[0]; const first = this.contracts[0];
this.form.contractId = first?.id; this.form.contractId = first?.id;
this.form.contractName = first?.contractName || ''; this.form.contractName = first?.contractName || '';
this.form.customerName = first?.partyA || '';
} }
}, },
async projectChange(projectId) { async projectChange(projectId) {
@@ -1019,6 +1033,7 @@ export default {
contractChange(contractId) { contractChange(contractId) {
const contract = this.contracts.find(item => String(item.id) === String(contractId)); const contract = this.contracts.find(item => String(item.id) === String(contractId));
this.form.contractName = contract?.contractName || ''; this.form.contractName = contract?.contractName || '';
this.form.customerName = contract?.partyA || '';
}, },
addressModel(target) { addressModel(target) {
if (target.startsWith('route-')) { if (target.startsWith('route-')) {
@@ -1939,12 +1954,6 @@ export default {
.master-editor__attachment { .master-editor__attachment {
width: 100%; width: 100%;
} }
.master-editor__attachment-head {
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: 16px;
}
.master-editor__attachment-upload { .master-editor__attachment-upload {
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
@@ -161,7 +161,7 @@ export default {
handleContractFileBatchDownload() { (this.selectedContractFiles.length ? this.selectedContractFiles : this.contractFileRows).forEach(row => { if (row.url) window.open(row.url, '_blank'); }); }, handleContractFileBatchDownload() { (this.selectedContractFiles.length ? this.selectedContractFiles : this.contractFileRows).forEach(row => { if (row.url) window.open(row.url, '_blank'); }); },
formatFileSize(value) { const size = Number(value || 0); return size > 1024 * 1024 ? `${(size / 1024 / 1024).toFixed(2)}MB` : `${Math.max(1, Math.ceil(size / 1024))}KB`; }, formatFileSize(value) { const size = Number(value || 0); return size > 1024 * 1024 ? `${(size / 1024 / 1024).toFixed(2)}MB` : `${Math.max(1, Math.ceil(size / 1024))}KB`; },
addPaymentRatioRow() { this.paymentRatioRows.push({ paymentTerm: `${this.paymentRatioRows.length + 1}`, ratioLimit: '', remark: '' }); }, addPaymentRatioRow() { this.paymentRatioRows.push({ paymentTerm: `${this.paymentRatioRows.length + 1}`, ratioLimit: '', remark: '' }); },
async submit() { await this.$refs.formRef.validate(); const total = this.paymentRatioRows.reduce((sum, row) => sum + Number(row.ratioLimit || 0), 0); if (this.paymentRatioRows.length && Math.abs(total - 100) > 0.0001) { this.$message.warning('付款比例上限合计必须等于100%'); return; } const settlementRule = { preSettlementConfig: this.preSettlementConfig, formalSettlementConfig: this.formalSettlementConfig }; await api.submitChange({ ...this.form, copyCount: normalizeOptionalPositiveInteger(this.form.copyCount), paymentDays: normalizeOptionalPositiveInteger(this.form.paymentDays), startDate: this.period[0], endDate: this.period[1], feeGenerationMode: this.feeGenerationMode, billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0, billingPlanJson: JSON.stringify(this.plans), settlementRuleJson: JSON.stringify(settlementRule), preSettlementConfigJson: JSON.stringify(this.preSettlementConfig), formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig), paymentRatioJson: JSON.stringify(this.paymentRatioRows), contractFileJson: JSON.stringify(this.contractFileRows), attachmentsJson: JSON.stringify(this.attachments), changeContent: this.form.changeContent, changeReason: this.form.changeReason, changeAttachmentsJson: JSON.stringify(this.changeMaterials) }); this.$message.success('变更已提交'); this.$router.back(); }, async submit() { await this.$refs.formRef.validate(); const total = this.paymentRatioRows.reduce((sum, row) => sum + Number(row.ratioLimit || 0), 0); if (this.paymentRatioRows.length && Math.abs(total - 100) > 0.0001) { this.$message.warning('付款比例上限合计必须等于100%'); return; } const settlementRule = { preSettlementConfig: this.preSettlementConfig, formalSettlementConfig: this.formalSettlementConfig }; await api.submitChange({ ...this.form, settlementCurrency: String(this.form.settlementCurrency || '').trim() || 'RMB', copyCount: normalizeOptionalPositiveInteger(this.form.copyCount), paymentDays: normalizeOptionalPositiveInteger(this.form.paymentDays), startDate: this.period[0], endDate: this.period[1], feeGenerationMode: this.feeGenerationMode, billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0, billingPlanJson: JSON.stringify(this.plans), settlementRuleJson: JSON.stringify(settlementRule), preSettlementConfigJson: JSON.stringify(this.preSettlementConfig), formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig), paymentRatioJson: JSON.stringify(this.paymentRatioRows), contractFileJson: JSON.stringify(this.contractFileRows), attachmentsJson: JSON.stringify(this.attachments), changeContent: this.form.changeContent, changeReason: this.form.changeReason, changeAttachmentsJson: JSON.stringify(this.changeMaterials) }); this.$message.success('变更已提交'); this.$router.back(); },
}, },
}; };
</script> </script>
+48 -12
View File
@@ -252,7 +252,7 @@
> >
<el-table-column type="selection" width="52" fixed="left" align="center" /> <el-table-column type="selection" width="52" fixed="left" align="center" />
<el-table-column type="index" label="序号" width="58" fixed="left" align="center" /> <el-table-column type="index" label="序号" width="58" fixed="left" align="center" />
<el-table-column label="配载单号" prop="loadingNo" min-width="170" fixed="left"> <el-table-column label="配载单号" prop="loadingNo" min-width="170">
<template #default="{ row }"> <template #default="{ row }">
<el-link <el-link
type="primary" type="primary"
@@ -1172,6 +1172,31 @@ const createDialogForm = () => ({
businessStatus: 'draft', businessStatus: 'draft',
}); });
// 后端以 -1 表示未填写里程,表单中应保持为空,避免给用户造成已填写的错觉。
const normalizeLoadingMileage = value => {
if (value === undefined || value === null || value === '' || Number(value) === -1) return '';
return value;
};
const createCopiedDialogForm = (detail = {}) => {
const form = createDialogForm();
Object.keys(form).forEach(prop => {
if (Object.prototype.hasOwnProperty.call(detail, prop)) form[prop] = detail[prop];
});
return {
...form,
id: '',
loadingNo: '',
startDate: '',
endDate: '',
dataSource: '手工创建',
currentProcessNode: '接单',
mileage: normalizeLoadingMileage(form.mileage),
taskInfoJson: '',
businessStatus: 'draft',
};
};
const unwrapRecords = res => { const unwrapRecords = res => {
const data = res?.data || res; const data = res?.data || res;
if (Array.isArray(data)) return data; if (Array.isArray(data)) return data;
@@ -1369,7 +1394,8 @@ export default {
this.syncStandaloneTagTitle(); this.syncStandaloneTagTitle();
if ( if (
to.query.mode !== from?.query?.mode || to.query.mode !== from?.query?.mode ||
String(to.query.id || '') !== String(from?.query.id || '') String(to.query.id || '') !== String(from?.query.id || '') ||
String(to.query.copyId || '') !== String(from?.query.copyId || '')
) { ) {
this.initStandaloneFormPage(); this.initStandaloneFormPage();
} }
@@ -1674,10 +1700,12 @@ export default {
async initStandaloneFormPage() { async initStandaloneFormPage() {
if (!this.isStandaloneFormPage) return; if (!this.isStandaloneFormPage) return;
const mode = this.$route.query.mode === 'edit' ? 'edit' : 'add'; const mode = this.$route.query.mode === 'edit' ? 'edit' : 'add';
const copyId = mode === 'add' ? this.$route.query.copyId : '';
await this.openLoadingDialog(mode, { await this.openLoadingDialog(mode, {
id: this.$route.query.id, id: copyId || this.$route.query.id,
copy: Boolean(copyId),
}); });
if (mode === 'add') await this.consumeLoadingCreateWaybills(); if (mode === 'add' && !copyId) await this.consumeLoadingCreateWaybills();
}, },
async consumeLoadingCreateWaybills() { async consumeLoadingCreateWaybills() {
const raw = sessionStorage.getItem(loadingCreateWaybillsStorageKey); const raw = sessionStorage.getItem(loadingCreateWaybillsStorageKey);
@@ -1711,10 +1739,11 @@ export default {
try { try {
if (row?.id) { if (row?.id) {
const res = await loadingApi.getDetail(row.id); const res = await loadingApi.getDetail(row.id);
this.dialogForm = { const detail = res.data?.data || res.data || {};
...createDialogForm(), this.dialogForm = row.copy
...(res.data?.data || res.data || {}), ? createCopiedDialogForm(detail)
}; : { ...createDialogForm(), ...detail };
this.dialogForm.mileage = normalizeLoadingMileage(this.dialogForm.mileage);
await this.restoreWaybillRows( await this.restoreWaybillRows(
this.dialogForm.waybillIdsJson, this.dialogForm.waybillIdsJson,
this.dialogForm.loadingSubNos this.dialogForm.loadingSubNos
@@ -2247,6 +2276,7 @@ export default {
normalizePayload() { normalizePayload() {
this.syncRouteAddressFields(); this.syncRouteAddressFields();
this.rebuildSummaryFromWaybills(false); this.rebuildSummaryFromWaybills(false);
this.dialogForm.mileage = normalizeLoadingMileage(this.dialogForm.mileage);
if (this.dialogForm.carrierType === '网货平台') { if (this.dialogForm.carrierType === '网货平台') {
this.dialogForm.carrierContractId = ''; this.dialogForm.carrierContractId = '';
this.dialogForm.carrierName = ''; this.dialogForm.carrierName = '';
@@ -2332,6 +2362,7 @@ export default {
...this.dialogForm, ...this.dialogForm,
...(res.data?.data || res.data || {}), ...(res.data?.data || res.data || {}),
}; };
this.dialogForm.mileage = normalizeLoadingMileage(this.dialogForm.mileage);
ElMessage.success('暂存成功'); ElMessage.success('暂存成功');
this.loadTable(); this.loadTable();
} finally { } finally {
@@ -2356,10 +2387,15 @@ export default {
this.dialogSaving = ''; this.dialogSaving = '';
} }
}, },
async copyLoading(row) { copyLoading(row) {
await loadingApi.copy(row.id); this.$router.push({
ElMessage.success('复制成功'); path: '/business/loading-manage/form',
this.loadTable(); query: {
mode: 'add',
copyId: row.id,
name: '新增配载管理',
},
});
}, },
deleteLoading(row) { deleteLoading(row) {
ElMessageBox.confirm(`确定删除配载单 ${row.loadingNo}`, '提示', { type: 'warning' }) ElMessageBox.confirm(`确定删除配载单 ${row.loadingNo}`, '提示', { type: 'warning' })
+1 -1
View File
@@ -262,7 +262,7 @@ export default {
result.unshift({ text: '调度', run: () => this.goDispatch(row.id) }); result.unshift({ text: '调度', run: () => this.goDispatch(row.id) });
if (row.businessStatus !== 'draft') if (row.businessStatus !== 'draft')
result.unshift({ text: '详情', run: () => this.goDetail(row.id) }); result.unshift({ text: '详情', run: () => this.goDetail(row.id) });
if (['dispatching', 'completed'].includes(row.businessStatus)) if (row.businessStatus === 'dispatching')
result.push({ result.push({
text: '关闭调度', text: '关闭调度',
run: () => run: () =>
+1 -1
View File
@@ -396,7 +396,7 @@ export default {
this.$message.warning('请选择至少一条审批通过的付款申请'); this.$message.warning('请选择至少一条审批通过的付款申请');
return; return;
} }
await Promise.all(rows.map(row => api.syncKingdee(row.id))); await api.syncKingdeeBatch(rows.map(row => row.id));
this.$message.success(`已同步${rows.length}条付款申请`); this.$message.success(`已同步${rows.length}条付款申请`);
this.loadTable(); this.loadTable();
}, },
@@ -254,11 +254,11 @@
{{ missingAttachmentTypeText }} {{ missingAttachmentTypeText }}
</span> </span>
</template> </template>
<div class="formal-editor__attachment-actions"> <template #extra>
<el-button type="primary" :disabled="!attachments.length" @click="downloadAttachments"> <el-button type="primary" :disabled="!attachments.length" @click="downloadAttachments">
批量下载 批量下载
</el-button> </el-button>
</div> </template>
<el-table :data="attachments" border @selection-change="handleAttachmentSelectionChange"> <el-table :data="attachments" border @selection-change="handleAttachmentSelectionChange">
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="64" align="center" /> <el-table-column type="index" label="序号" width="64" align="center" />
@@ -677,98 +677,157 @@
</el-dialog> </el-dialog>
<el-dialog <el-dialog
v-model="detailAmountAdjust.visible" v-model="adjust.visible"
title="结算明细调整" title="结算明细调整"
width="520px" width="96%"
append-to-body append-to-body
destroy-on-close
> >
<el-form
:model="detailAmountAdjust"
label-position="right"
label-width="auto"
class="formal-editor__amount-adjust"
>
<el-form-item label="原金额">
<span>{{ formatMoney(detailAmountAdjust.originalAmount) }}</span>
</el-form-item>
<el-form-item label="调整金额" required>
<el-input-number
v-model="detailAmountAdjust.adjustAmount"
:precision="2"
:step="0.01"
:controls="false"
/>
</el-form-item>
<el-form-item label="结算金额">
<span>{{ formatMoney(detailAmountAdjustSettlementAmount) }}</span>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="detailAmountAdjust.visible = false">取消</el-button>
<el-button type="primary" @click="confirmDetailAmountAdjustment">提交</el-button>
</template>
</el-dialog>
<el-dialog v-model="adjust.visible" title="结算明细调整" width="92%" append-to-body>
<el-table v-loading="adjust.loading" :data="adjust.rows" border> <el-table v-loading="adjust.loading" :data="adjust.rows" border>
<el-table-column type="index" label="序号" width="64" align="center" /> <el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
<el-table-column prop="cargoName" label="货物名称" min-width="130" 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="130" align="center" <el-table-column label="计费规则" min-width="190" align="center">
><template #default="{ row }" <template #default="{ row }">
><el-input-number <div class="formal-editor__billing-rule-cell">
<span>{{ displayValue(billingRuleName(row)) }}</span>
<el-icon
class="formal-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">
<template #default="{ row }">
<el-input-number
v-model="row.transportQuantity" v-model="row.transportQuantity"
:min="0" :min="0"
:precision="2" :precision="2"
:controls="false" /></template :controls="false"
></el-table-column> />
<el-table-column label="里程(KM" min-width="130" align="center" </template>
><template #default="{ row }" </el-table-column>
><el-input-number <el-table-column label="里程(KM" min-width="140" align="center">
v-model="row.mileage" <template #default="{ row }">
:min="0" <el-input-number v-model="row.mileage" :min="0" :precision="2" :controls="false" />
:precision="2" </template>
:controls="false" /></template </el-table-column>
></el-table-column> <el-table-column label="运输单价" min-width="140" align="center">
<el-table-column label="运输单价" min-width="130" align="center" <template #default="{ row }">
><template #default="{ row }" <el-input-number v-model="row.unitPrice" :min="0" :precision="2" :controls="false" />
><el-input-number </template>
v-model="row.unitPrice" </el-table-column>
:min="0" <el-table-column
:precision="2" v-for="feeItem in adjustFeeItemNames"
:controls="false" /></template :key="feeItem"
></el-table-column> :label="feeItem"
<el-table-column label="运费" min-width="130" align="center" min-width="140"
><template #default="{ row }" align="center"
><el-input-number
v-model="row.freightAmount"
:min="0"
:precision="2"
:controls="false" /></template
></el-table-column>
<el-table-column label="结算金额(含税)" min-width="165" align="center"
><template #default="{ row }"
><el-input-number
v-model="row.settlementAmountTax"
:min="0"
:precision="2"
:controls="false" /></template
></el-table-column>
<el-table-column label="备注" min-width="180" align="center"
><template #default="{ row }"><el-input v-model="row.remark" maxlength="200" /></template
></el-table-column>
</el-table>
<el-form label-position="right" label-width="auto" class="formal-editor__adjust-reason">
<el-form-item label="调整原因" required
><el-input v-model="adjust.reason" type="textarea" maxlength="200" show-word-limit
/></el-form-item>
</el-form>
<template #footer
><el-button @click="adjust.visible = false">取消</el-button
><el-button type="primary" :loading="adjust.saving" @click="saveAdjustment"
>保存</el-button
></template
> >
<template #default="{ row }">
<el-input-number
v-model="row.feeItems[feeItem]"
:min="0"
:precision="2"
:controls="false"
@change="recalculateAdjustRow(row, feeItem)"
/>
</template>
</el-table-column>
<el-table-column label="结算金额(含税)" min-width="170" align="center">
<template #default="{ row }">
<el-input-number v-model="row.settlementAmountTax" :precision="2" :controls="false" />
</template>
</el-table-column>
<el-table-column label="备注" min-width="200" align="center">
<template #default="{ row }">
<el-input v-model="row.remark" maxlength="200" />
</template>
</el-table-column>
</el-table>
<el-form
:model="adjust"
label-position="right"
label-width="auto"
class="formal-editor__adjust-reason"
>
<el-form-item label="调整原因" required>
<el-input v-model="adjust.reason" maxlength="200" show-word-limit />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="adjust.visible = false">取消</el-button>
<el-button type="primary" :loading="adjust.saving" @click="saveAdjustment">
保存
</el-button>
</template>
</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="formal-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 formal-editor__billing-rule-match-title">匹配条件</div>
<el-descriptions :column="2" border class="formal-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> </el-dialog>
</component> </component>
</template> </template>
@@ -786,7 +845,10 @@ import {
getReceiptClaims, getReceiptClaims,
save, save,
} from '@/api/settlement/formalSettlement'; } from '@/api/settlement/formalSettlement';
import { getDetail as getPreSettlementDetail } from '@/api/settlement/preSettlement'; import {
getDetail as getPreSettlementDetail,
getDetailFees as getPreSettlementDetailFees,
} from '@/api/settlement/preSettlement';
import { import {
createFormalSettlementForm, createFormalSettlementForm,
formalSettlementFormFields, formalSettlementFormFields,
@@ -800,12 +862,14 @@ import {
} from '@/option/settlement/formalSettlementTable'; } from '@/option/settlement/formalSettlementTable';
import { getDictionary } from '@/api/system/dictbiz'; import { getDictionary } from '@/api/system/dictbiz';
import { h } from 'vue'; import { h } from 'vue';
import { InfoFilled } from '@element-plus/icons-vue';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { downloadFileByUrl } from '@/utils/util'; import { downloadFileByUrl } from '@/utils/util';
import * as XLSX from 'xlsx'; import * as XLSX from 'xlsx';
export default { export default {
name: 'FormalSettlementEditor', name: 'FormalSettlementEditor',
components: { InfoFilled },
props: { props: {
modelValue: Boolean, modelValue: Boolean,
recordId: [String, Number], recordId: [String, Number],
@@ -893,12 +957,11 @@ export default {
detailId: null, detailId: null,
reason: '', reason: '',
rows: [], rows: [],
targetRow: null,
}, },
detailAmountAdjust: { billingRuleDialog: {
visible: false, visible: false,
row: null, rule: null,
originalAmount: 0,
adjustAmount: 0,
}, },
detailCollapsed: false, detailCollapsed: false,
detailQuery: { detailQuery: {
@@ -950,14 +1013,6 @@ export default {
summaryTotal() { summaryTotal() {
return this.summaryFees.reduce((total, row) => total + Number(row.settlementAmount || 0), 0); return this.summaryFees.reduce((total, row) => total + Number(row.settlementAmount || 0), 0);
}, },
detailAmountAdjustSettlementAmount() {
return Number(
(
Number(this.detailAmountAdjust.originalAmount || 0) +
Number(this.detailAmountAdjust.adjustAmount || 0)
).toFixed(2)
);
},
filteredDetails() { filteredDetails() {
return this.details.filter(row => return this.details.filter(row =>
Object.entries(this.appliedDetailQuery).every(([field, keyword]) => { Object.entries(this.appliedDetailQuery).every(([field, keyword]) => {
@@ -972,6 +1027,13 @@ export default {
const missing = this.getMissingAttachmentTypes(); const missing = this.getMissingAttachmentTypes();
return missing.length ? `未上传:${missing.join('、')}` : ''; return missing.length ? `未上传:${missing.join('、')}` : '';
}, },
adjustFeeItemNames() {
const names = new Set();
this.adjust.rows.forEach(row => {
Object.keys(row.feeItems || {}).forEach(name => names.add(name));
});
return Array.from(names);
},
}, },
watch: { watch: {
modelValue: { modelValue: {
@@ -993,6 +1055,34 @@ export default {
}, },
}, },
methods: { methods: {
async loadFormalDetail(id) {
const response = await getDetail(id);
const data = this.unwrapData(response);
this.form = {
...createFormalSettlementForm(),
...data,
sourcePreSettlementIds: (data.sources || []).map(item => item.preSettlementId),
sourceDetailIds: (data.details || [])
.filter(item => !item.sourcePreSettlementId)
.map(item => item.sourceDetailId),
};
this.sources = data.sources || [];
this.details = data.details || [];
this.summaryFees = data.summaryFees || [];
this.paymentApplications = data.paymentApplications || [];
if (this.readonly && this.form.settlementType === 'receivable') {
this.receiptClaims = this.unwrapData(await getReceiptClaims(id)) || [];
}
this.adjustments = data.adjustments || [];
this.changeRecords = data.changeRecords || [];
this.attachments = this.parseAttachments(data.attachmentsJson);
this.selectedAttachmentRows = [];
this.invoices = data.invoices || [];
this.sortAttachments();
this.contracts = this.allContracts.filter(
item => String(item.projectId) === String(this.form.projectId)
);
},
async initialize() { async initialize() {
const newRecordAudit = this.recordId const newRecordAudit = this.recordId
? null ? null
@@ -1014,11 +1104,9 @@ export default {
this.invoices = []; this.invoices = [];
this.invoiceClaimNo = ''; this.invoiceClaimNo = '';
this.attachmentUploadFiles = []; this.attachmentUploadFiles = [];
this.detailAmountAdjust = { this.billingRuleDialog = {
visible: false, visible: false,
row: null, rule: null,
originalAmount: 0,
adjustAmount: 0,
}; };
this.detailCollapsed = false; this.detailCollapsed = false;
this.resetDetailQuery(false); this.resetDetailQuery(false);
@@ -1037,32 +1125,7 @@ export default {
} }
this.loading = true; this.loading = true;
try { try {
const response = await getDetail(this.recordId); await this.loadFormalDetail(this.recordId);
const data = this.unwrapData(response);
this.form = {
...createFormalSettlementForm(),
...data,
sourcePreSettlementIds: (data.sources || []).map(item => item.preSettlementId),
sourceDetailIds: (data.details || [])
.filter(item => !item.sourcePreSettlementId)
.map(item => item.sourceDetailId),
};
this.sources = data.sources || [];
this.details = data.details || [];
this.summaryFees = data.summaryFees || [];
this.paymentApplications = data.paymentApplications || [];
if (this.readonly && this.form.settlementType === 'receivable') {
this.receiptClaims = this.unwrapData(await getReceiptClaims(this.recordId)) || [];
}
this.adjustments = data.adjustments || [];
this.changeRecords = data.changeRecords || [];
this.attachments = this.parseAttachments(data.attachmentsJson);
this.selectedAttachmentRows = [];
this.invoices = data.invoices || [];
this.sortAttachments();
this.contracts = this.allContracts.filter(
item => String(item.projectId) === String(this.form.projectId)
);
} finally { } finally {
this.loading = false; this.loading = false;
} }
@@ -1339,7 +1402,6 @@ export default {
const range = this.detailCandidate.query.createTimeRange || []; const range = this.detailCandidate.query.createTimeRange || [];
const params = { const params = {
contractId: this.form.contractId, contractId: this.form.contractId,
settlementType: this.form.settlementType,
batchNo: this.detailCandidate.query.batchNo, batchNo: this.detailCandidate.query.batchNo,
createStartDate: range[0], createStartDate: range[0],
createEndDate: range[1], createEndDate: range[1],
@@ -1529,66 +1591,228 @@ export default {
)}.xlsx` )}.xlsx`
); );
}, },
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')
: [];
const rule = this.parseObject(matchedRules[0] || parsedNested || 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);
return {
...rule,
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 ||
'',
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) {
if (changedField && this.isFreightFeeItem(changedField)) {
row.freightAmount = Number(row.feeItems[changedField] || 0);
}
const feeItemTotal = Object.values(row.feeItems || {}).reduce(
(total, value) => total + Number(value || 0),
0
);
const hasFreightItem = Object.keys(row.feeItems || {}).some(this.isFreightFeeItem);
row.settlementAmountTax = Number(
(hasFreightItem ? feeItemTotal : Number(row.freightAmount || 0) + feeItemTotal).toFixed(2)
);
},
async openAdjustDialog(row) { async openAdjustDialog(row) {
if (!row.formalSettlementId) { if (!row.formalSettlementId) {
const originalAmount = Number(
row.originalAmount ??
row.totalAmount ??
Number(row.settlementAmountTax || 0) - Number(row.adjustAmount || 0)
);
this.detailAmountAdjust = {
visible: true,
row,
originalAmount: Number(originalAmount.toFixed(2)),
adjustAmount: Number(row.adjustAmount || 0),
};
return;
}
this.adjust = { this.adjust = {
visible: true, visible: true,
loading: true, loading: true,
saving: false, saving: false,
detailId: row.id, detailId: null,
reason: '', reason: '',
rows: [], rows: [],
targetRow: row,
}; };
try { try {
const response = await getDetailFees(row.id); let feeRows = [];
if (row.sourcePreSettlementId && row.id) {
const response = await getPreSettlementDetailFees(row.id);
feeRows = this.unwrapData(response) || [];
}
this.adjust.rows = (feeRows.length ? feeRows : [row]).map(item => ({
...item,
feeItems: this.parseFeeItems(item.feeItemsJson || item.feeItems),
billingRule: this.normalizeBillingRule(item),
settlementAmountTax:
item.settlementAmountTax ??
item.totalAmount ??
item.afterAmount ??
item.settlementAmount ??
0,
}));
} finally {
this.adjust.loading = false;
}
return;
}
const detailRow = row;
this.adjust = {
visible: true,
loading: true,
saving: false,
detailId: detailRow.id,
reason: '',
rows: [],
targetRow: null,
};
try {
const response = await getDetailFees(detailRow.id);
const data = this.unwrapData(response); const data = this.unwrapData(response);
this.adjust.rows = (data || []).map(item => ({ this.adjust.rows = (data || []).map(item => ({
...item, ...item,
feeItems: this.parseFeeItems(item.feeItemsJson), feeItems: this.parseFeeItems(item.feeItemsJson),
billingRule: this.normalizeBillingRule(item),
})); }));
} finally { } finally {
this.adjust.loading = false; this.adjust.loading = false;
} }
}, },
confirmDetailAmountAdjustment() { async saveAdjustment() {
const settlementAmount = this.detailAmountAdjustSettlementAmount; if (!this.adjust.reason.trim()) return this.$message.warning('请输入调整原因');
if (!this.adjust.detailId) {
const target = this.adjust.targetRow;
const edited = this.adjust.rows[0];
if (!target || !edited) return;
const originalAmount = Number(
target.originalAmount ??
target.totalAmount ??
target.afterAmount ??
target.settlementAmountTax ??
target.settlementAmount ??
0
);
const settlementAmount = Number(
this.adjust.rows
.reduce((total, item) => total + Number(item.settlementAmountTax || 0), 0)
.toFixed(2)
);
const feeItems = this.adjust.rows.reduce((result, item) => {
Object.entries(item.feeItems || {}).forEach(([name, amount]) => {
result[name] = Number((Number(result[name] || 0) + Number(amount || 0)).toFixed(2));
});
return result;
}, {});
if (!Number.isFinite(settlementAmount) || settlementAmount < 0) { if (!Number.isFinite(settlementAmount) || settlementAmount < 0) {
return this.$message.warning('调整后的结算金额不能小于0'); return this.$message.warning('调整后的结算金额不能小于0');
} }
const row = this.detailAmountAdjust.row; Object.assign(target, {
if (!row) return; originalAmount,
row.originalAmount = this.detailAmountAdjust.originalAmount; adjustAmount: Number((settlementAmount - originalAmount).toFixed(2)),
row.adjustAmount = Number(this.detailAmountAdjust.adjustAmount || 0); settlementAmountTax: settlementAmount,
row.settlementAmountTax = settlementAmount; feeItemsJson: JSON.stringify(feeItems),
row.pendingDetailAdjustment = true; freightAmount: this.adjust.rows.reduce(
(total, item) => total + Number(item.freightAmount || 0),
0
),
transportQuantity: this.adjust.rows.reduce(
(total, item) => total + Number(item.transportQuantity || 0),
0
),
mileage: this.adjust.rows.reduce((total, item) => total + Number(item.mileage || 0), 0),
unitPrice: edited.unitPrice,
remark: edited.remark,
pendingDetailAdjustment: true,
});
this.buildSummaryFeesFromDetails(); this.buildSummaryFeesFromDetails();
this.detailAmountAdjust.visible = false; this.adjust.visible = false;
}, this.$message.success('调整已暂存,请保存正式结算单提交');
async saveAdjustment() { return;
if (!this.adjust.reason.trim()) return this.$message.warning('请输入调整原因'); }
this.adjust.saving = true; this.adjust.saving = true;
try { try {
await adjustDetail({ await adjustDetail({
detailId: this.adjust.detailId, detailId: this.adjust.detailId,
changeReason: this.adjust.reason, changeReason: this.adjust.reason,
rows: this.adjust.rows, rows: this.adjust.rows.map(row => ({
id: row.id,
transportQuantity: row.transportQuantity,
mileage: row.mileage,
unitPrice: row.unitPrice,
freightAmount: row.freightAmount,
feeItems: row.feeItems,
settlementAmountTax: row.settlementAmountTax,
settlementAmountNoTax: row.settlementAmountNoTax,
remark: row.remark,
})),
}); });
this.$message.success('调整保存成功'); this.$message.success('调整保存成功');
this.adjust.visible = false; this.adjust.visible = false;
await this.initialize(); await this.loadFormalDetail(this.form.id);
} finally { } finally {
this.adjust.saving = false; this.adjust.saving = false;
} }
@@ -1715,24 +1939,8 @@ export default {
if (!url) return this.$message.warning('当前Mock发票暂无可下载附件'); if (!url) return this.$message.warning('当前Mock发票暂无可下载附件');
downloadFileByUrl(url, this.invoiceAttachmentName(row)); downloadFileByUrl(url, this.invoiceAttachmentName(row));
}, },
async handleSave() { buildSavePayload() {
await this.$refs.formRef.validate(); return {
if (!this.form.sourcePreSettlementIds.length && !this.form.sourceDetailIds.length) {
return this.$message.warning('请选择结算明细');
}
const invalidManualFee = this.summaryFees.find(
row =>
row.manualFlag === 1 &&
(!String(row.feeType || '').trim() || !String(row.feeItem || '').trim())
);
if (invalidManualFee) {
return this.$message.warning('请完善手工费用的费用类型和费用项');
}
if (!this.validateInvoices()) return;
if (!this.validateAttachments()) return;
this.saving = true;
try {
await save({
id: this.form.id, id: this.form.id,
contractId: this.form.contractId, contractId: this.form.contractId,
settlementType: this.form.settlementType, settlementType: this.form.settlementType,
@@ -1776,7 +1984,26 @@ export default {
attachmentJson: row.attachmentJson || '', attachmentJson: row.attachmentJson || '',
})), })),
remark: this.form.remark, remark: this.form.remark,
}); };
},
async handleSave() {
await this.$refs.formRef.validate();
if (!this.form.sourcePreSettlementIds.length && !this.form.sourceDetailIds.length) {
return this.$message.warning('请选择结算明细');
}
const invalidManualFee = this.summaryFees.find(
row =>
row.manualFlag === 1 &&
(!String(row.feeType || '').trim() || !String(row.feeItem || '').trim())
);
if (invalidManualFee) {
return this.$message.warning('请完善手工费用的费用类型和费用项');
}
if (!this.validateInvoices()) return;
if (!this.validateAttachments()) return;
this.saving = true;
try {
await save(this.buildSavePayload());
this.$message.success('保存成功'); this.$message.success('保存成功');
this.visible = false; this.visible = false;
this.$emit('success'); this.$emit('success');
@@ -1994,6 +2221,19 @@ export default {
return {}; return {};
} }
}, },
parseJsonValue(value) {
if (value === undefined || value === null || value === '') return null;
if (typeof value === 'object') return value;
try {
return JSON.parse(value);
} catch {
return value;
}
},
parseObject(value) {
const parsed = this.parseJsonValue(value);
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
},
parseAttachments(value) { parseAttachments(value) {
if (!value) return []; if (!value) return [];
let attachments = value; let attachments = value;
@@ -2102,8 +2342,37 @@ export default {
.formal-editor__adjust-reason { .formal-editor__adjust-reason {
margin-top: 16px; margin-top: 16px;
} }
.formal-editor__amount-adjust :deep(.el-input-number) { .formal-editor__billing-rule-cell {
width: 100%; display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
max-width: 100%;
}
.formal-editor__billing-rule-icon {
flex: 0 0 auto;
color: var(--el-color-primary);
cursor: pointer;
}
.formal-editor__billing-rule-detail {
margin-top: 12px;
}
.formal-editor__billing-rule-match-title {
margin-top: 20px;
}
.dialog-section-title {
display: flex;
align-items: center;
min-height: 22px;
font-size: 16px;
font-weight: 600;
}
.dialog-section-title::before {
width: 4px;
height: 18px;
margin-right: 8px;
background: #409eff;
content: '';
} }
.formal-editor__attachment-missing { .formal-editor__attachment-missing {
margin-left: 12px; margin-left: 12px;
@@ -2111,11 +2380,6 @@ export default {
font-size: 13px; font-size: 13px;
font-weight: 400; font-weight: 400;
} }
.formal-editor__attachment-actions {
display: flex;
justify-content: flex-end;
margin-bottom: 12px;
}
.formal-editor__attachment-upload { .formal-editor__attachment-upload {
margin-top: 12px; margin-top: 12px;
} }
@@ -82,7 +82,12 @@
添加费用 添加费用
</el-button> </el-button>
</template> </template>
<el-table :data="summaryFees" border> <el-table
:data="summaryFees"
border
show-summary
:summary-method="getSummarySums"
>
<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 summaryColumns" v-for="column in summaryColumns"
@@ -161,9 +166,6 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<div class="pre-settlement-editor__summary-total">
合计:{{ formatMoney(summaryTotal, form.currency) }}
</div>
</section-card> </section-card>
<section-card title="结算明细"> <section-card title="结算明细">
@@ -298,12 +300,12 @@
</section-card> </section-card>
<section-card title="附件"> <section-card title="附件">
<div class="pre-settlement-editor__attachment"> <template #extra>
<div class="pre-settlement-editor__attachment-actions">
<el-button type="primary" :disabled="!attachments.length" @click="downloadAttachments"> <el-button type="primary" :disabled="!attachments.length" @click="downloadAttachments">
批量下载 批量下载
</el-button> </el-button>
</div> </template>
<div class="pre-settlement-editor__attachment">
<el-table :data="attachments" border @selection-change="handleAttachmentSelectionChange"> <el-table :data="attachments" border @selection-change="handleAttachmentSelectionChange">
<el-table-column type="selection" width="55" align="center" /> <el-table-column type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="70" align="center" /> <el-table-column type="index" label="序号" width="70" align="center" />
@@ -846,6 +848,7 @@
</template> </template>
<script> <script>
import { h } from 'vue';
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { InfoFilled } from '@element-plus/icons-vue'; import { InfoFilled } from '@element-plus/icons-vue';
import { import {
@@ -1506,6 +1509,19 @@ export default {
if (Number(row.manualFlag) !== 1) return; 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);
}, },
getSummarySums({ columns, data }) {
const sumProps = ['originalAmount', 'adjustAmount', 'settlementAmount'];
return columns.map((column, index) => {
if (index === 0) return '合计';
if (!sumProps.includes(column.property)) return '';
const total = data.reduce((sum, row) => sum + Number(row[column.property] || 0), 0);
const totalText = this.formatMoney(total, this.form.currency);
if (column.property === 'adjustAmount' && total < 0) {
return h('span', { style: { color: 'var(--el-color-danger)' } }, totalText);
}
return totalText;
});
},
buildSummaryFeesFromDetails() { buildSummaryFeesFromDetails() {
const summaryMap = new Map(); const summaryMap = new Map();
const defaultFreightItem = const defaultFreightItem =
@@ -1630,9 +1646,9 @@ export default {
this.details.push(this.normalizeSourceDetail(row)); this.details.push(this.normalizeSourceDetail(row));
} }
}); });
this.candidateDialog.confirming = true;
if (this.deferSave) {
this.buildSummaryFeesFromDetails(); this.buildSummaryFeesFromDetails();
this.candidateDialog.confirming = true;
if (this.deferSave || (this.pageMode && !this.form.id)) {
this.candidateDialog.visible = false; this.candidateDialog.visible = false;
this.candidateDialog.confirming = false; this.candidateDialog.confirming = false;
this.$message.success('结算明细添加成功,请点击保存提交'); this.$message.success('结算明细添加成功,请点击保存提交');
@@ -1678,10 +1694,6 @@ export default {
const isPersistedDetail = const isPersistedDetail =
row.id && sourceDetailId && String(row.id) !== String(sourceDetailId); row.id && sourceDetailId && String(row.id) !== String(sourceDetailId);
if (isPersistedDetail) return row; if (isPersistedDetail) return row;
if (this.deferSave) {
this.$message.warning('请先保存预结算单后再调整结算明细');
return null;
}
await this.$refs.formRef?.validate(); await this.$refs.formRef?.validate();
this.loading = true; this.loading = true;
@@ -2136,14 +2148,6 @@ export default {
} }
} }
&__attachment-actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
margin-bottom: 12px;
}
&__attachment-upload { &__attachment-upload {
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
@@ -2158,12 +2162,6 @@ export default {
} }
} }
&__summary-total {
padding: 12px 16px 0;
text-align: right;
font-weight: 600;
}
&__negative-amount { &__negative-amount {
color: var(--el-color-danger); color: var(--el-color-danger);
} }
@@ -228,11 +228,6 @@
<div <div
class="settlement-detail-page__detail-panel-body settlement-detail-page__adjust-panel-body" class="settlement-detail-page__detail-panel-body settlement-detail-page__adjust-panel-body"
> >
<div v-if="isPayable" class="settlement-detail-page__adjust-toolbar">
<el-button type="primary" :disabled="adjustDialog.loading" @click="addAdjustFee">
新增费用
</el-button>
</div>
<el-table v-loading="adjustDialog.loading" :data="adjustRows" border> <el-table v-loading="adjustDialog.loading" :data="adjustRows" border>
<el-table-column type="index" label="序号" width="64" align="center" /> <el-table-column type="index" label="序号" width="64" align="center" />
<el-table-column label="来源" width="110" align="center"> <el-table-column label="来源" width="110" align="center">
@@ -1139,13 +1134,21 @@ export default {
return this.searchExpanded ? this.searchFields : this.searchFields.slice(0, 4); return this.searchExpanded ? this.searchFields : this.searchFields.slice(0, 4);
}, },
feeDetailColumns() { feeDetailColumns() {
return [...feeDetailBaseColumns, ...this.dynamicFeeColumns, ...feeDetailTailColumns]; const baseColumns = feeDetailBaseColumns.filter(column => column.prop !== 'freightAmount');
return [...baseColumns, ...this.dynamicFeeColumns, ...feeDetailTailColumns];
}, },
displayTableColumns() { displayTableColumns() {
const columns = this.tableColumns.filter( const columns = this.tableColumns.filter(
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 otherFeeColumn = {
label: '其他费用',
prop: 'tableOtherFeeAmount',
feeSummaryType: 'other',
minWidth: 130,
align: 'right',
};
const dynamicColumns = this.isPayable const dynamicColumns = this.isPayable
? [ ? [
{ {
@@ -1155,22 +1158,19 @@ export default {
minWidth: 130, minWidth: 130,
align: 'right', align: 'right',
}, },
{ otherFeeColumn,
label: '其他费用',
prop: 'tableOtherFeeAmount',
feeSummaryType: 'other',
minWidth: 130,
align: 'right',
},
] ]
: this.tableFeeItemNames.map((name, index) => ({ : [
...this.tableFeeItemNames.map((name, index) => ({
label: name, label: name,
prop: `tableFeeItem${index}`, prop: `tableFeeItem${index}`,
feeItemName: name, feeItemName: name,
dynamic: true, dynamic: true,
minWidth: 130, minWidth: 130,
align: 'right', align: 'right',
})); })),
otherFeeColumn,
];
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;
@@ -1179,6 +1179,7 @@ export default {
return [...generatePreviewColumns, ...this.dynamicFeeColumns, ...feeDetailTailColumns]; return [...generatePreviewColumns, ...this.dynamicFeeColumns, ...feeDetailTailColumns];
}, },
adjustFeeColumns() { adjustFeeColumns() {
const baseColumns = feeDetailBaseColumns.filter(column => column.prop !== 'freightAmount');
const tailColumns = feeDetailTailColumns.filter(column => column.prop !== 'remark'); const tailColumns = feeDetailTailColumns.filter(column => column.prop !== 'remark');
const afterAmountIndex = tailColumns.findIndex(column => column.prop === 'afterAmountText'); const afterAmountIndex = tailColumns.findIndex(column => column.prop === 'afterAmountText');
tailColumns.splice(afterAmountIndex + 1, 0, { tailColumns.splice(afterAmountIndex + 1, 0, {
@@ -1186,7 +1187,7 @@ export default {
prop: 'changeReason', prop: 'changeReason',
minWidth: 220, minWidth: 220,
}); });
return [...feeDetailBaseColumns, ...this.adjustDynamicFeeColumns, ...tailColumns]; return [...baseColumns, ...this.adjustDynamicFeeColumns, ...tailColumns];
}, },
transportCargoTypeOptions() { transportCargoTypeOptions() {
return this.cargoTypeOptions.filter(item => item.children?.length); return this.cargoTypeOptions.filter(item => item.children?.length);
@@ -1741,6 +1742,7 @@ export default {
freightAmount: Number(item.freightAmount || 0), freightAmount: Number(item.freightAmount || 0),
originalAmount: Number(item.originalAmount || 0), originalAmount: Number(item.originalAmount || 0),
feeItems, feeItems,
changeReason: '',
dataSource: dataSource:
item.dataSource || (item.billingFactor === '手工调整' ? '手动录入' : '自动生成'), item.dataSource || (item.billingFactor === '手工调整' ? '手动录入' : '自动生成'),
manualFee: manualFee:
@@ -1758,39 +1760,6 @@ export default {
this.adjustDialog.loading = false; this.adjustDialog.loading = false;
} }
}, },
addAdjustFee() {
if (!this.isPayable) return;
const feeItems = this.adjustDynamicFeeColumns.reduce((items, column) => {
items[column.feeItemName] = 0;
return items;
}, {});
this.adjustRows.push({
dataSource: '手动录入',
cargoName: '',
cargoType: '',
cargoTypePath: [],
specification: '',
model: '',
billingFactor: '',
billingType: '',
transportQuantity: '',
priceUnit: '',
unitPrice: '',
mileage: null,
freightAmount: '',
originalAmount: 0,
originalAmountText: '-',
adjustAmount: 0,
afterAmount: 0,
feeItems,
manualFee: true,
remark: '',
changeReason: '',
calculating: false,
adjustCalculateTimer: null,
adjustCalculateVersion: 0,
});
},
feeSourceLabel(value) { feeSourceLabel(value) {
return ['手动录入', '手动添加', '手工录入'].includes(value) ? '手动录入' : '自动生成'; return ['手动录入', '手动添加', '手工录入'].includes(value) ? '手动录入' : '自动生成';
}, },
@@ -1908,6 +1877,11 @@ export default {
try { try {
await api.adjustFee({ await api.adjustFee({
detailId: this.adjustDialog.row.id, detailId: this.adjustDialog.row.id,
adjustReason: [
...new Set(
this.adjustRows.map(row => String(row.changeReason || '').trim()).filter(Boolean)
),
].join(''),
rows: this.adjustRows.map(row => ({ rows: this.adjustRows.map(row => ({
id: row.id, id: row.id,
cargoName: String(row.cargoName || '').trim(), cargoName: String(row.cargoName || '').trim(),
@@ -2768,14 +2742,6 @@ export default {
} }
} }
.settlement-detail-page__adjust-toolbar {
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: 12px;
background: transparent;
}
.settlement-detail-page__adjust-control { .settlement-detail-page__adjust-control {
width: 100%; width: 100%;
} }