From f858eb2bcddbfb9fa0f2df95af8218772a197e57 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Tue, 1 Sep 2026 11:52:22 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=94=B6=E4=BB=98?= =?UTF-8?q?=E6=AC=BE=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../payment/invoice-application-form.vue | 13 ++++ .../payment/payment-application-form.vue | 72 +++++++++++-------- .../components/pre-settlement-editor.vue | 36 ++++++++-- vite.config.mjs | 26 +++---- 4 files changed, 97 insertions(+), 50 deletions(-) diff --git a/src/views/payment/invoice-application-form.vue b/src/views/payment/invoice-application-form.vue index d759f52..fa4b1d1 100644 --- a/src/views/payment/invoice-application-form.vue +++ b/src/views/payment/invoice-application-form.vue @@ -1091,6 +1091,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() { return { id: this.form.id, @@ -1136,6 +1143,12 @@ export default { return true; }, async submitForm() { + try { + this.validateAvailableInvoiceAmount(); + } catch (error) { + this.$message.warning(error.message); + return; + } const saved = await this.saveDraft(); if (!saved) return; await api.submit({ id: this.form.id }); diff --git a/src/views/payment/payment-application-form.vue b/src/views/payment/payment-application-form.vue index 555281a..c40f27f 100644 --- a/src/views/payment/payment-application-form.vue +++ b/src/views/payment/payment-application-form.vue @@ -162,10 +162,10 @@ > - {{ form.bankAccount || '-' }} + + + + :value="item.id" + /> + + +
- 取消 + 同步 保存 @@ -368,6 +373,7 @@ @click="submitForm" >提交 + 返回
{ - const paidDate = this.$dayjs().subtract(index, 'day').format('YYYY-MM-DD'); + syncPaymentRecords() { + const appliedCents = Math.max(0, Math.round(Number(this.form.appliedAmount || 0) * 100)); + if (!appliedCents) { + this.$message.warning('请先填写申请付款金额'); + return; + } + 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 paidDate = this.$dayjs() + .subtract(Math.floor(Math.random() * 30), 'day') + .format('YYYY-MM-DD'); return { - paidAmount: Number((value / 100).toFixed(2)), + paidAmount: Number((paidCents / 100).toFixed(2)), paidDate, - paymentNo: `MOCK-${normalizedSettlementNo}-${sequence}`, + paymentNo: `MOCK-PAY-${timestamp}-${sequence}`, voucherJson: '', - kingdeeBillNo: `MOCK-KD${paidDate.replaceAll('-', '')}${sequence}`, + kingdeeBillNo: `MOCK-KD-${timestamp}-${sequence}`, }; }); - }, - referencePaymentAmount(settlementAmount) { - return Number( - ((Number(settlementAmount || 0) * Number(this.form.paymentRatio || 0)) / 100).toFixed(2) - ); + this.$message.success(`同步成功,已生成${recordCount}条付款记录`); }, applyTransferredPreSettlements(rows = []) { if (!rows.length) return; @@ -1246,7 +1263,7 @@ export default { billType: '预结算单', appliedAmount: Number(((settlementAmount * paymentRatio) / 100).toFixed(2)), }); - this.createMockPaymentRecords(this.form.preSettlementNo, this.form.appliedAmount); + this.paymentRecords = []; this.$nextTick(() => { this.amountSyncing = false; this.$refs.formRef?.clearValidate(); @@ -1957,10 +1974,7 @@ export default { detail?.invoices, detail?.formalSettlementNo || row.formalSettlementNo ); - this.createMockPaymentRecords( - row.formalSettlementNo, - this.referencePaymentAmount(settlementAmount) - ); + this.paymentRecords = []; await Promise.all([ this.loadReceiptAccountOptions(), this.loadAttachmentRuleData(detail?.id ? [detail] : []), @@ -2019,10 +2033,6 @@ export default { }); this.referenceVisible = false; this.refreshReferenceValidation(); - this.createMockPaymentRecords( - row.preSettlementNo, - this.referencePaymentAmount(settlementAmount) - ); const detail = this.unwrapData(detailResponse); await Promise.all([ this.loadReceiptAccountOptions(), diff --git a/src/views/settlement/components/pre-settlement-editor.vue b/src/views/settlement/components/pre-settlement-editor.vue index a616f2f..493383f 100644 --- a/src/views/settlement/components/pre-settlement-editor.vue +++ b/src/views/settlement/components/pre-settlement-editor.vue @@ -1300,18 +1300,41 @@ export default { }; this.appliedDetailQuery = { ...this.detailQuery }; }, - async openAdjustDialog(row, readonly) { - if (!row.id || row.id === row.sourceDetailId) { - this.$message.warning('请先保存预结算单'); - return; + async persistDetailForAdjustment(row) { + const sourceDetailId = row.sourceDetailId || row.id; + const isPersistedDetail = + row.id && sourceDetailId && String(row.id) !== String(sourceDetailId); + if (isPersistedDetail) return row; + + 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.loading = true; this.adjustDialog.readonly = readonly; - this.adjustDialog.detailId = row.id; + this.adjustDialog.detailId = detailRow.id; this.adjustDialog.reason = ''; try { - const { data } = await getDetailFees(row.id); + const { data } = await getDetailFees(detailRow.id); this.adjustRows = (data?.data || []).map(item => ({ ...item, feeItems: this.parseFeeItems(item.feeItemsJson), @@ -1545,6 +1568,7 @@ export default { &--page &__content { max-height: none; + padding-bottom: 72px; overflow: visible; } diff --git a/vite.config.mjs b/vite.config.mjs index 45a377c..1726a3c 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -47,26 +47,26 @@ export default ({ mode, command }) => { __VUE_I18N_LEGACY_API__: true, __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: { - port: 2889, + port: 2888, proxy: { '/api': { - target: 'http://172.16.203.228:8000', + target: 'http://localhost', + //target: 'https://saber3.bladex.cn/api', changeOrigin: true, + rewrite: path => path.replace(/^\/api/, ''), }, }, }, + // server: { + // port: 2889, + // proxy: { + // '/api': { + // target: 'http://172.16.203.228:8000', + // changeOrigin: true, + // }, + // }, + // }, resolve: { alias: { '~': resolve(__dirname, './'), From 80a15fc6262829f635057c95627ab4a6de8d137d Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Tue, 1 Sep 2026 17:35:53 +0800 Subject: [PATCH 2/3] =?UTF-8?q?1=E3=80=81=E8=B0=83=E6=95=B4=E6=94=B6?= =?UTF-8?q?=E4=BB=98=E6=AC=BE=202=E3=80=81=E8=B0=83=E6=95=B4=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E9=85=8D=E7=BD=AE=203=E3=80=81=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E7=BB=93=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/settlement/preSettlement.js | 12 + .../settlement/receivable-payable-detail.js | 7 + src/views/base/fee-item.vue | 68 ++- src/views/base/port-terminal.vue | 4 +- src/views/base/railway-station.vue | 18 +- .../components/pre-settlement-editor.vue | 392 +++++++++++-- .../settlement/receivable-payable-detail.vue | 552 ++++++++++++++++-- 7 files changed, 932 insertions(+), 121 deletions(-) diff --git a/src/api/settlement/preSettlement.js b/src/api/settlement/preSettlement.js index 2b96d13..bb61417 100644 --- a/src/api/settlement/preSettlement.js +++ b/src/api/settlement/preSettlement.js @@ -10,6 +10,18 @@ export const getDetail = id => request({ url: `${baseUrl}/detail`, method: 'get' export const getContractOptions = 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 getCandidateDetails = (current, size, params) => diff --git a/src/option/settlement/receivable-payable-detail.js b/src/option/settlement/receivable-payable-detail.js index dd91680..745ca2e 100644 --- a/src/option/settlement/receivable-payable-detail.js +++ b/src/option/settlement/receivable-payable-detail.js @@ -138,4 +138,11 @@ export const generatePreviewColumns = [ { label: '货物名称', prop: 'cargoName', minWidth: 140 }, { label: '货物类型', prop: 'cargoType', 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 }, ]; diff --git a/src/views/base/fee-item.vue b/src/views/base/fee-item.vue index de2b752..fd56038 100644 --- a/src/views/base/fee-item.vue +++ b/src/views/base/fee-item.vue @@ -35,7 +35,6 @@ v-model="form.feeCategory" class="fee-item-form-control" clearable - :disabled="dialogType !== 'add'" filterable placeholder="请选择费用类型" @change="handleFeeCategoryChange" @@ -43,7 +42,7 @@ @@ -54,7 +53,6 @@ :maxlength="feeItemCodeMaxlength" class="fee-item-form-control" clearable - :disabled="dialogType !== 'add'" placeholder="请输入费用项代码" @change="handleFeeItemCodeChange" > @@ -339,31 +337,63 @@ export default { values.englishName = this.getFeeItemCodeSuffix(values.englishName, values.feeCategory); 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) { - submit(this.normalizeRow(row)).then( - () => { + const submitRow = this.normalizeRow(row); + this.validateUnique(submitRow) + .then(() => submit(submitRow)) + .then(() => { this.onLoad(this.page); this.$message({ type: 'success', message: '操作成功!' }); done(); - }, - error => { - window.console.log(error); - loading(); - } - ); + }) + .catch(error => this.handleSubmitError(error, 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.$message({ type: 'success', message: '操作成功!' }); done(); - }, - error => { - window.console.log(error); - loading(); - } - ); + }) + .catch(error => this.handleSubmitError(error, loading)); }, rowDel(row) { this.$confirm('确定将选择数据删除?', { diff --git a/src/views/base/port-terminal.vue b/src/views/base/port-terminal.vue index 79589de..94ed862 100644 --- a/src/views/base/port-terminal.vue +++ b/src/views/base/port-terminal.vue @@ -110,7 +110,9 @@ maxlength="24" placeholder="请输入码头标识" @input="handleTerminalCodeChange" - /> + > + + @@ -698,6 +890,28 @@ import { exportBlob } from '@/api/common'; import { getDictionary } from '@/api/system/dictbiz'; import { downloadXls } from '@/utils/util'; 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 = [ '按重量', @@ -777,15 +991,22 @@ export default { priceUnitRequest: null, changeRows: [], 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 }, updateFeeForm: { contractId: '', billingPlanId: '' }, updateFeeRules: { contractId: [{ required: true, message: '请选择更新范围', trigger: 'change' }], }, contractOptions: [], - contractLoading: false, updateContractOptions: [], + contractCategoryOptions, + signTypeOptions, + effectiveTypeOptions, + contractStageOptions, + contractApprovalStatusOptions, + contractSelectionColumns, transportTypeOptions: [], billingPlanOptions: [], transferDialog: { visible: false, loading: false, submitting: false }, @@ -796,6 +1017,25 @@ export default { transferPage: { current: 1, size: 10, total: 0 }, generateDialog: { visible: false, loading: false }, 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: [], generateSelection: [], generatePage: { current: 1, size: 10, total: 0 }, @@ -805,6 +1045,30 @@ export default { }; }, 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() { if (this.settlementType === 'receivable') return '应收'; if (this.settlementType === 'payable') return '应付'; @@ -830,14 +1094,31 @@ export default { column => this.settlementType !== 'receivable' || column.prop !== 'preSettlementNo' ); const totalIndex = columns.findIndex(column => column.prop === 'totalAmountText'); - const dynamicColumns = this.tableFeeItemNames.map((name, index) => ({ - label: name, - prop: `tableFeeItem${index}`, - feeItemName: name, - dynamic: true, - minWidth: 130, - align: 'right', - })); + const dynamicColumns = this.isPayable + ? [ + { + label: '运输费', + prop: 'tableFreightAmount', + feeSummaryType: 'freight', + 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]; columns.splice(totalIndex, 0, ...dynamicColumns); return columns; @@ -846,12 +1127,14 @@ export default { return [...generatePreviewColumns, ...this.dynamicFeeColumns, ...feeDetailTailColumns]; }, adjustFeeColumns() { - return [ - ...feeDetailBaseColumns, - ...this.adjustDynamicFeeColumns, - ...feeDetailTailColumns, - { label: '变更原因', prop: 'changeReason', minWidth: 220 }, - ]; + const tailColumns = feeDetailTailColumns.filter(column => column.prop !== 'remark'); + const afterAmountIndex = tailColumns.findIndex(column => column.prop === 'afterAmountText'); + tailColumns.splice(afterAmountIndex + 1, 0, { + label: '调整原因', + prop: 'changeReason', + minWidth: 220, + }); + return [...feeDetailBaseColumns, ...this.adjustDynamicFeeColumns, ...tailColumns]; }, transportCargoTypeOptions() { return this.cargoTypeOptions.filter(item => item.children?.length); @@ -1114,6 +1397,12 @@ export default { formatColumnValue(row, column) { if (column.prop === 'transportType') return this.transportTypeLabel(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) { return this.money( this.normalizeFeeItems(row.feeItems)[column.feeItemName], @@ -1137,25 +1426,80 @@ export default { const res = await api.getList(this.page.current, this.page.size, params); const data = this.unwrapPage(res); 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; } finally { this.loading = false; } }, - async loadContracts() { - const contractCategory = this.settlementType === 'payable' ? '承运商合同' : '客户合同'; - this.contractLoading = true; + openGenerateContractDialog() { + this.generateContractDialog.visible = true; + this.generateContractDialog.expanded = false; + this.generateContractDialog.current = null; + this.generateContractDialog.page.current = 1; + this.loadGenerateContracts(); + }, + async loadGenerateContracts() { + this.generateContractDialog.loading = true; try { - const res = await getContractList(1, 999, { - contractCategory, - }); + const res = await getContractList( + this.generateContractDialog.page.current, + this.generateContractDialog.page.size, + { + ...this.generateContractDialog.query, + contractCategory: this.generateContractCategory, + approvalStatuses: 'approved,change_approved', + } + ); const data = this.unwrapPage(res); - this.contractOptions = data.records || []; + this.generateContractDialog.rows = data.records || []; + this.generateContractDialog.page.total = Number(data.total || 0); } finally { - this.contractLoading = false; + this.generateContractDialog.loading = false; } }, + 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() { this.page.current = 1; this.loadTable(); @@ -1177,6 +1521,7 @@ export default { closeDetailPanel() { this.detailDialog.visible = false; this.detailDialog.row = null; + this.changeRecordsDialog.visible = false; }, closeAdjustPanel() { this.clearAdjustCalculations(); @@ -1525,11 +1870,31 @@ export default { } }, 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() { if (!this.detailDialog.row) return; - this.changeDialog.loading = true; + this.changeRecordsDialog.loading = true; try { const res = await api.getChangeRecords(this.changePage.current, this.changePage.size, { detailId: this.detailDialog.row.id, @@ -1538,7 +1903,7 @@ export default { this.changeRows = data.records || []; this.changePage.total = data.total || 0; } finally { - this.changeDialog.loading = false; + this.changeRecordsDialog.loading = false; } }, handleChangeSizeChange() { @@ -1792,11 +2157,26 @@ export default { this.generateDialog.visible = true; this.generateQuery = {}; 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.generateRows = []; this.generateSelection = []; this.generatePage = { current: 1, size: 10, total: 0 }; - this.loadContracts(); }, handleGenerateContractChange(contractId) { const options = this.syncBillingPlanOptions(contractId); @@ -1911,6 +2291,7 @@ export default { row.vehicleNumber || sourceWaybill?.vehicleNo || '', + transportQuantityText: row.transportQuantityText ?? row.transportQuantity ?? '', ...dynamic, }; }); @@ -2032,6 +2413,16 @@ export default { 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) { if (value === null || value === undefined || value === '') return '-'; return `${Number(value).toFixed(2)} ${currency}`; @@ -2160,6 +2551,12 @@ export default { padding: 16px 0; } +.settlement-detail-page__empty { + padding: 24px 0; + color: #909399; + text-align: center; +} + .settlement-detail-page__dialog-form { padding: 16px 20px; background: #fff; @@ -2212,6 +2609,39 @@ export default { 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 { margin-bottom: 16px; padding: 12px 12px 4px; From 252df72f879a364d7dd4e750e98edae7978a03f7 Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Wed, 2 Sep 2026 03:22:51 +0800 Subject: [PATCH 3/3] =?UTF-8?q?1=E3=80=81=E8=B0=83=E6=95=B4=E4=B8=9A?= =?UTF-8?q?=E5=8A=A1=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/business/waybill-manage.js | 1 + src/api/payment/billLedger.js | 6 + src/api/payment/paymentApplication.js | 5 - src/option/business/transport-plan.js | 34 +- src/option/business/waybill-manage.js | 67 ++- src/option/payment/invoiceApplication.js | 4 +- .../components/business-crud-page.vue | 392 +++++++++++---- .../components/master-order-dispatch.vue | 12 +- .../components/master-order-editor.vue | 8 +- .../payment/invoice-application-form.vue | 37 +- src/views/payment/invoice-receipt-form.vue | 2 +- .../payment/payment-application-form.vue | 191 ++++++-- src/views/payment/receipt-flow-batch-form.vue | 447 ++++++++++++++++++ src/views/payment/receipt-flow-form.vue | 11 +- src/views/payment/receipt-flow.vue | 45 +- .../components/formal-settlement-editor.vue | 13 +- .../components/pre-settlement-editor.vue | 247 +++++++++- src/views/settlement/pre-settlement-form.vue | 1 + .../settlement/receivable-payable-detail.vue | 8 + 19 files changed, 1354 insertions(+), 177 deletions(-) create mode 100644 src/views/payment/receipt-flow-batch-form.vue diff --git a/src/api/business/waybill-manage.js b/src/api/business/waybill-manage.js index 6b1645c..d9ba828 100644 --- a/src/api/business/waybill-manage.js +++ b/src/api/business/waybill-manage.js @@ -7,6 +7,7 @@ const api = createCrudApi(baseUrl); export const getList = api.getList; export const getDetail = api.getDetail; export const submit = api.submit; +export const saveDraft = data => request({ url: `${baseUrl}/save-draft`, method: 'post', data }); export const remove = api.remove; export const copy = api.copy; export const cancel = api.cancel; diff --git a/src/api/payment/billLedger.js b/src/api/payment/billLedger.js index 706c051..d268f2b 100644 --- a/src/api/payment/billLedger.js +++ b/src/api/payment/billLedger.js @@ -12,6 +12,12 @@ export const getAvailableOptions = (keyword, deptId, selectedId) => method: 'get', 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 remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } }); diff --git a/src/api/payment/paymentApplication.js b/src/api/payment/paymentApplication.js index bc772d0..c51a9de 100644 --- a/src/api/payment/paymentApplication.js +++ b/src/api/payment/paymentApplication.js @@ -24,11 +24,6 @@ export const paymentTypeOptions = [ { label: '进度预付', value: 'progress_advance' }, { label: '结算付款', value: 'settlement_payment' }, ]; -export const paymentMethodOptions = [ - { label: '银行转账', value: 'bank_transfer' }, - { label: '银行承兑汇票', value: 'bank_draft' }, - { label: '商业承兑汇票', value: 'commercial_draft' }, -]; export const approvalStatusOptions = [ { label: '草稿', value: 'draft' }, { label: '审批中', value: 'reviewing' }, diff --git a/src/option/business/transport-plan.js b/src/option/business/transport-plan.js index 0b65b82..ec940fb 100644 --- a/src/option/business/transport-plan.js +++ b/src/option/business/transport-plan.js @@ -1,7 +1,6 @@ import { auditColumns, createCrudOption, - dataSourceOptions, phoneRule, planStatusOptions, selectRule, @@ -9,6 +8,12 @@ import { withSearchPlaceholders, } from './common'; +const transportPlanDataSourceOptions = [ + { label: '批量导入', value: '批量导入' }, + { label: '手工创建', value: '手工创建' }, + { label: '外部系统', value: '外部系统' }, +]; + const listDicFormatter = res => { const data = res?.data || res; if (Array.isArray(data)) return data; @@ -61,7 +66,17 @@ const getSecondCargoTypeName = item => '货物'; 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 || ''; const groups = rows.reduce((result, item = {}) => { @@ -78,11 +93,22 @@ const formatGoodsInfo = row => { item.goodsQuantity, item.quantityUnit, item.goodsQuantityUnit, + item.cargoUnit, item.unit, ].some(Boolean); 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]) { result[unit] = { typeName: getSecondCargoTypeName(item), @@ -388,7 +414,7 @@ export const option = { type: 'select', search: true, searchOrder: 1, - dicData: dataSourceOptions, + dicData: transportPlanDataSourceOptions, minWidth: 120, addDisplay: false, editDisplay: false, diff --git a/src/option/business/waybill-manage.js b/src/option/business/waybill-manage.js index 352c40f..4ddf551 100644 --- a/src/option/business/waybill-manage.js +++ b/src/option/business/waybill-manage.js @@ -207,6 +207,26 @@ const getFirstValue = (row, props) => { 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 freightRows = parseJsonArray(row.freightList || row.freightRows || row.freightJson); if (freightRows.length) return freightRows; @@ -218,9 +238,25 @@ const getBillingRows = row => { const joinText = list => list.filter(item => !isEmpty(item)).join('/'); 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']); - if (text) return text; - return getGoodsRows(row) + if (!goodsRows.length) return text || ''; + return goodsRows .map(item => { const name = getFirstValue(item, ['cargoName', 'goodsName', 'name']); const type = getFirstValue(item, ['cargoType', 'goodsType', 'typeName']); @@ -228,12 +264,13 @@ const formatGoodsInfo = row => { normalizeNumericDisplayValue( 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]); }) .filter(Boolean) - .join('; '); + .join('; ') || text || ''; }; const formatGoodsField = (row, props) => { @@ -246,6 +283,28 @@ const formatGoodsField = (row, props) => { const formatUnitPrice = row => { const value = normalizeNumericDisplayValue(getFirstValue(row, ['unitPrice', 'price'])); 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; const billing = getBillingRows(row).find( item => !isEmpty(normalizeNumericDisplayValue(item.unitPrice)) diff --git a/src/option/payment/invoiceApplication.js b/src/option/payment/invoiceApplication.js index 5c4a708..a2cef3c 100644 --- a/src/option/payment/invoiceApplication.js +++ b/src/option/payment/invoiceApplication.js @@ -21,8 +21,8 @@ export const invoiceApplicationDetailColumns = [ { prop: 'vehicleNo', label: '车号', minWidth: 120 }, { prop: 'departureAddress', label: '发货地址', minWidth: 180 }, { prop: 'arrivalAddress', label: '到货地址', minWidth: 180 }, - { prop: 'actualDepartureTime', label: '实际发货时间', minWidth: 170 }, - { prop: 'actualCompletionTime', label: '实际完成时间', minWidth: 170 }, + { prop: 'actualDepartureTime', label: '实际发货时间', minWidth: 170, dateTime: true }, + { prop: 'actualCompletionTime', label: '实际完成时间', minWidth: 170, dateTime: true }, { prop: 'transportType', label: '运输类型', minWidth: 120 }, { prop: 'cargoName', label: '货物名称', minWidth: 140 }, { prop: 'cargoType', label: '货物类型', minWidth: 130 }, diff --git a/src/views/business/components/business-crud-page.vue b/src/views/business/components/business-crud-page.vue index c640079..c12c759 100644 --- a/src/views/business/components/business-crud-page.vue +++ b/src/views/business/components/business-crud-page.vue @@ -146,7 +146,7 @@ {{ isTransportPlanPage ? formatTransportPlanProvinceCityDistrict(row.departureAddress) - : formatTransportPlanProvinceCityDistrict(row.departureAddress) + : formatWaybillListAddress(row.departureAddress) }} {{ row.departureAddress || '-' }} @@ -161,7 +161,7 @@ {{ isTransportPlanPage ? formatTransportPlanProvinceCityDistrict(row.arrivalAddress) - : formatTransportPlanProvinceCityDistrict(row.arrivalAddress) + : formatWaybillListAddress(row.arrivalAddress) }} {{ row.arrivalAddress || '-' }} @@ -644,7 +644,7 @@ @change="handleTaskCarrierTypeChange" > @@ -1195,7 +1196,7 @@ @change="handleTaskCarrierTypeChange" > -