diff --git a/src/option/settlement/formalSettlement.js b/src/option/settlement/formalSettlement.js index 6fdc26b..6e335c0 100644 --- a/src/option/settlement/formalSettlement.js +++ b/src/option/settlement/formalSettlement.js @@ -118,7 +118,7 @@ export const formalSettlementOption = (settlementType = '') => ({ emptyBtnText: '重置', searchShow: true, searchIcon: true, - searchLabelWidth: 88, + searchLabelWidth: 100, // 查询/重置紧跟首行 4 个条件(4×5 + 菜单 4 = 24),不再独占下一整行 searchMenuSpan: 4, searchIndex: 4, diff --git a/src/router/views/index.js b/src/router/views/index.js index e90da64..02c3516 100644 --- a/src/router/views/index.js +++ b/src/router/views/index.js @@ -129,7 +129,39 @@ export default [ name: '新增编辑结算单', meta: { keepAlive: false, - // 付款/收款结算单共用表单页,具体高亮菜单由页面按 settlementType 回写 + // 付款结算单表单入口,高亮付款结算单菜单 + activeMenu: '/settlement/payment-settlement', + }, + component: loadView(() => import('@/views/settlement/formal-settlement-form.vue')), + }, + ], + }, + { + // 收款结算单表单入口:与付款结算单共用表单组件,仅高亮菜单不同 + path: '/settlement/receipt-settlement/form', + component: Layout, + children: [ + { + path: '', + name: '新增编辑收款结算单', + meta: { + keepAlive: false, + activeMenu: '/settlement/receipt-settlement', + }, + component: loadView(() => import('@/views/settlement/formal-settlement-form.vue')), + }, + ], + }, + { + // 付款结算单表单入口:与 settlementListPath('payable') + '/form' 对应 + path: '/settlement/payment-settlement/form', + component: Layout, + children: [ + { + path: '', + name: '新增编辑付款结算单', + meta: { + keepAlive: false, activeMenu: '/settlement/payment-settlement', }, component: loadView(() => import('@/views/settlement/formal-settlement-form.vue')), diff --git a/src/views/payment/invoice-application-form.vue b/src/views/payment/invoice-application-form.vue index 9e91a2c..b4897ea 100644 --- a/src/views/payment/invoice-application-form.vue +++ b/src/views/payment/invoice-application-form.vue @@ -494,6 +494,9 @@ import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.v import { getMkPublicDetail } from '@/api/mk-process'; import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval'; +// 提交成功后带标记返回,列表页(keep-alive)据此重新拉取 +const invoiceApplicationListRefreshKey = 'invoice-application-list-refresh'; + let localId = 0; const emptyLine = () => ({ localId: ++localId, @@ -1227,6 +1230,7 @@ export default { mkEnabled: await loadMkEnabled().catch(() => false), }); this.$message.success('提交成功'); + sessionStorage.setItem(invoiceApplicationListRefreshKey, '1'); this.goBack(); }, async syncReferenceData() { diff --git a/src/views/payment/invoice-application.vue b/src/views/payment/invoice-application.vue index 27075a7..39c40a6 100644 --- a/src/views/payment/invoice-application.vue +++ b/src/views/payment/invoice-application.vue @@ -250,6 +250,11 @@ import organizationSearch from '@/mixins/organization-search'; import { getMKApprovalUrl } from '@/api/system/business-process'; import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval'; import { kingdeeStatusType } from '@/utils/kingdee'; +import { settlementListPath } from '@/utils/settlement-menu'; + +// 表单提交成功后带标记返回,keep-alive 缓存的列表页据此重新拉取 +const invoiceApplicationListRefreshKey = 'invoice-application-list-refresh'; +const invoiceApplicationListRoute = '/payment/invoice-application'; const emptyQuery = () => ({ applicationNo: '', @@ -308,7 +313,28 @@ export default { this.mkEnabled = false; }); }, + activated() { + this.consumeListRefreshFlag(); + }, + watch: { + $route(to, from) { + if ( + to.path === invoiceApplicationListRoute && + from?.path?.includes('/invoice-application/form') + ) { + this.consumeListRefreshFlag(); + } + }, + }, methods: { + /** 表单提交成功后带标记返回时,重新拉取列表 */ + consumeListRefreshFlag() { + if (!sessionStorage.getItem(invoiceApplicationListRefreshKey)) { + return; + } + sessionStorage.removeItem(invoiceApplicationListRefreshKey); + this.loadTable(); + }, hasPermission(code) { return this.permission?.[code] !== false; }, @@ -372,12 +398,18 @@ export default { item => String(item.formalSettlementNo || '').trim() === normalizedNo ); if (!row?.id) { - this.$message.warning('未找到对应的付款结算单'); + this.$message.warning('未找到对应的结算单'); return; } + // 开票申请关联的多为收款结算单,按单据类型路由到对应表单入口,避免误落付款结算单 this.$router.push({ - path: '/settlement/formal-settlement/form', - query: { mode: 'view', id: row.id, name: '查看正式结算' }, + path: settlementListPath(row.settlementType) + '/form', + query: { + mode: 'view', + id: row.id, + settlementType: row.settlementType, + name: '查看正式结算', + }, }); } catch (error) { this.$message.error('正式结算单详情加载失败'); diff --git a/src/views/payment/invoice-receipt-form.vue b/src/views/payment/invoice-receipt-form.vue index b9e6538..50cbfb8 100644 --- a/src/views/payment/invoice-receipt-form.vue +++ b/src/views/payment/invoice-receipt-form.vue @@ -100,18 +100,28 @@ - - - + + +
+ + + +
+
+
+
+ + + @@ -214,6 +224,48 @@ :query-api="apiQueryInvoicePool" @confirm="handleInvoicePicked" /> + + + + + + + + + + + 查询 + 重置 + + + + + + + + + + + + @@ -221,7 +273,7 @@ import { Search } from '@element-plus/icons-vue'; import { mapGetters } from 'vuex'; import * as api from '@/api/payment/invoiceReceipt'; -import { getDeptTree } from '@/api/system/dept'; +import { getList as getContractList } from '@/api/business/contract-manage'; import KingdeeInvoicePoolDialog from '@/components/kingdee-invoice-pool-dialog/main.vue'; import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue'; import { getMkPublicDetail } from '@/api/mk-process'; @@ -247,6 +299,8 @@ const emptyForm = () => ({ customerEmails: [], departmentEmails: [], projectName: '', + contractNo: '', + contractName: '', deptId: null, deptName: '', payerName: '', @@ -269,16 +323,19 @@ export default { customerEmailOptions: [], departmentEmailOptions: [], invoiceItems: [], - deptTreeOptions: [], - deptMap: new Map(), - // 结算组织=合同承办部门:部门树级联单选(取末级 id),后端沿部门树上溯平台公司取金蝶组织 - deptCascaderProps: { checkStrictly: true, emitPath: true }, + contractDialog: { + visible: false, + loading: false, + rows: [], + page: { current: 1, size: 10, total: 0 }, + query: { contractNo: '', contractName: '' }, + }, invoiceDialog: { visible: false, }, rules: { invoiceNo: [{ required: true, validator: this.validateInvoice, trigger: 'change' }], - deptId: [{ required: true, validator: this.validateDept, trigger: 'change' }], + contractNo: [{ required: true, validator: this.validateContract, trigger: 'change' }], customerEmails: [{ validator: this.validateEmails, trigger: 'change' }], departmentEmails: [{ validator: this.validateEmails, trigger: 'change' }], }, @@ -299,12 +356,6 @@ export default { const code = this.recordId ? 'invoice_receipt_edit' : 'invoice_receipt_add'; return this.hasPermission(code); }, - deptCascaderValue() { - if (this.form.deptId === null || this.form.deptId === undefined || this.form.deptId === '') { - return []; - } - return [String(this.form.deptId)]; - }, }, created() { this.initialize(); @@ -339,7 +390,6 @@ export default { .filter(Boolean); }, async initialize() { - this.loadDepartments(); if (this.isPublicViewPage) { await this.loadPublicDetail(); return; @@ -380,9 +430,9 @@ export default { } callback(); }, - validateDept(rule, value, callback) { - if (this.form.deptId === null || this.form.deptId === undefined || this.form.deptId === '') { - callback(new Error('请选择结算组织')); + validateContract(rule, value, callback) { + if (!this.form.contractNo) { + callback(new Error('请选择所属合同')); return; } callback(); @@ -454,47 +504,43 @@ export default { this.invoiceItems = []; } }, - loadDepartments() { - // 结算组织取租户部门树(过滤外部组织),选中取末级部门 id - getDeptTree(this.userInfo?.tenantId) - .then(response => { - const rows = this.excludeExternalOrganization(this.unwrapData(response) || []); - this.deptTreeOptions = this.toDeptCascaderOptions(rows); - this.deptMap = new Map(); - this.flattenDepartments(rows).forEach(item => - this.deptMap.set(String(item.id), item.title || item.deptName || item.name) - ); - }) - .catch(() => {}); + // 所属合同=承运商合同:选择后带出承办部门(结算组织=承办部门对应平台公司金蝶编码,后端派生) + openContractDialog() { + this.contractDialog.visible = true; + this.searchContracts(); }, - excludeExternalOrganization(list = []) { - return list.reduce((result, item) => { - const deptName = item.title || item.deptName || item.name; - if (String(deptName || '').trim() === '外部组织') return result; - result.push({ - ...item, - children: item.children?.length - ? this.excludeExternalOrganization(item.children) - : item.children, + searchContracts() { + this.contractDialog.page.current = 1; + this.loadContracts(); + }, + resetContractQuery() { + this.contractDialog.query = { contractNo: '', contractName: '' }; + this.searchContracts(); + }, + async loadContracts() { + this.contractDialog.loading = true; + try { + const res = await getContractList(this.contractDialog.page.current, this.contractDialog.page.size, { + ...this.contractDialog.query, + contractCategory: '承运商合同', + approvalStatuses: 'approved,change_approved', }); - return result; - }, []); + const data = this.unwrapData(res) || {}; + this.contractDialog.rows = data.records || []; + this.contractDialog.page.total = Number(data.total || 0); + } finally { + this.contractDialog.loading = false; + } }, - toDeptCascaderOptions(list = []) { - return list.map(item => ({ - label: item.title || item.deptName || item.name, - value: item.id == null || item.id === '' ? '' : String(item.id), - children: item.children?.length ? this.toDeptCascaderOptions(item.children) : undefined, - })); - }, - flattenDepartments(rows) { - return (rows || []).flatMap(item => [item, ...this.flattenDepartments(item.children || [])]); - }, - handleDeptChange(value) { - const deptId = Array.isArray(value) ? value.at(-1) : value; - this.form.deptId = deptId || null; - this.form.deptName = deptId ? this.deptMap.get(String(deptId)) || '' : ''; - this.$refs.formRef?.validateField('deptId').catch(() => {}); + pickContract(row) { + if (!row) return; + Object.assign(this.form, { + contractNo: row.contractNo, + contractName: row.contractName, + deptName: row.organizationName, + }); + this.contractDialog.visible = false; + this.$refs.formRef?.validateField('contractNo').catch(() => {}); }, openSettlementDetail(row) { const id = row.formalSettlementId || row.settlementId || row.id; @@ -527,9 +573,8 @@ export default { departmentEmails: this.form.departmentEmails.join(';'), attachmentsJson: JSON.stringify(this.form.attachments || []), remark: this.form.remark, - // 结算组织=页面手选部门(先票后款不挂结算单,认领关系由结算单侧维护,不再传 settlements) - deptId: this.form.deptId, - deptName: this.form.deptName, + // 所属合同仅传编号,合同名称/承办部门由后端按编号带出(结算组织=合同承办部门) + contractNo: this.form.contractNo, }; }, async saveDraft() { diff --git a/src/views/payment/payment-application-form.vue b/src/views/payment/payment-application-form.vue index 0a01f1c..f93c25f 100644 --- a/src/views/payment/payment-application-form.vue +++ b/src/views/payment/payment-application-form.vue @@ -1296,9 +1296,11 @@ export default { this.form = { ...emptyForm(), ...data, + // 后端可能返回 -1 占位预付 id,统一归一为 null,防止点击结算单号误跳预付 + preSettlementId: this.isValidReferenceId(data.preSettlementId) ? data.preSettlementId : null, preSettlementIds: data.preSettlementIds?.length ? data.preSettlementIds - : data.preSettlementId + : this.isValidReferenceId(data.preSettlementId) ? [data.preSettlementId] : [], attachments: this.parse(data.attachmentsJson), @@ -1344,9 +1346,13 @@ export default { this.form = { ...emptyForm(), ...data, + // 后端可能返回 -1 占位预付 id,统一归一为 null,防止点击结算单号误跳预付 + preSettlementId: this.isValidReferenceId(data.preSettlementId) + ? data.preSettlementId + : null, preSettlementIds: data.preSettlementIds?.length ? data.preSettlementIds - : data.preSettlementId + : this.isValidReferenceId(data.preSettlementId) ? [data.preSettlementId] : [], attachments: this.parse(data.attachmentsJson), @@ -1587,17 +1593,22 @@ export default { }); }, clearFormalReferenceWhenPreOnly() { + // 纯预付引用才收敛为预付;-1 占位 id 不得充当预付关联 const progressAdvance = this.form.paymentType === 'progress_advance'; const onlyPre = progressAdvance || (this.referenceRows.length > 0 && this.referenceRows.every(item => item.referenceType === 'pre')); if (!onlyPre) return; - if (!this.form.preSettlementId) { + if (!this.isValidReferenceId(this.form.preSettlementId)) { const preId = - this.referenceRows.find(item => item.referenceType === 'pre')?.id || - (progressAdvance ? this.form.settlementId : null); - if (preId) { + this.referenceRows.find( + item => item.referenceType === 'pre' && this.isValidReferenceId(item.id) + )?.id || + (progressAdvance && this.isValidReferenceId(this.form.settlementId) + ? this.form.settlementId + : null); + if (this.isValidReferenceId(preId)) { this.form.preSettlementId = preId; this.form.preSettlementIds = [preId]; } @@ -1608,13 +1619,16 @@ export default { }, restoreReferenceRows(data, fallbackRow) { const relations = Array.isArray(data?.settlements) ? data.settlements : []; + // 行级预付/正式类型以过滤占位 id 后的关联为准,决定点击结算单号的跳转目标 const rows = relations .map(item => { - const isPre = Boolean(item.preSettlementId || item.preSettlementNo); + // 后端用 -1 占位空 id,必须先过滤再判预付,否则正式结算行会被误标为预付 + const preId = this.isValidReferenceId(item.preSettlementId) ? item.preSettlementId : null; + const isPre = Boolean(preId || item.preSettlementNo); const settlementNo = item.preSettlementNo || item.formalSettlementNo || ''; if (!settlementNo) return null; return { - id: isPre ? item.preSettlementId || item.formalSettlementId : item.formalSettlementId, + id: isPre ? preId || item.formalSettlementId : item.formalSettlementId, settlementNo, referenceType: isPre ? 'pre' : 'formal', settlementAmount: Number(Number(item.settlementAmount || 0).toFixed(2)), @@ -1642,9 +1656,10 @@ export default { }, createReferenceRow(row) { const settlementAmount = Number(row.settlementAmount || 0); + // 后端以 -1 占位空预付 id,必须先校验否则正式结算行会被误判为预付 const isPre = Boolean( row.preSettlementNo || - row.preSettlementId || + this.isValidReferenceId(row.preSettlementId) || row.referenceType === 'pre' || row.paymentType === 'progress_advance' ); @@ -2770,23 +2785,26 @@ export default { }, async openSettlementDetail(row = {}) { const settlementNo = String(row.settlementNo || '').trim(); + // 行上 referenceType 优先;发票行等只带单号的,按单号回查引用行;都缺省才回落表单级推断 + const matchedReference = this.referenceRows.find( + item => String(item.settlementNo || '').trim() === settlementNo + ); + const referenceType = row.referenceType || matchedReference?.referenceType; const isPre = - row.referenceType === 'pre' || - this.form.paymentType === 'progress_advance' || - Boolean(this.form.preSettlementId || this.form.preSettlementNo); + referenceType === 'pre' || + (!referenceType && + (this.form.paymentType === 'progress_advance' || + Boolean( + this.isValidReferenceId(this.form.preSettlementId) || this.form.preSettlementNo + ))); let id = row.id || row.preSettlementId || row.settlementId || + matchedReference?.id || (isPre ? this.form.preSettlementId || this.form.preSettlementIds?.[0] : this.form.settlementId || this.form.settlementIds?.[0]); - if (!id && settlementNo) { - const matched = this.referenceRows.find( - item => String(item.settlementNo || '').trim() === settlementNo - ); - id = matched?.id; - } if (!id && settlementNo) { try { const listApi = isPre ? preApi.getList : formalApi.getList; diff --git a/src/views/settlement/formal-settlement.vue b/src/views/settlement/formal-settlement.vue index f4b9dd4..b2e5905 100644 --- a/src/views/settlement/formal-settlement.vue +++ b/src/views/settlement/formal-settlement.vue @@ -436,8 +436,9 @@ export default { (handlers[type] || (() => {}))(); }, openCreate() { + // 按列表锁定的结算类型路由到对应表单入口,保证菜单高亮与关闭回落列表正确 this.$router.push({ - path: '/settlement/formal-settlement/form', + path: settlementListPath(this.activeSettlementType) + '/form', query: { mode: 'add', name: `新增${this.billLabel}`, @@ -448,8 +449,9 @@ export default { }, openEdit(row) { const settlementType = row.settlementType || this.activeSettlementType; + // 行类型可能与列表锁定类型不同,按行类型路由 this.$router.push({ - path: '/settlement/formal-settlement/form', + path: settlementListPath(settlementType) + '/form', query: { mode: 'edit', id: row.id, diff --git a/src/views/settlement/pre-settlement.vue b/src/views/settlement/pre-settlement.vue index 8648db4..a36e4b4 100644 --- a/src/views/settlement/pre-settlement.vue +++ b/src/views/settlement/pre-settlement.vue @@ -1,7 +1,7 @@