|
|
|
|
@ -268,7 +268,12 @@
|
|
|
|
|
/>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="预计回款时间" width="220" align="center">
|
|
|
|
|
<el-table-column label="预计回款金额(元)" width="220" align="center">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<el-input :model-value="formatRepaymentAmount(scope.row.repaymentAmount)" disabled />
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="预计回款日期" width="220" align="center">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<el-date-picker
|
|
|
|
|
v-model="scope.row.repaymentTime"
|
|
|
|
|
@ -851,6 +856,8 @@ const data = reactive<{ form: ProjectInfoFormEx; rules: any }>({
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const { form, rules } = toRefs(data);
|
|
|
|
|
const REPAYMENT_RATE_TOTAL = 100;
|
|
|
|
|
const REPAYMENT_RATE_EPSILON = 0.01;
|
|
|
|
|
|
|
|
|
|
/** 是否禁用表单(查看/审批模式) */
|
|
|
|
|
const isFormDisabled = computed(() => routeParams.value.type === 'view' || routeParams.value.type === 'approval');
|
|
|
|
|
@ -906,10 +913,20 @@ function normalizePeopleId(value: string | string[] | number | undefined): strin
|
|
|
|
|
return Array.isArray(value) ? value.join(',') : value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 校验回款比例之和是否为 100% */
|
|
|
|
|
function isRepaymentRateTotalValid(): boolean {
|
|
|
|
|
const totalRate = planStageList.value.reduce((sum, item) => sum + toNumber(item.repaymentRate), 0);
|
|
|
|
|
return Math.abs(totalRate - REPAYMENT_RATE_TOTAL) < REPAYMENT_RATE_EPSILON;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 提交按钮:暂存走 save/update,正式提交走 submitContractOrderAndFlowStart,关联项目一并提交 */
|
|
|
|
|
const submitForm = (status: string, mode: boolean) => {
|
|
|
|
|
projectInfoFormRef.value?.validate(async (valid: boolean) => {
|
|
|
|
|
if (!valid) return;
|
|
|
|
|
if (!isRepaymentRateTotalValid()) {
|
|
|
|
|
proxy?.$modal.msgWarning('回款信息预计回款比例之和必须为100%');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!projectList.value?.length) {
|
|
|
|
|
proxy?.$modal.msgWarning('合同订单需新建项目或者关联已有项目!');
|
|
|
|
|
return;
|
|
|
|
|
@ -967,6 +984,25 @@ const loadSelectOptions = async () => {
|
|
|
|
|
await Promise.all([getUserList(), getDeptInfoListSelect(), getPaymentStageList(), getCustomerInfoListSelect()]);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 日期格式化为 YYYY-MM-DD */
|
|
|
|
|
const formatDateToYmd = (date: Date): string => {
|
|
|
|
|
const year = date.getFullYear();
|
|
|
|
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
|
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
|
|
|
return `${year}-${month}-${day}`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 计算预计回款日期:默认当天;若 paymentDeadline/payment_deadline 有值则当天 + 天数 */
|
|
|
|
|
const getDefaultRepaymentDate = (paymentMethod?: Record<string, any>): string => {
|
|
|
|
|
const deadline = paymentMethod?.paymentDeadline;
|
|
|
|
|
const days = toNumber(deadline);
|
|
|
|
|
const target = new Date();
|
|
|
|
|
if (days > 0) {
|
|
|
|
|
target.setDate(target.getDate() + Math.trunc(days));
|
|
|
|
|
}
|
|
|
|
|
return formatDateToYmd(target);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 根据路由 contractId 加载合同信息并回填表单;有项目则加载主项目+阶段+关联项目,无则从合同带出 */
|
|
|
|
|
const loadContractInfo = async () => {
|
|
|
|
|
isFromContentChange.value = false;
|
|
|
|
|
@ -1030,7 +1066,7 @@ const loadContractInfo = async () => {
|
|
|
|
|
collectionStage: stage.paymentStageId,
|
|
|
|
|
repaymentRate: pm.paymentPercentage ?? undefined,
|
|
|
|
|
repaymentAmount: undefined,
|
|
|
|
|
repaymentTime: undefined,
|
|
|
|
|
repaymentTime: getDefaultRepaymentDate(pm),
|
|
|
|
|
delayDay: undefined,
|
|
|
|
|
receivableDate: undefined,
|
|
|
|
|
reasonsExplanation: '',
|
|
|
|
|
@ -1118,6 +1154,42 @@ const approvalVerifyOpen = async () => {
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
const planStageList = ref<ErpProjectPlanStageForm[]>([]);
|
|
|
|
|
|
|
|
|
|
/** 将任意值转为数字;非法值返回 0 */
|
|
|
|
|
const toNumber = (value: unknown): number => {
|
|
|
|
|
const n = Number(value);
|
|
|
|
|
return Number.isFinite(n) ? n : 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 保留两位小数并避免浮点误差 */
|
|
|
|
|
const calcRepaymentAmount = (totalAmount: unknown, repaymentRate: unknown): number => {
|
|
|
|
|
const amount = toNumber(totalAmount);
|
|
|
|
|
const rate = toNumber(repaymentRate);
|
|
|
|
|
return Math.round(((amount * rate) / 100) * 100) / 100;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 表格展示格式化 */
|
|
|
|
|
const formatRepaymentAmount = (value: unknown): string => {
|
|
|
|
|
if (value === undefined || value === null || value === '') return '';
|
|
|
|
|
const n = Number(value);
|
|
|
|
|
return Number.isFinite(n) ? n.toFixed(2) : '';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 根据合同总价和预计回款比例,实时刷新每行预计回款金额 */
|
|
|
|
|
const recalculateRepaymentAmounts = () => {
|
|
|
|
|
const totalAmount = form.value.amount;
|
|
|
|
|
planStageList.value.forEach((item) => {
|
|
|
|
|
item.repaymentAmount = calcRepaymentAmount(totalAmount, item.repaymentRate) as any;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
[() => form.value.amount, () => planStageList.value.map((item) => item.repaymentRate)],
|
|
|
|
|
() => {
|
|
|
|
|
recalculateRepaymentAmounts();
|
|
|
|
|
},
|
|
|
|
|
{ deep: true }
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const getPlanStageList = async () => {
|
|
|
|
|
if (!form.value.projectId) {
|
|
|
|
|
planStageList.value = [];
|
|
|
|
|
@ -1145,6 +1217,7 @@ const getPlanStageList = async () => {
|
|
|
|
|
sortOrder: item.sortOrder || planStageList.value.length + 1,
|
|
|
|
|
activeFlag: item.activeFlag || '1'
|
|
|
|
|
}));
|
|
|
|
|
recalculateRepaymentAmounts();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('查询项目阶段计划失败:', error);
|
|
|
|
|
planStageList.value = [];
|
|
|
|
|
|