Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 78bb38e955 | |||
| 2cf966d6e3 | |||
| d23a9b00de | |||
| d89fd9db97 | |||
| 94ee14b683 | |||
| a725d3078f | |||
| 1957a78c00 | |||
| acfe9bdd74 | |||
| 698f808f0b | |||
| ea6ec13ea4 | |||
| 6119c44394 | |||
| 9dab7f7495 | |||
| f5697e111a | |||
| 775307f5c0 | |||
| 4502881063 | |||
| 358add743e | |||
| f67a4036ed | |||
| 7ba6620f06 | |||
| 9a3652d767 | |||
| 450505715a | |||
| 2b51619728 | |||
| 5f167aae50 | |||
| 0d8eb25253 | |||
| e53852b05f | |||
| 8d3e87d710 | |||
| c7b07f0c95 | |||
| 59199397e0 | |||
| 70d49d022b | |||
| 7d16660bfc | |||
| eb32a275f3 | |||
| 9150213b0f | |||
| 4e8163dd18 | |||
| 89bbc43f12 | |||
| 4199318aa9 | |||
| 555189177c |
@@ -2,6 +2,7 @@
|
||||
node_modules
|
||||
pnpm-lock.yaml
|
||||
/dist
|
||||
/dist.zip
|
||||
|
||||
/tests/e2e/videos/
|
||||
/tests/e2e/screenshots/
|
||||
|
||||
+3
-2
@@ -4,8 +4,9 @@
|
||||
"scripts": {
|
||||
"dev": "vite --host",
|
||||
"prod": "vite --mode production",
|
||||
"build": "vite build",
|
||||
"build:prod": "vite build --mode production",
|
||||
"build": "vite build && node scripts/zip-dist.mjs",
|
||||
"build:prod": "vite build --mode production && node scripts/zip-dist.mjs",
|
||||
"zip:dist": "node scripts/zip-dist.mjs",
|
||||
"serve": "vite preview --host",
|
||||
"typecheck": "vue-tsc --noEmit"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { existsSync, rmSync, statSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
|
||||
const root = process.cwd();
|
||||
const distDir = resolve(root, 'dist');
|
||||
const zipPath = resolve(root, 'dist.zip');
|
||||
|
||||
if (!existsSync(distDir) || !statSync(distDir).isDirectory()) {
|
||||
console.error('[zip-dist] dist 目录不存在,请先完成构建');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (existsSync(zipPath)) {
|
||||
rmSync(zipPath);
|
||||
}
|
||||
|
||||
// 压缩 dist 内文件到根级(解压即 index.html / assets,便于直接上传部署)
|
||||
execFileSync('zip', ['-qr', zipPath, '.', '-x', '*.DS_Store', '-x', '__MACOSX/*'], {
|
||||
cwd: distDir,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
const sizeMb = (statSync(zipPath).size / (1024 * 1024)).toFixed(2);
|
||||
console.log(`[zip-dist] 已生成 ${zipPath}(${sizeMb} MB)`);
|
||||
@@ -0,0 +1,61 @@
|
||||
import { createCrudApi } from './common';
|
||||
import { APPROVE_TIMEOUT } from '@/api/common';
|
||||
import request from '@/axios';
|
||||
|
||||
const baseUrl = '/blade-transport/contract-change';
|
||||
const api = createCrudApi(baseUrl);
|
||||
|
||||
export const getList = api.getList;
|
||||
export const getDetail = api.getDetail;
|
||||
export const submit = api.submit;
|
||||
export const remove = api.remove;
|
||||
|
||||
export const createFromContract = (contractId, changeType) =>
|
||||
request({
|
||||
url: `${baseUrl}/create-from-contract`,
|
||||
method: 'post',
|
||||
params: { contractId, changeType },
|
||||
});
|
||||
|
||||
export const saveDraft = row =>
|
||||
request({
|
||||
url: `${baseUrl}/save-draft`,
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
|
||||
export const submitApproval = id =>
|
||||
request({
|
||||
url: `${baseUrl}/submit-approval`,
|
||||
method: 'post',
|
||||
params: { id },
|
||||
});
|
||||
|
||||
export const approve = id =>
|
||||
request({
|
||||
url: `${baseUrl}/approve`,
|
||||
method: 'post',
|
||||
params: { id },
|
||||
timeout: APPROVE_TIMEOUT,
|
||||
});
|
||||
|
||||
export const reject = id =>
|
||||
request({
|
||||
url: `${baseUrl}/reject`,
|
||||
method: 'post',
|
||||
params: { id },
|
||||
});
|
||||
|
||||
export const withdraw = id =>
|
||||
request({
|
||||
url: `${baseUrl}/withdraw`,
|
||||
method: 'post',
|
||||
params: { id },
|
||||
});
|
||||
|
||||
export const markSyncStatus = (ids, syncStatus) =>
|
||||
request({
|
||||
url: `${baseUrl}/mark-sync-status`,
|
||||
method: 'post',
|
||||
params: { ids, syncStatus },
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createCrudApi } from './common';
|
||||
import { APPROVE_TIMEOUT } from '@/api/common';
|
||||
import request from '@/axios';
|
||||
|
||||
const baseUrl = '/blade-transport/contract-manage';
|
||||
@@ -36,8 +37,7 @@ export const approve = id =>
|
||||
url: `${baseUrl}/approve`,
|
||||
method: 'post',
|
||||
params: { id },
|
||||
// 审批通过后同步推送合规(附件转传可达 20s+),覆盖全局 10s 超时
|
||||
timeout: 120000,
|
||||
timeout: APPROVE_TIMEOUT,
|
||||
});
|
||||
|
||||
export const reject = id =>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { createCrudApi } from './common';
|
||||
import { APPROVE_TIMEOUT } from '@/api/common';
|
||||
import request from '@/axios';
|
||||
|
||||
const baseUrl = '/blade-transport/project-change';
|
||||
const api = createCrudApi(baseUrl);
|
||||
|
||||
export const getList = api.getList;
|
||||
export const getDetail = api.getDetail;
|
||||
export const submit = api.submit;
|
||||
export const remove = api.remove;
|
||||
|
||||
export const createFromProject = (projectId, changeType) =>
|
||||
request({
|
||||
url: `${baseUrl}/create-from-project`,
|
||||
method: 'post',
|
||||
params: { projectId, changeType },
|
||||
});
|
||||
|
||||
export const saveDraft = row =>
|
||||
request({
|
||||
url: `${baseUrl}/save-draft`,
|
||||
method: 'post',
|
||||
data: row,
|
||||
});
|
||||
|
||||
export const submitApproval = id =>
|
||||
request({
|
||||
url: `${baseUrl}/submit-approval`,
|
||||
method: 'post',
|
||||
params: { id },
|
||||
});
|
||||
|
||||
export const approve = id =>
|
||||
request({
|
||||
url: `${baseUrl}/approve`,
|
||||
method: 'post',
|
||||
params: { id },
|
||||
timeout: APPROVE_TIMEOUT,
|
||||
});
|
||||
|
||||
export const reject = id =>
|
||||
request({
|
||||
url: `${baseUrl}/reject`,
|
||||
method: 'post',
|
||||
params: { id },
|
||||
});
|
||||
|
||||
export const withdraw = id =>
|
||||
request({
|
||||
url: `${baseUrl}/withdraw`,
|
||||
method: 'post',
|
||||
params: { id },
|
||||
});
|
||||
@@ -8,20 +8,12 @@ export const getDetail = id => request({ url: `${baseUrl}/detail`, method: 'get'
|
||||
export const updateAttachments = data =>
|
||||
request({ url: `${baseUrl}/attachments`, method: 'post', data });
|
||||
export const voidClaim = id => request({ url: `${baseUrl}/void`, method: 'post', params: { id } });
|
||||
export const queryKingdeeResult = () =>
|
||||
request({ url: `${baseUrl}/query-kingdee-result`, method: 'post' });
|
||||
|
||||
export const claimStatusOptions = [
|
||||
{ label: '已认领', value: 'claimed' },
|
||||
{ label: '已作废', value: 'voided' },
|
||||
];
|
||||
|
||||
export const pushStatusOptions = [
|
||||
{ label: '未推送', value: 'none' },
|
||||
{ label: '已推送', value: 'pushed' },
|
||||
{ label: '推送失败', value: 'failed' },
|
||||
];
|
||||
|
||||
export const kingdeeBillStatusOptions = [
|
||||
{ label: '未生成', value: 'none' },
|
||||
{ label: '已推送', value: 'pushed' },
|
||||
|
||||
@@ -2,6 +2,9 @@ import request from '@/axios';
|
||||
|
||||
const baseUrl = '/blade-transport/receipt-flow';
|
||||
|
||||
/** 收款流水同步需远程拉取金蝶收款单并逐条落库,覆盖全局 10s 超时 */
|
||||
export const SYNC_TIMEOUT = 30000;
|
||||
|
||||
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 } });
|
||||
@@ -12,9 +15,7 @@ export const getSettlementCandidates = (keyword, flowId) =>
|
||||
params: { keyword, flowId },
|
||||
});
|
||||
export const claim = data => request({ url: `${baseUrl}/claim`, method: 'post', data });
|
||||
export const sync = () => request({ url: `${baseUrl}/sync`, method: 'post' });
|
||||
export const pushKingdee = flowId =>
|
||||
request({ url: `${baseUrl}/push-kingdee`, method: 'post', params: { flowId } });
|
||||
export const sync = () => request({ url: `${baseUrl}/sync`, method: 'post', timeout: SYNC_TIMEOUT });
|
||||
|
||||
export const claimStatusOptions = [
|
||||
{ label: '未认领', value: 'unclaimed' },
|
||||
@@ -22,12 +23,6 @@ export const claimStatusOptions = [
|
||||
{ label: '认领完成', value: 'claimed' },
|
||||
];
|
||||
|
||||
export const pushStatusOptions = [
|
||||
{ label: '未推送', value: 'none' },
|
||||
{ label: '已推送', value: 'pushed' },
|
||||
{ label: '推送失败', value: 'failed' },
|
||||
];
|
||||
|
||||
export const kingdeeBillStatusOptions = [
|
||||
{ label: '已推送', value: 'pushed' },
|
||||
{ label: '审核通过', value: 'approved' },
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
import {
|
||||
approvalStatusOptions,
|
||||
contractCategoryOptions,
|
||||
createCrudOption,
|
||||
hcsSyncStatusOptions,
|
||||
signTypeOptions,
|
||||
} from './common';
|
||||
|
||||
const listDicFormatter = res => {
|
||||
const data = res?.data || res;
|
||||
if (Array.isArray(data)) return data;
|
||||
if (Array.isArray(data?.records)) return data.records;
|
||||
if (Array.isArray(data?.data)) return data.data;
|
||||
if (Array.isArray(data?.data?.records)) return data.data.records;
|
||||
return [];
|
||||
};
|
||||
|
||||
const projectNameDicFormatter = res => {
|
||||
const list = listDicFormatter(res);
|
||||
return list.filter(item => item && item.projectName && String(item.projectName).trim());
|
||||
};
|
||||
|
||||
export const changeTypeOptions = [
|
||||
{ label: '合同信息变更', value: '合同信息变更' },
|
||||
{ label: '结算规则变更', value: '结算规则变更' },
|
||||
];
|
||||
|
||||
export const config = {
|
||||
title: '合同变更',
|
||||
permission: 'contract_change',
|
||||
statusProp: 'approvalStatus',
|
||||
statusTextProp: 'approvalStatusName',
|
||||
deleteStatus: ['draft', 'withdrawn', 'rejected'],
|
||||
editStatus: ['draft', 'withdrawn', 'rejected'],
|
||||
viewStatus: ['approved'],
|
||||
operations: [
|
||||
{
|
||||
action: 'submitApproval',
|
||||
label: '提交',
|
||||
statusProp: 'approvalStatus',
|
||||
status: ['draft'],
|
||||
permission: 'contract_change_submit',
|
||||
},
|
||||
{
|
||||
action: 'withdraw',
|
||||
label: '撤回',
|
||||
statusProp: 'approvalStatus',
|
||||
status: ['reviewing'],
|
||||
permission: 'contract_change_withdraw',
|
||||
},
|
||||
{
|
||||
action: 'mkAudit',
|
||||
label: '流程',
|
||||
statusProp: 'approvalStatus',
|
||||
status: ['reviewing', 'rejected', 'approved'],
|
||||
permission: 'contract_change_approve',
|
||||
mkOnly: true,
|
||||
},
|
||||
{
|
||||
action: 'approve',
|
||||
label: '通过',
|
||||
type: 'success',
|
||||
statusProp: 'approvalStatus',
|
||||
status: ['reviewing'],
|
||||
permission: 'contract_change_approve',
|
||||
localOnly: true,
|
||||
},
|
||||
{
|
||||
action: 'reject',
|
||||
label: '驳回',
|
||||
type: 'warning',
|
||||
statusProp: 'approvalStatus',
|
||||
status: ['reviewing'],
|
||||
permission: 'contract_change_reject',
|
||||
localOnly: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const option = createCrudOption([
|
||||
{
|
||||
label: '合同编号',
|
||||
prop: 'contractNo',
|
||||
search: true,
|
||||
searchPlaceholder: '请输入',
|
||||
searchOrder: 20,
|
||||
minWidth: 160,
|
||||
},
|
||||
{
|
||||
label: '合同名称',
|
||||
prop: 'contractName',
|
||||
search: true,
|
||||
searchPlaceholder: '请输入',
|
||||
searchOrder: 30,
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
label: '所属项目',
|
||||
prop: 'projectName',
|
||||
search: true,
|
||||
searchType: 'select',
|
||||
searchPlaceholder: '请选择',
|
||||
searchOrder: 60,
|
||||
dicUrl: '/blade-transport/project-apply/list?current=1&size=9999&contractSelectable=true',
|
||||
dicFormatter: projectNameDicFormatter,
|
||||
props: {
|
||||
label: 'projectName',
|
||||
value: 'projectName',
|
||||
},
|
||||
filterable: true,
|
||||
minWidth: 160,
|
||||
},
|
||||
{
|
||||
label: '所属组织',
|
||||
prop: 'organizationName',
|
||||
search: true,
|
||||
searchPlaceholder: '请输入',
|
||||
searchOrder: 10,
|
||||
minWidth: 150,
|
||||
},
|
||||
{
|
||||
label: '合同类别',
|
||||
prop: 'contractCategory',
|
||||
type: 'select',
|
||||
search: true,
|
||||
searchPlaceholder: '请选择',
|
||||
searchOrder: 70,
|
||||
dicData: contractCategoryOptions,
|
||||
minWidth: 130,
|
||||
},
|
||||
{
|
||||
label: '签约类型',
|
||||
prop: 'signType',
|
||||
type: 'select',
|
||||
search: true,
|
||||
searchPlaceholder: '请选择',
|
||||
searchOrder: 50,
|
||||
dicData: signTypeOptions,
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
label: '甲方',
|
||||
prop: 'partyA',
|
||||
minWidth: 160,
|
||||
},
|
||||
{
|
||||
label: '乙方',
|
||||
prop: 'partyB',
|
||||
minWidth: 160,
|
||||
},
|
||||
{
|
||||
label: '开始日期',
|
||||
prop: 'startDate',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
minWidth: 130,
|
||||
},
|
||||
{
|
||||
label: '结束日期',
|
||||
prop: 'endDate',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
minWidth: 130,
|
||||
},
|
||||
{
|
||||
label: '临时效力起',
|
||||
prop: 'temporaryStartDate',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
minWidth: 130,
|
||||
},
|
||||
{
|
||||
label: '临时效力止',
|
||||
prop: 'temporaryEndDate',
|
||||
type: 'date',
|
||||
format: 'YYYY-MM-DD',
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
minWidth: 130,
|
||||
},
|
||||
{
|
||||
label: '经办人',
|
||||
prop: 'handlerUserName',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
prop: 'createTime',
|
||||
type: 'datetime',
|
||||
minWidth: 170,
|
||||
},
|
||||
{
|
||||
label: '审核状态',
|
||||
prop: 'approvalStatus',
|
||||
type: 'select',
|
||||
search: true,
|
||||
searchPlaceholder: '请选择',
|
||||
searchOrder: 40,
|
||||
dicData: approvalStatusOptions,
|
||||
minWidth: 110,
|
||||
},
|
||||
{
|
||||
label: '当前节点',
|
||||
prop: 'currentNode',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
label: '当前处理人',
|
||||
prop: 'currentProcessor',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
label: '同步状态',
|
||||
prop: 'syncStatus',
|
||||
type: 'select',
|
||||
dicData: hcsSyncStatusOptions,
|
||||
minWidth: 110,
|
||||
},
|
||||
]);
|
||||
@@ -0,0 +1,161 @@
|
||||
import {
|
||||
approvalStatusOptions,
|
||||
createCrudOption,
|
||||
hcsSyncStatusOptions,
|
||||
projectTypeOptions,
|
||||
} from './common';
|
||||
|
||||
export const changeTypeOptions = [
|
||||
{ label: '项目变更', value: '项目变更' },
|
||||
{ label: '项目备案调整', value: '项目备案调整' },
|
||||
];
|
||||
|
||||
export const config = {
|
||||
title: '项目变更',
|
||||
permission: 'project_change',
|
||||
statusProp: 'approvalStatus',
|
||||
statusTextProp: 'approvalStatusName',
|
||||
deleteStatus: ['draft', 'withdrawn', 'rejected'],
|
||||
editStatus: ['draft', 'withdrawn', 'rejected'],
|
||||
viewStatus: ['approved', 'rejected', 'withdrawn'],
|
||||
operations: [
|
||||
{
|
||||
action: 'submitApproval',
|
||||
label: '提交',
|
||||
statusProp: 'approvalStatus',
|
||||
status: ['draft'],
|
||||
permission: 'project_change_submit',
|
||||
},
|
||||
{
|
||||
action: 'withdraw',
|
||||
label: '撤回',
|
||||
statusProp: 'approvalStatus',
|
||||
status: ['reviewing'],
|
||||
permission: 'project_change_withdraw',
|
||||
},
|
||||
{
|
||||
action: 'mkAudit',
|
||||
label: '流程',
|
||||
statusProp: 'approvalStatus',
|
||||
status: ['reviewing'],
|
||||
permission: 'project_change_approve',
|
||||
mkOnly: true,
|
||||
},
|
||||
{
|
||||
action: 'approve',
|
||||
label: '通过',
|
||||
type: 'success',
|
||||
statusProp: 'approvalStatus',
|
||||
status: ['reviewing'],
|
||||
permission: 'project_change_approve',
|
||||
localOnly: true,
|
||||
},
|
||||
{
|
||||
action: 'reject',
|
||||
label: '驳回',
|
||||
type: 'warning',
|
||||
statusProp: 'approvalStatus',
|
||||
status: ['reviewing'],
|
||||
permission: 'project_change_reject',
|
||||
localOnly: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const option = createCrudOption([
|
||||
{
|
||||
label: '项目编号',
|
||||
prop: 'projectCode',
|
||||
search: true,
|
||||
searchPlaceholder: '请输入',
|
||||
searchOrder: 70,
|
||||
minWidth: 160,
|
||||
},
|
||||
{
|
||||
label: '项目名称',
|
||||
prop: 'projectName',
|
||||
search: true,
|
||||
searchPlaceholder: '请输入',
|
||||
searchOrder: 60,
|
||||
minWidth: 180,
|
||||
},
|
||||
{
|
||||
label: '变更类型',
|
||||
prop: 'changeType',
|
||||
type: 'select',
|
||||
search: true,
|
||||
searchPlaceholder: '请选择',
|
||||
searchOrder: 10,
|
||||
dicData: changeTypeOptions,
|
||||
minWidth: 130,
|
||||
},
|
||||
{
|
||||
label: '项目类型',
|
||||
prop: 'projectType',
|
||||
type: 'select',
|
||||
search: true,
|
||||
searchPlaceholder: '请选择',
|
||||
searchOrder: 20,
|
||||
dicData: projectTypeOptions,
|
||||
minWidth: 110,
|
||||
},
|
||||
{
|
||||
label: '客户名称',
|
||||
prop: 'customerNames',
|
||||
minWidth: 160,
|
||||
},
|
||||
{
|
||||
label: '下游承运商',
|
||||
prop: 'carrierNames',
|
||||
minWidth: 160,
|
||||
},
|
||||
{
|
||||
label: '承办部门',
|
||||
prop: 'undertakeDeptName',
|
||||
search: true,
|
||||
searchPlaceholder: '请输入',
|
||||
searchOrder: 50,
|
||||
minWidth: 140,
|
||||
},
|
||||
{
|
||||
label: '项目负责人',
|
||||
prop: 'principalUserName',
|
||||
search: true,
|
||||
searchPlaceholder: '请输入',
|
||||
searchOrder: 40,
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
label: '审批状态',
|
||||
prop: 'approvalStatus',
|
||||
type: 'select',
|
||||
search: true,
|
||||
searchPlaceholder: '请选择',
|
||||
searchOrder: 30,
|
||||
dicData: approvalStatusOptions,
|
||||
minWidth: 110,
|
||||
},
|
||||
{
|
||||
label: '当前节点',
|
||||
prop: 'currentNode',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
label: '当前处理人',
|
||||
prop: 'currentProcessor',
|
||||
minWidth: 120,
|
||||
},
|
||||
{
|
||||
label: '审核通过时间',
|
||||
prop: 'approvedTime',
|
||||
type: 'datetime',
|
||||
minWidth: 170,
|
||||
},
|
||||
{
|
||||
label: '同步状态',
|
||||
prop: 'syncStatus',
|
||||
type: 'select',
|
||||
dicData: hcsSyncStatusOptions,
|
||||
minWidth: 110,
|
||||
},
|
||||
]);
|
||||
@@ -169,7 +169,7 @@ export const paymentApplicationOption = ({
|
||||
{ label: '当前节点', prop: 'currentNode', minWidth: 120, formatter: textFormatter },
|
||||
{ label: '当前处理人', prop: 'currentProcessor', minWidth: 120, formatter: textFormatter },
|
||||
{ label: '金蝶单据号', prop: 'kingdeeBillNo', minWidth: 130, formatter: textFormatter },
|
||||
{ label: '金蝶单据状态', prop: 'kingdeeStatusName', minWidth: 130, formatter: textFormatter },
|
||||
{ label: '金蝶单据状态', prop: 'kingdeeStatusName', slot: true, minWidth: 130 },
|
||||
{ label: '金蝶锁状态', prop: 'kingdeeLockStatus', slot: true, minWidth: 150 },
|
||||
{ label: '附件同步', prop: 'attachmentSyncStatusName', minWidth: 100, formatter: textFormatter },
|
||||
],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export const receiptClaimRecordTableColumns = [
|
||||
{ prop: 'receiptNoticeNo', label: '认领通知单', minWidth: 150, link: true },
|
||||
{ prop: 'receiptNoticeNo', label: '收款单号', minWidth: 150, link: true },
|
||||
{ prop: 'payerName', label: '付款人', minWidth: 130 },
|
||||
{ prop: 'receiptAmount', label: '付款金额', minWidth: 130, money: true },
|
||||
{ prop: 'receiptAmount', label: '收款金额', minWidth: 130, money: true },
|
||||
{ prop: 'counterpartyName', label: '对方户名', minWidth: 150 },
|
||||
{ prop: 'counterpartyAccount', label: '对方账号', minWidth: 170 },
|
||||
{ prop: 'counterpartyBank', label: '对方开户行', minWidth: 150 },
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
export const receiptFlowTableColumns = [
|
||||
{ prop: 'receiptNoticeNo', label: '认领通知单', minWidth: 150, link: true },
|
||||
{ prop: 'receiptNoticeNo', label: '收款单号', minWidth: 150, link: true },
|
||||
{ prop: 'payerName', label: '付款人', minWidth: 130 },
|
||||
{ prop: 'receiptAmount', label: '付款金额', minWidth: 130, money: true },
|
||||
{ prop: 'receiptAmount', label: '收款金额', minWidth: 130, money: true },
|
||||
{ prop: 'counterpartyName', label: '对方户名', minWidth: 150 },
|
||||
{ prop: 'counterpartyAccount', label: '对方账号', minWidth: 170 },
|
||||
{ prop: 'counterpartyBank', label: '对方开户行', minWidth: 150 },
|
||||
{ prop: 'payeeName', label: '收款人(公司)', minWidth: 150 },
|
||||
//{ prop: 'orgNumber', label: '收款公司编码', minWidth: 130 },
|
||||
//{ prop: 'openOrgNumber', label: '核算组织编码', minWidth: 130 },
|
||||
{ prop: 'summary', label: '摘要', minWidth: 160 },
|
||||
{ prop: 'transactionTime', label: '交易时间', minWidth: 170 },
|
||||
{ prop: 'claimStatusName', label: '认领状态', minWidth: 110, status: true },
|
||||
{ prop: 'kingdeeClaimStatusName', label: '公告状态', minWidth: 110 },
|
||||
{ prop: 'claimedAmount', label: '已认领金额', minWidth: 130, money: true },
|
||||
{ prop: 'unclaimedAmount', label: '金蝶未认领金额', minWidth: 140, money: true },
|
||||
{ prop: 'pushStatusName', label: '推送状态', minWidth: 110, status: true },
|
||||
{ prop: 'pushFailReason', label: '推送失败原因', minWidth: 160 },
|
||||
{ prop: 'kingdeeBillNo', label: '金蝶收款单号', minWidth: 150 },
|
||||
{ prop: 'remainingAmount', label: '剩余可认领金额', minWidth: 140, money: true },
|
||||
{ prop: 'unmatchAmountRec', label: '未匹配金额', minWidth: 140, money: true },
|
||||
//{ prop: 'matchAmountRec', label: '已匹配收款金额', minWidth: 140, money: true },
|
||||
//{ prop: 'matchAmountPay', label: '已匹配付款金额', minWidth: 140, money: true },
|
||||
//{ prop: 'unmatchAmountPay', label: '未匹配付款金额', minWidth: 140, money: true },
|
||||
//{ prop: 'settleAmount', label: '已结算金额', minWidth: 130, money: true },
|
||||
//{ prop: 'unsettleAmount', label: '未结算金额', minWidth: 130, money: true },
|
||||
{ prop: 'kingdeeBillStatusName', label: '金蝶状态', minWidth: 110 },
|
||||
];
|
||||
|
||||
@@ -99,7 +99,8 @@ const buildTableColumns = () =>
|
||||
}
|
||||
// link / status 标记的列统一走单元格插槽
|
||||
if (column.link || column.status) extra.slot = true;
|
||||
if (!extra.formatter) extra.formatter = textFormatter;
|
||||
// 插槽列不挂兜底 formatter:avue 中 formatter 会覆盖单元格插槽渲染
|
||||
if (!extra.slot && !extra.formatter) extra.formatter = textFormatter;
|
||||
return { ...column, ...extra };
|
||||
});
|
||||
|
||||
|
||||
@@ -203,10 +203,9 @@ export default {
|
||||
overHidden: true,
|
||||
hide: true,
|
||||
addDisplay: false,
|
||||
editDisplay: true,
|
||||
editDisplay: false,
|
||||
viewDisplay: true,
|
||||
order: 40,
|
||||
placeholder: '可清空以解绑微信',
|
||||
},
|
||||
{
|
||||
label: '状态',
|
||||
|
||||
@@ -95,6 +95,17 @@ export default [
|
||||
bizType: 'project-apply',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/business/project-change/public-view',
|
||||
name: '查看项目变更信息',
|
||||
component: () => import('@/views/business/project-apply-public-view.vue'),
|
||||
meta: {
|
||||
keepAlive: false,
|
||||
isTab: false,
|
||||
isAuth: false,
|
||||
bizType: 'project-change',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/business/contract-manage/public-view',
|
||||
name: '查看合同信息',
|
||||
@@ -106,6 +117,17 @@ export default [
|
||||
bizType: 'contract-manage',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/business/contract-change/public-view',
|
||||
name: '查看合同变更信息',
|
||||
component: () => import('@/views/business/contract-change-public-view.vue'),
|
||||
meta: {
|
||||
keepAlive: false,
|
||||
isTab: false,
|
||||
isAuth: false,
|
||||
bizType: 'contract-change',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/settlement/pre-settlement/public-view',
|
||||
name: '查看预付结算单信息',
|
||||
|
||||
@@ -198,6 +198,18 @@ export default [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/business/project-change/form',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: '项目变更表单',
|
||||
meta: { keepAlive: false, activeMenu: '/business/project-change' },
|
||||
component: loadView(() => import('@/views/business/project-apply.vue')),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/business/waybill-manage/form',
|
||||
component: Layout,
|
||||
@@ -342,6 +354,18 @@ export default [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/business/contract-change/form',
|
||||
component: Layout,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: '合同变更表单',
|
||||
meta: { keepAlive: false, activeMenu: '/business/contract-change' },
|
||||
component: loadView(() => import('@/views/business/contract-manage-change.vue')),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/wel',
|
||||
component: () =>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 金蝶单据状态公共映射
|
||||
*/
|
||||
|
||||
/** 金蝶单据状态 → el-tag 颜色类型(failed 状态列表页悬浮展示失败原因) */
|
||||
export const KINGDEE_STATUS_TAG_TYPES = {
|
||||
synced: 'success',
|
||||
paid: 'success',
|
||||
invoiced: 'success',
|
||||
paying: 'warning',
|
||||
failed: 'danger',
|
||||
};
|
||||
|
||||
/** 金蝶单据状态 → el-tag 颜色类型,未映射状态返回 info */
|
||||
export const kingdeeStatusType = status => KINGDEE_STATUS_TAG_TYPES[status] || 'info';
|
||||
@@ -15,12 +15,24 @@ export const MK_BIZ = {
|
||||
rejected: ['rejected', 'change_rejected', 'withdrawn'],
|
||||
approved: ['approved', 'change_approved'],
|
||||
},
|
||||
'project-change': {
|
||||
dictName: '提交项目变更审核流',
|
||||
subjectPrefix: '项目变更审批',
|
||||
rejected: ['rejected', 'withdrawn'],
|
||||
approved: ['approved'],
|
||||
},
|
||||
'contract-manage': {
|
||||
dictName: '提交合同审核流',
|
||||
subjectPrefix: '合同审批',
|
||||
rejected: ['rejected', 'change_rejected', 'withdrawn'],
|
||||
approved: ['approved', 'change_approved'],
|
||||
},
|
||||
'contract-change': {
|
||||
dictName: '提交合同变更审核流',
|
||||
subjectPrefix: '合同变更审批',
|
||||
rejected: ['rejected', 'withdrawn'],
|
||||
approved: ['approved'],
|
||||
},
|
||||
'pre-settlement': {
|
||||
dictName: '提交预结算审核流',
|
||||
subjectPrefix: '预付结算审批',
|
||||
|
||||
@@ -1607,7 +1607,7 @@
|
||||
v-if="canConfirmReturn(row)"
|
||||
@click="handleAction('confirmReturn', row)"
|
||||
>
|
||||
确认回单
|
||||
{{ completeActionLabel }}
|
||||
</el-link>
|
||||
<el-link type="primary" v-if="canMaintainMileage(row)" @click="openMileageDialog(row)">
|
||||
维护里程
|
||||
@@ -5168,8 +5168,12 @@ export default {
|
||||
return false;
|
||||
}
|
||||
const status = this.statusValue(row);
|
||||
// 待回单确认也可直接完成,不经确认回单
|
||||
return status === 'processing' || status === 'running' || status === 'pending_return_confirm';
|
||||
// 待回单确认的「完成」由确认回单入口承担(走打卡节点与未了结异常校验);
|
||||
// 只有确认回单不可用时才退回这里的普通完成
|
||||
if (status === 'pending_return_confirm') {
|
||||
return !this.canConfirmReturn(row);
|
||||
}
|
||||
return status === 'processing' || status === 'running';
|
||||
},
|
||||
canConfirmReturn(row) {
|
||||
return (
|
||||
@@ -10215,12 +10219,12 @@ export default {
|
||||
cancel: '取消',
|
||||
complete: this.completeActionLabel,
|
||||
reassign: '重新派单',
|
||||
confirmReturn: '确认回单',
|
||||
confirmReturn: '完成',
|
||||
}[action];
|
||||
// 确认回单是终态动作(本轮不做撤销),把后果写在提示里,别让人以为还能退回
|
||||
// 回单确认是终态动作(本轮不做撤销),把后果写在提示里,别让人以为还能退回
|
||||
const confirmText =
|
||||
action === 'confirmReturn'
|
||||
? '确认回单后该运单将置为已完成,且不可撤销。是否继续?'
|
||||
? '回单确认后该运单将置为已完成,且不可撤销。是否继续?'
|
||||
: `确定${actionName}该${this.config.title}?`;
|
||||
this.$confirm(confirmText, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<mk-public-shell biz-type="contract-change" :get-form="getForm">
|
||||
<contract-manage-change ref="page" />
|
||||
</mk-public-shell>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MkPublicShell from '@/views/mk/mk-public-shell.vue';
|
||||
import ContractManageChange from '@/views/business/contract-manage-change.vue';
|
||||
|
||||
export default {
|
||||
name: 'ContractChangePublicView',
|
||||
components: { MkPublicShell, ContractManageChange },
|
||||
methods: {
|
||||
getForm() {
|
||||
const row = this.$refs.page?.form || {};
|
||||
return { ...row, subject: row.contractName || row.contractNo || '' };
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,407 @@
|
||||
<template>
|
||||
<basic-container class="contract-change-page">
|
||||
<avue-crud
|
||||
:option="tableOption"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
v-model="tableForm"
|
||||
ref="crud"
|
||||
:permission="permissionList"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button type="primary" plain :loading="hcsRepushingId === 'batch'" @click="handleHcsBatchRepush">
|
||||
批量同步
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="hasPermission(`${config.permission}_delete`)"
|
||||
@click="handleDelete"
|
||||
>
|
||||
批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<template #approvalStatus="{ row }">
|
||||
{{ row.approvalStatusName || statusLabel(row.approvalStatus) }}
|
||||
</template>
|
||||
|
||||
<template #syncStatus="{ row }">
|
||||
<el-tag v-if="row.syncStatus" :type="syncTagType(row.syncStatus)" class="status-text">
|
||||
{{ syncLabel(row.syncStatus) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
<template #menu="{ row }">
|
||||
<el-link type="primary" v-if="canView(row)" @click="openForm('view', row)"> 查看 </el-link>
|
||||
<el-link type="primary" v-if="canEdit(row)" @click="openForm('edit', row)"> 编辑 </el-link>
|
||||
<el-link type="danger" v-if="canDelete(row)" @click="rowDel(row)"> 删除 </el-link>
|
||||
<el-link
|
||||
v-for="operation in customOperations(row)"
|
||||
:key="operation.action"
|
||||
:type="
|
||||
operation.type === 'warning'
|
||||
? 'warning'
|
||||
: operation.type === 'success'
|
||||
? 'success'
|
||||
: 'primary'
|
||||
"
|
||||
@click="handleCustomOperation(operation, row)"
|
||||
>
|
||||
{{ operation.label }}
|
||||
</el-link>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as api from '@/api/business/contract-change';
|
||||
import { repushBatch as hcsRepushBatch } from '@/api/business/hcs';
|
||||
import { getMKApprovalUrl } from '@/api/system/business-process';
|
||||
import { config, option } from '@/option/business/contract-change';
|
||||
import { approvalStatusOptions } from '@/option/business/common';
|
||||
import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
import { isAdministrator } from '@/utils/auth';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
const contractChangeFormRoute = '/business/contract-change/form';
|
||||
const contractChangeListRefreshKey = 'contract-change-list-refresh';
|
||||
|
||||
export default {
|
||||
name: 'ContractChange',
|
||||
data() {
|
||||
return {
|
||||
api,
|
||||
config,
|
||||
tableForm: {},
|
||||
tableOption: this.buildTableOption(),
|
||||
loading: true,
|
||||
data: [],
|
||||
page: {
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
query: {},
|
||||
selectionList: [],
|
||||
mkEnabled: false,
|
||||
hcsRepushingId: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo', 'website']),
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: false,
|
||||
viewBtn: false,
|
||||
delBtn: false,
|
||||
editBtn: false,
|
||||
};
|
||||
},
|
||||
isAdmin() {
|
||||
return isAdministrator(this.userInfo);
|
||||
},
|
||||
ids() {
|
||||
return this.selectionList.map(item => item.id).join(',');
|
||||
},
|
||||
},
|
||||
created() {
|
||||
loadMkEnabled()
|
||||
.then(enabled => {
|
||||
this.mkEnabled = enabled;
|
||||
})
|
||||
.catch(() => {
|
||||
this.mkEnabled = false;
|
||||
});
|
||||
},
|
||||
activated() {
|
||||
if (sessionStorage.getItem(contractChangeListRefreshKey)) {
|
||||
sessionStorage.removeItem(contractChangeListRefreshKey);
|
||||
this.onLoad(this.page, this.query);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
buildTableOption() {
|
||||
return {
|
||||
...option,
|
||||
addBtn: false,
|
||||
viewBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
menuWidth: 320,
|
||||
column: (option.column || []).map(column => ({
|
||||
...column,
|
||||
display: false,
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
})),
|
||||
};
|
||||
},
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission && this.permission[code], false);
|
||||
},
|
||||
statusLabel(status) {
|
||||
return approvalStatusOptions.find(item => item.value === status)?.label || status || '-';
|
||||
},
|
||||
syncLabel(value) {
|
||||
return { unsynced: '待推送', synced: '已推送', failed: '推送失败' }[value] || value;
|
||||
},
|
||||
syncTagType(value) {
|
||||
return { unsynced: 'info', synced: 'success', failed: 'danger' }[value] || 'info';
|
||||
},
|
||||
canHcsRepush(row) {
|
||||
return row.approvalStatus === 'approved' && row.contractId && row.syncStatus !== 'synced';
|
||||
},
|
||||
handleHcsBatchRepush() {
|
||||
if (this.hcsRepushingId) return;
|
||||
if (!this.selectionList.length) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
const rows = this.selectionList.filter(item => this.canHcsRepush(item));
|
||||
if (!rows.length) {
|
||||
this.$message.warning('请选择审批通过且未同步成功的变更单');
|
||||
return;
|
||||
}
|
||||
this.$confirm(`确定同步选中的${rows.length}条合同变更到合规系统?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
const loading = this.$loading({
|
||||
lock: true,
|
||||
text: '正在推送合规,请稍候…',
|
||||
background: 'rgba(255, 255, 255, 0.6)',
|
||||
});
|
||||
this.hcsRepushingId = 'batch';
|
||||
const contractIds = [...new Set(rows.map(item => item.contractId))];
|
||||
const changeIds = rows.map(item => item.id).join(',');
|
||||
hcsRepushBatch('contract', contractIds)
|
||||
.then(res => {
|
||||
if (res.data?.success === false) {
|
||||
this.$message.error(res.data?.msg || '批量同步失败');
|
||||
return api.markSyncStatus(changeIds, 'failed');
|
||||
}
|
||||
return api.markSyncStatus(changeIds, 'synced').then(() => {
|
||||
this.$message.success(res.data?.data || '批量同步成功');
|
||||
this.onLoad(this.page, this.query);
|
||||
});
|
||||
})
|
||||
.catch(() => api.markSyncStatus(changeIds, 'failed'))
|
||||
.finally(() => {
|
||||
this.hcsRepushingId = null;
|
||||
loading.close();
|
||||
});
|
||||
});
|
||||
},
|
||||
inStatus(row, statusList) {
|
||||
if (!statusList || !statusList.length) return true;
|
||||
return statusList.includes(row.approvalStatus);
|
||||
},
|
||||
canEdit(row) {
|
||||
return (
|
||||
this.hasPermission(`${this.config.permission}_edit`) &&
|
||||
!row.readonly &&
|
||||
this.inStatus(row, this.config.editStatus)
|
||||
);
|
||||
},
|
||||
canDelete(row) {
|
||||
return (
|
||||
this.hasPermission(`${this.config.permission}_delete`) &&
|
||||
!row.readonly &&
|
||||
this.inStatus(row, this.config.deleteStatus)
|
||||
);
|
||||
},
|
||||
canView(row) {
|
||||
return (
|
||||
this.hasPermission(`${this.config.permission}_view`) &&
|
||||
this.inStatus(row, this.config.viewStatus)
|
||||
);
|
||||
},
|
||||
canOperation(operation, row) {
|
||||
if (operation.permission && !this.hasPermission(operation.permission)) return false;
|
||||
if (
|
||||
row.readonly &&
|
||||
['submitApproval', 'withdraw', 'approve', 'reject'].includes(operation.action)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return this.inStatus(row, operation.status);
|
||||
},
|
||||
customOperations(row) {
|
||||
return (this.config.operations || []).filter(operation => {
|
||||
if (operation.mkOnly && !this.mkEnabled) return false;
|
||||
if (operation.localOnly && this.mkEnabled) return false;
|
||||
return this.canOperation(operation, row);
|
||||
});
|
||||
},
|
||||
openForm(mode, row = {}) {
|
||||
const query = { mode, id: row.id };
|
||||
if (mode === 'edit') query.name = '编辑合同变更';
|
||||
else if (mode === 'view') query.name = '查看合同变更';
|
||||
this.$router.push({ path: contractChangeFormRoute, query });
|
||||
},
|
||||
handleCustomOperation(operation, row) {
|
||||
if (operation.action === 'mkAudit') {
|
||||
this.handleMkAudit(row);
|
||||
return;
|
||||
}
|
||||
const run = () => {
|
||||
const request = () => this.api[operation.action](row.id);
|
||||
const afterMk =
|
||||
operation.action === 'submitApproval'
|
||||
? submitMkApprovalFlow({
|
||||
bizType: 'contract-change',
|
||||
bizId: row.id,
|
||||
subjectName: row.contractName || '',
|
||||
approvalStatus: row.approvalStatus || '',
|
||||
mkEnabled: this.mkEnabled,
|
||||
}).then(() => request())
|
||||
: request();
|
||||
const loading =
|
||||
operation.action === 'approve'
|
||||
? this.$loading({
|
||||
lock: true,
|
||||
text: '正在推送合规,请稍候…',
|
||||
background: 'rgba(255, 255, 255, 0.6)',
|
||||
})
|
||||
: null;
|
||||
afterMk
|
||||
.then(res => {
|
||||
if (operation.action === 'approve' && res.data?.data === false) {
|
||||
this.$message.warning(res.data?.msg);
|
||||
this.onLoad(this.page, this.query);
|
||||
return;
|
||||
}
|
||||
if (res.data?.success === false) {
|
||||
this.$message.error(res.data?.msg || `${operation.label}失败,请联系管理员`);
|
||||
return;
|
||||
}
|
||||
this.$message.success(`${operation.label}成功`);
|
||||
this.onLoad(this.page, this.query);
|
||||
})
|
||||
.finally(() => loading && loading.close());
|
||||
};
|
||||
this.$confirm(operation.confirm || `确定${operation.label}该${this.config.title}?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => run());
|
||||
},
|
||||
handleMkAudit(row) {
|
||||
if (!row?.id) {
|
||||
this.$message.warning('变更单ID为空,无法打开审核页');
|
||||
return;
|
||||
}
|
||||
getMKApprovalUrl({ bizId: String(row.id) })
|
||||
.then(res => {
|
||||
const url = res?.data?.data;
|
||||
if (!url) {
|
||||
this.$message.error('获取审核跳转地址失败');
|
||||
return;
|
||||
}
|
||||
window.open(url, '_blank');
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error('获取审核跳转地址失败');
|
||||
});
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud?.toggleSelection?.();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
this.api.remove(row.id).then(() => {
|
||||
this.$message.success('删除成功!');
|
||||
this.onLoad(this.page, this.query);
|
||||
});
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (!this.selectionList.length) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
this.api.remove(this.ids).then(() => {
|
||||
this.$message.success('删除成功!');
|
||||
this.selectionClear();
|
||||
this.onLoad(this.page, this.query);
|
||||
});
|
||||
});
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
const values = {
|
||||
...this.query,
|
||||
...params,
|
||||
};
|
||||
this.api
|
||||
.getList(page.currentPage, page.pageSize, values)
|
||||
.then(res => {
|
||||
const data = res.data.data || {};
|
||||
this.page.total = data.total || 0;
|
||||
this.data = data.records || [];
|
||||
this.loading = false;
|
||||
this.selectionClear();
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.contract-change-page {
|
||||
:deep(.avue-crud__menu) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,12 +1,20 @@
|
||||
<template>
|
||||
<basic-container class="contract-change-page">
|
||||
<div class="archive-page-form__title">{{ pageTitle }}</div>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-position="right" label-width="auto" class="contract-change-form">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:disabled="pageReadonly"
|
||||
label-position="right"
|
||||
label-width="auto"
|
||||
class="contract-change-form"
|
||||
>
|
||||
<section class="change-section contract-basic-section">
|
||||
<div class="dialog-section-title">基本信息</div>
|
||||
<div class="contract-basic-section__grid">
|
||||
<el-form-item label="变更类型" class="contract-basic-section__change-type">
|
||||
<el-radio-group v-model="form.changeType">
|
||||
<el-radio-group v-model="form.changeType" :disabled="pageReadonly">
|
||||
<el-radio label="合同信息变更" />
|
||||
<el-radio label="结算规则变更" />
|
||||
<el-radio label="终止合同" />
|
||||
@@ -280,7 +288,7 @@
|
||||
<div class="section-head">
|
||||
<div class="dialog-section-title">计费信息</div>
|
||||
<el-button
|
||||
v-if="feeGenerationMode !== 'manual'"
|
||||
v-if="!pageReadonly && feeGenerationMode !== 'manual'"
|
||||
type="primary"
|
||||
plain
|
||||
@click="addPlan"
|
||||
@@ -301,6 +309,7 @@
|
||||
row.defaultPlan ? '是' : '否'
|
||||
}}</template></el-table-column
|
||||
><el-table-column prop="remark" label="备注" min-width="260" /><el-table-column
|
||||
v-if="!pageReadonly"
|
||||
label="操作"
|
||||
width="180"
|
||||
><template #default="{ row, $index }"
|
||||
@@ -416,7 +425,7 @@
|
||||
:value="item.value"
|
||||
/></el-select></template
|
||||
></el-table-column>
|
||||
<el-table-column label="操作" width="100"
|
||||
<el-table-column v-if="!pageReadonly" label="操作" width="100"
|
||||
><template #default="{ $index }"
|
||||
><el-link v-if="$index === 0" type="primary" @click="addCustomPeriodRow">添加</el-link
|
||||
><el-link v-else type="danger" @click="removeCustomPeriodRow($index)"
|
||||
@@ -425,7 +434,7 @@
|
||||
></el-table-column
|
||||
>
|
||||
<template #empty
|
||||
><el-link type="primary" @click="addCustomPeriodRow">添加</el-link></template
|
||||
><el-link v-if="!pageReadonly" type="primary" @click="addCustomPeriodRow">添加</el-link></template
|
||||
>
|
||||
</el-table>
|
||||
</section>
|
||||
@@ -461,7 +470,7 @@
|
||||
:disabled="isSettlementRuleChange"
|
||||
/></template
|
||||
></el-table-column>
|
||||
<el-table-column v-if="!isSettlementRuleChange" label="操作" width="100"
|
||||
<el-table-column v-if="!pageReadonly && !isSettlementRuleChange" label="操作" width="100"
|
||||
><template #default="{ $index }"
|
||||
><el-link type="danger" @click="paymentRatioRows.splice($index, 1)"
|
||||
>删除</el-link
|
||||
@@ -470,7 +479,7 @@
|
||||
>
|
||||
</el-table>
|
||||
<el-button
|
||||
v-if="!isSettlementRuleChange"
|
||||
v-if="!pageReadonly && !isSettlementRuleChange"
|
||||
type="primary"
|
||||
plain
|
||||
@click="addPaymentRatioRow"
|
||||
@@ -525,8 +534,9 @@
|
||||
@update:rows="changeMaterials = $event"
|
||||
@upload-to-location="handleAttachmentUploadToLocation"
|
||||
/>
|
||||
<div class="page-footer">
|
||||
<el-button @click="$router.back()">取消</el-button>
|
||||
<div v-if="!pageReadonly" class="page-footer">
|
||||
<el-button @click="goBack">取消</el-button>
|
||||
<el-button v-if="isContractChangeModule" @click="saveDraft">保存草稿</el-button>
|
||||
<el-button type="primary" @click="submit">提交</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -539,7 +549,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as api from '@/api/business/contract-manage';
|
||||
import * as contractApi from '@/api/business/contract-manage';
|
||||
import * as changeApi from '@/api/business/contract-change';
|
||||
import { getMkPublicDetail } from '@/api/mk-process';
|
||||
import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
import BillingPlanEditor from './components/billing-plan-editor.vue';
|
||||
import ContractAttachmentSection, {
|
||||
@@ -577,6 +589,8 @@ const defaultContractFormatOptions = [
|
||||
{ label: '电子合同', value: '电子合同' },
|
||||
{ label: '纸质合同', value: '纸质合同' },
|
||||
];
|
||||
const contractChangeListRoute = '/business/contract-change';
|
||||
const contractChangeListRefreshKey = 'contract-change-list-refresh';
|
||||
const normalizeOptionalPositiveInteger = value => {
|
||||
if (value === undefined || value === null || value === '') return null;
|
||||
const number = Number(value);
|
||||
@@ -713,8 +727,21 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
pageTitle() {
|
||||
if (this.pageReadonly) return this.$route.query.name || '查看合同变更';
|
||||
return this.$route.query.name || '合同变更';
|
||||
},
|
||||
isContractChangeModule() {
|
||||
return (
|
||||
this.$route.path === '/business/contract-change/form' ||
|
||||
this.$route.path === '/business/contract-change/public-view'
|
||||
);
|
||||
},
|
||||
isPublicViewPage() {
|
||||
return this.$route.path === '/business/contract-change/public-view';
|
||||
},
|
||||
pageReadonly() {
|
||||
return this.isPublicViewPage || this.$route.query.mode === 'view';
|
||||
},
|
||||
// 高风险/黑名单客商提示:按合同推送合规留痕 hcs_risk_level/hcs_black_status 判定(与合同详情口径一致)
|
||||
riskTipText() {
|
||||
const riskLevel = String(this.form.hcsRiskLevel || '').trim();
|
||||
@@ -833,13 +860,25 @@ export default {
|
||||
async load() {
|
||||
const id = this.$route.query.id;
|
||||
if (!id) return;
|
||||
const res = await api.getDetail(id);
|
||||
const data = res.data?.data || res.data || {};
|
||||
let data = {};
|
||||
if (this.isPublicViewPage) {
|
||||
const res = await getMkPublicDetail('contract-change', id);
|
||||
data = res.data?.data || res.data || {};
|
||||
} else if (this.isContractChangeModule) {
|
||||
const res = await changeApi.getDetail(id);
|
||||
data = res.data?.data || res.data || {};
|
||||
} else {
|
||||
const res = await contractApi.getDetail(id);
|
||||
data = res.data?.data || res.data || {};
|
||||
}
|
||||
const isChangeRecord = this.isContractChangeModule || this.isPublicViewPage;
|
||||
this.form = {
|
||||
...data,
|
||||
changeContent: '',
|
||||
changeReason: '',
|
||||
changeAttachmentsJson: '',
|
||||
// 独立变更单保留 contractId;旧路由以合同 id 作为主键
|
||||
contractId: data.contractId || (isChangeRecord ? data.contractId : data.id),
|
||||
changeContent: isChangeRecord ? data.changeContent || '' : '',
|
||||
changeReason: isChangeRecord ? data.changeReason || '' : '',
|
||||
changeAttachmentsJson: data.changeAttachmentsJson || '',
|
||||
copyCount: normalizeOptionalPositiveInteger(data.copyCount),
|
||||
invoiceCycle: normalizeOptionalPositiveInteger(data.invoiceCycle),
|
||||
paymentDays: normalizeOptionalPositiveInteger(data.paymentDays),
|
||||
@@ -847,14 +886,14 @@ export default {
|
||||
templateFlag: data.templateFlag ?? 0,
|
||||
electronicSealFlag: data.electronicSealFlag ?? 0,
|
||||
archiveStatus: data.archiveStatus || '未归档',
|
||||
changeType: '合同信息变更',
|
||||
changeType: data.changeType || '合同信息变更',
|
||||
};
|
||||
this.changeMaterials = [];
|
||||
this.changeMaterials = this.parse(data.changeAttachmentsJson);
|
||||
this.period = data.startDate && data.endDate ? [data.startDate, data.endDate] : [];
|
||||
this.plans = this.parse(data.billingPlanJson);
|
||||
this.attachments = this.mergeChangeAttachments(
|
||||
this.parse(data.attachmentsJson),
|
||||
this.parse(data.changeAttachmentsJson)
|
||||
isChangeRecord ? [] : this.parse(data.changeAttachmentsJson)
|
||||
);
|
||||
this.contractFileRows = normalizeContractFileRows(this.parse(data.contractFileJson));
|
||||
const rules = this.parseObject(data.settlementRuleJson);
|
||||
@@ -896,6 +935,70 @@ export default {
|
||||
data.feeGenerationMode || (Number(data.billingEnabled) === 0 ? 'manual' : 'system');
|
||||
this.paymentRatioRows = this.parse(data.paymentRatioJson);
|
||||
},
|
||||
goBack() {
|
||||
if (this.isContractChangeModule) {
|
||||
this.$router.push(contractChangeListRoute);
|
||||
return;
|
||||
}
|
||||
this.$router.back();
|
||||
},
|
||||
buildChangePayload() {
|
||||
if (this.settlementConfigTab === 'pre') this.preSettlementConfig = { ...this.settlementRule };
|
||||
else this.formalSettlementConfig = { ...this.settlementRule };
|
||||
const settlementRule = {
|
||||
preSettlementConfig: this.preSettlementConfig,
|
||||
formalSettlementConfig: this.formalSettlementConfig,
|
||||
};
|
||||
if (this.isSettlementRuleChange) {
|
||||
return {
|
||||
id: this.form.id,
|
||||
contractId: this.form.contractId,
|
||||
changeType: this.form.changeType,
|
||||
changeContent: this.form.changeContent,
|
||||
changeReason: this.form.changeReason,
|
||||
feeGenerationMode: this.feeGenerationMode,
|
||||
billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0,
|
||||
billingPlanJson: JSON.stringify(this.plans),
|
||||
settlementRuleJson: JSON.stringify(settlementRule),
|
||||
preSettlementConfigJson: JSON.stringify(this.preSettlementConfig),
|
||||
formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig),
|
||||
changeAttachmentsJson: JSON.stringify(this.changeMaterials),
|
||||
};
|
||||
}
|
||||
return {
|
||||
...this.form,
|
||||
contractId: this.form.contractId,
|
||||
changeType: this.form.changeType || '合同信息变更',
|
||||
settlementCurrency: String(this.form.settlementCurrency || '').trim() || 'RMB',
|
||||
copyCount: normalizeOptionalPositiveInteger(this.form.copyCount),
|
||||
invoiceCycle: normalizeOptionalPositiveInteger(this.form.invoiceCycle),
|
||||
paymentDays: normalizeOptionalPositiveInteger(this.form.paymentDays),
|
||||
contractAmount: normalizeOptionalAmount(this.form.contractAmount),
|
||||
startDate: this.period[0],
|
||||
endDate: this.period[1],
|
||||
feeGenerationMode: this.feeGenerationMode,
|
||||
billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0,
|
||||
billingPlanJson: JSON.stringify(this.plans),
|
||||
settlementRuleJson: JSON.stringify(settlementRule),
|
||||
preSettlementConfigJson: JSON.stringify(this.preSettlementConfig),
|
||||
formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig),
|
||||
paymentRatioJson: JSON.stringify(this.paymentRatioRows),
|
||||
contractFileJson: JSON.stringify(this.contractFileRows),
|
||||
attachmentsJson: JSON.stringify(this.attachments),
|
||||
changeContent: this.form.changeContent,
|
||||
changeReason: this.form.changeReason,
|
||||
changeAttachmentsJson: JSON.stringify(this.changeMaterials),
|
||||
};
|
||||
},
|
||||
async saveDraft() {
|
||||
if (this.form.changeType === '终止合同') {
|
||||
this.$message.warning('终止合同无需保存草稿,请直接提交');
|
||||
return;
|
||||
}
|
||||
const payload = this.buildChangePayload();
|
||||
await changeApi.saveDraft(payload);
|
||||
this.$message.success('草稿已保存');
|
||||
},
|
||||
parse(value) {
|
||||
try {
|
||||
const result = JSON.parse(value || '[]');
|
||||
@@ -989,9 +1092,10 @@ export default {
|
||||
validateSettlementRule(rule, label) { if (Number(rule.autoGenerate) !== 1) return true; if (!rule.billStartDate || !rule.settlementType) { this.$message.warning(`${label}:请完整填写账单起始日和结算类型`); return false; } if (rule.settlementType === '月结' && !rule.billCycleType) { this.$message.warning(`${label}:请选择结算周期`); return false; } if (rule.settlementType === '月结' && rule.billCycleType === '固定截单日' && !rule.billCutoffDay) { this.$message.warning(`${label}:请选择账单截单日`); return false; } if (rule.settlementType === '月结' && rule.billCycleType === '自定义多周期') return this.validateCustomPeriods(rule.customPeriods, label); if (rule.settlementType === '固定天数周期结算' && !rule.cycleDays) { this.$message.warning(`${label}:请选择周期天数`); return false; } return true; },
|
||||
async submit() {
|
||||
if (this.form.changeType === '终止合同') {
|
||||
await api.terminate(this.form.id, this.form.changeReason);
|
||||
const contractId = this.form.contractId || this.form.id;
|
||||
await contractApi.terminate(contractId, this.form.changeReason);
|
||||
this.$message.success('合同已终止');
|
||||
this.$router.back();
|
||||
this.goBack();
|
||||
return;
|
||||
}
|
||||
await this.$refs.formRef.validate();
|
||||
@@ -1020,48 +1124,22 @@ export default {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const settlementRule = {
|
||||
preSettlementConfig: this.preSettlementConfig,
|
||||
formalSettlementConfig: this.formalSettlementConfig,
|
||||
};
|
||||
const payload = this.isSettlementRuleChange
|
||||
? {
|
||||
id: this.form.id,
|
||||
changeType: this.form.changeType,
|
||||
changeContent: this.form.changeContent,
|
||||
changeReason: this.form.changeReason,
|
||||
feeGenerationMode: this.feeGenerationMode,
|
||||
billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0,
|
||||
billingPlanJson: JSON.stringify(this.plans),
|
||||
settlementRuleJson: JSON.stringify(settlementRule),
|
||||
preSettlementConfigJson: JSON.stringify(this.preSettlementConfig),
|
||||
formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig),
|
||||
changeAttachmentsJson: JSON.stringify(this.changeMaterials),
|
||||
}
|
||||
: {
|
||||
...this.form,
|
||||
changeType: this.form.changeType || '合同信息变更',
|
||||
settlementCurrency: String(this.form.settlementCurrency || '').trim() || 'RMB',
|
||||
copyCount: normalizeOptionalPositiveInteger(this.form.copyCount),
|
||||
invoiceCycle: normalizeOptionalPositiveInteger(this.form.invoiceCycle),
|
||||
paymentDays: normalizeOptionalPositiveInteger(this.form.paymentDays),
|
||||
contractAmount: normalizeOptionalAmount(this.form.contractAmount),
|
||||
startDate: this.period[0],
|
||||
endDate: this.period[1],
|
||||
feeGenerationMode: this.feeGenerationMode,
|
||||
billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0,
|
||||
billingPlanJson: JSON.stringify(this.plans),
|
||||
settlementRuleJson: JSON.stringify(settlementRule),
|
||||
preSettlementConfigJson: JSON.stringify(this.preSettlementConfig),
|
||||
formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig),
|
||||
paymentRatioJson: JSON.stringify(this.paymentRatioRows),
|
||||
contractFileJson: JSON.stringify(this.contractFileRows),
|
||||
attachmentsJson: JSON.stringify(this.attachments),
|
||||
changeContent: this.form.changeContent,
|
||||
changeReason: this.form.changeReason,
|
||||
changeAttachmentsJson: JSON.stringify(this.changeMaterials),
|
||||
};
|
||||
await api.submitChange(payload);
|
||||
const payload = this.buildChangePayload();
|
||||
if (this.isContractChangeModule) {
|
||||
await changeApi.submit(payload);
|
||||
await submitMkApprovalFlow({
|
||||
bizType: 'contract-change',
|
||||
bizId: this.form.id,
|
||||
subjectName: this.form.contractName || '',
|
||||
approvalStatus: this.form.approvalStatus || '',
|
||||
mkEnabled: await loadMkEnabled().catch(() => false),
|
||||
});
|
||||
sessionStorage.setItem(contractChangeListRefreshKey, '1');
|
||||
this.$message.success('变更已提交');
|
||||
this.$router.push(contractChangeListRoute);
|
||||
return;
|
||||
}
|
||||
await contractApi.submitChange(payload);
|
||||
await submitMkApprovalFlow({
|
||||
bizType: 'contract-manage',
|
||||
bizId: this.form.id,
|
||||
|
||||
@@ -1089,6 +1089,7 @@ import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import * as api from '@/api/business/contract-manage';
|
||||
import { createFromContract } from '@/api/business/contract-change';
|
||||
import { repush as hcsRepush, repushBatch as hcsRepushBatch, getRisk } from '@/api/business/hcs';
|
||||
import { Position } from '@element-plus/icons-vue';
|
||||
import { getMkPublicDetail } from '@/api/mk-process';
|
||||
@@ -3220,12 +3221,33 @@ export default {
|
||||
if (value === undefined || value === null || value === '') return '-';
|
||||
return options.find(item => String(item.value) === String(value))?.label || value;
|
||||
},
|
||||
handleStartContractChange(row) {
|
||||
if (!row?.id) {
|
||||
this.$message.warning('合同ID为空,无法发起变更');
|
||||
return;
|
||||
}
|
||||
const loading = this.$loading({
|
||||
lock: true,
|
||||
text: '正在创建变更单…',
|
||||
background: 'rgba(255, 255, 255, 0.6)',
|
||||
});
|
||||
createFromContract(row.id)
|
||||
.then(res => {
|
||||
const data = res?.data?.data || res?.data || {};
|
||||
if (!data.id) {
|
||||
this.$message.error(res?.data?.msg || '创建合同变更草稿失败');
|
||||
return;
|
||||
}
|
||||
this.$router.push({
|
||||
path: '/business/contract-change/form',
|
||||
query: { mode: 'edit', id: data.id, name: '合同变更' },
|
||||
});
|
||||
})
|
||||
.finally(() => loading.close());
|
||||
},
|
||||
handleOperation(operation, row) {
|
||||
if (operation.action === 'startChange') {
|
||||
this.$router.push({
|
||||
path: '/business/contract-manage/change',
|
||||
query: { id: row.id, name: '合同变更' },
|
||||
});
|
||||
this.handleStartContractChange(row);
|
||||
return;
|
||||
}
|
||||
if (operation.action === 'flow') {
|
||||
|
||||
@@ -898,16 +898,6 @@
|
||||
>
|
||||
<div title="配载信息" class="loading-manage-dialog__load-info-card">
|
||||
<section-card>
|
||||
<div class="loading-manage-dialog__admission-tip">
|
||||
<div class="loading-manage-dialog__admission-title">可配载条件</div>
|
||||
<div class="loading-manage-dialog__admission-text">
|
||||
同时满足以下条件的运单可被配载:运输方式为「公路」;运单状态为「待执行」或「进行中」;未关联其他有效配载单;同一配载单的子运单须属同一项目,且承运商合同乙方须存在共同可选值(按各子单客户合同甲方匹配后取交集)。
|
||||
</div>
|
||||
<div class="loading-manage-dialog__admission-title">配载后状态说明</div>
|
||||
<div class="loading-manage-dialog__admission-text">
|
||||
子运单:按项目过程配置是否需司机接单,无需接单则为进行中,需接单则为待执行。配载单:按所选运单所属项目的过程配置,需接单则为待执行,无需接单则为进行中。
|
||||
</div>
|
||||
</div>
|
||||
<div class="loading-manage-dialog__candidate-search">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="6">
|
||||
@@ -5030,30 +5020,6 @@ export default {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.loading-manage-dialog__admission-tip {
|
||||
margin-bottom: 12px;
|
||||
padding: 12px 14px;
|
||||
background: #f5f9ff;
|
||||
border: 1px solid #d9ecff;
|
||||
border-radius: 4px;
|
||||
line-height: 1.6;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.loading-manage-dialog__admission-title {
|
||||
margin-bottom: 4px;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.loading-manage-dialog__admission-title + .loading-manage-dialog__admission-text + .loading-manage-dialog__admission-title {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.loading-manage-dialog__admission-text {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.loading-manage-dialog__candidate-search {
|
||||
padding: 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
@@ -84,7 +84,8 @@ export default {
|
||||
this.postFormData('save', lbpmFormValues, this.buildFormData());
|
||||
},
|
||||
postFormData(status, formValues, formData) {
|
||||
postMkPublicProcessMessage('project-apply', {
|
||||
const bizType = this.$route.meta?.bizType || 'project-apply';
|
||||
postMkPublicProcessMessage(bizType, {
|
||||
status,
|
||||
formValues: formValues || {},
|
||||
formData,
|
||||
|
||||
@@ -810,7 +810,21 @@
|
||||
<template v-if="isChangeDialog">
|
||||
<el-button v-if="!isProjectFormPage" @click="handleCancelProject">取消</el-button>
|
||||
<el-button v-if="isProjectFormPage" @click="closeProjectForm">取消</el-button>
|
||||
<el-button type="primary" :loading="submitLoading" @click="submitChangeProject">
|
||||
<el-button
|
||||
v-if="isProjectChangeModule && !dialogReadonly"
|
||||
type="primary"
|
||||
plain
|
||||
:loading="submitLoading"
|
||||
@click="saveProjectChangeDraft"
|
||||
>
|
||||
保存
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="!dialogReadonly"
|
||||
type="primary"
|
||||
:loading="submitLoading"
|
||||
@click="submitChangeProject"
|
||||
>
|
||||
提交
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -985,6 +999,7 @@ import {
|
||||
projectTypeOptions,
|
||||
} from '@/option/business/common';
|
||||
import { config, option } from '@/option/business/project-apply';
|
||||
import * as projectChangeApi from '@/api/business/project-change';
|
||||
|
||||
const emptyForm = () => ({
|
||||
projectType: '',
|
||||
@@ -1060,6 +1075,9 @@ const attachmentViewerPlugins = [
|
||||
const projectApplyListRoute = '/business/project-apply';
|
||||
const projectApplyFormRoute = '/business/project-apply/form';
|
||||
const projectApplyListRefreshKey = 'project-apply-list-refresh';
|
||||
const projectChangeListRoute = '/business/project-change';
|
||||
const projectChangeFormRoute = '/business/project-change/form';
|
||||
const projectChangeListRefreshKey = 'project-change-list-refresh';
|
||||
|
||||
const otherAttachmentType = { label: '其他附件', value: '其他附件' };
|
||||
|
||||
@@ -1245,7 +1263,40 @@ export default {
|
||||
},
|
||||
],
|
||||
changeReason: [
|
||||
{ max: 2000, message: '变更原因不能超过2000个字符', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (!this.isChangeDialog) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
if (!value || !String(value).trim()) {
|
||||
callback(new Error('请输入变更原因'));
|
||||
return;
|
||||
}
|
||||
if (String(value).length > 2000) {
|
||||
callback(new Error('变更原因不能超过2000个字符'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
changeType: [
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (!this.isChangeDialog) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
if (!value) {
|
||||
callback(new Error('请选择变更类型'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
},
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
projectIntro: [
|
||||
{ max: 1000, message: '项目简介不能超过1000个字符', trigger: 'blur' },
|
||||
@@ -1374,8 +1425,11 @@ export default {
|
||||
isMajorProjectSupplementDialog() {
|
||||
return this.dialogType === 'majorSupplement';
|
||||
},
|
||||
isProjectChangeModule() {
|
||||
return this.$route.path === projectChangeFormRoute;
|
||||
},
|
||||
isChangeDialog() {
|
||||
return this.dialogType === 'change';
|
||||
return this.dialogType === 'change' || this.isProjectChangeModule;
|
||||
},
|
||||
isNewProjectDialog() {
|
||||
return ['add', 'majorSupplement'].includes(this.dialogType);
|
||||
@@ -1384,7 +1438,13 @@ export default {
|
||||
return this.isFormPageInstance;
|
||||
},
|
||||
isPublicViewPage() {
|
||||
return this.$route.path === '/business/project-apply/public-view';
|
||||
return (
|
||||
this.$route.path === '/business/project-apply/public-view' ||
|
||||
this.$route.path === '/business/project-change/public-view'
|
||||
);
|
||||
},
|
||||
publicBizType() {
|
||||
return this.$route.meta?.bizType || 'project-apply';
|
||||
},
|
||||
projectFormContainer() {
|
||||
// 表单页实例始终用页面容器,避免 keep-alive 失活时误切弹窗
|
||||
@@ -1451,7 +1511,7 @@ export default {
|
||||
this.loadCargoTypeOptions();
|
||||
this.loadTransportTypeOptions();
|
||||
this.loadSettlementModeOptions();
|
||||
if (this.$route.path === projectApplyFormRoute) {
|
||||
if (this.$route.path === projectApplyFormRoute || this.$route.path === projectChangeFormRoute) {
|
||||
this.isFormPageInstance = true;
|
||||
this.openProjectFormPage();
|
||||
}
|
||||
@@ -1517,14 +1577,14 @@ export default {
|
||||
},
|
||||
openPublicProjectForm() {
|
||||
const id = this.$route.query.id;
|
||||
this.dialogType = 'view';
|
||||
this.dialogType = this.publicBizType === 'project-change' ? 'change' : 'view';
|
||||
this.dialogReadonly = true;
|
||||
this.projectBox = true;
|
||||
if (!id) {
|
||||
this.$message.error('缺少项目ID');
|
||||
return;
|
||||
}
|
||||
getMkPublicDetail('project-apply', id)
|
||||
getMkPublicDetail(this.publicBizType, id)
|
||||
.then(res => {
|
||||
const detail = res.data?.data || {};
|
||||
this.applyPublicDictOptions(detail);
|
||||
@@ -1587,9 +1647,25 @@ export default {
|
||||
openProjectFormPage() {
|
||||
const type = this.$route.query.mode || 'add';
|
||||
const id = this.$route.query.id;
|
||||
this.dialogType = type;
|
||||
this.dialogType = this.isProjectChangeModule ? (type === 'view' ? 'view' : 'change') : type;
|
||||
this.dialogReadonly = type === 'view';
|
||||
this.projectBox = true;
|
||||
if (this.isProjectChangeModule) {
|
||||
if (!id) {
|
||||
this.$message.error('缺少项目变更ID');
|
||||
return;
|
||||
}
|
||||
projectChangeApi.getDetail(id).then(res => {
|
||||
const detail = res.data.data || {};
|
||||
this.applyProjectDetail({
|
||||
...detail,
|
||||
changeType: detail.changeType || '项目变更',
|
||||
changeNo: detail.changeNo || '',
|
||||
changeDate: detail.changeDate || this.$dayjs().format('YYYY-MM-DD'),
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (['add', 'majorSupplement'].includes(type)) {
|
||||
this.applyProjectDetail({
|
||||
...emptyForm(),
|
||||
@@ -1613,6 +1689,12 @@ export default {
|
||||
},
|
||||
closeProjectForm() {
|
||||
if (this.isProjectFormPage) {
|
||||
if (this.isProjectChangeModule) {
|
||||
sessionStorage.setItem(projectChangeListRefreshKey, '1');
|
||||
this.$router.$avueRouter?.closeTag?.();
|
||||
this.$router.push(projectChangeListRoute);
|
||||
return;
|
||||
}
|
||||
this.markProjectApplyListNeedRefresh();
|
||||
this.$router.$avueRouter?.closeTag?.();
|
||||
this.$router.push(projectApplyListRoute);
|
||||
@@ -1858,7 +1940,7 @@ export default {
|
||||
},
|
||||
handleCustomOperation(operation, row) {
|
||||
if (operation.action === 'startChange') {
|
||||
this.openProjectDialog('change', row);
|
||||
this.startIndependentProjectChange(row);
|
||||
return;
|
||||
}
|
||||
if (operation.action === 'mkAudit') {
|
||||
@@ -2169,6 +2251,52 @@ export default {
|
||||
this.closeProjectForm();
|
||||
});
|
||||
},
|
||||
startIndependentProjectChange(row) {
|
||||
if (!row?.id) {
|
||||
this.$message.warning('项目ID为空,无法发起变更');
|
||||
return;
|
||||
}
|
||||
const loading = this.$loading({
|
||||
lock: true,
|
||||
text: '正在创建项目变更单…',
|
||||
background: 'rgba(255, 255, 255, 0.6)',
|
||||
});
|
||||
projectChangeApi
|
||||
.createFromProject(row.id)
|
||||
.then(res => {
|
||||
if (res.data?.success === false) {
|
||||
this.$message.error(res.data?.msg || '创建项目变更失败');
|
||||
return;
|
||||
}
|
||||
const detail = res.data?.data || {};
|
||||
if (!detail.id) {
|
||||
this.$message.error('创建项目变更失败:未返回变更单ID');
|
||||
return;
|
||||
}
|
||||
this.$router.push({
|
||||
path: projectChangeFormRoute,
|
||||
query: { mode: 'edit', id: detail.id, name: '项目变更' },
|
||||
});
|
||||
})
|
||||
.finally(() => loading.close());
|
||||
},
|
||||
saveProjectChangeDraft() {
|
||||
if (!this.isProjectChangeModule) return;
|
||||
this.submitLoading = true;
|
||||
projectChangeApi
|
||||
.saveDraft(this.normalizeSubmitForm({ includeChangeType: true }))
|
||||
.then(res => {
|
||||
if (res.data?.success === false || res.data?.data === false) {
|
||||
this.$message.error(res.data?.msg || '保存失败,请联系管理员');
|
||||
return;
|
||||
}
|
||||
this.$message.success('保存成功!');
|
||||
this.closeProjectForm();
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitLoading = false;
|
||||
});
|
||||
},
|
||||
submitChangeProject() {
|
||||
this.$refs.projectForm.validate(valid => {
|
||||
if (!valid) {
|
||||
@@ -2177,7 +2305,34 @@ export default {
|
||||
}
|
||||
if (!this.validateAttachmentFileTypes()) return;
|
||||
this.submitLoading = true;
|
||||
// 变更记录仅在提交时生成,保存草稿不落记录
|
||||
if (this.isProjectChangeModule) {
|
||||
const payload = this.normalizeSubmitForm({ includeChangeType: true });
|
||||
const approvalStatus = this.form.approvalStatus || '';
|
||||
projectChangeApi
|
||||
.submit(payload)
|
||||
.then(res => {
|
||||
if (res.data?.success === false || res.data?.data === false) {
|
||||
this.$message.error(res.data?.msg || '提交失败,请联系管理员');
|
||||
return;
|
||||
}
|
||||
const id = this.form.id;
|
||||
return submitMkApprovalFlow({
|
||||
bizType: 'project-change',
|
||||
bizId: id,
|
||||
subjectName: this.form.projectName || '',
|
||||
approvalStatus,
|
||||
mkEnabled: this.mkEnabled,
|
||||
}).then(() => {
|
||||
this.$message.success('提交成功!');
|
||||
this.closeProjectForm();
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitLoading = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 兼容旧入口:变更记录仅在提交时生成,保存草稿不落记录
|
||||
this.api
|
||||
.submitChange(this.normalizeSubmitForm({ includeChangeType: true }))
|
||||
.then(res => {
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
<template>
|
||||
<basic-container class="project-change-page">
|
||||
<avue-crud
|
||||
:option="tableOption"
|
||||
:table-loading="loading"
|
||||
:data="data"
|
||||
v-model:page="page"
|
||||
v-model="tableForm"
|
||||
ref="crud"
|
||||
:permission="permissionList"
|
||||
@search-change="searchChange"
|
||||
@search-reset="searchReset"
|
||||
@selection-change="selectionChange"
|
||||
@current-change="currentChange"
|
||||
@size-change="sizeChange"
|
||||
@refresh-change="refreshChange"
|
||||
@on-load="onLoad"
|
||||
>
|
||||
<template #menu-left>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-delete"
|
||||
plain
|
||||
v-if="hasPermission(`${config.permission}_delete`)"
|
||||
@click="handleDelete"
|
||||
>
|
||||
批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
|
||||
<template #approvalStatus="{ row }">
|
||||
{{ row.approvalStatusName || statusLabel(row.approvalStatus) }}
|
||||
</template>
|
||||
|
||||
<template #syncStatus="{ row }">
|
||||
<el-tag v-if="row.syncStatus" :type="syncTagType(row.syncStatus)" class="status-text">
|
||||
{{ syncLabel(row.syncStatus) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
<template #menu="{ row }">
|
||||
<el-link
|
||||
type="primary"
|
||||
v-if="canView(row)"
|
||||
@click="openForm('view', row)"
|
||||
>
|
||||
查看
|
||||
</el-link>
|
||||
<el-link type="primary" v-if="canEdit(row)" @click="openForm('edit', row)">
|
||||
编辑
|
||||
</el-link>
|
||||
<el-link
|
||||
type="danger"
|
||||
v-if="canDelete(row)"
|
||||
@click="rowDel(row)"
|
||||
>
|
||||
删除
|
||||
</el-link>
|
||||
<el-link
|
||||
v-for="operation in customOperations(row)"
|
||||
:key="operation.action"
|
||||
:type="operation.type === 'warning' ? 'warning' : operation.type === 'success' ? 'success' : 'primary'"
|
||||
@click="handleCustomOperation(operation, row)"
|
||||
>
|
||||
{{ operation.label }}
|
||||
</el-link>
|
||||
</template>
|
||||
</avue-crud>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as api from '@/api/business/project-change';
|
||||
import { getMKApprovalUrl } from '@/api/system/business-process';
|
||||
import { config, option } from '@/option/business/project-change';
|
||||
import { approvalStatusOptions } from '@/option/business/common';
|
||||
import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval';
|
||||
import { isAdministrator } from '@/utils/auth';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
const projectChangeFormRoute = '/business/project-change/form';
|
||||
const projectChangeListRefreshKey = 'project-change-list-refresh';
|
||||
|
||||
export default {
|
||||
name: 'ProjectChange',
|
||||
data() {
|
||||
return {
|
||||
api,
|
||||
config,
|
||||
tableForm: {},
|
||||
tableOption: this.buildTableOption(),
|
||||
loading: true,
|
||||
data: [],
|
||||
page: {
|
||||
pageSize: 10,
|
||||
currentPage: 1,
|
||||
total: 0,
|
||||
},
|
||||
query: {},
|
||||
selectionList: [],
|
||||
mkEnabled: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['permission', 'userInfo', 'website']),
|
||||
permissionList() {
|
||||
return {
|
||||
addBtn: false,
|
||||
viewBtn: false,
|
||||
delBtn: false,
|
||||
editBtn: false,
|
||||
};
|
||||
},
|
||||
isAdmin() {
|
||||
return isAdministrator(this.userInfo);
|
||||
},
|
||||
ids() {
|
||||
return this.selectionList.map(item => item.id).join(',');
|
||||
},
|
||||
},
|
||||
created() {
|
||||
loadMkEnabled()
|
||||
.then(enabled => {
|
||||
this.mkEnabled = enabled;
|
||||
})
|
||||
.catch(() => {
|
||||
this.mkEnabled = false;
|
||||
});
|
||||
},
|
||||
activated() {
|
||||
if (sessionStorage.getItem(projectChangeListRefreshKey)) {
|
||||
sessionStorage.removeItem(projectChangeListRefreshKey);
|
||||
this.onLoad(this.page, this.query);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
buildTableOption() {
|
||||
return {
|
||||
...option,
|
||||
addBtn: false,
|
||||
viewBtn: false,
|
||||
editBtn: false,
|
||||
delBtn: false,
|
||||
menuWidth: 320,
|
||||
column: (option.column || []).map(column => ({
|
||||
...column,
|
||||
display: false,
|
||||
align: 'center',
|
||||
headerAlign: 'center',
|
||||
})),
|
||||
};
|
||||
},
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission && this.permission[code], false);
|
||||
},
|
||||
statusLabel(status) {
|
||||
return approvalStatusOptions.find(item => item.value === status)?.label || status || '-';
|
||||
},
|
||||
syncLabel(value) {
|
||||
return { unsynced: '待推送', synced: '已推送', failed: '推送失败' }[value] || value;
|
||||
},
|
||||
syncTagType(value) {
|
||||
return { unsynced: 'info', synced: 'success', failed: 'danger' }[value] || 'info';
|
||||
},
|
||||
inStatus(row, statusList) {
|
||||
if (!statusList || !statusList.length) return true;
|
||||
return statusList.includes(row.approvalStatus);
|
||||
},
|
||||
canEdit(row) {
|
||||
return (
|
||||
this.hasPermission(`${this.config.permission}_edit`) &&
|
||||
!row.readonly &&
|
||||
this.inStatus(row, this.config.editStatus)
|
||||
);
|
||||
},
|
||||
canDelete(row) {
|
||||
return (
|
||||
this.hasPermission(`${this.config.permission}_delete`) &&
|
||||
!row.readonly &&
|
||||
this.inStatus(row, this.config.deleteStatus)
|
||||
);
|
||||
},
|
||||
canView(row) {
|
||||
return (
|
||||
this.hasPermission(`${this.config.permission}_view`) &&
|
||||
this.inStatus(row, this.config.viewStatus)
|
||||
);
|
||||
},
|
||||
canOperation(operation, row) {
|
||||
if (operation.permission && !this.hasPermission(operation.permission)) return false;
|
||||
if (row.readonly && ['submitApproval', 'withdraw', 'approve', 'reject'].includes(operation.action)) {
|
||||
return false;
|
||||
}
|
||||
return this.inStatus(row, operation.status);
|
||||
},
|
||||
customOperations(row) {
|
||||
return (this.config.operations || []).filter(operation => {
|
||||
if (operation.mkOnly && !this.mkEnabled) return false;
|
||||
if (operation.localOnly && this.mkEnabled) return false;
|
||||
return this.canOperation(operation, row);
|
||||
});
|
||||
},
|
||||
openForm(mode, row = {}) {
|
||||
const query = { mode, id: row.id };
|
||||
if (mode === 'edit') query.name = '编辑项目变更';
|
||||
else if (mode === 'view') query.name = '查看项目变更';
|
||||
this.$router.push({ path: projectChangeFormRoute, query });
|
||||
},
|
||||
handleCustomOperation(operation, row) {
|
||||
if (operation.action === 'mkAudit') {
|
||||
this.handleMkAudit(row);
|
||||
return;
|
||||
}
|
||||
const run = () => {
|
||||
const request = () => this.api[operation.action](row.id);
|
||||
const afterMk =
|
||||
operation.action === 'submitApproval'
|
||||
? submitMkApprovalFlow({
|
||||
bizType: 'project-change',
|
||||
bizId: row.id,
|
||||
subjectName: row.projectName || '',
|
||||
approvalStatus: row.approvalStatus || '',
|
||||
mkEnabled: this.mkEnabled,
|
||||
}).then(() => request())
|
||||
: request();
|
||||
const loading =
|
||||
operation.action === 'approve'
|
||||
? this.$loading({
|
||||
lock: true,
|
||||
text: '正在推送合规,请稍候…',
|
||||
background: 'rgba(255, 255, 255, 0.6)',
|
||||
})
|
||||
: null;
|
||||
afterMk
|
||||
.then(res => {
|
||||
if (operation.action === 'approve' && res.data?.data === false) {
|
||||
this.$message.warning(res.data?.msg);
|
||||
this.onLoad(this.page, this.query);
|
||||
return;
|
||||
}
|
||||
if (res.data?.success === false) {
|
||||
this.$message.error(res.data?.msg || `${operation.label}失败,请联系管理员`);
|
||||
return;
|
||||
}
|
||||
this.$message.success(`${operation.label}成功`);
|
||||
this.onLoad(this.page, this.query);
|
||||
})
|
||||
.finally(() => loading && loading.close());
|
||||
};
|
||||
this.$confirm(operation.confirm || `确定${operation.label}该${this.config.title}?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => run());
|
||||
},
|
||||
handleMkAudit(row) {
|
||||
if (!row?.id) {
|
||||
this.$message.warning('变更单ID为空,无法打开审核页');
|
||||
return;
|
||||
}
|
||||
getMKApprovalUrl({ bizId: String(row.id) })
|
||||
.then(res => {
|
||||
const url = res?.data?.data;
|
||||
if (!url) {
|
||||
this.$message.error('获取审核跳转地址失败');
|
||||
return;
|
||||
}
|
||||
window.open(url, '_blank');
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error('获取审核跳转地址失败');
|
||||
});
|
||||
},
|
||||
searchReset() {
|
||||
this.query = {};
|
||||
this.onLoad(this.page);
|
||||
},
|
||||
searchChange(params, done) {
|
||||
this.query = params;
|
||||
this.page.currentPage = 1;
|
||||
this.onLoad(this.page, params);
|
||||
done();
|
||||
},
|
||||
selectionChange(list) {
|
||||
this.selectionList = list;
|
||||
},
|
||||
selectionClear() {
|
||||
this.selectionList = [];
|
||||
this.$refs.crud?.toggleSelection?.();
|
||||
},
|
||||
currentChange(currentPage) {
|
||||
this.page.currentPage = currentPage;
|
||||
},
|
||||
sizeChange(pageSize) {
|
||||
this.page.pageSize = pageSize;
|
||||
},
|
||||
refreshChange() {
|
||||
this.onLoad(this.page, this.query);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定将选择数据删除?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
this.api.remove(row.id).then(() => {
|
||||
this.$message.success('删除成功!');
|
||||
this.onLoad(this.page, this.query);
|
||||
});
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (!this.selectionList.length) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定将选择数据删除?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
this.api.remove(this.ids).then(() => {
|
||||
this.$message.success('删除成功!');
|
||||
this.selectionClear();
|
||||
this.onLoad(this.page, this.query);
|
||||
});
|
||||
});
|
||||
},
|
||||
onLoad(page, params = {}) {
|
||||
this.loading = true;
|
||||
const values = {
|
||||
...this.query,
|
||||
...params,
|
||||
};
|
||||
this.api
|
||||
.getList(page.currentPage, page.pageSize, values)
|
||||
.then(res => {
|
||||
const data = res.data.data || {};
|
||||
this.page.total = data.total || 0;
|
||||
this.data = data.records || [];
|
||||
this.loading = false;
|
||||
this.selectionClear();
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.project-change-page {
|
||||
:deep(.avue-crud__menu) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -156,9 +156,17 @@ export default {
|
||||
});
|
||||
},
|
||||
async handleApprove(row) {
|
||||
await api.approve({ id: row.id });
|
||||
this.$message.success('审批通过');
|
||||
this.loadTable();
|
||||
await this.$confirm('确认审批通过该汇票付款?', '提示', { type: 'warning' });
|
||||
// 防止重复点击:请求期间表格遮罩,后续点击直接忽略
|
||||
if (this.loading) return;
|
||||
this.loading = true;
|
||||
try {
|
||||
await api.approve({ id: row.id });
|
||||
this.$message.success('审批通过');
|
||||
await this.loadTable();
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
async handleReturn(row) {
|
||||
const { value } = await this.$prompt('请输入驳回原因', '审批驳回');
|
||||
|
||||
@@ -293,6 +293,12 @@
|
||||
<el-input v-model="row.remark" :disabled="readonly" maxlength="200" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="readonly" label="开票状态" min-width="90" align="center">
|
||||
<template #default="{ row }">{{ sheet.invoiceStatusName || '未开票' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="readonly" label="金蝶开票结果备注" min-width="200">
|
||||
<template #default="{ row }">{{ sheet.kingdeeResultRemark }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="!readonly" label="操作" width="110" fixed="right" align="center">
|
||||
<template #default="{ $index }">
|
||||
<div class="invoice-form-page__links">
|
||||
@@ -323,17 +329,8 @@
|
||||
<el-table-column label="可开票金额" min-width="140" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.availableInvoiceAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分摊发票金额" min-width="160">
|
||||
<template #default="{ row }">
|
||||
<el-input-number
|
||||
v-model="row.allocatedInvoiceAmount"
|
||||
:disabled="readonly"
|
||||
:min="0"
|
||||
:max="Number(row.availableInvoiceAmount || 0)"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
/>
|
||||
</template>
|
||||
<el-table-column label="分摊发票金额" min-width="140" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.allocatedInvoiceAmount) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</section-card>
|
||||
@@ -454,6 +451,9 @@
|
||||
<el-table-column label="结算金额" min-width="130" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.settlementAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="已申请开票金额" min-width="140" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.invoicedAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="可开票金额" min-width="140" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.availableInvoiceAmount) }}</template>
|
||||
</el-table-column>
|
||||
@@ -861,7 +861,6 @@ export default {
|
||||
keyword: this.settlementDialog.keyword,
|
||||
contractCategory: '客户合同',
|
||||
settlementType: 'receivable',
|
||||
invoiceStatus: 'unreceived',
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -913,7 +912,7 @@ export default {
|
||||
...item,
|
||||
formalSettlementId: item.id,
|
||||
settlementId: item.id,
|
||||
allocatedInvoiceAmount: rows.length === 1 ? Number(item.availableInvoiceAmount || 0) : 0,
|
||||
allocatedInvoiceAmount: 0,
|
||||
}));
|
||||
this.form.projectName = first.projectName;
|
||||
this.form.deptName = first.deptName;
|
||||
|
||||
@@ -125,6 +125,17 @@
|
||||
{{ displayValue(row[column.prop]) }}
|
||||
</el-tag>
|
||||
<span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||
<template v-else-if="column.prop === 'kingdeeStatusName'">
|
||||
<el-tooltip
|
||||
:disabled="row.kingdeeStatus !== 'failed'"
|
||||
:content="row.kingdeeFailReason || '推送金蝶失败,可点击「同步金蝶」重试'"
|
||||
placement="top"
|
||||
>
|
||||
<el-tag :type="kingdeeStatusType(row.kingdeeStatus)">{{
|
||||
row.kingdeeStatusName || '-'
|
||||
}}</el-tag>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
<template v-else-if="column.prop === 'kingdeeLockStatus'">
|
||||
<el-tooltip v-if="lockTag(row).show" :content="lockTag(row).tip" placement="top">
|
||||
<el-tag :type="lockTag(row).type" class="status-text">{{ lockTag(row).label }}</el-tag>
|
||||
@@ -238,6 +249,7 @@ import TableHeaderTools from '@/components/table-header-tools/main.vue';
|
||||
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';
|
||||
|
||||
const emptyQuery = () => ({
|
||||
applicationNo: '',
|
||||
@@ -500,6 +512,7 @@ export default {
|
||||
''
|
||||
);
|
||||
},
|
||||
kingdeeStatusType,
|
||||
lockTag(row) {
|
||||
if (row.kingdeeLockStatus === 'locked') {
|
||||
return { show: true, type: 'danger', label: '金蝶锁定中', tip: '该单据已被金蝶锁定,不可编辑/作废' };
|
||||
|
||||
@@ -2702,7 +2702,8 @@ export default {
|
||||
this.referenceParams('formal')
|
||||
);
|
||||
const data = this.unwrapData(response) || {};
|
||||
const records = data.records || [];
|
||||
// 已调整的结算单为调整重推后的归档原单,金额已作废,不可再发起付款
|
||||
const records = (data.records || []).filter(row => row.adjustStatus !== 'adjusted');
|
||||
this.formalRows = await this.fillReferencePayableAmounts(records, 'settlement_payment');
|
||||
this.formalPage.total = Number(data.total || 0);
|
||||
} finally {
|
||||
@@ -3124,18 +3125,21 @@ export default {
|
||||
if (!target) return;
|
||||
target.paymentRatio = value;
|
||||
this.syncRowPayment(target, 'ratio');
|
||||
this.syncHeaderSettlementTotalsFromRows();
|
||||
this.syncHeaderSettlementTotalsFromRows(true);
|
||||
},
|
||||
handleRowAppliedAmountChange(row, value) {
|
||||
const target = this.findReferenceRow(row);
|
||||
if (!target) return;
|
||||
target.appliedAmount = value;
|
||||
this.syncRowPayment(target, 'amount');
|
||||
this.syncHeaderSettlementTotalsFromRows();
|
||||
this.syncHeaderSettlementTotalsFromRows(true);
|
||||
},
|
||||
/** 多结算单时仅同步结算金额/可付款金额合计,申请付款金额与比例由上方独立维护 */
|
||||
syncHeaderSettlementTotalsFromRows() {
|
||||
if (this.referenceRows.length <= 1) return;
|
||||
/**
|
||||
* 结算信息行合计回填付款基本信息。
|
||||
* @param {boolean} syncApplied 行内编辑时同步申请付款金额与比例;回显加载时保持原值,避免改写历史单据
|
||||
*/
|
||||
syncHeaderSettlementTotalsFromRows(syncApplied = false) {
|
||||
if (!this.referenceRows.length) return;
|
||||
const settlementAmount = this.referenceRows.reduce(
|
||||
(total, item) => total + Number(item.settlementAmount || 0),
|
||||
0
|
||||
@@ -3144,9 +3148,21 @@ export default {
|
||||
(total, item) => total + Number(item.payableAmount || 0),
|
||||
0
|
||||
);
|
||||
const appliedAmount = this.referenceRows.reduce(
|
||||
(total, item) => total + Number(item.appliedAmount || 0),
|
||||
0
|
||||
);
|
||||
this.amountSyncing = true;
|
||||
this.form.settlementAmount = Number(settlementAmount.toFixed(2));
|
||||
this.form.payableAmount = Number(payableAmount.toFixed(2));
|
||||
if (syncApplied) {
|
||||
this.form.appliedAmount = Number(appliedAmount.toFixed(2));
|
||||
if (this.paymentAmountBase) {
|
||||
this.form.paymentRatio = Number(
|
||||
((appliedAmount / this.paymentAmountBase) * 100).toFixed(2)
|
||||
);
|
||||
}
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this.amountSyncing = false;
|
||||
this.$refs.formRef?.validateField?.(['settlementAmount', 'appliedAmount']);
|
||||
@@ -3320,6 +3336,18 @@ export default {
|
||||
ElMessage.warning(`${exceededRow.settlementNo || '结算单'}付款金额不能超过可付款金额`);
|
||||
return false;
|
||||
}
|
||||
const rowAppliedTotal = this.referenceRows.reduce(
|
||||
(total, item) => total + Number(item.appliedAmount || 0),
|
||||
0
|
||||
);
|
||||
// 金蝶按付款基本信息申请金额推单,行合计不等时预付建档分摊会与金蝶金额不一致
|
||||
if (Math.abs(rowAppliedTotal - appliedAmount) > 0.005) {
|
||||
this.clearMessages();
|
||||
ElMessage.warning(
|
||||
`结算行付款金额合计${rowAppliedTotal.toFixed(2)}与申请付款金额${appliedAmount.toFixed(2)}不一致,请调整`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (this.form.paymentType === 'project_advance' && !this.form.projectId) {
|
||||
this.clearMessages();
|
||||
|
||||
@@ -68,6 +68,18 @@
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
<template #kingdeeStatusName="{ row }">
|
||||
<el-tooltip
|
||||
:disabled="row.kingdeeStatus !== 'failed'"
|
||||
:content="row.kingdeeFailReason || '推送金蝶失败,可点击「同步金蝶」重试'"
|
||||
placement="top"
|
||||
>
|
||||
<el-tag :type="kingdeeStatusType(row.kingdeeStatus)">{{
|
||||
row.kingdeeStatusName || '-'
|
||||
}}</el-tag>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<template #menu="{ row }">
|
||||
<el-link
|
||||
v-if="hasPermission('payment_application_view')"
|
||||
@@ -151,6 +163,7 @@ import { paymentApplicationOption } from '@/option/payment/paymentApplication';
|
||||
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';
|
||||
|
||||
const paymentApplicationListRoute = '/payment/payment-application';
|
||||
const paymentApplicationListRefreshKey = 'payment-application-list-refresh';
|
||||
@@ -328,15 +341,17 @@ export default {
|
||||
this.refreshChange();
|
||||
},
|
||||
async handleApprove(row) {
|
||||
// 防止重复点击:请求进行中忽略后续点击
|
||||
// 防止重复点击:请求进行中忽略后续点击,避免重复审批和重复提示
|
||||
if (this.approveLoading) return;
|
||||
this.approveLoading = true;
|
||||
const loading = this.$loading({
|
||||
lock: true,
|
||||
text: '审批处理中,请稍候…',
|
||||
background: 'rgba(255, 255, 255, 0.6)',
|
||||
});
|
||||
let loading = null;
|
||||
try {
|
||||
await this.$confirm('确认审批通过该付款申请?', '提示', { type: 'warning' });
|
||||
loading = this.$loading({
|
||||
lock: true,
|
||||
text: '审批处理中,请稍候…',
|
||||
background: 'rgba(255, 255, 255, 0.6)',
|
||||
});
|
||||
const res = await api.approve({ id: row.id });
|
||||
// data=false 表示审批已通过但自动同步金蝶失败,msg 带具体原因
|
||||
if (res.data.data === false) {
|
||||
@@ -346,7 +361,7 @@ export default {
|
||||
}
|
||||
this.refreshChange();
|
||||
} finally {
|
||||
loading.close();
|
||||
loading?.close();
|
||||
this.approveLoading = false;
|
||||
}
|
||||
},
|
||||
@@ -461,6 +476,7 @@ export default {
|
||||
''
|
||||
);
|
||||
},
|
||||
kingdeeStatusType,
|
||||
lockTag(row) {
|
||||
if (row.kingdeeLockStatus === 'locked') {
|
||||
return { show: true, type: 'danger', label: '金蝶锁定中', tip: '该单据已被金蝶锁定,不可编辑/作废' };
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<section-card title="收款信息">
|
||||
<el-row :gutter="32">
|
||||
<el-col :span="6">
|
||||
<el-form-item label="认领通知单">
|
||||
<el-form-item label="收款单号">
|
||||
<el-input v-model="form.receiptNoticeNo" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<section-card v-if="searchVisible" class="claim-record-page__search">
|
||||
<el-form :model="query" label-position="right" label-width="88px" @submit.prevent>
|
||||
<div class="claim-record-page__search-grid">
|
||||
<el-form-item label="认领通知单">
|
||||
<el-form-item label="收款单号">
|
||||
<el-input v-model="query.receiptNoticeNo" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="对方户名">
|
||||
@@ -56,16 +56,6 @@
|
||||
<el-form-item v-show="searchExpanded" label="所属组织">
|
||||
<el-input v-model="query.claimerDeptName" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item v-show="searchExpanded" label="推送状态">
|
||||
<el-select v-model="query.pushStatus" clearable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in pushStatusOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-show="searchExpanded" label="金蝶单据状态">
|
||||
<el-select v-model="query.kingdeeBillStatus" clearable placeholder="请选择">
|
||||
<el-option
|
||||
@@ -93,9 +83,6 @@
|
||||
|
||||
<section class="claim-record-page__table-panel">
|
||||
<div class="claim-record-page__toolbar">
|
||||
<el-button :loading="refreshing" @click="handleRefreshKingdeeResult">
|
||||
查询金蝶认领结果
|
||||
</el-button>
|
||||
<table-header-tools
|
||||
:columns="columns"
|
||||
v-model:visible-column-keys="visibleColumnKeys"
|
||||
@@ -131,11 +118,7 @@
|
||||
<el-table-column label="操作" width="120" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-link
|
||||
v-if="
|
||||
hasPermission('receipt_claim_record_void') &&
|
||||
row.claimStatus === 'claimed' &&
|
||||
row.pushStatus !== 'pushed'
|
||||
"
|
||||
v-if="hasPermission('receipt_claim_record_void') && row.claimStatus === 'claimed'"
|
||||
type="danger"
|
||||
@click="handleVoid(row)"
|
||||
>作废</el-link
|
||||
@@ -181,7 +164,6 @@ const emptyQuery = () => ({
|
||||
claimDateRange: [],
|
||||
claimerDeptName: '',
|
||||
kingdeeBillStatus: '',
|
||||
pushStatus: '',
|
||||
});
|
||||
|
||||
export default {
|
||||
@@ -195,17 +177,15 @@ export default {
|
||||
searchExpanded: false,
|
||||
// 与 avue-crud 一致:搜索项数量超过 searchIndex(默认一行 4 项)时才显示“展开/收起”
|
||||
searchIndex: 4,
|
||||
searchItemCount: 11,
|
||||
searchItemCount: 10,
|
||||
searchVisible: true,
|
||||
tableSize: 'default',
|
||||
claimStatusOptions: api.claimStatusOptions,
|
||||
kingdeeBillStatusOptions: api.kingdeeBillStatusOptions,
|
||||
pushStatusOptions: api.pushStatusOptions,
|
||||
columns: receiptClaimRecordTableColumns,
|
||||
visibleColumnKeys: receiptClaimRecordTableColumns.map(column => column.prop),
|
||||
rows: [],
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
page: { current: 1, size: 10, total: 0 },
|
||||
};
|
||||
},
|
||||
@@ -272,7 +252,7 @@ export default {
|
||||
},
|
||||
async handleVoid(row) {
|
||||
await this.$confirm(
|
||||
'作废后将回退本次认领金额,是否继续?',
|
||||
'作废后将回退本次认领金额,并同步作废合同合规系统认领记录,是否继续?',
|
||||
'作废认领记录',
|
||||
{ type: 'warning', confirmButtonText: '确定', cancelButtonText: '取消' }
|
||||
);
|
||||
@@ -280,16 +260,6 @@ export default {
|
||||
this.$message.success('作废成功');
|
||||
await this.loadTable();
|
||||
},
|
||||
async handleRefreshKingdeeResult() {
|
||||
this.refreshing = true;
|
||||
try {
|
||||
const updated = Number(this.unwrapData(await api.queryKingdeeResult()) || 0);
|
||||
this.$message.success(`金蝶认领结果回写完成,更新${updated}条`);
|
||||
await this.loadTable();
|
||||
} finally {
|
||||
this.refreshing = false;
|
||||
}
|
||||
},
|
||||
hasPermission(code) {
|
||||
return this.isAdmin || this.validData(this.permission?.[code], false);
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column
|
||||
prop="receiptNoticeNo"
|
||||
label="认领通知单"
|
||||
label="收款单号"
|
||||
min-width="160"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
@@ -43,8 +43,8 @@
|
||||
<el-table-column label="已认领金额" min-width="130" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.claimedAmount) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金蝶未认领金额" min-width="140" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.unclaimedAmount) }}</template>
|
||||
<el-table-column label="未匹配金额" min-width="140" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(row.unmatchAmountRec) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="剩余可认领金额" min-width="140" align="right">
|
||||
<template #default="{ row }">{{ formatMoney(flowRemaining(row)) }}</template>
|
||||
@@ -96,7 +96,7 @@
|
||||
formatMoney(row.settlement.claimedReceiptAmount)
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="认领通知单" min-width="180">
|
||||
<el-table-column label="收款单号" min-width="180">
|
||||
<template #default="{ row }">{{ row.flow.receiptNoticeNo || '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收款金额" min-width="140" align="right">
|
||||
@@ -302,10 +302,8 @@ export default {
|
||||
);
|
||||
},
|
||||
flowRemaining(flow) {
|
||||
const unclaimed = Number(flow.unclaimedAmount || 0);
|
||||
const receipt = Number(flow.receiptAmount || 0);
|
||||
const ceiling = unclaimed > 0 && receipt > 0 ? Math.min(unclaimed, receipt) : unclaimed || receipt;
|
||||
return Math.max(ceiling - Number(flow.claimedAmount || 0), 0);
|
||||
// 认领只登记本地:剩余可认领 = 收款金额 − 已认领金额
|
||||
return Math.max(Number(flow.receiptAmount || 0) - Number(flow.claimedAmount || 0), 0);
|
||||
},
|
||||
maxAllocation(row) {
|
||||
const current = Number(row.settlement.allocations?.[row.flow.id] || 0);
|
||||
@@ -355,12 +353,8 @@ export default {
|
||||
}
|
||||
for (const flow of this.flows) {
|
||||
const total = this.flowAllocatedTotal(flow.id);
|
||||
const remaining = Math.max(
|
||||
Number(flow.unclaimedAmount || 0) - Number(flow.claimedAmount || 0),
|
||||
0
|
||||
);
|
||||
if (total > remaining + 0.001) {
|
||||
this.$message.warning(`流水${flow.receiptNoticeNo}的累计认领金额不能超过公告未认领金额`);
|
||||
if (total > this.flowRemaining(flow) + 0.001) {
|
||||
this.$message.warning(`流水${flow.receiptNoticeNo}的累计认领金额不能超过剩余可认领金额`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<section-card title="收款信息">
|
||||
<el-row :gutter="32">
|
||||
<el-col :span="6"
|
||||
><el-form-item label="认领通知单"
|
||||
><el-form-item label="收款单号"
|
||||
><el-input v-model="form.receiptNoticeNo" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="6"
|
||||
@@ -48,8 +48,8 @@
|
||||
><el-input :model-value="formatMoney(form.claimedAmount)" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="6"
|
||||
><el-form-item label="金蝶未认领金额"
|
||||
><el-input :model-value="formatMoney(form.unclaimedAmount)" disabled /></el-form-item
|
||||
><el-form-item label="未匹配金额"
|
||||
><el-input :model-value="formatMoney(form.unmatchAmountRec)" disabled /></el-form-item
|
||||
></el-col>
|
||||
<el-col :span="6"
|
||||
><el-form-item label="剩余可认领金额"
|
||||
@@ -205,7 +205,7 @@ const emptyForm = () => ({
|
||||
transactionTime: '',
|
||||
detailSerialNo: '',
|
||||
claimedAmount: 0,
|
||||
unclaimedAmount: 0,
|
||||
unmatchAmountRec: 0,
|
||||
claimerName: '',
|
||||
claimerDeptName: '',
|
||||
claimDate: '',
|
||||
@@ -252,11 +252,10 @@ export default {
|
||||
);
|
||||
},
|
||||
claimableAmount() {
|
||||
const unclaimed = Number(this.form.unclaimedAmount || 0);
|
||||
// 认领只登记本地:剩余可认领 = 收款金额 − 已认领金额
|
||||
const receipt = Number(this.form.receiptAmount || 0);
|
||||
const ceiling =
|
||||
unclaimed > 0 && receipt > 0 ? Math.min(unclaimed, receipt) : unclaimed || receipt;
|
||||
return Math.max(ceiling - Number(this.form.claimedAmount || 0), 0);
|
||||
const claimed = Number(this.form.claimedAmount || 0);
|
||||
return Math.max(receipt - claimed, 0);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<section v-if="searchVisible" class="receipt-flow-page__search">
|
||||
<el-form :model="query" label-position="right" label-width="88px" @submit.prevent>
|
||||
<div class="receipt-flow-page__search-grid">
|
||||
<el-form-item label="认领通知单">
|
||||
<el-form-item label="收款单号">
|
||||
<el-input v-model="query.receiptNoticeNo" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="对方户名">
|
||||
@@ -15,16 +15,6 @@
|
||||
<el-form-item label="对方账号">
|
||||
<el-input v-model="query.counterpartyAccount" clearable placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="推送状态">
|
||||
<el-select v-model="query.pushStatus" clearable placeholder="请选择">
|
||||
<el-option
|
||||
v-for="item in pushStatusOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="认领状态">
|
||||
<el-select v-model="query.claimStatus" clearable placeholder="请选择">
|
||||
<el-option
|
||||
@@ -159,23 +149,10 @@
|
||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="170" fixed="right" align="center">
|
||||
<el-table-column label="操作" width="100" fixed="right" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-link
|
||||
v-if="canClaim(row)"
|
||||
type="primary"
|
||||
:disabled="pushingId === row.id"
|
||||
@click="openClaim(row)"
|
||||
>认领</el-link
|
||||
>
|
||||
<el-link
|
||||
v-if="canPush(row)"
|
||||
type="warning"
|
||||
:disabled="pushingId === row.id"
|
||||
@click="handlePush(row)"
|
||||
>推送金蝶</el-link
|
||||
>
|
||||
<span v-if="!canClaim(row) && !canPush(row)">-</span>
|
||||
<el-link v-if="canClaim(row)" type="primary" @click="openClaim(row)">认领</el-link>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -201,13 +178,13 @@
|
||||
>
|
||||
<div v-loading="detailDialog.loading">
|
||||
<el-descriptions :column="3" border>
|
||||
<el-descriptions-item label="认领通知单">
|
||||
<el-descriptions-item label="收款单号">
|
||||
{{ displayValue(detailDialog.data.receiptNoticeNo) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="付款人">
|
||||
{{ displayValue(detailDialog.data.payerName) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="付款金额">
|
||||
<el-descriptions-item label="收款金额">
|
||||
{{ formatMoney(detailDialog.data.receiptAmount) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="交易时间">
|
||||
@@ -236,13 +213,8 @@
|
||||
<el-descriptions-item label="剩余可认领金额">
|
||||
{{ formatMoney(detailDialog.data.remainingAmount) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="金蝶未认领金额">
|
||||
{{ formatMoney(detailDialog.data.unclaimedAmount) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="推送状态">
|
||||
<el-tag :type="statusType(detailDialog.data, { prop: 'pushStatusName' })">
|
||||
{{ displayValue(detailDialog.data.pushStatusName) }}
|
||||
</el-tag>
|
||||
<el-descriptions-item label="未匹配金额">
|
||||
{{ formatMoney(detailDialog.data.unmatchAmountRec) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="金蝶收款单号">
|
||||
{{ displayValue(detailDialog.data.kingdeeBillNo) }}
|
||||
@@ -250,9 +222,6 @@
|
||||
<el-descriptions-item label="金蝶状态">
|
||||
{{ displayValue(detailDialog.data.kingdeeBillStatusName) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="推送失败原因">
|
||||
{{ displayValue(detailDialog.data.pushFailReason) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="摘要">
|
||||
{{ displayValue(detailDialog.data.summary) }}
|
||||
</el-descriptions-item>
|
||||
@@ -267,9 +236,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="operatorName" label="认领人" min-width="120" align="center" />
|
||||
<el-table-column label="状态变化" min-width="170" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.actionType === 'push' ? row.actionName : claimStatusChange(row) }}
|
||||
</template>
|
||||
<template #default="{ row }">{{ claimStatusChange(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="content" label="认领说明" min-width="220" />
|
||||
</el-table>
|
||||
@@ -297,7 +264,6 @@ const emptyQuery = () => ({
|
||||
summary: '',
|
||||
detailSerialNo: '',
|
||||
claimStatus: '',
|
||||
pushStatus: '',
|
||||
kingdeeBillStatus: '',
|
||||
receiptAmountStart: undefined,
|
||||
receiptAmountEnd: undefined,
|
||||
@@ -314,7 +280,6 @@ export default {
|
||||
ArrowUp,
|
||||
query: emptyQuery(),
|
||||
claimStatusOptions: api.claimStatusOptions,
|
||||
pushStatusOptions: api.pushStatusOptions,
|
||||
kingdeeBillStatusOptions: api.kingdeeBillStatusOptions,
|
||||
columns: receiptFlowTableColumns,
|
||||
visibleColumnKeys: receiptFlowTableColumns.map(column => column.prop),
|
||||
@@ -324,7 +289,6 @@ export default {
|
||||
selectedRows: [],
|
||||
loading: false,
|
||||
syncing: false,
|
||||
pushingId: null,
|
||||
searchExpanded: false,
|
||||
// 与 avue-crud 一致:搜索项数量超过 searchIndex(默认一行 4 项)时才显示“展开/收起”
|
||||
searchIndex: 4,
|
||||
@@ -389,7 +353,7 @@ export default {
|
||||
this.syncing = true;
|
||||
try {
|
||||
const syncedCount = Number(this.unwrapData(await api.sync()) || 0);
|
||||
this.$message.success(`流水同步完成,共同步${syncedCount}条认领公告`);
|
||||
this.$message.success(`流水同步完成,共同步${syncedCount}条收款流水`);
|
||||
await this.loadTable();
|
||||
} finally {
|
||||
this.syncing = false;
|
||||
@@ -448,38 +412,7 @@ export default {
|
||||
canClaim(row) {
|
||||
return this.hasPermission('receipt_flow_claim') && Number(row.remainingAmount || 0) > 0;
|
||||
},
|
||||
canPush(row) {
|
||||
return (
|
||||
this.hasPermission('receipt_flow_push') &&
|
||||
!!row.billId &&
|
||||
row.claimStatus === 'claimed' &&
|
||||
row.pushStatus !== 'pushed'
|
||||
);
|
||||
},
|
||||
async handlePush(row) {
|
||||
try {
|
||||
await this.$confirm(
|
||||
`确认将流水【${row.receiptNoticeNo || row.detailSerialNo || ''}】推送金蝶生成收款单?`,
|
||||
'推送金蝶',
|
||||
{ type: 'warning' }
|
||||
);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
this.pushingId = row.id;
|
||||
try {
|
||||
const data = this.unwrapData(await api.pushKingdee(row.id));
|
||||
const billNo = data?.kingdeeBillNo;
|
||||
this.$message.success(billNo ? `推送成功,金蝶收款单号:${billNo}` : '推送成功');
|
||||
await this.loadTable();
|
||||
} finally {
|
||||
this.pushingId = null;
|
||||
}
|
||||
},
|
||||
statusType(row, column) {
|
||||
if (column?.prop === 'pushStatusName') {
|
||||
return { pushed: 'success', failed: 'danger' }[row.pushStatus] || 'info';
|
||||
}
|
||||
return (
|
||||
{ claimed: 'success', partial: 'warning', unclaimed: 'info' }[row.claimStatus] || ''
|
||||
);
|
||||
|
||||
@@ -559,7 +559,7 @@
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column
|
||||
prop="receiptNoticeNo"
|
||||
label="认领通知单"
|
||||
label="收款单号"
|
||||
min-width="160"
|
||||
align="center"
|
||||
/>
|
||||
|
||||
@@ -60,7 +60,17 @@
|
||||
}}</el-tag>
|
||||
</template>
|
||||
|
||||
<template #kingdeeSyncStatus="{ row }">{{ kingdeeSyncName(row.kingdeeSyncStatus) }}</template>
|
||||
<template #kingdeeSyncStatus="{ row }">
|
||||
<el-tooltip
|
||||
:disabled="row.kingdeeSyncStatus !== 'failed'"
|
||||
:content="row.kingdeeFailReason || '推送金蝶失败,可点击「同步金蝶」重试'"
|
||||
placement="top"
|
||||
>
|
||||
<el-tag :type="kingdeeStatusType(row.kingdeeSyncStatus)">{{
|
||||
kingdeeSyncName(row.kingdeeSyncStatus)
|
||||
}}</el-tag>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<template #kingdeeLockStatus="{ row }">
|
||||
<el-tooltip v-if="lockTag(row).show" :content="lockTag(row).tip" placement="top">
|
||||
@@ -84,6 +94,7 @@
|
||||
<script>
|
||||
import { invoiceStatusName } from '@/option/settlement/formalSettlementSearch';
|
||||
import { kingdeeSyncName, paymentStatusName } from '@/option/settlement/formalSettlement';
|
||||
import { kingdeeStatusType } from '@/utils/kingdee';
|
||||
|
||||
export default {
|
||||
name: 'FormalSettlementTablePanel',
|
||||
@@ -186,6 +197,7 @@ export default {
|
||||
''
|
||||
);
|
||||
},
|
||||
kingdeeStatusType,
|
||||
invoiceName(value, settlementType) {
|
||||
return invoiceStatusName(value, settlementType);
|
||||
},
|
||||
|
||||
@@ -486,6 +486,8 @@ export default {
|
||||
const rows = this.selection;
|
||||
if (rows.some(row => row.approvalStatus !== 'approved' || row.settlementType !== 'payable'))
|
||||
return this.$message.warning('仅审批通过的付款结算单允许发起付款申请');
|
||||
if (rows.some(row => row.adjustStatus === 'adjusted'))
|
||||
return this.$message.warning('已调整的结算单不可再发起付款,请使用调整后的新单');
|
||||
const contractIds = new Set(rows.map(row => String(row.contractId || '')));
|
||||
if (contractIds.size !== 1 || contractIds.has(''))
|
||||
return this.$message.warning('付款申请必须选择同一个合同的付款结算单');
|
||||
|
||||
@@ -93,11 +93,15 @@
|
||||
><el-tag v-else-if="column.status" :type="statusType(row.approvalStatus)" class="status-text">{{
|
||||
row[column.prop] || '-'
|
||||
}}</el-tag
|
||||
><el-tag
|
||||
><el-tooltip
|
||||
v-else-if="column.kingdee"
|
||||
:disabled="row.kingdeeSyncStatus !== 'failed'"
|
||||
:content="row.kingdeeFailReason || '推送金蝶失败,请重试'"
|
||||
placement="top"
|
||||
><el-tag
|
||||
:type="kingdeeStatusType(row.kingdeeSyncStatus)"
|
||||
class="status-text"
|
||||
>{{ kingdeeSyncName(row.kingdeeSyncStatus) }}</el-tag
|
||||
></el-tooltip
|
||||
><span v-else-if="column.money">{{ formatMoney(row[column.prop]) }}</span
|
||||
><span v-else>{{ displayValue(row[column.prop]) }}</span></template
|
||||
></el-table-column
|
||||
@@ -182,6 +186,7 @@ import { settlementAdjustmentSearchFields } from '@/option/settlement/settlement
|
||||
import organizationSearch from '@/mixins/organization-search';
|
||||
import { settlementAdjustmentTableColumns } from '@/option/settlement/settlementAdjustmentTable';
|
||||
import { kingdeeSyncStatusOptions } from '@/option/settlement/formalSettlementSearch';
|
||||
import { kingdeeStatusType } from '@/utils/kingdee';
|
||||
import SettlementAdjustmentEditor from './components/settlement-adjustment-editor.vue';
|
||||
import SettlementTableTools from './components/settlement-table-tools.vue';
|
||||
import { isAdministrator } from '@/utils/auth';
|
||||
@@ -317,11 +322,18 @@ export default {
|
||||
},
|
||||
async handleApprove(row) {
|
||||
await this.$confirm('审批通过后将更新关联正式结算单,确认继续?', '提示');
|
||||
const res = await api.approve(row.id);
|
||||
// data=false:自动同步金蝶失败;data=true 且 msg 带已推送场景的手工冲销提示
|
||||
if (res.data.data === true || res.data.data === false) this.$message.warning(res.data.msg);
|
||||
else this.$message.success(res.data.msg || '审批通过');
|
||||
this.loadTable();
|
||||
// 防止重复点击:请求期间表格遮罩,后续点击直接忽略
|
||||
if (this.loading) return;
|
||||
this.loading = true;
|
||||
try {
|
||||
const res = await api.approve(row.id);
|
||||
// data=false:自动同步金蝶失败;data=true 且 msg 带已推送场景的手工冲销提示
|
||||
if (res.data.data === true || res.data.data === false) this.$message.warning(res.data.msg);
|
||||
else this.$message.success(res.data.msg || '审批通过');
|
||||
await this.loadTable();
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
async handleReturn(row) {
|
||||
const { value } = await this.$prompt('请输入驳回原因', '驳回', {
|
||||
@@ -358,9 +370,7 @@ export default {
|
||||
'info'
|
||||
);
|
||||
},
|
||||
kingdeeStatusType(status) {
|
||||
return { synced: 'success', failed: 'danger' }[status] || 'info';
|
||||
},
|
||||
kingdeeStatusType,
|
||||
kingdeeSyncName(value) {
|
||||
return kingdeeSyncStatusOptions.find(item => item.value === value)?.label || value || '-';
|
||||
},
|
||||
|
||||
@@ -26,9 +26,6 @@
|
||||
<el-button type="primary" plain v-if="permission.mp_user_reset" @click="handleReset">
|
||||
密码重置
|
||||
</el-button>
|
||||
<el-button type="danger" plain v-if="permission.mp_user_delete" @click="handleDelete">
|
||||
批量删除
|
||||
</el-button>
|
||||
</template>
|
||||
<template #identities="{ row }">
|
||||
{{ row.identitiesName || formatIdentities(row.identities) }}
|
||||
@@ -102,9 +99,6 @@
|
||||
<el-link type="primary" v-if="permission.mp_user_reset" @click="handleResetForRow(row)">
|
||||
重置密码
|
||||
</el-link>
|
||||
<el-link type="danger" v-if="permission.mp_user_delete" @click="rowDel(row)">
|
||||
删除
|
||||
</el-link>
|
||||
</template>
|
||||
<template #menu-form="{ size }">
|
||||
<el-button
|
||||
@@ -121,7 +115,7 @@
|
||||
|
||||
<script>
|
||||
import { h } from 'vue';
|
||||
import { getList, getDetail, submit, remove, changeStatus, resetPassword } from '@/api/system/mpUser';
|
||||
import { getList, getDetail, submit, changeStatus, resetPassword } from '@/api/system/mpUser';
|
||||
import { getDeptTree } from '@/api/system/dept';
|
||||
import option from '@/option/system/mpUser';
|
||||
import { mapGetters } from 'vuex';
|
||||
@@ -159,7 +153,6 @@ export default {
|
||||
return {
|
||||
addBtn: this.validData(this.permission.mp_user_add, false),
|
||||
viewBtn: this.validData(this.permission.mp_user_view, false),
|
||||
delBtn: this.validData(this.permission.mp_user_delete, false),
|
||||
editBtn: this.validData(this.permission.mp_user_edit, false),
|
||||
};
|
||||
},
|
||||
@@ -345,35 +338,6 @@ export default {
|
||||
() => loading()
|
||||
);
|
||||
},
|
||||
rowDel(row) {
|
||||
this.$confirm('确定删除该小程序用户?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(row.id))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message.success('操作成功');
|
||||
});
|
||||
},
|
||||
handleDelete() {
|
||||
if (this.selectionList.length === 0) {
|
||||
this.$message.warning('请选择至少一条数据');
|
||||
return;
|
||||
}
|
||||
this.$confirm('确定删除选中的小程序用户?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => remove(this.ids))
|
||||
.then(() => {
|
||||
this.onLoad(this.page);
|
||||
this.$message.success('操作成功');
|
||||
this.$refs.crud.toggleSelection();
|
||||
});
|
||||
},
|
||||
handleStatus(row) {
|
||||
const status = row.status === 1 ? 0 : 1;
|
||||
const text = status === 1 ? '启用' : '停用';
|
||||
|
||||
@@ -135,7 +135,13 @@
|
||||
<template #kingdeeStatus="{ row }">
|
||||
<el-tag v-if="row.kingdeeStatus === 'synced'" type="success">已推送</el-tag>
|
||||
<el-tag v-else-if="row.kingdeeStatus === 'exist'" type="primary">金蝶已有</el-tag>
|
||||
<el-tag v-else-if="row.kingdeeStatus === 'failed'" type="danger">推送失败</el-tag>
|
||||
<el-tooltip
|
||||
v-else-if="row.kingdeeStatus === 'failed'"
|
||||
:content="row.kingdeeFailReason || '推送金蝶失败,请重试'"
|
||||
placement="top"
|
||||
>
|
||||
<el-tag type="danger">推送失败</el-tag>
|
||||
</el-tooltip>
|
||||
<el-tag v-else type="info">未推送</el-tag>
|
||||
</template>
|
||||
<template #menu="{ row }">
|
||||
@@ -2462,6 +2468,7 @@ export default {
|
||||
resetKingdeeBackfill() {
|
||||
const form = this.archiveForm;
|
||||
form.kingdeeStatus = 'none';
|
||||
form.customerCode = '';
|
||||
form.fullName = '';
|
||||
form.legalPerson = '';
|
||||
form.registeredCapital = '';
|
||||
@@ -2517,6 +2524,8 @@ export default {
|
||||
applyKingdeeCustomer(customer) {
|
||||
const form = this.archiveForm;
|
||||
form.kingdeeStatus = 'exist';
|
||||
// 客商编号回显金蝶客商编码,与后端同步金蝶时的采纳口径保持一致
|
||||
if (customer.number) form.customerCode = customer.number;
|
||||
// 工商信息以金蝶为准,直接覆盖(后续字段锁定)
|
||||
if (customer.name) form.fullName = customer.name;
|
||||
if (customer.name) this.checkArchiveNameRisk();
|
||||
|
||||
Reference in New Issue
Block a user