diff --git a/src/api/business/hcs.js b/src/api/business/hcs.js new file mode 100644 index 0000000..4dac778 --- /dev/null +++ b/src/api/business/hcs.js @@ -0,0 +1,93 @@ +import request from '@/axios'; + +const baseUrl = '/blade-transport/hcs'; + +// 合规推送涉及 HCS 远程多次调用(风险校验+附件转传+起草),可达 20s+,覆盖全局 10s 超时 +export const repush = (bizType, id) => + request({ + url: `${baseUrl}/sync/${bizType}/${id}`, + method: 'post', + timeout: 60000, + }); + +export const getTemplates = () => + request({ + url: `${baseUrl}/master-data/template`, + method: 'get', + }); + +export const refreshMasterData = () => + request({ + url: `${baseUrl}/master-data/refresh`, + method: 'post', + timeout: 60000, + }); + +export const getRisk = customerCode => + request({ + url: `${baseUrl}/risk`, + method: 'get', + params: { customerCode }, + timeout: 30000, + }); + +export const uploadAttachment = formData => + request({ + url: `${baseUrl}/attachment/upload`, + method: 'post', + data: formData, + timeout: 60000, + }); + +export const downloadAttachment = (fdId, name) => + request({ + url: `${baseUrl}/attachment/download`, + method: 'get', + params: { fdId, name }, + responseType: 'blob', + timeout: 120000, + }); + +export const reconProject = projectCode => + request({ + url: `${baseUrl}/recon/project`, + method: 'get', + params: { projectCode }, + timeout: 30000, + }); + +export const reconContract = contractNo => + request({ + url: `${baseUrl}/recon/contract`, + method: 'get', + params: { contractNo }, + timeout: 30000, + }); + +export const reconContractDetail = params => + request({ + url: `${baseUrl}/recon/contract/detail`, + method: 'get', + params, + timeout: 30000, + }); + +export const applyContractSpec = data => + request({ + url: `${baseUrl}/contract/spec-apply`, + method: 'post', + data, + timeout: 60000, + }); + +export const confirmContractSpec = (id, result) => + request({ + url: `${baseUrl}/contract/spec/confirm`, + method: 'post', + params: { id, result }, + }); + +export const unwrapData = response => { + const data = response?.data?.data ?? response?.data; + return data && typeof data === 'object' && 'data' in data ? data.data : data; +}; diff --git a/src/option/business/common.js b/src/option/business/common.js index 1f6feb2..490c9b4 100644 --- a/src/option/business/common.js +++ b/src/option/business/common.js @@ -1,4 +1,4 @@ -export const transportTypeOptions = [ +export const transportTypeOptions = [ { label: '公路运输', value: '公路运输' }, { label: '铁路运输', value: '铁路运输' }, { label: '水路运输', value: '水路运输' }, @@ -142,6 +142,12 @@ export const contractArchiveStatusOptions = [ { label: '已归档', value: '已归档' }, ]; +export const hcsSyncStatusOptions = [ + { label: '待推送', value: 'unsynced' }, + { label: '已推送', value: 'synced' }, + { label: '推送失败', value: 'failed' }, +]; + export const nodeOptions = [ { label: '接单', value: '接单' }, { label: '到场', value: '到场' }, diff --git a/src/option/business/contract-manage.js b/src/option/business/contract-manage.js index 4a72be5..8789e72 100644 --- a/src/option/business/contract-manage.js +++ b/src/option/business/contract-manage.js @@ -6,6 +6,7 @@ import { contractStageOptions, createCrudOption, effectiveTypeOptions, + hcsSyncStatusOptions, selectRule, signTypeOptions, textRule, @@ -589,6 +590,19 @@ export const option = createCrudOption([ addDisplay: false, editDisplay: false, }, + { + label: '合规状态', + prop: 'hcsSyncStatus', + type: 'select', + search: true, + searchOrder: 6, + searchPlaceholder: '请选择', + slot: true, + dicData: hcsSyncStatusOptions, + minWidth: 110, + addDisplay: false, + editDisplay: false, + }, { label: '当前节点', prop: 'currentNode', diff --git a/src/option/business/project-apply.js b/src/option/business/project-apply.js index 721413a..c3a23e5 100644 --- a/src/option/business/project-apply.js +++ b/src/option/business/project-apply.js @@ -3,6 +3,7 @@ import { businessModeOptions, businessTypeOptions, createCrudOption, + hcsSyncStatusOptions, nonNegativeRule, projectApprovalStatusOptions, projectSourceOptions, @@ -405,6 +406,19 @@ export const option = createCrudOption([ addDisplay: false, editDisplay: false, }, + { + label: '合规状态', + prop: 'hcsSyncStatus', + type: 'select', + search: true, + searchPlaceholder: '请选择', + searchOrder: 1, + slot: true, + dicData: hcsSyncStatusOptions, + minWidth: 110, + addDisplay: false, + editDisplay: false, + }, { label: '生效类型', prop: 'effectiveType', diff --git a/src/views/business/components/hcs-contract-panel.vue b/src/views/business/components/hcs-contract-panel.vue new file mode 100644 index 0000000..e0e7f47 --- /dev/null +++ b/src/views/business/components/hcs-contract-panel.vue @@ -0,0 +1,284 @@ + + + + + diff --git a/src/views/business/components/hcs-recon-dialog.vue b/src/views/business/components/hcs-recon-dialog.vue new file mode 100644 index 0000000..6e80405 --- /dev/null +++ b/src/views/business/components/hcs-recon-dialog.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/src/views/business/contract-manage.vue b/src/views/business/contract-manage.vue index c3b48e7..33e1f36 100644 --- a/src/views/business/contract-manage.vue +++ b/src/views/business/contract-manage.vue @@ -72,6 +72,13 @@ + + + + + + + + + { + const risk = this.partyRisk[prop]; + if (!risk) return; + const name = this.form[prop] || ''; + const label = prop === 'partyA' ? '甲方' : '乙方'; + if (risk.blacklist) parts.push(`${label}[${name}]为黑名单客商`); + else if (['高', '高风险', 'high'].includes(String(risk.riskLevel).trim())) + parts.push(`${label}[${name}]为高风险客商`); + }); + if (!parts.length) return ''; + return `${parts.join(';')},按集团规定不可直接签约,审批通过后需在合同详情发起特批`; + }, detailLegalSealText() { const value = this.detailRow.legalSealFlag; if (value === undefined || value === null || value === '') return '-'; @@ -1573,6 +1633,55 @@ export default { if (stage === 'terminated') return 'danger'; return 'info'; }, + // ==================== 合规(HCS)推送/对账(照“同步金蝶”按钮模式) ==================== + hcsSyncLabel(value) { + return { unsynced: '待推送', synced: '已推送', failed: '推送失败' }[value] || value; + }, + hcsSyncTagType(value) { + return { unsynced: 'info', synced: 'success', failed: 'danger' }[value] || 'info'; + }, + canHcsRepush(row) { + return ( + ['approved', 'change_approved'].includes(row.approvalStatus) && + row.hcsSyncStatus !== 'synced' + ); + }, + canHcsRecon(row) { + return ['approved', 'change_approved'].includes(row.approvalStatus); + }, + handleHcsRepush(row) { + if (this.hcsRepushingId) return; + this.$confirm('确定推送该数据到合同合规系统?', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning', + }).then(() => { + this.hcsRepushingId = row.id; + hcsRepush('contract', row.id) + .then(res => { + if (res.data?.success === false) { + this.$message.error(res.data?.msg || '推送合规失败'); + return; + } + this.$message.success(res.data?.data || '推送合规成功'); + this.onLoad(this.page, this.query); + }) + .catch(error => { + this.$message.error(error?.response?.data?.msg || '推送合规失败'); + }) + .finally(() => { + this.hcsRepushingId = null; + }); + }); + }, + openHcsRecon(row) { + if (!row.contractNo) { + this.$message.warning('该合同无合同编号,无法对账'); + return; + } + this.hcsReconLoader = () => reconContract(row.contractNo); + this.hcsReconVisible = true; + }, canEdit(row) { return ( this.hasPermission(`${this.config.permission}_edit`) && @@ -2113,8 +2222,21 @@ export default { ]) .then(responses => { if (requestId !== this.contractPartyRequestId) return; - const names = responses - .flatMap(extractRecords) + const records = responses.flatMap(extractRecords); + // 名称→客商编码映射,供 C101-1 风险查询使用(§9-U6 编码体系联调确认) + const codeMap = {}; + records.forEach(item => { + const code = item.customerCode; + if (!code) return; + [item.fullName, item.shortName, item.customerName] + .map(name => String(name || '').trim()) + .filter(Boolean) + .forEach(name => { + codeMap[name] = code; + }); + }); + this.contractPartyCodeMap = codeMap; + const names = records .map(item => item.fullName || item.shortName || item.customerName || item.customerCode) .map(name => String(name || '').trim()) .filter(Boolean); @@ -2127,6 +2249,29 @@ export default { if (requestId === this.contractPartyRequestId) this.contractPartyLoading = false; }); }, + // C101-1 客商风险查询(展示用,强校验在后端推送/付款链路):失败静默不阻塞填写 + queryPartyRisk(prop) { + const name = this.form[prop]; + const code = name ? this.contractPartyCodeMap[name] : null; + if (!code) { + this.partyRisk = { ...this.partyRisk, [prop]: null }; + return; + } + getRisk(code) + .then(res => { + const risk = res?.data?.data ?? res?.data ?? {}; + this.partyRisk = { + ...this.partyRisk, + [prop]: { + riskLevel: risk.fdRiskLevel || '', + blacklist: String(risk.fdBlackStatus || '').toLowerCase() === 'yes', + }, + }; + }) + .catch(() => { + this.partyRisk = { ...this.partyRisk, [prop]: null }; + }); + }, loadOrganizationOptions() { if (this.organizationLoading) return; this.organizationLoading = true; @@ -2961,6 +3106,10 @@ export default { vertical-align: bottom; } +.contract-manage-form__risk-alert { + width: 100%; +} + .contract-manage-page { &__expiry-search { display: flex; diff --git a/src/views/business/hcs-master-data.vue b/src/views/business/hcs-master-data.vue new file mode 100644 index 0000000..0cf5872 --- /dev/null +++ b/src/views/business/hcs-master-data.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/src/views/business/project-apply.vue b/src/views/business/project-apply.vue index 0b4dc50..6a55654 100644 --- a/src/views/business/project-apply.vue +++ b/src/views/business/project-apply.vue @@ -58,6 +58,17 @@ {{ displayStatus(row, 'approvalStatus') }} + + + + import('@/views/vehicle/customer-archive.vue')), + HcsReconDialog: defineAsyncComponent(() => import('./components/hcs-recon-dialog.vue')), ChangeRecordDetailDialog, ElImageViewer, OpenFileViewer, @@ -1107,6 +1136,10 @@ export default { { label: '高风险', value: 'high', statKey: 'high' }, { label: '中风险', value: 'medium', statKey: 'medium' }, ], + // 合规(HCS)推送/对账状态 + hcsRepushingId: null, + hcsReconVisible: false, + hcsReconLoader: null, page: { pageSize: 10, pageSizes: [10, 20, 50, 100], @@ -1578,6 +1611,56 @@ export default { return this.canOperation(operation, row); }); }, + // ==================== 合规(HCS)推送/对账(照“同步金蝶”按钮模式) ==================== + hcsSyncLabel(value) { + return { unsynced: '待推送', synced: '已推送', failed: '推送失败' }[value] || value; + }, + hcsSyncTagType(value) { + return { unsynced: 'info', synced: 'success', failed: 'danger' }[value] || 'info'; + }, + canHcsRepush(row) { + return ( + !this.readonlyScope && + ['approved', 'change_approved'].includes(row.approvalStatus) && + row.hcsSyncStatus !== 'synced' + ); + }, + canHcsRecon(row) { + return ['approved', 'change_approved'].includes(row.approvalStatus); + }, + handleHcsRepush(row) { + if (this.hcsRepushingId) return; + this.$confirm('确定推送该数据到合同合规系统?', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning', + }).then(() => { + this.hcsRepushingId = row.id; + hcsRepush('project', row.id) + .then(res => { + if (res.data?.success === false) { + this.$message.error(res.data?.msg || '推送合规失败'); + return; + } + this.$message.success(res.data?.data || '推送合规成功'); + this.onLoad(this.page, this.query); + }) + .catch(error => { + this.$message.error(error?.response?.data?.msg || '推送合规失败'); + }) + .finally(() => { + this.hcsRepushingId = null; + }); + }); + }, + openHcsRecon(row) { + if (!row.projectCode) { + this.$message.warning('该项目无项目编号,无法对账'); + return; + } + this.hcsReconLoader = () => reconProject(row.projectCode); + this.hcsReconVisible = true; + }, canOperation(operation, row) { const permission = operation.permission || `${this.config.permission}_${operation.action}`; if (!this.hasPermission(permission) || this.readonlyScope || row.readonly) return false;