diff --git a/src/api/payment/paymentApplication.js b/src/api/payment/paymentApplication.js index 3e4c802..59c9aa1 100644 --- a/src/api/payment/paymentApplication.js +++ b/src/api/payment/paymentApplication.js @@ -10,6 +10,13 @@ export const getReferenceAmount = (paymentType, referenceId, excludeId) => method: 'get', params: { paymentType, referenceId, excludeId: excludeId || undefined }, }); +/** 按合同查询可选项目预付核销列表(审批通过且仍有可核销余额) */ +export const getProjectAdvanceWriteOffOptions = params => + request({ + url: `${baseUrl}/project-advance-write-off-options`, + method: 'get', + params, + }); export const save = data => request({ url: `${baseUrl}/save`, method: 'post', data }); export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } }); export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data }); diff --git a/src/api/settlement/settlementImport.js b/src/api/settlement/settlementImport.js new file mode 100644 index 0000000..131493b --- /dev/null +++ b/src/api/settlement/settlementImport.js @@ -0,0 +1,55 @@ +import request from '@/axios'; + +const baseUrl = '/blade-transport/settlement-import'; + +export const getOptions = () => request({ url: `${baseUrl}/options`, method: 'get' }); + +export const getNextCode = () => request({ url: `${baseUrl}/next-code`, method: 'get' }); + +export const getList = (current, size, params) => + request({ + url: `${baseUrl}/list`, + method: 'get', + params: { current, size, ...params }, + }); + +export const getDetail = id => + request({ url: `${baseUrl}/detail`, method: 'get', params: { id } }); + +export const exportTemplate = () => + request({ + url: `${baseUrl}/export-template`, + method: 'get', + responseType: 'blob', + }); + +export const parseFile = file => { + const data = new FormData(); + data.append('file', file); + return request({ + url: `${baseUrl}/parse`, + method: 'post', + data, + timeout: 120000, + }); +}; + +export const validate = data => + request({ url: `${baseUrl}/validate`, method: 'post', data, timeout: 120000 }); + +export const exportFailures = data => + request({ + url: `${baseUrl}/export-failures`, + method: 'post', + data, + responseType: 'blob', + timeout: 120000, + }); + +export const saveDraft = data => + request({ url: `${baseUrl}/draft`, method: 'post', data, timeout: 120000 }); + +export const confirm = data => + request({ url: `${baseUrl}/confirm`, method: 'post', data, timeout: 120000 }); + +export const voidBatch = data => request({ url: `${baseUrl}/void`, method: 'post', data }); diff --git a/src/router/views/index.js b/src/router/views/index.js index 11d72aa..546ffb2 100644 --- a/src/router/views/index.js +++ b/src/router/views/index.js @@ -119,6 +119,21 @@ export default [ }, ], }, + { + path: '/settlement/settlement-import/form', + component: Layout, + children: [ + { + path: '', + name: '新增导入结算单', + meta: { + keepAlive: false, + activeMenu: '/settlement/settlement-import', + }, + component: loadView(() => import('@/views/settlement/settlement-import-form.vue')), + }, + ], + }, { path: '/settlement/transport-reconciliation/form', component: Layout, diff --git a/src/utils/mk-approval.js b/src/utils/mk-approval.js index 60ee800..83806da 100644 --- a/src/utils/mk-approval.js +++ b/src/utils/mk-approval.js @@ -61,9 +61,18 @@ export const MK_BIZ = { const MK_SWITCH_NAME = '开启MK'; -async function loadMkTemplateList() { +/** 进程内缓存,避免每次提交都打字典;登录后同一会话开关一般不变 */ +let mkEnabledCache = null; +let mkEnabledPromise = null; +let mkTemplateListCache = null; + +async function loadMkTemplateList(force = false) { + if (!force && mkTemplateListCache) { + return mkTemplateListCache; + } const res = await getDictionary({ code: 'mk_template' }); - return res?.data?.data || []; + mkTemplateListCache = res?.data?.data || []; + return mkTemplateListCache; } /** 业务字典 mk_template:名称「开启MK」且键值为 1 时才请求 MK */ @@ -72,10 +81,37 @@ export function isMkEnabled(list = []) { return String(matched?.dictKey ?? '').trim() === '1'; } -/** 加载业务字典并判断是否开启 MK */ -export async function loadMkEnabled() { - const list = await loadMkTemplateList(); - return isMkEnabled(list); +/** 清除 MK 开关缓存(字典变更后可调用) */ +export function clearMkEnabledCache() { + mkEnabledCache = null; + mkEnabledPromise = null; + mkTemplateListCache = null; +} + +/** + * 加载业务字典并判断是否开启 MK + * @param {boolean} [force=false] 是否强制刷新缓存 + */ +export async function loadMkEnabled(force = false) { + if (!force && mkEnabledCache !== null) { + return mkEnabledCache; + } + if (!force && mkEnabledPromise) { + return mkEnabledPromise; + } + mkEnabledPromise = loadMkTemplateList(force) + .then(list => { + mkEnabledCache = isMkEnabled(list); + return mkEnabledCache; + }) + .catch(error => { + mkEnabledCache = false; + throw error; + }) + .finally(() => { + mkEnabledPromise = null; + }); + return mkEnabledPromise; } export async function resolveMkTemplateCode(dictName, list) { @@ -99,19 +135,22 @@ export function isMkApprovedStatus(bizType, approvalStatus) { } /** - * 提交 MK 审核流 + * 提交 MK 审核流。未开启 MK 时直接跳过,不请求流程实例相关接口。 * @param {Object} options * @param {string} options.bizType 业务类型 - * @param {string|number} options.bizId 业务主键(后端据此生成/复用 formInstanceId) + * @param {string|number} options.bizId 业务主键 * @param {string} [options.subjectName] 标题后缀 - * @param {string} [options.approvalStatus] 提交前的审批状态(须在业务 submit 改状态前取值;通过态强制新建实例) + * @param {string} [options.approvalStatus] 提交前的审批状态 + * @param {boolean} [options.mkEnabled] 调用方已知开关时传入,false 时立即跳过 + * @returns {Promise} true=已发起/复用 MK 流程,false=未开启或已跳过 */ export async function submitMkApprovalFlow({ bizType, bizId, subjectName = '', approvalStatus = '', -}) { + mkEnabled, +} = {}) { const conf = MK_BIZ[bizType]; if (!conf) { return Promise.reject(new Error(`不支持的MK业务类型:${bizType}`)); @@ -119,17 +158,29 @@ export async function submitMkApprovalFlow({ if (bizId == null || bizId === '') { return Promise.reject(new Error('业务id不能为空')); } + + // 调用方显式关闭,或字典未开启:不请求 hasMkProcessInstance / processSubmit 等 MK 接口 + if (mkEnabled === false) { + return false; + } + const enabled = mkEnabled === true ? true : await loadMkEnabled(); + if (!enabled) { + return false; + } + const list = await loadMkTemplateList(); if (!isMkEnabled(list)) { - return; + mkEnabledCache = false; + return false; } + const bizIdText = String(bizId); const forceNewFormInstance = isMkApprovedStatus(bizType, approvalStatus); // 审核通过后再提交必须新建流程;其余场景已有有效实例则跳过 if (!forceNewFormInstance) { const existsRes = await hasMkProcessInstance({ bizId: bizIdText }); if (existsRes?.data?.data) { - return; + return true; } } if ((conf.rejected || []).includes(approvalStatus)) { @@ -139,11 +190,12 @@ export async function submitMkApprovalFlow({ const subject = subjectName ? `${conf.subjectPrefix}:${subjectName}` : `${conf.subjectPrefix}:${bizIdText}`; - return processSubmit({ + await processSubmit({ templateCode, bizId: bizIdText, subject, bizType, forceNewFormInstance, }); + return true; } diff --git a/src/utils/search-range.js b/src/utils/search-range.js index e5723cd..34865bb 100644 --- a/src/utils/search-range.js +++ b/src/utils/search-range.js @@ -1,3 +1,35 @@ +/** + * 合并列表搜索条件:新条件覆盖旧条件,本次显式清空的字段从查询中移除。 + */ +export const applyIncomingSearch = (current = {}, incoming = {}) => { + const merged = { ...current, ...incoming }; + Object.keys(incoming || {}).forEach(key => { + const value = incoming[key]; + if ( + value === '' || + value === null || + value === undefined || + (Array.isArray(value) && value.length === 0) + ) { + delete merged[key]; + } + }); + return merged; +}; + +/** + * 启停状态下拉「全部」在数字类型下会被转成 0/-1,不能当作查询条件。 + * 状态 0 表示草稿等有效值的页面不要调用。 + */ +export const omitInvalidListStatus = (params = {}) => { + const values = { ...params }; + const status = values.status; + if (status === 0 || status === '0' || status === -1 || status === '-1') { + delete values.status; + } + return values; +}; + export const normalizeSearchRangeParams = (params = {}, rangeMap = {}) => { const values = { ...params }; diff --git a/src/views/base/airport-master.vue b/src/views/base/airport-master.vue index 3c94260..9969378 100644 --- a/src/views/base/airport-master.vue +++ b/src/views/base/airport-master.vue @@ -150,6 +150,7 @@ import { mapGetters } from 'vuex'; import { downloadXls } from '@/utils/util'; import { openImportDialog } from '@/utils/import-excel'; import { formatUpdateUserName } from '@/utils/audit'; +import { omitInvalidListStatus } from '@/utils/search-range'; import { getToken, isAdministrator } from '@/utils/auth'; import { getCoordinateValidationMessage, normalizeCoordinateInput } from '@/utils/coordinate'; import AddressMapPicker from '@/components/address-map-picker/main.vue'; @@ -1182,7 +1183,7 @@ export default { }, onLoad(page, params = {}) { this.loading = true; - getList(page.currentPage, page.pageSize, { ...params, ...this.query }) + getList(page.currentPage, page.pageSize, omitInvalidListStatus({ ...params, ...this.query })) .then(res => { const data = res.data.data; this.page.total = data.total; diff --git a/src/views/base/currency.vue b/src/views/base/currency.vue index 0ad39fe..903fae0 100644 --- a/src/views/base/currency.vue +++ b/src/views/base/currency.vue @@ -104,6 +104,7 @@ import { mapGetters } from 'vuex'; import { downloadXls } from '@/utils/util'; import { openImportDialog } from '@/utils/import-excel'; import { formatUpdateUserName } from '@/utils/audit'; +import { omitInvalidListStatus } from '@/utils/search-range'; import { getToken } from '@/utils/auth'; import NProgress from 'nprogress'; import 'nprogress/nprogress.css'; @@ -507,7 +508,7 @@ export default { this.onLoad(this.page, this.query); }, buildQuery(params = {}) { - const values = { ...params, ...this.query }; + const values = omitInvalidListStatus({ ...params, ...this.query }); const rangeMap = { effectiveDateRange: ['effectiveDateStart', 'effectiveDateEnd'], expiryDateRange: ['expiryDateStart', 'expiryDateEnd'], diff --git a/src/views/base/customer-type.vue b/src/views/base/customer-type.vue index fb68968..6653a85 100644 --- a/src/views/base/customer-type.vue +++ b/src/views/base/customer-type.vue @@ -63,6 +63,7 @@ diff --git a/src/views/business/contract-manage.vue b/src/views/business/contract-manage.vue index 088ea83..3623a5e 100644 --- a/src/views/business/contract-manage.vue +++ b/src/views/business/contract-manage.vue @@ -1986,7 +1986,9 @@ export default { bizType: 'contract-manage', bizId: id, subjectName: saved.contractName || submit.contractName || '', - approvalStatus: previousApprovalStatus || saved.approvalStatus || submit.approvalStatus || '', + approvalStatus: + previousApprovalStatus || saved.approvalStatus || submit.approvalStatus || '', + mkEnabled: this.mkEnabled, }) ); } @@ -2025,6 +2027,7 @@ export default { bizId: payload.id || this.form.id, subjectName: payload.contractName || this.form.contractName || '', approvalStatus: this.form.approvalStatus || 'change_rejected', + mkEnabled: this.mkEnabled, }).then(() => { this.$message.success('已重新提交变更审批'); this.closeForm(); @@ -3001,6 +3004,7 @@ export default { bizId: row.id, subjectName: row.contractName || '', approvalStatus: row.approvalStatus || '', + mkEnabled: this.mkEnabled, }) ) : request(); @@ -3338,7 +3342,7 @@ export default { :deep(.el-form-item) { align-items: center; - margin-bottom: 24px; + //margin-bottom: 24px; } :deep(.el-form-item__label) { diff --git a/src/views/business/process-config.vue b/src/views/business/process-config.vue index 4cf9f31..d676238 100644 --- a/src/views/business/process-config.vue +++ b/src/views/business/process-config.vue @@ -416,6 +416,7 @@ import { getList as getProjectList } from '@/api/business/project-apply'; import { getDeptTree } from '@/api/system/dept'; import { exportBlob } from '@/api/common'; import { config, option } from '@/option/business/process-config'; +import { applyIncomingSearch } from '@/utils/search-range'; import { getToken, isAdministrator } from '@/utils/auth'; import { downloadXls } from '@/utils/util'; import { mapGetters } from 'vuex'; @@ -603,7 +604,8 @@ export default { }); }, normalizeListQuery(params = {}) { - const query = { ...params, ...this.query }; + // 新条件覆盖旧条件,清空的筛选项不再沿用上一次的值。 + const query = applyIncomingSearch(this.query, params); const rawDeptId = query.deptId ?? query.searchDeptId; delete query.searchDeptId; if (rawDeptId !== undefined && rawDeptId !== null && String(rawDeptId).trim() !== '') { diff --git a/src/views/business/project-apply.vue b/src/views/business/project-apply.vue index 4580fd0..e05c03d 100644 --- a/src/views/business/project-apply.vue +++ b/src/views/business/project-apply.vue @@ -1750,6 +1750,7 @@ export default { bizId: row.id, subjectName: row.projectName || '', approvalStatus: row.approvalStatus || '', + mkEnabled: this.mkEnabled, }).then(() => request()) : request(); afterMk.then(res => { @@ -1956,6 +1957,7 @@ export default { bizId: id, subjectName: name, approvalStatus: status, + mkEnabled: this.mkEnabled, }).then(() => { this.$message.success('提交成功!'); this.closeProjectForm(); @@ -2027,6 +2029,7 @@ export default { bizId: id, subjectName: this.form.projectName || '', approvalStatus: this.form.approvalStatus || 'change_rejected', + mkEnabled: this.mkEnabled, }).then(() => { this.$message.success('提交成功!'); this.closeProjectForm(); @@ -2503,8 +2506,9 @@ export default { const list = res.data.data || []; this.platformCompanyOptions = list.map(item => ({ label: item.deptName || item.fullName || item.title || item.name, - value: item.id, - })); + // 雪花 ID 必须以字符串绑定,避免 JS Number 精度丢失导致多选提交异常 + value: item.id == null || item.id === '' ? '' : String(item.id), + })).filter(item => item.value); this.ensureCurrentPlatformOption(); }); }, diff --git a/src/views/payment/bill-payment-form.vue b/src/views/payment/bill-payment-form.vue index c8654a9..77297ed 100644 --- a/src/views/payment/bill-payment-form.vue +++ b/src/views/payment/bill-payment-form.vue @@ -140,7 +140,7 @@ import * as api from '@/api/payment/billPayment'; import * as billLedgerApi from '@/api/payment/billLedger'; import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue'; import { getMkPublicDetail } from '@/api/mk-process'; -import { submitMkApprovalFlow } from '@/utils/mk-approval'; +import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval'; const emptyForm = () => ({ id: null, @@ -348,6 +348,7 @@ export default { bizId: this.form.id, subjectName: this.form.paymentNo || this.form.billNo || '', approvalStatus: previousApprovalStatus, + mkEnabled: await loadMkEnabled().catch(() => false), }); this.$message.success('提交成功'); this.goBack(); diff --git a/src/views/payment/bill-payment.vue b/src/views/payment/bill-payment.vue index d44acbd..defddf9 100644 --- a/src/views/payment/bill-payment.vue +++ b/src/views/payment/bill-payment.vue @@ -132,6 +132,7 @@ export default { bizId: row.id, subjectName: row.paymentNo || row.billNo || '', approvalStatus: previousApprovalStatus, + mkEnabled: this.mkEnabled, }); this.$message.success('提交成功'); this.loadTable(); diff --git a/src/views/payment/invoice-application-form.vue b/src/views/payment/invoice-application-form.vue index 0f47253..0c6f4cd 100644 --- a/src/views/payment/invoice-application-form.vue +++ b/src/views/payment/invoice-application-form.vue @@ -492,7 +492,7 @@ import { getDictionary } from '@/api/system/dictbiz'; import { invoiceApplicationDetailColumns } from '@/option/payment/invoiceApplication'; import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue'; import { getMkPublicDetail } from '@/api/mk-process'; -import { submitMkApprovalFlow } from '@/utils/mk-approval'; +import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval'; let localId = 0; const emptyLine = () => ({ @@ -1208,6 +1208,7 @@ export default { bizId: this.form.id, subjectName: this.form.applicationNo || '', approvalStatus: previousApprovalStatus, + mkEnabled: await loadMkEnabled().catch(() => false), }); this.$message.success('提交成功'); this.goBack(); diff --git a/src/views/payment/invoice-application.vue b/src/views/payment/invoice-application.vue index 8cc072b..0deb659 100644 --- a/src/views/payment/invoice-application.vue +++ b/src/views/payment/invoice-application.vue @@ -384,6 +384,7 @@ export default { bizId: row.id, subjectName: row.applicationNo || '', approvalStatus: previousApprovalStatus, + mkEnabled: this.mkEnabled, }); this.$message.success('提交成功'); this.loadTable(); @@ -504,6 +505,16 @@ export default { tip: row.kingdeeLockFailReason || '系统将自动补偿重锁', }; } + // 金蝶解锁终态标识:删单解锁的源单已作废,此标签用于区分人工作废与金蝶删单 + if (row.kingdeeUnlockTime) { + const isDelete = row.kingdeeUnlockOperationName === '金蝶删单'; + return { + show: true, + type: isDelete ? 'danger' : 'warning', + label: row.kingdeeUnlockOperationName || '金蝶解锁', + tip: `${row.kingdeeUnlockOperator || '-'} 于 ${this.$dayjs(row.kingdeeUnlockTime).format('YYYY-MM-DD HH:mm:ss')} 解锁`, + }; + } return { show: false }; }, displayValue(value) { diff --git a/src/views/payment/invoice-receipt-form.vue b/src/views/payment/invoice-receipt-form.vue index ac424da..1d06b34 100644 --- a/src/views/payment/invoice-receipt-form.vue +++ b/src/views/payment/invoice-receipt-form.vue @@ -219,7 +219,7 @@ import * as api from '@/api/payment/invoiceReceipt'; 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'; -import { submitMkApprovalFlow } from '@/utils/mk-approval'; +import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval'; const emptyForm = () => ({ id: null, @@ -571,6 +571,7 @@ export default { bizId: this.form.id, subjectName: this.form.invoiceNo || '', approvalStatus: previousApprovalStatus, + mkEnabled: await loadMkEnabled().catch(() => false), }); this.$message.success('提交成功'); this.goBack(); diff --git a/src/views/payment/invoice-receipt.vue b/src/views/payment/invoice-receipt.vue index 02b2ad1..8710449 100644 --- a/src/views/payment/invoice-receipt.vue +++ b/src/views/payment/invoice-receipt.vue @@ -341,6 +341,7 @@ export default { bizId: row.id, subjectName: row.invoiceNo || '', approvalStatus: previousApprovalStatus, + mkEnabled: this.mkEnabled, }); this.$message.success('提交成功'); this.loadTable(); diff --git a/src/views/payment/payment-application-form.vue b/src/views/payment/payment-application-form.vue index acee4e5..a3e337d 100644 --- a/src/views/payment/payment-application-form.vue +++ b/src/views/payment/payment-application-form.vue @@ -673,7 +673,7 @@ import { import { readSettlementTransfer, removeSettlementTransfer } from '@/utils/settlement-transfer'; import { formatContractOptionLabel } from '@/utils/contract'; import { downloadFileByUrl } from '@/utils/util'; -import { submitMkApprovalFlow } from '@/utils/mk-approval'; +import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval'; import { downloadKingdeeInvoiceFile, isKingdeePathAttachment, @@ -688,6 +688,9 @@ const attachmentViewerPlugins = [ fallbackPlugin(), ]; +const paymentApplicationListRoute = '/payment/payment-application'; +const paymentApplicationListRefreshKey = 'payment-application-list-refresh'; + const ATTACHMENT_MATERIALS = { weighingSlip: { key: 'weighingSlip', label: '磅单', keywords: ['磅单'] }, entrustOrder: { key: 'entrustOrder', label: '委托单', keywords: ['委托单'] }, @@ -1230,7 +1233,7 @@ export default { ]; } const referenceRow = this.createReferenceRow(data); - this.referenceRows = referenceRow.settlementNo ? [referenceRow] : []; + this.referenceRows = this.restoreReferenceRows(data, referenceRow); this.paymentRecords = data.paymentRecords || []; }, async initialize() { @@ -1253,7 +1256,7 @@ export default { invoices: data.invoices || [], }; const referenceRow = this.createReferenceRow(data); - this.referenceRows = referenceRow.settlementNo ? [referenceRow] : []; + this.referenceRows = this.restoreReferenceRows(data, referenceRow); this.paymentRecords = data.paymentRecords || []; if (this.form.paymentType === 'project_advance') { await this.loadProjectOptions(); @@ -1471,6 +1474,33 @@ export default { this.$refs.formRef?.clearValidate(); }); }, + restoreReferenceRows(data, fallbackRow) { + const relations = Array.isArray(data?.settlements) ? data.settlements : []; + const rows = relations + .map(item => { + const isPre = item.preSettlementId !== undefined && item.preSettlementId !== null && item.preSettlementId !== ''; + const settlementNo = item.preSettlementNo || item.formalSettlementNo || ''; + if (!settlementNo) return null; + return { + id: isPre ? item.preSettlementId : item.formalSettlementId, + settlementNo, + referenceType: isPre ? 'pre' : 'formal', + settlementAmount: Number(Number(item.settlementAmount || 0).toFixed(2)), + payableAmount: Number(Number(item.payableAmount ?? item.settlementAmount ?? 0).toFixed(2)), + appliedAmount: Number(Number(item.appliedAmount || 0).toFixed(2)), + fixedAppliedAmount: this.readonly, + }; + }) + .filter(Boolean); + if (rows.length > 1) { + this.form.preSettlementIds = rows.filter(item => item.referenceType === 'pre').map(item => item.id); + this.form.settlementIds = rows.filter(item => item.referenceType === 'formal').map(item => item.id); + if (this.form.preSettlementIds.length) this.form.preSettlementId = this.form.preSettlementIds[0]; + if (this.form.settlementIds.length) this.form.settlementId = this.form.settlementIds[0]; + return rows; + } + return fallbackRow?.settlementNo ? [fallbackRow] : []; + }, createReferenceRow(row) { const settlementAmount = Number(row.settlementAmount || 0); const appliedAmount = Number(row.advanceAppliedAmount || row.appliedPaymentAmount || 0); @@ -1479,11 +1509,13 @@ export default { id: row.preSettlementId || row.settlementId || row.id, settlementNo: row.preSettlementNo || row.formalSettlementNo || row.settlementNo || '', referenceType: isPre ? 'pre' : 'formal', - settlementAmount, - payableAmount: - row.payableAmount === undefined || row.payableAmount === null + settlementAmount: Number(Number(settlementAmount || 0).toFixed(2)), + payableAmount: Number( + (row.payableAmount === undefined || row.payableAmount === null ? Math.max(0, settlementAmount - appliedAmount) - : Number(row.payableAmount || 0), + : Number(row.payableAmount || 0) + ).toFixed(2) + ), }; }, createInvoiceRows(invoices = [], settlementNo = '') { @@ -1541,9 +1573,24 @@ export default { } const first = rows[0]; this.form.invoices = []; - this.referenceRows = rows.map(item => - this.createReferenceRow({ ...item, referenceType: 'pre' }) + const paymentType = + this.form.paymentType === 'settlement_payment' ? 'settlement_payment' : 'progress_advance'; + const amountResponses = await Promise.all( + rows.map(item => { + const referenceId = item.preSettlementId || item.id; + if (!this.isValidReferenceId(referenceId)) return Promise.resolve(null); + return api.getReferenceAmount(paymentType, referenceId, this.form.id).catch(() => null); + }) ); + this.referenceRows = rows.map((item, index) => { + const amount = this.unwrapData(amountResponses[index]) || {}; + return this.createReferenceRow({ + ...item, + referenceType: 'pre', + settlementAmount: amount.settlementAmount ?? item.settlementAmount, + payableAmount: amount.payableAmount, + }); + }); const settlementAmount = this.referenceRows.reduce( (total, item) => total + Number(item.settlementAmount || 0), 0 @@ -1554,8 +1601,6 @@ export default { ); const paymentRatio = Number(this.form.paymentRatio || 0); // 尾款付款场景下选择预结算单时保留尾款付款类型,不强制改为进度预付 - const paymentType = - this.form.paymentType === 'settlement_payment' ? 'settlement_payment' : 'progress_advance'; this.amountSyncing = true; Object.assign(this.form, { paymentType, @@ -2709,14 +2754,17 @@ export default { bizId: this.form.id, subjectName: this.form.paymentNo || '', approvalStatus: previousApprovalStatus, + mkEnabled: await loadMkEnabled().catch(() => false), }); this.$message.success('提交成功'); + // 通知列表页(keep-alive)重新拉取数据 + sessionStorage.setItem(paymentApplicationListRefreshKey, '1'); this.goBack(); }, goBack() { removeSettlementTransfer(this.$route.query.transferToken); this.$router.$avueRouter?.closeTag?.(); - this.$router.push('/payment/payment-application'); + this.$router.push(paymentApplicationListRoute); }, formatMoney(value) { const amount = Number(value); diff --git a/src/views/payment/payment-application.vue b/src/views/payment/payment-application.vue index 90b6915..44cde10 100644 --- a/src/views/payment/payment-application.vue +++ b/src/views/payment/payment-application.vue @@ -152,6 +152,9 @@ import organizationSearch from '@/mixins/organization-search'; import { getMKApprovalUrl } from '@/api/system/business-process'; import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval'; +const paymentApplicationListRoute = '/payment/payment-application'; +const paymentApplicationListRefreshKey = 'payment-application-list-refresh'; + export default { name: 'PaymentApplication', mixins: [organizationSearch], @@ -193,7 +196,28 @@ export default { this.mkEnabled = false; }); }, + activated() { + this.consumeListRefreshFlag(); + }, + watch: { + $route(to, from) { + if ( + to.path === paymentApplicationListRoute && + from?.path?.includes('/payment-application/form') + ) { + this.consumeListRefreshFlag(); + } + }, + }, methods: { + /** 表单提交成功后带标记返回时,重新拉取列表 */ + consumeListRefreshFlag() { + if (!sessionStorage.getItem(paymentApplicationListRefreshKey)) { + return; + } + sessionStorage.removeItem(paymentApplicationListRefreshKey); + this.onLoad(this.page); + }, hasPermission(code) { return this.permission?.[code] !== false; }, @@ -296,6 +320,7 @@ export default { bizId: row.id, subjectName: row.paymentNo || '', approvalStatus: previousApprovalStatus, + mkEnabled: this.mkEnabled, }); this.$message.success('提交成功'); this.refreshChange(); @@ -441,6 +466,16 @@ export default { tip: row.kingdeeLockFailReason || '系统将自动补偿重锁', }; } + // 金蝶解锁终态标识:后端按 unlocked 锁记录回填(付款申请不作废,仅此标识可区分金蝶删单与推送失败) + if (row.kingdeeUnlockTime) { + const isDelete = row.kingdeeUnlockOperationName === '金蝶删单'; + return { + show: true, + type: isDelete ? 'danger' : 'warning', + label: row.kingdeeUnlockOperationName || '金蝶解锁', + tip: `${row.kingdeeUnlockOperator || '-'} 于 ${this.$dayjs(row.kingdeeUnlockTime).format('YYYY-MM-DD HH:mm:ss')} 解锁`, + }; + } return { show: false }; }, displayValue(value) { diff --git a/src/views/payment/receipt-flow-batch-form.vue b/src/views/payment/receipt-flow-batch-form.vue index b7f0bd8..156a5f5 100644 --- a/src/views/payment/receipt-flow-batch-form.vue +++ b/src/views/payment/receipt-flow-batch-form.vue @@ -46,6 +46,9 @@ + + + @@ -298,10 +301,21 @@ export default { 0 ); }, + flowRemaining(flow) { + return Math.max(Number(flow.unclaimedAmount || 0) - Number(flow.claimedAmount || 0), 0); + }, maxAllocation(row) { const current = Number(row.settlement.allocations?.[row.flow.id] || 0); const otherTotal = this.settlementAllocatedTotal(row.settlement) - current; - return Math.max(this.settlementRemaining(row.settlement) - otherTotal, 0); + // 流水维度:该流水其余分摊已占用部分同样挤占剩余可认领金额 + const flowOtherTotal = this.flowAllocatedTotal(row.flow.id) - current; + return Math.max( + Math.min( + this.settlementRemaining(row.settlement) - otherTotal, + this.flowRemaining(row.flow) - flowOtherTotal + ), + 0 + ); }, handleAllocationChange(settlement) { const total = this.settlementAllocatedTotal(settlement); diff --git a/src/views/settlement/components/formal-settlement-editor.vue b/src/views/settlement/components/formal-settlement-editor.vue index b9d29d3..930e2a1 100644 --- a/src/views/settlement/components/formal-settlement-editor.vue +++ b/src/views/settlement/components/formal-settlement-editor.vue @@ -252,31 +252,36 @@ :index="detailIndexMethod" /> @@ -391,6 +396,16 @@ + +
@@ -1030,6 +1045,7 @@ import { viewKingdeeInvoiceFile, } from '@/utils/kingdee-attachment'; import KingdeeInvoicePoolDialog from '@/components/kingdee-invoice-pool-dialog/main.vue'; +import ProjectAdvanceWriteOff from './project-advance-write-off.vue'; import * as XLSX from 'xlsx'; import { isAdministrator } from '@/utils/auth'; @@ -1038,7 +1054,7 @@ const LOCAL_CURRENCY_HIDDEN_FIELDS = ['exchangeRateDate', 'exchangeRate', 'local export default { name: 'FormalSettlementEditor', - components: { InfoFilled, KingdeeInvoicePoolDialog }, + components: { InfoFilled, KingdeeInvoicePoolDialog, ProjectAdvanceWriteOff }, props: { modelValue: Boolean, recordId: [String, Number], @@ -1067,6 +1083,7 @@ export default { receiptClaims: [], adjustments: [], changeRecords: [], + projectAdvanceWriteOffs: [], attachments: [], selectedAttachmentRows: [], invoices: [], @@ -1205,6 +1222,26 @@ export default { title() { return this.readonly ? '查看正式结算单' : this.recordId ? '编辑正式结算单' : '新增正式结算单'; }, + detailFeeItemNames() { + const names = new Set(); + this.details.forEach(row => { + Object.keys(this.parseFeeItems(row.feeItemsJson || row.feeItems)).forEach(name => + names.add(name) + ); + }); + return Array.from(names); + }, + detailDisplayColumns() { + const columns = this.detailTableColumns; + const unitPriceIndex = columns.findIndex(column => column.prop === 'unitPrice'); + const beforeColumns = unitPriceIndex < 0 ? columns : columns.slice(0, unitPriceIndex + 1); + const afterColumns = unitPriceIndex < 0 ? [] : columns.slice(unitPriceIndex + 1); + return [ + ...beforeColumns.map(column => ({ kind: 'field', key: column.prop, column })), + ...this.detailFeeItemNames.map(name => ({ kind: 'fee', key: `fee-${name}`, name })), + ...afterColumns.map(column => ({ kind: 'field', key: column.prop, column })), + ]; + }, // 币种为空按人民币处理,与后端预结算/正式结算对空币种的默认取值一致 isLocalCurrencySettlement() { return normalizeCurrencyCode(this.form.currency) === 'RMB'; @@ -1305,6 +1342,13 @@ export default { } this.adjustments = data.adjustments || []; this.changeRecords = data.changeRecords || []; + this.projectAdvanceWriteOffs = (data.projectAdvanceWriteOffs || []).map(row => ({ + ...row, + writeOffAmount: Number(row.writeOffAmount || 0), + projectAdvanceAmount: Number(row.projectAdvanceAmount || 0), + availableWriteOffAmount: Number(row.availableWriteOffAmount || 0), + createTime: row.paymentCreateTime || row.createTime, + })); this.attachments = this.parseAttachments(data.attachmentsJson); this.selectedAttachmentRows = []; this.invoices = data.invoices || []; @@ -1335,6 +1379,7 @@ export default { this.receiptClaims = []; this.adjustments = []; this.changeRecords = []; + this.projectAdvanceWriteOffs = []; this.attachments = []; this.selectedAttachmentRows = []; this.invoices = []; @@ -2594,6 +2639,7 @@ export default { matchedAmount: Number(row.matchedAmount || 0), attachmentJson: row.attachmentJson || '', })), + projectAdvanceWriteOffs: this.$refs.projectAdvanceWriteOffRef?.toSavePayload?.() || [], remark: this.form.remark, }; }, @@ -2602,6 +2648,9 @@ export default { }, async handleDraftSave() { // 保存草稿:不做表单校验,直接落草稿并返回列表刷新 + if (this.$refs.projectAdvanceWriteOffRef?.validateBeforeSave?.() === false) { + return; + } this.draftSaving = true; try { await save({ @@ -2629,6 +2678,9 @@ export default { return this.$message.warning('请完善手工费用的费用类型和费用项'); } if (!this.validateInvoices()) return; + if (this.$refs.projectAdvanceWriteOffRef?.validateBeforeSave?.() === false) { + return; + } // 附件类型仅做提示,不阻断正式结算单新增/保存提交。 // this.validateAttachments(); this.saving = true; diff --git a/src/views/settlement/components/formal-settlement-table-panel.vue b/src/views/settlement/components/formal-settlement-table-panel.vue index 8d0d028..9108e5c 100644 --- a/src/views/settlement/components/formal-settlement-table-panel.vue +++ b/src/views/settlement/components/formal-settlement-table-panel.vue @@ -295,6 +295,16 @@ export default { tip: row.kingdeeLockFailReason || '系统将自动补偿重锁', }; } + // 金蝶解锁终态标识:删单解锁的源单已作废,此标签用于区分人工作废与金蝶删单 + if (row.kingdeeUnlockTime) { + const isDelete = row.kingdeeUnlockOperationName === '金蝶删单'; + return { + show: true, + type: isDelete ? 'danger' : 'warning', + label: row.kingdeeUnlockOperationName || '金蝶解锁', + tip: `${row.kingdeeUnlockOperator || '-'} 于 ${this.$dayjs(row.kingdeeUnlockTime).format('YYYY-MM-DD HH:mm:ss')} 解锁`, + }; + } return { show: false }; }, displayValue(value) { diff --git a/src/views/settlement/components/pre-settlement-editor.vue b/src/views/settlement/components/pre-settlement-editor.vue index 17310ff..5d5ad8e 100644 --- a/src/views/settlement/components/pre-settlement-editor.vue +++ b/src/views/settlement/components/pre-settlement-editor.vue @@ -247,60 +247,52 @@ :index="detailIndexMethod" /> - - - @@ -425,6 +417,16 @@
+ + @@ -780,12 +782,13 @@ @@ -943,6 +946,7 @@ import { h } from 'vue'; import { mapGetters } from 'vuex'; import { InfoFilled } from '@element-plus/icons-vue'; import ChangeRecordDetailDialog from '@/components/change-record-detail-dialog/main.vue'; +import ProjectAdvanceWriteOff from './project-advance-write-off.vue'; import { getMkPublicDetail } from '@/api/mk-process'; import { adjustDetail, @@ -987,7 +991,7 @@ import { import { formatContractOptionLabel } from '@/utils/contract'; import { normalizeCurrencyCode } from '@/utils/currency'; import { downloadFileByUrl } from '@/utils/util'; -import { submitMkApprovalFlow } from '@/utils/mk-approval'; +import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval'; import * as XLSX from 'xlsx'; import { isAdministrator } from '@/utils/auth'; @@ -996,7 +1000,7 @@ const LOCAL_CURRENCY_HIDDEN_FIELDS = ['exchangeRateDate', 'exchangeRate', 'local export default { name: 'PreSettlementEditor', - components: { InfoFilled, ChangeRecordDetailDialog }, + components: { InfoFilled, ChangeRecordDetailDialog, ProjectAdvanceWriteOff }, props: { modelValue: { type: Boolean, @@ -1099,6 +1103,7 @@ export default { size: 10, }, advances: [], + projectAdvanceWriteOffs: [], changeRecords: [], attachments: [], attachmentTypeOptions: [], @@ -1232,6 +1237,17 @@ export default { }); return Array.from(names); }, + detailDisplayColumns() { + const columns = this.visibleDetailColumns; + const unitPriceIndex = columns.findIndex(column => column.prop === 'unitPrice'); + const beforeColumns = unitPriceIndex < 0 ? columns : columns.slice(0, unitPriceIndex + 1); + const afterColumns = unitPriceIndex < 0 ? [] : columns.slice(unitPriceIndex + 1); + return [ + ...beforeColumns.map(column => ({ kind: 'field', key: column.prop, column })), + ...this.detailFeeItemNames.map(name => ({ kind: 'fee', key: `fee-${name}`, name })), + ...afterColumns.map(column => ({ kind: 'field', key: column.prop, column })), + ]; + }, // 按 preSettlementDetailId 分组的明细数据(用于拼接货物名称和类型) groupedDetails() { const groups = {}; @@ -1445,6 +1461,7 @@ export default { this.appliedDetailQuery = { ...this.detailQuery }; this.detailPage = { current: 1, size: 10 }; this.advances = []; + this.projectAdvanceWriteOffs = []; this.changeRecords = []; this.attachments = []; this.selectedAttachmentRows = []; @@ -1484,6 +1501,13 @@ export default { createUserName: row.createUserName || detail.createUserName, createTime: row.createTime || detail.createTime, })); + this.projectAdvanceWriteOffs = (detail.projectAdvanceWriteOffs || []).map(row => ({ + ...row, + writeOffAmount: Number(row.writeOffAmount || 0), + projectAdvanceAmount: Number(row.projectAdvanceAmount || 0), + availableWriteOffAmount: Number(row.availableWriteOffAmount || 0), + createTime: row.paymentCreateTime || row.createTime, + })); this.changeRecords = detail.changeRecords || []; this.attachments = this.parseAttachments(detail.attachmentsJson); this.sortAttachments(); @@ -1628,6 +1652,7 @@ export default { this.form.payerName = ''; this.form.payeeName = ''; this.summaryFees = []; + this.projectAdvanceWriteOffs = []; return; } this.form.contractId = contract.id; @@ -1643,6 +1668,7 @@ export default { if (contract.partyB) this.form.payeeName = contract.partyB; this.form.currency = contract.settlementCurrency || 'RMB'; this.summaryFees = []; + this.projectAdvanceWriteOffs = []; if (this.form.currency !== 'RMB') { this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD'); this.fetchBfiExchangeRate(); @@ -1663,6 +1689,9 @@ export default { this.$message.warning(`请完善结算合计第${invalidManualFeeIndex + 1}行的费用类型和费用项`); return; } + if (this.$refs.projectAdvanceWriteOffRef?.validateBeforeSave?.() === false) { + return; + } const stateKey = shouldSubmit ? 'submitting' : 'saving'; this[stateKey] = true; try { @@ -1673,13 +1702,14 @@ export default { if (shouldSubmit) { const previousApprovalStatus = this.form.approvalStatus || ''; await submit({ id: this.form.id }); - await submitMkApprovalFlow({ + const mkStarted = await submitMkApprovalFlow({ bizType: 'pre-settlement', bizId: this.form.id, subjectName: this.form.preSettlementNo || this.form.contractName || '', approvalStatus: previousApprovalStatus, + mkEnabled: await loadMkEnabled().catch(() => false), }); - this.$message.success('审批流程已发起'); + this.$message.success(mkStarted ? '审批流程已发起' : '提交成功'); this.visible = false; this.$emit('success', this.form.id); return; @@ -1709,6 +1739,7 @@ export default { remark: row.remark, manualFlag: row.manualFlag || 0, })), + projectAdvanceWriteOffs: this.$refs.projectAdvanceWriteOffRef?.toSavePayload?.() || [], }; }, addSummaryFee() { @@ -1765,7 +1796,7 @@ export default { return option?.dictValue || value; }, formatDetailMileage(value) { - if (value === undefined || value === null || value === '' || Number(value) === -1) return ''; + if (value === undefined || value === null || value === '' || Number(value) === -1) return '-'; return value; }, removeSummaryFee(index) { diff --git a/src/views/settlement/components/project-advance-write-off.vue b/src/views/settlement/components/project-advance-write-off.vue new file mode 100644 index 0000000..a273121 --- /dev/null +++ b/src/views/settlement/components/project-advance-write-off.vue @@ -0,0 +1,300 @@ + + + + + diff --git a/src/views/settlement/components/settlement-import-page.vue b/src/views/settlement/components/settlement-import-page.vue new file mode 100644 index 0000000..e9a2475 --- /dev/null +++ b/src/views/settlement/components/settlement-import-page.vue @@ -0,0 +1,1118 @@ + + + + + diff --git a/src/views/settlement/formal-settlement.vue b/src/views/settlement/formal-settlement.vue index b65d98c..c4b9da0 100644 --- a/src/views/settlement/formal-settlement.vue +++ b/src/views/settlement/formal-settlement.vue @@ -368,6 +368,7 @@ export default { bizId: row.id, subjectName: row.formalSettlementNo || '', approvalStatus: previousApprovalStatus, + mkEnabled: this.mkEnabled, }); this.$message.success('提交成功'); this.loadTable(); diff --git a/src/views/settlement/pre-settlement.vue b/src/views/settlement/pre-settlement.vue index 414acc6..75caf7a 100644 --- a/src/views/settlement/pre-settlement.vue +++ b/src/views/settlement/pre-settlement.vue @@ -661,6 +661,7 @@ export default { bizId: row.id, subjectName: row.preSettlementNo || '', approvalStatus: previousApprovalStatus, + mkEnabled: this.mkEnabled, }); this.$message.success('提交成功'); this.loadTable(); diff --git a/src/views/settlement/receivable-payable-detail.vue b/src/views/settlement/receivable-payable-detail.vue index aae040e..b0a39ef 100644 --- a/src/views/settlement/receivable-payable-detail.vue +++ b/src/views/settlement/receivable-payable-detail.vue @@ -2691,12 +2691,12 @@ export default { return `${Number(value).toFixed(2)} ${currency}`; }, formatDetailCell(row, prop) { + if (prop === 'mileage' && Number(row?.[prop]) === -1) { + return '-'; + } if ( prop === 'mileage' && - (row?.[prop] === null || - row?.[prop] === undefined || - row?.[prop] === '' || - Number(row[prop]) === -1) + (row?.[prop] === null || row?.[prop] === undefined || row?.[prop] === '') ) { return ''; } diff --git a/src/views/settlement/settlement-import-form.vue b/src/views/settlement/settlement-import-form.vue new file mode 100644 index 0000000..77f982f --- /dev/null +++ b/src/views/settlement/settlement-import-form.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/src/views/settlement/settlement-import.vue b/src/views/settlement/settlement-import.vue new file mode 100644 index 0000000..6cf0d43 --- /dev/null +++ b/src/views/settlement/settlement-import.vue @@ -0,0 +1,531 @@ + + + + + diff --git a/src/views/vehicle/accident-record.vue b/src/views/vehicle/accident-record.vue index c62761b..82d4283 100644 --- a/src/views/vehicle/accident-record.vue +++ b/src/views/vehicle/accident-record.vue @@ -158,7 +158,7 @@ import { exportBlob } from '@/api/common'; import { downloadXls } from '@/utils/util'; import { openImportDialog } from '@/utils/import-excel'; import { getToken, isAdministrator } from '@/utils/auth'; -import { normalizeSearchRangeParams } from '@/utils/search-range'; +import { applyIncomingSearch, normalizeSearchRangeParams } from '@/utils/search-range'; import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue'; import { mapGetters } from 'vuex'; import { excelOption, exportColumns, option } from '@/option/vehicle/accident-record'; @@ -550,7 +550,7 @@ export default { }; }, buildQuery(params = {}) { - return normalizeSearchRangeParams({ ...params, ...this.query }, createTimeRangeMap); + return normalizeSearchRangeParams(applyIncomingSearch(this.query, params), createTimeRangeMap); }, handleTemplate() { exportBlob( diff --git a/src/views/vehicle/credit-score-quantification.vue b/src/views/vehicle/credit-score-quantification.vue index ec5d126..3a145b2 100644 --- a/src/views/vehicle/credit-score-quantification.vue +++ b/src/views/vehicle/credit-score-quantification.vue @@ -468,7 +468,11 @@ import { publish, changeStatus, } from '@/api/vehicle/credit-score-quantification'; -import { normalizeSearchRangeParams } from '@/utils/search-range'; +import { + applyIncomingSearch, + normalizeSearchRangeParams, + omitInvalidListStatus, +} from '@/utils/search-range'; import { mapGetters } from 'vuex'; import { isAdministrator } from '@/utils/auth'; @@ -1324,7 +1328,9 @@ export default { }); }, buildQuery(params = {}) { - return normalizeSearchRangeParams({ ...params, ...this.query }, createTimeRangeMap); + return omitInvalidListStatus( + normalizeSearchRangeParams(applyIncomingSearch(this.query, params), createTimeRangeMap) + ); }, }, }; diff --git a/src/views/vehicle/customer-archive.vue b/src/views/vehicle/customer-archive.vue index 53156a8..510fa24 100644 --- a/src/views/vehicle/customer-archive.vue +++ b/src/views/vehicle/customer-archive.vue @@ -208,7 +208,7 @@ v-if=" hasPermission('customer_archive_sync') && row.approvalStatus === 'approved' && - row.kingdeeStatus === 'failed' + !['synced', 'exist'].includes(row.kingdeeStatus) " @click="handleSyncKingdee(row)" > @@ -1791,6 +1791,8 @@ export default { }, /** 业务字典 mk_template「开启MK」=1 */ mkEnabled: false, + /** 是否已完成 MK 开关加载,避免提交时误判 */ + _mkEnabledResolved: false, formRules: { customerCode: [{ max: 20, message: '客商编号最多20个字符', trigger: 'blur' }], fullName: [{ required: true, message: '请输入客商名称', trigger: 'blur' }], @@ -1871,7 +1873,7 @@ export default { editBtn: false, selection: true, dialogClickModal: false, - menuWidth: 320, + menuWidth: 400, column: [ { label: '客商编号', @@ -2041,7 +2043,6 @@ export default { search: true, searchOrder: 6, searchValue: '', - dataType: 'number', minWidth: 100, dicData: [ { label: '全部', value: '' }, @@ -2340,7 +2341,13 @@ export default { sessionStorage.setItem(customerArchiveListRefreshKey, '1'); }, hasPermission(code) { - return this.isAdmin || this.validData(this.permission[code], false); + if (this.isAdmin) { + return true; + } + const permission = this.permission || {}; + const value = permission[code]; + // 不依赖 Avue validData,避免权限值为 true 以外的真值时被误判 + return value === true || value === 'true' || value === 1 || value === '1'; }, mergeSelectOptions(target, values) { const list = Array.isArray(values) ? values : values ? [values] : []; @@ -3100,12 +3107,15 @@ export default { }); }, initMkEnabled() { + this._mkEnabledResolved = false; loadMkEnabled() .then(enabled => { this.mkEnabled = !!enabled; + this._mkEnabledResolved = true; }) .catch(() => { this.mkEnabled = false; + this._mkEnabledResolved = true; }); }, initScoreQuantificationOptions() { @@ -4481,15 +4491,34 @@ export default { }); }, /** - * 列表 / 新增 / 编辑提交共用:开启 MK 时提交审核流,并更新客商审批状态 + * 列表 / 新增 / 编辑提交共用: + * 未开启 MK 时仅走本地提交审批(与新增一致,不请求 MK 流程); + * 开启 MK 时先提交审核流,再更新客商审批状态。 */ submitCustomerApproval(id, fullName = '', approvalStatus = '') { - return submitMkApprovalFlow({ - bizType: 'customer-archive', - bizId: id, - subjectName: fullName, - approvalStatus, - }).then(() => submitApproval(id)); + const submitLocal = () => submitApproval(id); + const run = enabled => { + this.mkEnabled = !!enabled; + this._mkEnabledResolved = true; + if (!this.mkEnabled) { + return submitLocal(); + } + return submitMkApprovalFlow({ + bizType: 'customer-archive', + bizId: id, + subjectName: fullName, + approvalStatus, + mkEnabled: true, + }).then(submitLocal); + }; + + // 与列表按钮一致:以页面 mkEnabled 为准,未开启时不请求 MK + if (this._mkEnabledResolved) { + return run(this.mkEnabled); + } + return loadMkEnabled() + .then(enabled => run(enabled)) + .catch(() => run(false)); }, openReviewScore() { const scores = this.archiveForm.scores || []; @@ -5194,17 +5223,20 @@ export default { this.$message.error('获取审核跳转地址失败'); }); }, - handleApprove(row) { - this.$confirm('是否确认审核通过?', '提示', { + async handleApprove(row) { + await this.$confirm('是否确认审核通过?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', - }) - .then(() => approve(row.id)) - .then(() => { - this.onLoad(this.page); - this.$message({ type: 'success', message: '审核通过!' }); - }); + }); + const res = await approve(row.id); + // data=false 表示审核已通过但推送金蝶失败,msg 带具体原因 + if (res.data.data === false) { + this.$message.warning(res.data.msg); + } else { + this.$message.success(res.data.msg || '审核通过!'); + } + this.onLoad(this.page); }, async handleSyncKingdee(row) { if (this.kingdeeSyncingId) return; @@ -5264,19 +5296,9 @@ export default { this.onLoad(this.page); }, searchChange(params, done) { - // 搜索区所属组织级联返回的是 ID 路径数组,取末级 ID 转成部门名称传给后端(与列表接口 deptName 字段一致) - if (Array.isArray(params.deptName)) { - const path = params.deptName.filter( - item => item !== undefined && item !== null && item !== '' - ); - const deptId = path.length ? path[path.length - 1] : ''; - params.deptName = deptId - ? this.deptOptions.find(item => String(item.value) === String(deptId))?.rawLabel || '' - : ''; - } this.query = this.buildQuery(params); this.page.currentPage = 1; - this.onLoad(this.page); + this.onLoad(this.page, this.query); done(); }, selectionChange(list) { @@ -5337,7 +5359,38 @@ export default { }; }, buildQuery(params = {}) { - return normalizeSearchRangeParams({ ...params, ...this.query }, createTimeRangeMap); + // 新条件覆盖旧条件;本次显式清空的字段必须从查询里去掉,避免第二次搜索仍带着上一次的客商名称。 + const merged = { ...this.query, ...params }; + Object.keys(params).forEach(key => { + const value = params[key]; + if ( + value === '' || + value === null || + value === undefined || + (Array.isArray(value) && value.length === 0) + ) { + delete merged[key]; + } + }); + if (Array.isArray(merged.deptName)) { + const path = merged.deptName.filter( + item => item !== undefined && item !== null && item !== '' + ); + const deptId = path.length ? path[path.length - 1] : ''; + merged.deptName = deptId + ? this.deptOptions.find(item => String(item.value) === String(deptId))?.rawLabel || '' + : ''; + if (!merged.deptName) { + delete merged.deptName; + } + } + const values = normalizeSearchRangeParams(merged, createTimeRangeMap); + // 状态下拉「全部」会被数字类型转成 0/-1,二次搜索会把列表滤空。 + const status = values.status; + if (status === 0 || status === '0' || status === -1 || status === '-1') { + delete values.status; + } + return values; }, }, }; diff --git a/src/views/vehicle/insurance-record.vue b/src/views/vehicle/insurance-record.vue index 5a6f1cc..ac574be 100644 --- a/src/views/vehicle/insurance-record.vue +++ b/src/views/vehicle/insurance-record.vue @@ -220,7 +220,7 @@ import { downloadXls } from '@/utils/util'; import { handleImportExcel } from '@/utils/import-excel'; import { getToken, isAdministrator } from '@/utils/auth'; import { getUploadHeaders } from '@/utils/upload'; -import { normalizeSearchRangeParams } from '@/utils/search-range'; +import { applyIncomingSearch, normalizeSearchRangeParams } from '@/utils/search-range'; import { mapGetters } from 'vuex'; import { ElLoading } from 'element-plus'; import { excelOption, exportColumns, option } from '@/option/vehicle/insurance-record'; @@ -409,7 +409,7 @@ export default { : integerPart; }, buildQuery(params = {}) { - return normalizeSearchRangeParams({ ...params, ...this.query }, createTimeRangeMap); + return normalizeSearchRangeParams(applyIncomingSearch(this.query, params), createTimeRangeMap); }, validateRow(row) { if (row.startDate && row.endDate && row.endDate < row.startDate) { diff --git a/src/views/vehicle/maintenance-plan.vue b/src/views/vehicle/maintenance-plan.vue index 2612cd1..89f4bf1 100644 --- a/src/views/vehicle/maintenance-plan.vue +++ b/src/views/vehicle/maintenance-plan.vue @@ -195,7 +195,7 @@ import { downloadXls } from '@/utils/util'; import { openImportDialog } from '@/utils/import-excel'; import { formatUpdateUserName } from '@/utils/audit'; import { getToken, isAdministrator } from '@/utils/auth'; -import { normalizeSearchRangeParams } from '@/utils/search-range'; +import { applyIncomingSearch, normalizeSearchRangeParams } from '@/utils/search-range'; import AddressMapPicker from '@/components/address-map-picker/main.vue'; import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue'; import { mapGetters } from 'vuex'; @@ -903,7 +903,7 @@ export default { }; }, buildQuery(params = {}) { - return normalizeSearchRangeParams({ ...params, ...this.query }, createTimeRangeMap); + return normalizeSearchRangeParams(applyIncomingSearch(this.query, params), createTimeRangeMap); }, handleTemplate() { exportBlob( diff --git a/src/views/vehicle/maintenance-record.vue b/src/views/vehicle/maintenance-record.vue index 05feed6..886c483 100644 --- a/src/views/vehicle/maintenance-record.vue +++ b/src/views/vehicle/maintenance-record.vue @@ -168,7 +168,7 @@ import { downloadXls } from '@/utils/util'; import { openImportDialog } from '@/utils/import-excel'; import { formatUpdateUserName } from '@/utils/audit'; import { getToken, isAdministrator } from '@/utils/auth'; -import { normalizeSearchRangeParams } from '@/utils/search-range'; +import { applyIncomingSearch, normalizeSearchRangeParams } from '@/utils/search-range'; import AddressMapPicker from '@/components/address-map-picker/main.vue'; import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue'; import { mapGetters } from 'vuex'; @@ -831,7 +831,7 @@ export default { }; }, buildQuery(params = {}) { - return normalizeSearchRangeParams({ ...params, ...this.query }, createTimeRangeMap); + return normalizeSearchRangeParams(applyIncomingSearch(this.query, params), createTimeRangeMap); }, handleTemplate() { exportBlob( diff --git a/src/views/vehicle/mileage-record.vue b/src/views/vehicle/mileage-record.vue index 0c9c5c4..a443be6 100644 --- a/src/views/vehicle/mileage-record.vue +++ b/src/views/vehicle/mileage-record.vue @@ -210,7 +210,7 @@ import { exportBlob } from '@/api/common'; import { downloadXls } from '@/utils/util'; import { openImportDialog } from '@/utils/import-excel'; import { getToken, isAdministrator } from '@/utils/auth'; -import { normalizeSearchRangeParams } from '@/utils/search-range'; +import { applyIncomingSearch, normalizeSearchRangeParams } from '@/utils/search-range'; import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue'; import { mapGetters } from 'vuex'; import { excelOption, exportColumns, option } from '@/option/vehicle/mileage-record'; @@ -607,7 +607,7 @@ export default { }, buildQuery(params = {}) { return normalizeSearchRangeParams( - { ...params, ...this.query, vehicleType: '车辆' }, + { ...applyIncomingSearch(this.query, params), vehicleType: '车辆' }, createTimeRangeMap ); }, diff --git a/src/views/vehicle/tire-replacement-record.vue b/src/views/vehicle/tire-replacement-record.vue index 08fea1a..f5b457f 100644 --- a/src/views/vehicle/tire-replacement-record.vue +++ b/src/views/vehicle/tire-replacement-record.vue @@ -155,7 +155,7 @@ import { exportBlob } from '@/api/common'; import { downloadXls } from '@/utils/util'; import { openImportDialog } from '@/utils/import-excel'; import { getToken, isAdministrator } from '@/utils/auth'; -import { normalizeSearchRangeParams } from '@/utils/search-range'; +import { applyIncomingSearch, normalizeSearchRangeParams } from '@/utils/search-range'; import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue'; import { mapGetters } from 'vuex'; import { excelOption, exportColumns, option } from '@/option/vehicle/tire-replacement-record'; @@ -500,7 +500,7 @@ export default { }; }, buildQuery(params = {}) { - return normalizeSearchRangeParams({ ...params, ...this.query }, createTimeRangeMap); + return normalizeSearchRangeParams(applyIncomingSearch(this.query, params), createTimeRangeMap); }, handleTemplate() { exportBlob( diff --git a/src/views/vehicle/transport-change-record.vue b/src/views/vehicle/transport-change-record.vue index 202bd0e..28839c3 100644 --- a/src/views/vehicle/transport-change-record.vue +++ b/src/views/vehicle/transport-change-record.vue @@ -130,7 +130,7 @@ import { exportBlob } from '@/api/common'; import { downloadXls } from '@/utils/util'; import { openImportDialog } from '@/utils/import-excel'; import { getToken, isAdministrator } from '@/utils/auth'; -import { normalizeSearchRangeParams } from '@/utils/search-range'; +import { applyIncomingSearch, normalizeSearchRangeParams } from '@/utils/search-range'; import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue'; import { mapGetters } from 'vuex'; import { excelOption, exportColumns, option } from '@/option/vehicle/transport-change-record'; @@ -478,7 +478,7 @@ export default { }; }, buildQuery(params = {}) { - return normalizeSearchRangeParams({ ...params, ...this.query }, createTimeRangeMap); + return normalizeSearchRangeParams(applyIncomingSearch(this.query, params), createTimeRangeMap); }, handleTemplate() { exportBlob( diff --git a/src/views/vehicle/violation-record.vue b/src/views/vehicle/violation-record.vue index 0ee312d..79a4eef 100644 --- a/src/views/vehicle/violation-record.vue +++ b/src/views/vehicle/violation-record.vue @@ -197,7 +197,7 @@ import { exportBlob } from '@/api/common'; import { downloadXls } from '@/utils/util'; import { openImportDialog } from '@/utils/import-excel'; import { getToken, isAdministrator } from '@/utils/auth'; -import { normalizeSearchRangeParams } from '@/utils/search-range'; +import { applyIncomingSearch, normalizeSearchRangeParams } from '@/utils/search-range'; import AddressMapPicker from '@/components/address-map-picker/main.vue'; import VehicleAttachmentTable from '@/components/vehicle-attachment-table/main.vue'; import { mapGetters } from 'vuex'; @@ -673,7 +673,7 @@ export default { }; }, buildQuery(params = {}) { - return normalizeSearchRangeParams({ ...params, ...this.query }, createTimeRangeMap); + return normalizeSearchRangeParams(applyIncomingSearch(this.query, params), createTimeRangeMap); }, handleTemplate() { exportBlob( diff --git a/vite.config.mjs b/vite.config.mjs index b59a8be..3e0426e 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -49,28 +49,28 @@ export default ({ mode, command }) => { __VUE_I18N_LEGACY_API__: true, __INTLIFY_PROD_DEVTOOLS__: false, }, - server: { - port: 2888, - proxy: { - '/api': { - // 本地网关默认 80 端口(blade-gateway bootstrap.yml);开发时需先启动网关及 blade-auth/blade-system 等服务 - // 注意:本机 8080 一般是 Nacos,不是业务网关 - target: 'http://localhost', - // target: 'http://172.16.203.228:8000', // 仅联调远程网关时启用(且不要 rewrite) - changeOrigin: true, - rewrite: path => path.replace(/^\/api/, ''), - }, - }, - }, - //server: { - // port: 2889, - // proxy: { - // '/api': { - // target: 'http://172.16.203.221:8000', - // changeOrigin: true, - // }, - // }, - //}, + server: { + port: 2888, + proxy: { + '/api': { + // 本地网关默认 80 端口(blade-gateway bootstrap.yml);开发时需先启动网关及 blade-auth/blade-system 等服务 + // 注意:本机 8080 一般是 Nacos,不是业务网关 + target: 'http://localhost', + // target: 'http://172.16.203.228:8000', // 仅联调远程网关时启用(且不要 rewrite) + 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, './'),