|
|
|
|
@ -0,0 +1,604 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div class="p-2">
|
|
|
|
|
<el-card shadow="never" style="margin-top: 0">
|
|
|
|
|
<approvalButton
|
|
|
|
|
@submitForm="submitForm"
|
|
|
|
|
@approvalVerifyOpen="approvalVerifyOpen"
|
|
|
|
|
@handleApprovalRecord="handleApprovalRecord"
|
|
|
|
|
:buttonLoading="buttonLoading"
|
|
|
|
|
:id="form.accountInstallmentId"
|
|
|
|
|
:status="form.flowStatus"
|
|
|
|
|
:pageType="routeParams.type"
|
|
|
|
|
:mode="false"
|
|
|
|
|
/>
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
<el-card shadow="never" style="margin-top: 0">
|
|
|
|
|
<template #header>
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
<div style="text-align: left; font-weight: bold; font-size: 24px">{{ pageTitle }}</div>
|
|
|
|
|
<el-button v-if="canAllocate" type="primary" @click="handleAddDetail">
|
|
|
|
|
<el-icon class="mr-1"><Plus /></el-icon>
|
|
|
|
|
新增分款
|
|
|
|
|
</el-button>
|
|
|
|
|
<el-tag v-else-if="routeParams.type === 'allocate'" type="info">{{ allocateHintText }}</el-tag>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<el-descriptions :column="3" border>
|
|
|
|
|
<el-descriptions-item label="回款编号">{{ form.installmentCode }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="客户名称">{{ form.customerName }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="回款日期">{{ parseTime(form.paymentDate, '{y}-{m}-{d}') }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="回款金额">
|
|
|
|
|
<span class="font-medium text-blue-600">¥{{ formatMoney(form.paymentAmount) }}</span>
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="已分款金额">
|
|
|
|
|
<span class="font-medium text-orange-600">¥{{ formatMoney(allocatedAmount) }}</span>
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="剩余可分">
|
|
|
|
|
<span :class="remainingAmount < 0 ? 'text-red-600' : 'text-green-600'" class="font-medium">
|
|
|
|
|
¥{{ formatMoney(remainingAmount) }}
|
|
|
|
|
</span>
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="客户经理">{{ form.managerNickNames || '-' }}</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="流程状态">
|
|
|
|
|
<dict-tag :options="flow_status" :value="form.flowStatus" />
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="分款进度">
|
|
|
|
|
<dict-tag :options="installment_status" :value="form.installmentStatus" />
|
|
|
|
|
</el-descriptions-item>
|
|
|
|
|
<el-descriptions-item label="备注" :span="3">{{ form.remark || '-' }}</el-descriptions-item>
|
|
|
|
|
</el-descriptions>
|
|
|
|
|
|
|
|
|
|
<el-alert
|
|
|
|
|
v-if="remainingAmount !== 0"
|
|
|
|
|
:title="remainingAmount < 0 ? '分款金额超出回款金额' : '分款金额不足回款金额'"
|
|
|
|
|
type="warning"
|
|
|
|
|
:closable="false"
|
|
|
|
|
class="mt-3"
|
|
|
|
|
show-icon
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<el-table v-loading="detailLoading" :data="detailList" border size="small" class="mt-3">
|
|
|
|
|
<el-table-column label="项目编号" align="center" prop="projectCode" width="120" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="项目名称" align="center" prop="projectName" min-width="140" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="合同编号" align="center" prop="contractCode" width="120" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="合同名称" align="center" prop="contractName" min-width="140" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="分款阶段" align="center" prop="stageName" width="100" />
|
|
|
|
|
<el-table-column label="分款金额" align="center" width="110">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<span class="font-medium">¥{{ formatMoney(row.detailAmount) }}</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="备注" align="center" prop="remark" show-overflow-tooltip />
|
|
|
|
|
<el-table-column v-if="canAllocate" label="操作" align="center" width="100" fixed="right">
|
|
|
|
|
<template #default="{ row }">
|
|
|
|
|
<el-button link type="primary" size="small" @click="handleEditDetail(row)">编辑</el-button>
|
|
|
|
|
<el-button link type="danger" size="small" @click="handleDeleteDetail(row)">删除</el-button>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
</el-table>
|
|
|
|
|
<el-empty
|
|
|
|
|
v-if="!detailLoading && !detailList.length"
|
|
|
|
|
:description="canAllocate ? '暂无分款明细,请点击新增分款添加' : '暂无分款明细'"
|
|
|
|
|
class="mt-3"
|
|
|
|
|
/>
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
<ProjectSelectDialog v-model:visible="showProjectDialog" @project-selected="handleProjectSelected" />
|
|
|
|
|
<ContractSelectDialog v-model:visible="showContractDialog" @contract-selected="handleContractSelected" />
|
|
|
|
|
|
|
|
|
|
<el-dialog :title="detailDialog.title" v-model="detailDialog.visible" width="560px" append-to-body>
|
|
|
|
|
<el-form ref="detailFormRef" :model="detailForm" :rules="detailRules" label-width="120px">
|
|
|
|
|
<el-form-item label="项目编号">
|
|
|
|
|
<el-input v-model="detailForm.projectCode" disabled />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="项目名称">
|
|
|
|
|
<el-input v-model="detailForm.projectName" disabled />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="合同编号">
|
|
|
|
|
<el-input v-model="detailForm.contractCode" disabled />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="合同名称">
|
|
|
|
|
<el-input v-model="detailForm.contractName" disabled />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="合同额">
|
|
|
|
|
<span class="font-medium text-gray-800">{{ contractTotalPriceDisplay }}</span>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="分款节点" prop="paymentStageId">
|
|
|
|
|
<div style="display: flex; flex-flow: row nowrap; align-items: center; gap: 12px; width: 100%; min-width: 0; overflow-x: auto">
|
|
|
|
|
<el-select v-model="detailForm.paymentStageId" placeholder="请选择分款节点" style="width: 150px; flex-shrink: 0">
|
|
|
|
|
<el-option v-for="stage in paymentStageList" :key="stage.paymentStageId" :label="stage.stageName" :value="stage.paymentStageId" />
|
|
|
|
|
</el-select>
|
|
|
|
|
<template v-if="detailForm.paymentStageId">
|
|
|
|
|
<span style="white-space: nowrap; flex-shrink: 0">
|
|
|
|
|
回款比例 <span>{{ selectedStagePaymentRatioText }}</span>
|
|
|
|
|
</span>
|
|
|
|
|
<span style="white-space: nowrap; flex-shrink: 0">
|
|
|
|
|
金额 <span>¥{{ formatMoneyOrDash(selectedStageContractShareAmount) }}</span>
|
|
|
|
|
</span>
|
|
|
|
|
</template>
|
|
|
|
|
</div>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="分款金额" prop="detailAmount">
|
|
|
|
|
<el-input-number
|
|
|
|
|
v-model="detailForm.detailAmount"
|
|
|
|
|
:precision="2"
|
|
|
|
|
:min="0.01"
|
|
|
|
|
:max="999999999"
|
|
|
|
|
placeholder="请输入分款金额"
|
|
|
|
|
style="width: 100%"
|
|
|
|
|
controls-position="right"
|
|
|
|
|
/>
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-form-item label="备注">
|
|
|
|
|
<el-input v-model="detailForm.remark" type="textarea" :rows="2" placeholder="请输入备注" />
|
|
|
|
|
</el-form-item>
|
|
|
|
|
<el-alert
|
|
|
|
|
v-if="formRemainingAmount !== 0"
|
|
|
|
|
:title="formRemainingAmount < 0 ? '分款金额超出回款金额' : '分款金额不足回款金额'"
|
|
|
|
|
:type="formRemainingAmount < 0 ? 'error' : 'warning'"
|
|
|
|
|
:closable="false"
|
|
|
|
|
show-icon
|
|
|
|
|
class="mb-2"
|
|
|
|
|
>
|
|
|
|
|
<template #default>
|
|
|
|
|
<span class="font-medium">
|
|
|
|
|
{{ formRemainingAmount < 0 ? '已超出' : '还差' }} ¥{{ formatMoney(Math.abs(formRemainingAmount)) }},分款总金额不能超出回款金额
|
|
|
|
|
</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-alert>
|
|
|
|
|
</el-form>
|
|
|
|
|
<template #footer>
|
|
|
|
|
<div class="dialog-footer">
|
|
|
|
|
<el-button :loading="detailButtonLoading" type="primary" @click="submitDetailForm">确 定</el-button>
|
|
|
|
|
<el-button @click="cancelDetail">取 消</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
|
|
|
|
|
<submitVerify ref="submitVerifyRef" :task-variables="taskVariables" @submit-callback="submitCallback" />
|
|
|
|
|
<approvalRecord ref="approvalRecordRef" />
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup name="FinAccountInstallmentEdit" lang="ts">
|
|
|
|
|
import { Plus } from '@element-plus/icons-vue';
|
|
|
|
|
import { getFinAccountInstallment } from '@/api/oa/erp/finAccountInstallment';
|
|
|
|
|
import {
|
|
|
|
|
addFinAccountInstallmentDetail,
|
|
|
|
|
getErpFinAccountInstallmentDetailList,
|
|
|
|
|
updateFinAccountInstallmentDetail,
|
|
|
|
|
deleteInstallmentDetailByBo
|
|
|
|
|
} from '@/api/oa/erp/finAccountInstallmentDetail';
|
|
|
|
|
import { FinAccountInstallmentForm } from '@/api/oa/erp/finAccountInstallment/types';
|
|
|
|
|
import { FinAccountInstallmentDetailForm, FinAccountInstallmentDetailVO } from '@/api/oa/erp/finAccountInstallmentDetail/types';
|
|
|
|
|
import { getErpContractPaymentMethodJoinList } from '@/api/oa/erp/finAccountReceivable';
|
|
|
|
|
import ProjectSelectDialog from '@/views/oa/components/ProjectSelectDialog.vue';
|
|
|
|
|
import ContractSelectDialog from '@/views/oa/components/ContractSelectDialog.vue';
|
|
|
|
|
import ApprovalButton from '@/components/Process/approvalButton.vue';
|
|
|
|
|
import SubmitVerify from '@/components/Process/submitVerify.vue';
|
|
|
|
|
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
|
|
|
|
|
import { parseTime } from '@/utils/ruoyi';
|
|
|
|
|
|
|
|
|
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
|
|
const route = useRoute();
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
|
|
|
|
const { installment_status, flow_status } = toRefs<any>(proxy?.useDict('installment_status', 'flow_status'));
|
|
|
|
|
|
|
|
|
|
const routeParams = ref<Record<string, any>>({});
|
|
|
|
|
const buttonLoading = ref(false);
|
|
|
|
|
const detailLoading = ref(false);
|
|
|
|
|
const detailList = ref<FinAccountInstallmentDetailVO[]>([]);
|
|
|
|
|
|
|
|
|
|
const submitVerifyRef = ref<InstanceType<typeof SubmitVerify>>();
|
|
|
|
|
const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
|
|
|
|
|
|
|
|
|
|
const taskVariables = ref<Record<string, any>>({});
|
|
|
|
|
|
|
|
|
|
const FLOW_STATUS = { DRAFT: 'draft', WAITING: 'waiting', FINISH: 'finish', BACK: 'back', CANCEL: 'cancel' };
|
|
|
|
|
const INSTALLMENT_STATUS = { NOT_ALLOCATED: '0', DISPATCHED: '1', COMPLETE: '2' };
|
|
|
|
|
|
|
|
|
|
const isFlowEditable = (flowStatus?: string) =>
|
|
|
|
|
!flowStatus || flowStatus === FLOW_STATUS.DRAFT || flowStatus === FLOW_STATUS.BACK || flowStatus === FLOW_STATUS.CANCEL;
|
|
|
|
|
|
|
|
|
|
const initFormData: FinAccountInstallmentForm = {
|
|
|
|
|
accountInstallmentId: undefined,
|
|
|
|
|
installmentCode: undefined,
|
|
|
|
|
customerName: undefined,
|
|
|
|
|
paymentAmount: undefined,
|
|
|
|
|
paymentDate: undefined,
|
|
|
|
|
currency: 'CNY',
|
|
|
|
|
remark: undefined,
|
|
|
|
|
flowStatus: undefined,
|
|
|
|
|
installmentStatus: undefined,
|
|
|
|
|
accountManagerIds: undefined,
|
|
|
|
|
managerUserIds: [],
|
|
|
|
|
managerNickNames: undefined,
|
|
|
|
|
canAllocate: undefined
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const form = ref<FinAccountInstallmentForm>({ ...initFormData });
|
|
|
|
|
|
|
|
|
|
const pageTitle = computed(() => {
|
|
|
|
|
const t = routeParams.value.type;
|
|
|
|
|
if (t === 'view') return '分款 - 查看';
|
|
|
|
|
if (t === 'approval') return '分款审核';
|
|
|
|
|
return '客户分款';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/** 客户经理分款:查看模式只读,审批/分款模式在 canAllocate 时可维护明细 */
|
|
|
|
|
const canAllocate = computed(() => form.value.canAllocate === true && routeParams.value.type !== 'view');
|
|
|
|
|
|
|
|
|
|
const allocateHintText = computed(() => {
|
|
|
|
|
if (form.value.canAllocate) return '';
|
|
|
|
|
if (isFlowEditable(form.value.flowStatus) && form.value.installmentStatus === INSTALLMENT_STATUS.NOT_ALLOCATED) {
|
|
|
|
|
return '请先由财务派发给客户经理';
|
|
|
|
|
}
|
|
|
|
|
if (form.value.flowStatus === FLOW_STATUS.FINISH || form.value.installmentStatus === INSTALLMENT_STATUS.COMPLETE) {
|
|
|
|
|
return '分款已完成';
|
|
|
|
|
}
|
|
|
|
|
if (form.value.flowStatus !== FLOW_STATUS.WAITING) return '流程未处于待处理状态';
|
|
|
|
|
return '您无权操作该回款分款';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const allocatedAmount = computed(() =>
|
|
|
|
|
Number((detailList.value || []).reduce((sum, item) => sum + Number(item.detailAmount || 0), 0))
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const remainingAmount = computed(() => Number(form.value.paymentAmount || 0) - allocatedAmount.value);
|
|
|
|
|
|
|
|
|
|
const formRemainingAmount = computed(() => {
|
|
|
|
|
const currentDetailAmount = Number(detailForm.detailAmount) || 0;
|
|
|
|
|
const existingTotal = Number(
|
|
|
|
|
(detailList.value || []).reduce((sum, item) => {
|
|
|
|
|
if (detailForm.installmentDetailId && item.installmentDetailId === detailForm.installmentDetailId) {
|
|
|
|
|
return sum;
|
|
|
|
|
}
|
|
|
|
|
return sum + Number(item.detailAmount || 0);
|
|
|
|
|
}, 0)
|
|
|
|
|
);
|
|
|
|
|
return Number(form.value.paymentAmount || 0) - existingTotal - currentDetailAmount;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const showProjectDialog = ref(false);
|
|
|
|
|
const showContractDialog = ref(false);
|
|
|
|
|
const selectedProject = ref<any>(null);
|
|
|
|
|
|
|
|
|
|
interface ContractPaymentStageJoinRow {
|
|
|
|
|
paymentStageId: string | number;
|
|
|
|
|
stageName?: string;
|
|
|
|
|
paymentPercentage?: number | string;
|
|
|
|
|
paymentAmount?: number | string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const paymentStageList = ref<ContractPaymentStageJoinRow[]>([]);
|
|
|
|
|
const detailFormContractTotalPrice = ref<number | undefined>(undefined);
|
|
|
|
|
|
|
|
|
|
const formatMoney = (val: number | string | undefined | null) => {
|
|
|
|
|
if (val === undefined || val === null) return '0.00';
|
|
|
|
|
const n = Number(val || 0);
|
|
|
|
|
return n.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const formatMoneyOrDash = (money: number | undefined | null) => {
|
|
|
|
|
if (money === undefined || money === null || Number.isNaN(Number(money))) return '—';
|
|
|
|
|
return formatMoney(money);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const contractTotalPriceDisplay = computed(() => {
|
|
|
|
|
const v = detailFormContractTotalPrice.value;
|
|
|
|
|
if (v === undefined || v === null || Number.isNaN(Number(v))) return '—';
|
|
|
|
|
return `¥${formatMoney(v)}`;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const selectedPaymentStageRow = computed(() => {
|
|
|
|
|
const id = detailForm.paymentStageId;
|
|
|
|
|
if (id == null || id === '') return null;
|
|
|
|
|
return paymentStageList.value.find((s) => String(s.paymentStageId) === String(id)) ?? null;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const selectedStagePaymentRatioText = computed(() => {
|
|
|
|
|
const row = selectedPaymentStageRow.value;
|
|
|
|
|
if (row && row.paymentPercentage !== undefined && row.paymentPercentage !== null && row.paymentPercentage !== '') {
|
|
|
|
|
const n = Number(row.paymentPercentage);
|
|
|
|
|
if (!Number.isNaN(n)) return `${n}%`;
|
|
|
|
|
}
|
|
|
|
|
const p = detailForm.contractPaymentPercentage;
|
|
|
|
|
if (p !== undefined && p !== null && !Number.isNaN(Number(p))) {
|
|
|
|
|
const n = Number(p);
|
|
|
|
|
if (!Number.isNaN(n)) return `${n}%`;
|
|
|
|
|
}
|
|
|
|
|
return '—';
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const selectedStageContractShareAmount = computed(() => {
|
|
|
|
|
const row = selectedPaymentStageRow.value;
|
|
|
|
|
if (row) {
|
|
|
|
|
const backend = row.paymentAmount !== undefined && row.paymentAmount !== null && row.paymentAmount !== '' ? Number(row.paymentAmount) : NaN;
|
|
|
|
|
if (!Number.isNaN(backend)) return backend;
|
|
|
|
|
const total = Number(detailFormContractTotalPrice.value);
|
|
|
|
|
const pct = Number(row.paymentPercentage);
|
|
|
|
|
if (!Number.isNaN(total) && !Number.isNaN(pct)) return (total * pct) / 100;
|
|
|
|
|
}
|
|
|
|
|
const dAmt = detailForm.contractPaymentAmount;
|
|
|
|
|
if (dAmt !== undefined && dAmt !== null && !Number.isNaN(Number(dAmt))) {
|
|
|
|
|
return Number(dAmt);
|
|
|
|
|
}
|
|
|
|
|
const total = Number(detailFormContractTotalPrice.value);
|
|
|
|
|
const pct = Number(detailForm.contractPaymentPercentage);
|
|
|
|
|
if (!Number.isNaN(total) && !Number.isNaN(pct)) return (total * pct) / 100;
|
|
|
|
|
return undefined;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const getPaymentStages = async (contractId: string | number | undefined) => {
|
|
|
|
|
paymentStageList.value = [];
|
|
|
|
|
if (contractId && contractId !== '') {
|
|
|
|
|
const res = await getErpContractPaymentMethodJoinList({ contractId: contractId });
|
|
|
|
|
paymentStageList.value = (res.data || []) as ContractPaymentStageJoinRow[];
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const detailDialog = reactive({
|
|
|
|
|
visible: false,
|
|
|
|
|
title: '新增分款明细'
|
|
|
|
|
});
|
|
|
|
|
const detailButtonLoading = ref(false);
|
|
|
|
|
const detailFormRef = ref<ElFormInstance>();
|
|
|
|
|
const initDetailForm: FinAccountInstallmentDetailForm = {
|
|
|
|
|
installmentDetailId: undefined,
|
|
|
|
|
accountInstallmentId: undefined,
|
|
|
|
|
projectId: undefined,
|
|
|
|
|
projectCode: undefined,
|
|
|
|
|
projectName: undefined,
|
|
|
|
|
contractId: undefined,
|
|
|
|
|
contractCode: undefined,
|
|
|
|
|
contractName: undefined,
|
|
|
|
|
paymentStageId: undefined,
|
|
|
|
|
detailAmount: undefined,
|
|
|
|
|
installmentStatus: undefined,
|
|
|
|
|
remark: undefined,
|
|
|
|
|
contractTotalPrice: undefined,
|
|
|
|
|
contractPaymentPercentage: undefined,
|
|
|
|
|
contractPaymentAmount: undefined
|
|
|
|
|
};
|
|
|
|
|
const detailForm = reactive<FinAccountInstallmentDetailForm>({ ...initDetailForm });
|
|
|
|
|
|
|
|
|
|
function validateDetailAmountNotExceedNode(_rule: unknown, value: unknown, callback: (e?: Error) => void) {
|
|
|
|
|
const max = selectedStageContractShareAmount.value;
|
|
|
|
|
if (max == null || Number.isNaN(Number(max))) {
|
|
|
|
|
callback();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const maxN = Number(max);
|
|
|
|
|
const v = Number(value);
|
|
|
|
|
if (value === undefined || value === null || value === '' || Number.isNaN(v)) {
|
|
|
|
|
callback();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const vCents = Math.round(v * 100);
|
|
|
|
|
const maxCents = Math.round(maxN * 100);
|
|
|
|
|
if (vCents > maxCents) {
|
|
|
|
|
callback(new Error(`分款金额不能大于节点约定金额(¥${formatMoney(maxN)})`));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
callback();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const detailRules = {
|
|
|
|
|
paymentStageId: [{ required: true, message: '请选择分款节点', trigger: 'change' }],
|
|
|
|
|
detailAmount: [
|
|
|
|
|
{ required: true, message: '请输入分款金额', trigger: 'blur' },
|
|
|
|
|
{ validator: validateDetailAmountNotExceedNode, trigger: ['blur', 'change'] }
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const syncTaskVariables = () => {
|
|
|
|
|
if (form.value.accountManagerIds) {
|
|
|
|
|
taskVariables.value.accountManagerId = form.value.accountManagerIds;
|
|
|
|
|
} else if (form.value.managerUserIds?.length) {
|
|
|
|
|
taskVariables.value.accountManagerId = form.value.managerUserIds.map((id) => String(id)).join(',');
|
|
|
|
|
} else {
|
|
|
|
|
taskVariables.value.accountManagerId = '';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const loadDetailList = async () => {
|
|
|
|
|
if (!form.value.accountInstallmentId) return;
|
|
|
|
|
detailLoading.value = true;
|
|
|
|
|
try {
|
|
|
|
|
const res = await getErpFinAccountInstallmentDetailList({
|
|
|
|
|
accountInstallmentId: form.value.accountInstallmentId
|
|
|
|
|
});
|
|
|
|
|
detailList.value = res.data || [];
|
|
|
|
|
} finally {
|
|
|
|
|
detailLoading.value = false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const refreshInstallment = async () => {
|
|
|
|
|
if (!form.value.accountInstallmentId) return;
|
|
|
|
|
const res = await getFinAccountInstallment(form.value.accountInstallmentId);
|
|
|
|
|
Object.assign(form.value, res.data);
|
|
|
|
|
syncTaskVariables();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const loadData = async (id: string | number) => {
|
|
|
|
|
proxy?.$modal.loading('正在加载数据,请稍后...');
|
|
|
|
|
try {
|
|
|
|
|
const res = await getFinAccountInstallment(id);
|
|
|
|
|
Object.assign(form.value, res.data);
|
|
|
|
|
syncTaskVariables();
|
|
|
|
|
await loadDetailList();
|
|
|
|
|
} finally {
|
|
|
|
|
proxy?.$modal.closeLoading();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleAddDetail = () => {
|
|
|
|
|
if (!form.value.accountInstallmentId) {
|
|
|
|
|
proxy?.$modal.msgWarning('回款数据未加载');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!canAllocate.value) {
|
|
|
|
|
proxy?.$modal.msgWarning(allocateHintText.value || '当前不可分款');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
showProjectDialog.value = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleProjectSelected = (data: any) => {
|
|
|
|
|
selectedProject.value = data.project;
|
|
|
|
|
if (data.project.contractFlag !== '1') {
|
|
|
|
|
showContractDialog.value = true;
|
|
|
|
|
} else {
|
|
|
|
|
fillDetailForm(data.project, data.contract);
|
|
|
|
|
getPaymentStages(data.contract.contractId);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleContractSelected = (contract: any) => {
|
|
|
|
|
if (selectedProject.value) {
|
|
|
|
|
fillDetailForm(selectedProject.value, contract);
|
|
|
|
|
getPaymentStages(contract.contractId);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const fillDetailForm = (project: any, contract: any) => {
|
|
|
|
|
const totalPriceRaw = contract?.totalPrice;
|
|
|
|
|
detailFormContractTotalPrice.value =
|
|
|
|
|
totalPriceRaw !== undefined && totalPriceRaw !== null && totalPriceRaw !== '' ? Number(totalPriceRaw) : undefined;
|
|
|
|
|
Object.assign(detailForm, {
|
|
|
|
|
installmentDetailId: undefined,
|
|
|
|
|
accountInstallmentId: form.value.accountInstallmentId,
|
|
|
|
|
projectId: project.projectId || project.id,
|
|
|
|
|
projectCode: project.projectCode,
|
|
|
|
|
projectName: project.projectName,
|
|
|
|
|
contractId: contract?.contractId || contract?.id,
|
|
|
|
|
contractCode: contract?.contractCode || '',
|
|
|
|
|
contractName: contract?.contractName || '',
|
|
|
|
|
paymentStageId: undefined,
|
|
|
|
|
detailAmount: undefined,
|
|
|
|
|
remark: undefined,
|
|
|
|
|
contractTotalPrice: undefined,
|
|
|
|
|
contractPaymentPercentage: undefined,
|
|
|
|
|
contractPaymentAmount: undefined
|
|
|
|
|
});
|
|
|
|
|
detailDialog.title = '新增分款明细';
|
|
|
|
|
detailDialog.visible = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const syncAddDetailAmountFromSelectedStage = () => {
|
|
|
|
|
if (!detailDialog.visible || detailForm.installmentDetailId) return;
|
|
|
|
|
let next: number | undefined;
|
|
|
|
|
if (detailForm.paymentStageId != null && detailForm.paymentStageId !== '') {
|
|
|
|
|
const raw = selectedStageContractShareAmount.value;
|
|
|
|
|
const n = raw != null && !Number.isNaN(Number(raw)) ? Number(raw) : NaN;
|
|
|
|
|
if (!Number.isNaN(n) && n > 0) {
|
|
|
|
|
const rounded = Math.round(n * 100) / 100;
|
|
|
|
|
next = rounded < 0.01 ? 0.01 : rounded;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
detailForm.detailAmount = next;
|
|
|
|
|
if (detailFormRef.value) {
|
|
|
|
|
nextTick(() => detailFormRef.value?.validateField('detailAmount'));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleEditDetail = async (row: FinAccountInstallmentDetailVO) => {
|
|
|
|
|
Object.assign(detailForm, row);
|
|
|
|
|
detailDialog.title = '编辑分款明细';
|
|
|
|
|
detailDialog.visible = true;
|
|
|
|
|
const tp = row.contractTotalPrice;
|
|
|
|
|
detailFormContractTotalPrice.value =
|
|
|
|
|
tp !== undefined && tp !== null && !Number.isNaN(Number(tp)) ? Number(tp) : undefined;
|
|
|
|
|
await getPaymentStages(detailForm.contractId);
|
|
|
|
|
nextTick(() => detailFormRef.value?.validateField('detailAmount'));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleDeleteDetail = async (row: FinAccountInstallmentDetailVO) => {
|
|
|
|
|
await proxy?.$modal.confirm('是否确认删除该分款明细?');
|
|
|
|
|
await deleteInstallmentDetailByBo({
|
|
|
|
|
installmentDetailId: row.installmentDetailId,
|
|
|
|
|
accountInstallmentId: form.value.accountInstallmentId!
|
|
|
|
|
});
|
|
|
|
|
proxy?.$modal.msgSuccess('删除成功');
|
|
|
|
|
await loadDetailList();
|
|
|
|
|
await refreshInstallment();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const submitDetailForm = async () => {
|
|
|
|
|
const valid = await detailFormRef.value?.validate();
|
|
|
|
|
if (!valid) return;
|
|
|
|
|
detailButtonLoading.value = true;
|
|
|
|
|
try {
|
|
|
|
|
if (detailForm.installmentDetailId) {
|
|
|
|
|
await updateFinAccountInstallmentDetail(detailForm);
|
|
|
|
|
proxy?.$modal.msgSuccess('修改成功');
|
|
|
|
|
} else {
|
|
|
|
|
await addFinAccountInstallmentDetail(detailForm);
|
|
|
|
|
proxy?.$modal.msgSuccess('新增成功');
|
|
|
|
|
}
|
|
|
|
|
detailDialog.visible = false;
|
|
|
|
|
await loadDetailList();
|
|
|
|
|
await refreshInstallment();
|
|
|
|
|
} finally {
|
|
|
|
|
detailButtonLoading.value = false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const cancelDetail = () => {
|
|
|
|
|
detailDialog.visible = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
() => detailDialog.visible,
|
|
|
|
|
(val) => {
|
|
|
|
|
if (!val) {
|
|
|
|
|
Object.assign(detailForm, initDetailForm);
|
|
|
|
|
detailFormContractTotalPrice.value = undefined;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
() => [
|
|
|
|
|
detailDialog.visible,
|
|
|
|
|
detailForm.installmentDetailId,
|
|
|
|
|
detailForm.paymentStageId,
|
|
|
|
|
paymentStageList.value,
|
|
|
|
|
detailFormContractTotalPrice.value
|
|
|
|
|
],
|
|
|
|
|
() => {
|
|
|
|
|
syncAddDetailAmountFromSelectedStage();
|
|
|
|
|
},
|
|
|
|
|
{ flush: 'post' }
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
routeParams.value = route.query;
|
|
|
|
|
const id = routeParams.value.id as string | number;
|
|
|
|
|
if (id) {
|
|
|
|
|
loadData(id);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const submitForm = () => {
|
|
|
|
|
proxy?.$modal.msgWarning('分款审核由财务派发后自动发起,无需在此提交');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleApprovalRecord = () => {
|
|
|
|
|
approvalRecordRef.value?.init(form.value.accountInstallmentId);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const approvalVerifyOpen = async () => {
|
|
|
|
|
syncTaskVariables();
|
|
|
|
|
await submitVerifyRef.value?.openDialog(routeParams.value.taskId);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const submitCallback = () => {
|
|
|
|
|
proxy?.$tab.closePage(route);
|
|
|
|
|
router.go(-1);
|
|
|
|
|
};
|
|
|
|
|
</script>
|