1.0.81 合同变更审批流完善

dev
yinq 7 days ago
parent 9526af3a02
commit 69aeb3c17d

@ -93,6 +93,20 @@ export const saveContractChange = (data: ContractChangeSaveForm): AxiosPromise<n
}); });
}; };
/**
*
* @param data
*/
export const contractChangeSubmitAndFlowStart = (
data: ContractChangeSaveForm
): AxiosPromise<ContractChangeVO> => {
return request({
url: '/oa/erp/contractChange/contractChangeSubmitAndFlowStart',
method: 'post',
data
});
};
/** /**
* +++ * +++
* @param contractChangeId * @param contractChangeId

@ -301,6 +301,12 @@ export interface ContractChangeSaveForm {
changeInfo?: any; changeInfo?: any;
changeMaterialList?: any[]; changeMaterialList?: any[];
changePaymentMethodList?: any[]; changePaymentMethodList?: any[];
/** 流程定义编码(提交审批时) */
flowCode?: string;
/** 流程变量(提交审批时) */
variables?: Record<string, any>;
/** 流程实例业务扩展(提交审批时) */
bizExt?: { businessTitle?: string; businessCode?: string; businessId?: string };
} }
/** 合同变更详情(主表+信息+物料+付款方式) */ /** 合同变更详情(主表+信息+物料+付款方式) */

@ -0,0 +1,241 @@
<template>
<div>
<el-dialog v-model="dialog.visible" :title="title" width="960px" top="8vh" append-to-body destroy-on-close>
<el-row :gutter="20">
<el-col :span="24">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="90px">
<el-form-item label="合同编号" prop="contractCode">
<el-input v-model="queryParams.contractCode" placeholder="请输入合同编号" clearable style="width: 180px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="合同名称" prop="contractName">
<el-input v-model="queryParams.contractName" placeholder="请输入合同名称" clearable style="width: 220px" @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="合同状态" prop="contractStatus">
<el-select v-model="queryParams.contractStatus" placeholder="请选择合同状态" clearable style="width: 160px">
<el-option v-for="dict in contract_status" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery"></el-button>
<el-button icon="Refresh" @click="() => resetQuery()">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<vxe-table
ref="tableRef"
height="420px"
border
show-overflow
:data="contractList"
:loading="loading"
:row-config="{ keyField: 'contractId', isHover: true }"
:checkbox-config="{ reserve: true, trigger: 'row', highlight: true, showHeader: prop.multiple }"
@checkbox-all="handleCheckboxAll"
@checkbox-change="handleCheckboxChange"
>
<vxe-column type="checkbox" width="50" align="center" />
<vxe-column type="seq" title="序号" width="55" align="center" />
<vxe-column field="contractCode" title="合同编号" align="center" min-width="140" />
<vxe-column field="contractName" title="合同名称" align="center" min-width="220" show-overflow />
<vxe-column field="contractCategory" title="合同大类" align="center" width="100">
<template #default="scope">
<dict-tag :options="contract_category" :value="scope.row.contractCategory" />
</template>
</vxe-column>
<vxe-column field="businessDirection" title="业务方向" align="center" width="100">
<template #default="scope">
<dict-tag :options="business_direction" :value="scope.row.businessDirection" />
</template>
</vxe-column>
<vxe-column field="deptName" title="部门" align="center" width="120" />
<vxe-column field="oneCustomerName" title="甲方公司" align="center" min-width="160" show-overflow />
<vxe-column field="twoCustomerName" title="乙方公司" align="center" min-width="160" show-overflow />
<vxe-column field="contractDate" title="合同签订日期" align="center" width="140" />
<vxe-column field="totalPrice" title="合同总价" align="center" width="120" />
<vxe-column field="contractStatus" title="合同状态" align="center" width="100">
<template #default="scope">
<dict-tag :options="contract_status" :value="scope.row.contractStatus" />
</template>
</vxe-column>
<vxe-column field="contractManagerName" title="合同负责人" align="center" width="120" />
</vxe-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="pageList"
/>
</el-card>
</el-col>
</el-row>
<template #footer>
<el-button @click="close"></el-button>
<el-button type="primary" @click="confirm"></el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { listContractInfo } from '@/api/oa/erp/contractInfo';
import type { ContractInfoQuery, ContractInfoVO } from '@/api/oa/erp/contractInfo/types';
import { getCurrentInstance, reactive, ref, watch, toRefs, nextTick } from 'vue';
import type { FormInstance } from 'element-plus';
import { VxeTableInstance } from 'vxe-table';
interface Props {
/** 弹框标题 */
title?: string;
/** 是否多选 */
multiple?: boolean;
/** 状态合同 */
contractStatus?: string | number;
/** 激活标识 */
activeFlag?: string | number;
}
const prop = withDefaults(defineProps<Props>(), {
title: '选择合同',
multiple: false,
contractStatus: '5',
activeFlag: '1'
});
const emit = defineEmits<{
confirmCallBack: [ContractInfoVO[]];
}>();
const proxy = (getCurrentInstance()?.proxy || {}) as any;
const { contract_category, business_direction, contract_status } = toRefs<any>(
proxy?.useDict('contract_category', 'business_direction', 'contract_status')
);
const dialog = reactive({ visible: false });
const title = prop.title;
const showSearch = ref(true);
const loading = ref(false);
const total = ref(0);
const contractList = ref<ContractInfoVO[]>([]);
const selectContractList = ref<ContractInfoVO[]>([]);
const queryFormRef = ref<FormInstance>();
const tableRef = ref<VxeTableInstance<ContractInfoVO>>();
const queryParams = reactive<ContractInfoQuery>({
pageNum: 1,
pageSize: 10,
contractCode: undefined,
contractName: undefined,
contractStatus: String(prop.contractStatus) as any,
activeFlag: String(prop.activeFlag) as any
});
const getList = async () => {
loading.value = true;
try {
const res = await listContractInfo(queryParams);
contractList.value = res.rows || [];
total.value = res.total || 0;
} finally {
loading.value = false;
}
};
const pageList = async () => {
await getList();
const rows = contractList.value.filter((item) =>
selectContractList.value.some((c) => c.contractId === item.contractId)
);
await nextTick(() => tableRef.value?.setCheckboxRow(rows, true));
};
const handleQuery = () => {
queryParams.pageNum = 1;
getList();
};
const resetQuery = (refresh = true) => {
queryFormRef.value?.resetFields();
queryParams.contractStatus = String(prop.contractStatus) as any;
queryParams.pageNum = 1;
refresh && handleQuery();
};
const handleCheckboxChange = (checked: any) => {
if (!prop.multiple && checked.checked) {
tableRef.value?.setCheckboxRow(selectContractList.value, false);
selectContractList.value = [];
}
const row = checked.row as ContractInfoVO;
if (checked.checked) {
selectContractList.value.push(row);
} else {
selectContractList.value = selectContractList.value.filter((item) => item.contractId !== row.contractId);
}
};
const handleCheckboxAll = (checked: any) => {
const rows = contractList.value;
if (checked.checked) {
rows.forEach((row) => {
if (!selectContractList.value.some((item) => item.contractId === row.contractId)) {
selectContractList.value.push(row);
}
});
} else {
selectContractList.value = selectContractList.value.filter(
(item) => !rows.some((row) => row.contractId === item.contractId)
);
}
};
const confirm = () => {
if (selectContractList.value.length === 0) {
proxy?.$modal?.msgWarning?.('请先勾选合同');
return;
}
emit('confirmCallBack', selectContractList.value);
close();
};
const open = async () => {
dialog.visible = true;
};
const close = () => {
dialog.visible = false;
};
watch(
() => dialog.visible,
async (val) => {
if (val) {
selectContractList.value = [];
queryParams.pageNum = 1;
await getList();
} else {
tableRef.value?.clearCheckboxReserve?.();
tableRef.value?.clearCheckboxRow?.();
resetQuery(false);
contractList.value = [];
total.value = 0;
selectContractList.value = [];
}
}
);
defineExpose({
open,
close
});
</script>

@ -20,6 +20,9 @@
<el-button class="float-right" link @click="goBack"></el-button> <el-button class="float-right" link @click="goBack"></el-button>
</template> </template>
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px"> <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="变更编号" prop="changeCode" :disabled="isFormDisabled">
<el-input v-model="form.changeCode" placeholder="保存后自动生成" />
</el-form-item>
<el-form-item label="变更类型" prop="changeType"> <el-form-item label="变更类型" prop="changeType">
<el-radio-group v-model="form.changeType" :disabled="isFormDisabled"> <el-radio-group v-model="form.changeType" :disabled="isFormDisabled">
<el-radio v-for="dict in contract_change_type" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio> <el-radio v-for="dict in contract_change_type" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
@ -393,76 +396,7 @@
<SaleMaterialSelect ref="saleMaterialSelectRef" :multiple="false" @confirm-call-back="saleMaterialSelectCallBack" /> <SaleMaterialSelect ref="saleMaterialSelectRef" :multiple="false" @confirm-call-back="saleMaterialSelectCallBack" />
<!-- 选择合同弹框列表 + 合同详情 --> <ContractSelect ref="contractSelectRef" @confirm-call-back="contractSelectCallBack" />
<el-dialog v-model="contractDialog.visible" title="选择合同" width="960px" append-to-body>
<el-form :model="contractQueryParams" :inline="true" label-width="90px">
<el-form-item label="合同编号">
<el-input
v-model="contractQueryParams.contractCode"
placeholder="请输入合同编号"
clearable
style="width: 160px"
@keyup.enter="getContractList"
/>
</el-form-item>
<el-form-item label="合同名称">
<el-input
v-model="contractQueryParams.contractName"
placeholder="请输入合同名称"
clearable
style="width: 180px"
@keyup.enter="getContractList"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="getContractList"></el-button>
<el-button icon="Refresh" @click="resetContractQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="16">
<el-col :span="14">
<el-table
v-loading="contractLoading"
:data="contractList"
border
max-height="360"
highlight-current-row
@current-change="handleContractRowChange"
>
<el-table-column type="index" label="序号" width="55" />
<el-table-column label="合同编号" align="center" prop="contractCode" min-width="120" />
<el-table-column label="合同名称" align="center" prop="contractName" min-width="160" show-overflow-tooltip />
<el-table-column label="合同总价" align="center" prop="totalPrice" width="100" />
</el-table>
<pagination
v-show="contractTotal > 0"
:total="contractTotal"
v-model:page="contractQueryParams.pageNum"
v-model:limit="contractQueryParams.pageSize"
@pagination="getContractList"
/>
</el-col>
<el-col :span="10">
<div class="contract-detail-panel">
<div class="panel-title">合同详情</div>
<template v-if="contractDetailPreview">
<el-descriptions :column="1" border size="small">
<el-descriptions-item label="合同编号">{{ contractDetailPreview.contractCode }}</el-descriptions-item>
<el-descriptions-item label="合同名称">{{ contractDetailPreview.contractName }}</el-descriptions-item>
<el-descriptions-item label="合同总价">{{ contractDetailPreview.totalPrice }}</el-descriptions-item>
<el-descriptions-item label="客户">{{ (contractDetailPreview as any).oneCustomerName || (contractDetailPreview as any).twoCustomerName || '-' }}</el-descriptions-item>
<el-descriptions-item label="签订日期">{{ contractDetailPreview.contractDate || '-' }}</el-descriptions-item>
</el-descriptions>
</template>
<div v-else class="panel-placeholder">点击左侧列表行可查看合同详情</div>
</div>
</el-col>
</el-row>
<template #footer>
<el-button @click="contractDialog.visible = false"> </el-button>
<el-button type="primary" @click="submitContractSelect"> </el-button>
</template>
</el-dialog>
<!-- 审批记录 --> <!-- 审批记录 -->
<approvalRecord ref="approvalRecordRef" /> <approvalRecord ref="approvalRecordRef" />
@ -472,9 +406,9 @@
<script setup name="ContractChangeApply" lang="ts"> <script setup name="ContractChangeApply" lang="ts">
import { Search } from '@element-plus/icons-vue'; import { Search } from '@element-plus/icons-vue';
import { listContractInfo, getContractInfo } from '@/api/oa/erp/contractInfo'; import { getContractInfo } from '@/api/oa/erp/contractInfo';
import type { ContractInfoVO, ContractInfoQuery } from '@/api/oa/erp/contractInfo/types'; import type { ContractInfoVO } from '@/api/oa/erp/contractInfo/types';
import { saveContractChange, getContractChangeDetail } from '@/api/oa/erp/contractChange'; import { saveContractChange, getContractChangeDetail, contractChangeSubmitAndFlowStart } from '@/api/oa/erp/contractChange';
import type { ContractChangeSaveForm } from '@/api/oa/erp/contractChange/types'; import type { ContractChangeSaveForm } from '@/api/oa/erp/contractChange/types';
import type { ContractMaterialForm } from '@/api/oa/erp/contractMaterial/types'; import type { ContractMaterialForm } from '@/api/oa/erp/contractMaterial/types';
import type { ContractPaymentMethodForm } from '@/api/oa/erp/contractPaymentMethod/types'; import type { ContractPaymentMethodForm } from '@/api/oa/erp/contractPaymentMethod/types';
@ -485,12 +419,12 @@ import { getCrmCustomerInfoList } from '@/api/oa/crm/customerInfo';
import { getCrmPaymentAccountList } from '@/api/oa/crm/paymentAccount'; import { getCrmPaymentAccountList } from '@/api/oa/crm/paymentAccount';
import { getUserList } from '@/api/system/user'; import { getUserList } from '@/api/system/user';
import { getBasePrintTemplateList } from '@/api/oa/base/printTemplate'; import { getBasePrintTemplateList } from '@/api/oa/base/printTemplate';
import { startWorkFlow } from '@/api/workflow/task';
import ApprovalButton from '@/components/Process/approvalButton.vue'; import ApprovalButton from '@/components/Process/approvalButton.vue';
import ApprovalRecord from '@/components/Process/approvalRecord.vue'; import ApprovalRecord from '@/components/Process/approvalRecord.vue';
import SubmitVerify from '@/components/Process/submitVerify.vue'; import SubmitVerify from '@/components/Process/submitVerify.vue';
import FileUpload from '@/components/FileUpload/index.vue'; import FileUpload from '@/components/FileUpload/index.vue';
import SaleMaterialSelect from '@/components/SaleMaterialSelect/index.vue'; import SaleMaterialSelect from '@/components/SaleMaterialSelect/index.vue';
import ContractSelect from '@/components/ContractSelect/index.vue';
import { FlowCodeEnum } from '@/enums/OAEnum'; import { FlowCodeEnum } from '@/enums/OAEnum';
const route = useRoute(); const route = useRoute();
@ -650,20 +584,8 @@ const onPaymentStageChange = (paymentStageId: string | number | undefined) => {
const saleMaterialSelectRef = ref<InstanceType<typeof SaleMaterialSelect>>(); const saleMaterialSelectRef = ref<InstanceType<typeof SaleMaterialSelect>>();
//
const contractDialog = reactive({ visible: false });
const contractLoading = ref(false);
const contractList = ref<ContractInfoVO[]>([]);
const contractTotal = ref(0);
const contractQueryParams = ref<ContractInfoQuery>({
pageNum: 1,
pageSize: 10,
contractStatus: '3',
activeFlag: '1'
});
const selectedContract = ref<ContractInfoVO | null>(null);
const contractDetailPreview = ref<ContractInfoVO | null>(null);
const selectedContractName = ref<string>(''); const selectedContractName = ref<string>('');
const contractSelectRef = ref<InstanceType<typeof ContractSelect>>();
const isEdit = computed(() => !!(routeParams.value.id && (routeParams.value.type === 'update' || routeParams.value.type === 'view' || routeParams.value.type === 'approval'))); const isEdit = computed(() => !!(routeParams.value.id && (routeParams.value.type === 'update' || routeParams.value.type === 'view' || routeParams.value.type === 'approval')));
const isFormDisabled = computed(() => routeParams.value.type === 'view' || routeParams.value.type === 'approval'); const isFormDisabled = computed(() => routeParams.value.type === 'view' || routeParams.value.type === 'approval');
@ -714,57 +636,15 @@ function goBack() {
/** 打开合同选择弹框 */ /** 打开合同选择弹框 */
function openContractSelect() { function openContractSelect() {
if (isFormDisabled.value) return; if (isFormDisabled.value) return;
contractDialog.visible = true; contractSelectRef.value?.open();
selectedContract.value = null;
contractDetailPreview.value = null;
contractQueryParams.value.pageNum = 1;
getContractList();
} }
/** 查询合同列表(仅已激活) */ function contractSelectCallBack(list: ContractInfoVO[]) {
async function getContractList() { const c = Array.isArray(list) && list.length > 0 ? list[0] : null;
contractLoading.value = true; if (!c?.contractId) return;
try { form.value.contractId = c.contractId as any;
const res = await listContractInfo(contractQueryParams.value); selectedContractName.value = c.contractName || '';
contractList.value = res.rows || []; onContractChange(c.contractId);
contractTotal.value = res.total || 0;
} finally {
contractLoading.value = false;
}
}
function resetContractQuery() {
contractQueryParams.value.contractCode = undefined;
contractQueryParams.value.contractName = undefined;
contractQueryParams.value.pageNum = 1;
getContractList();
}
/** 表格当前行变化:选中合同并加载详情 */
async function handleContractRowChange(row: ContractInfoVO | null) {
selectedContract.value = row || null;
contractDetailPreview.value = null;
if (row?.contractId) {
try {
const res = await getContractInfo(row.contractId);
contractDetailPreview.value = res.data || null;
} catch (e) {
console.error(e);
}
}
}
/** 确定选择合同 */
function submitContractSelect() {
if (selectedContract.value) {
const c = selectedContract.value;
form.value.contractId = c.contractId as any;
selectedContractName.value = c.contractName || '';
contractDialog.visible = false;
onContractChange(c.contractId);
} else {
proxy?.$modal.msgWarning('请先选择一条合同');
}
} }
async function onContractChange(contractId: string | number) { async function onContractChange(contractId: string | number) {
@ -1061,7 +941,7 @@ function buildPayload(changeStatus: '1' | '2'): ContractChangeSaveForm {
return payload; return payload;
} }
/** 审批栏:提交(暂存 / 提交审批) */ /** 审批栏:提交(暂存 / 提交审批),仿照合同 contractInfo/edit 提交流程,提交审批走新接口 contractChangeSubmitAndFlowStart */
function submitForm(status: string, _mode: boolean) { function submitForm(status: string, _mode: boolean) {
formRef.value?.validate((valid: boolean) => { formRef.value?.validate((valid: boolean) => {
if (!valid) return; if (!valid) return;
@ -1071,35 +951,28 @@ function submitForm(status: string, _mode: boolean) {
} }
buttonLoading.value = true; buttonLoading.value = true;
if (status !== 'draft') { if (status !== 'draft') {
// //
const payload = buildPayload('2'); const payload = buildPayload('2');
saveContractChange(payload) payload.flowCode = FlowCodeEnum.CONTRACT_CHANGE_CODE;
payload.flowStatus = 'waiting';
payload.variables = {
contractChangeId: form.value.contractChangeId,
changeCode: form.value.changeCode || '',
contractName: form.value.contractName,
changeReason: form.value.changeReason
};
payload.bizExt = {
businessTitle: '合同变更',
businessCode: form.value.changeCode || '合同变更'
};
contractChangeSubmitAndFlowStart(payload)
.then((res) => { .then((res) => {
const id = res.data; if (res.data) {
if (id) form.value.contractChangeId = id; form.value.contractChangeId = res.data.contractChangeId;
form.value.changeStatus = '2'; form.value.changeStatus = '2';
form.value.flowStatus = 'waiting'; form.value.flowStatus = res.data.flowStatus ?? 'waiting';
const variables = {
contractChangeId: id,
changeCode: form.value.changeCode || '',
contractName: form.value.contractName,
changeReason: form.value.changeReason
};
taskVariables.value = variables;
return startWorkFlow({
businessId: String(id),
flowCode: FlowCodeEnum.CONTRACT_CHANGE_CODE,
variables,
bizExt: { businessTitle: '合同变更', businessCode: form.value.changeCode || '合同变更' }
});
})
.then((resp) => {
if (resp?.data?.taskId && submitVerifyRef.value) {
return submitVerifyRef.value.openDialog(resp.data.taskId);
} }
}) proxy?.$modal.msgSuccess('操作成功');
.then(() => {
proxy?.$modal.msgSuccess('提交成功');
goBack(); goBack();
}) })
.finally(() => { .finally(() => {
@ -1149,19 +1022,4 @@ onMounted(async () => {
</script> </script>
<style scoped> <style scoped>
.contract-detail-panel {
border: 1px solid var(--el-border-color);
border-radius: 4px;
padding: 12px;
min-height: 200px;
}
.panel-title {
font-weight: 600;
margin-bottom: 8px;
}
.panel-placeholder {
color: var(--el-text-color-secondary);
font-size: 12px;
padding: 16px 0;
}
</style> </style>

@ -82,15 +82,12 @@
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" fixed="right" width="160" class-name="small-padding fixed-width"> <el-table-column label="操作" align="center" fixed="right" width="160" class-name="small-padding fixed-width">
<template #default="scope"> <template #default="scope">
<el-tooltip content="查看详情" placement="top"> <el-tooltip content="查看详情" placement="top" v-if="canViewDetail(scope.row)">
<el-button link type="info" icon="DocumentChecked" @click="handleView(scope.row)" v-hasPermi="['oa/erp:contractChange:query']"></el-button> <el-button link type="info" icon="DocumentChecked" @click="handleView(scope.row)" v-hasPermi="['oa/erp:contractChange:query']"></el-button>
</el-tooltip> </el-tooltip>
<el-tooltip content="修改" placement="top"> <el-tooltip content="修改" placement="top" v-if="!canViewDetail(scope.row)">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['oa/erp:contractChange:edit']"></el-button> <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['oa/erp:contractChange:edit']"></el-button>
</el-tooltip> </el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['oa/erp:contractChange:remove']"></el-button>
</el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -169,6 +166,12 @@ const handleAdd = () => {
}); });
}; };
/** 状态不为1草稿时可查看详情 */
const canViewDetail = (row: ContractChangeVO) => {
const status = row?.changeStatus;
return status != null && Number(status) !== 1;
};
/** 查看详情:使用菜单路由打开 */ /** 查看详情:使用菜单路由打开 */
const handleView = (row?: ContractChangeVO) => { const handleView = (row?: ContractChangeVO) => {
const _contractChangeId = row?.contractChangeId || ids.value[0]; const _contractChangeId = row?.contractChangeId || ids.value[0];

Loading…
Cancel
Save