add 项目利润结算文档生成

pull/853/head
wersd 2025-09-25 22:21:13 +08:00
parent f08f0df6b6
commit 563db197b3
11 changed files with 162 additions and 48 deletions

View File

@ -69,3 +69,13 @@ export const exportProjectSettlement = async (params: any) => {
export const exportProjectSettlementDetail = async (params: any) => { export const exportProjectSettlementDetail = async (params: any) => {
return await request.download({ url: `/crm/receivable/export-settlement-excel`, params }) return await request.download({ url: `/crm/receivable/export-settlement-excel`, params })
} }
// 导出项目利润结算明细 DOCX
export const exportProjectSettlementDetailDocx = async (params: any) => {
return await request.download({ url: `/crm/project/settlement/export-docx`, params })
}
// 项目利润结算
export const settelmentProjectSettlementDetail = async (id: number) => {
return await request.post({ url: `/crm/project/settlement/generate?id=` + id })
}

View File

@ -10,6 +10,7 @@ export interface ProjectTransactionDetailVO {
userName: string // 投资人名称 userName: string // 投资人名称
investmentAmount: number // 投资金额 investmentAmount: number // 投资金额
saveAmount: number // 缴存金额 saveAmount: number // 缴存金额
remainAmount: number // 余额
} }

View File

@ -228,6 +228,8 @@ export enum DICT_TYPE {
CRM_FINANCE_INVOICE_TYPE = 'crm_finance_invoice_type', // CRM 票据发票类型 CRM_FINANCE_INVOICE_TYPE = 'crm_finance_invoice_type', // CRM 票据发票类型
CRM_FINANCE_INVOICE_AUXILIARY_TYPE = 'crm_finance_invoice_auxiliary_type', // CRM 票据辅助类型 CRM_FINANCE_INVOICE_AUXILIARY_TYPE = 'crm_finance_invoice_auxiliary_type', // CRM 票据辅助类型
CRM_FINANCE_INVOICE_WRITE_OFF_STATUS = 'crm_finance_invoice_write_off_status', // CRM 票据核销状态 CRM_FINANCE_INVOICE_WRITE_OFF_STATUS = 'crm_finance_invoice_write_off_status', // CRM 票据核销状态
CRM_PROJECT_SETTLEMENT_STATUS = 'crm_project_settlement_status', // CRM 项目结算状态
// ========== ERP - 企业资源计划模块 ========== // ========== ERP - 企业资源计划模块 ==========
ERP_AUDIT_STATUS = 'erp_audit_status', // ERP 审批状态 ERP_AUDIT_STATUS = 'erp_audit_status', // ERP 审批状态

View File

@ -153,6 +153,23 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row>
<el-col :span="12">
<el-form-item v-if="formData.type === 2 || formData.type === 3"
label="合作投资协议日期"
prop="investmentAgreementDate"
label-width="150px"
>
<el-date-picker
v-model="formData.investmentAgreementDate"
type="date"
placeholder="选择合作投资协议日期"
style="width: 100%"
popper-class="full-width-datepicker"
/>
</el-form-item>
</el-col>
</el-row>
<el-row> <el-row>
<el-col> <el-col>
<el-tabs v-if="formData.type === 2" v-model="subTabsName" class="-mt-15px mb-10px"> <el-tabs v-if="formData.type === 2" v-model="subTabsName" class="-mt-15px mb-10px">
@ -302,6 +319,7 @@ const formData = ref({
projectRecord: undefined, projectRecord: undefined,
powerSupplyReply: undefined, powerSupplyReply: undefined,
investmentAgreement: undefined, investmentAgreement: undefined,
investmentAgreementDate: undefined,
investmentShareAgreement: undefined, investmentShareAgreement: undefined,
supplementaryMaterials: [], supplementaryMaterials: [],
partners:[], partners:[],
@ -320,6 +338,7 @@ const formRules = computed(() => ({
cooperationAgreement: [{ required: true, message: '场地合同不能为空', trigger: 'blur' }], cooperationAgreement: [{ required: true, message: '场地合同不能为空', trigger: 'blur' }],
epcContractId: [{ required: true, message: 'EPC合同不能为空', trigger: 'blur' }], epcContractId: [{ required: true, message: 'EPC合同不能为空', trigger: 'blur' }],
purchaseContractId: [{ required: true, message: '设备合同不能为空', trigger: 'blur' }], purchaseContractId: [{ required: true, message: '设备合同不能为空', trigger: 'blur' }],
investmentAgreementDate: [{ required: true, message: '合作投资协议日期不能为空', trigger: 'blur' }],
})) }))
const formRef = ref() // Ref const formRef = ref() // Ref
const deptList = ref<DeptApi.DeptVO[]>([]) const deptList = ref<DeptApi.DeptVO[]>([])
@ -420,6 +439,7 @@ const resetForm = () => {
projectRecord: undefined, projectRecord: undefined,
powerSupplyReply: undefined, powerSupplyReply: undefined,
investmentAgreement: undefined, investmentAgreement: undefined,
investmentAgreementDate: undefined,
investmentShareAgreement: undefined, investmentShareAgreement: undefined,
supplementaryMaterials: [], supplementaryMaterials: [],
partners:[], partners:[],

View File

@ -14,7 +14,7 @@
<el-option <el-option
v-for="item in projectList" v-for="item in projectList"
:key="item.id" :key="item.id"
:label="item.deptName" :label="item.name"
:value="item.id" :value="item.id"
/> />
</el-select> </el-select>

View File

@ -11,7 +11,7 @@
<el-option <el-option
v-for="item in projectList" v-for="item in projectList"
:key="item.id" :key="item.id"
:label="item.deptName" :label="item.name"
:value="item.id" :value="item.id"
/> />
</el-select> </el-select>
@ -149,7 +149,7 @@ function updateChartOption() {
onMounted(async () => { onMounted(async () => {
// //
const list = await ProjectMonthlyStatApi.getProjectSimpleList() const list = await ProjectMonthlyStatApi.getProjectSimpleList()
projectList.value = [{ id: undefined, deptName: '全部' }, ...list] projectList.value = [{ id: undefined, name: '全部' }, ...list]
queryParams.projectIds = [] queryParams.projectIds = []
await getChartData() await getChartData()
}) })

View File

@ -126,8 +126,8 @@
:show-overflow-tooltip="true" :show-overflow-tooltip="true"
@sort-change="handleSortChange" @sort-change="handleSortChange"
> >
{{ SCOPE.row }}
<el-table-column label="项目名称" align="center" prop="name" fixed="left" min-width="200px"/> <el-table-column label="项目名称" align="center" prop="projectName" fixed="left" min-width="200px"/>
<el-table-column label="枪数" align="center" prop="estimatedPiles" width="100px"/> <el-table-column label="枪数" align="center" prop="estimatedPiles" width="100px"/>
<el-table-column label="统计月份" align="center" prop="statMonth" sortable="custom" width="160px"/> <el-table-column label="统计月份" align="center" prop="statMonth" sortable="custom" width="160px"/>
<el-table-column label="总充电量(度)" align="center" prop="chargingQuantity" sortable="custom" width="160px"/> <el-table-column label="总充电量(度)" align="center" prop="chargingQuantity" sortable="custom" width="160px"/>

View File

@ -9,6 +9,14 @@
> >
导出明细 导出明细
</el-button> </el-button>
<el-button
type="primary"
:loading="exportLoading"
@click="handleExportDocxDetailRow(settlement)"
v-hasPermi="['crm:project-settlement:export']"
>
导出文档
</el-button>
</ProjectSettlementDetailsHeader> </ProjectSettlementDetailsHeader>
<div class="settlement-detail-section"> <div class="settlement-detail-section">
@ -37,12 +45,45 @@
<el-table-column prop="remark" label="说明" /> <el-table-column prop="remark" label="说明" />
</el-table> </el-table>
<div>备注当期利润如有亏损则自动结转至下一季度支出项</div> <div>备注当期利润如有亏损则自动结转至下一季度支出项</div>
<h4 style="margin-top: 32px;">利润分配</h4>
<el-table :data="profitDistributionTable" border style="width: 100%; margin-bottom: 16px;">
<el-table-column prop="name" label="投资人" />
<el-table-column prop="ratio" label="出资比例" />
<el-table-column prop="shareAmount" label="可分配利润/元" />
<el-table-column prop="payableAmount" label="应付金额/元" />
<el-table-column prop="method" label="分配方式" />
</el-table>
<div style="margin-bottom: 12px;">合计100%</div>
<h4 style="margin-top: 32px;">往来款平账利润分配</h4>
<el-table :data="clearingDistributionTable" border style="width: 100%; margin-bottom: 16px;">
<el-table-column prop="name" label="投资人" />
<el-table-column prop="lastUnbalancedAmount" label="上期往来款未平账金额/元" />
<el-table-column prop="afterSettlementAmount" label="本期结算平账后金额/元" />
</el-table>
<div style="margin-bottom: 12px;">备注未平账期间利润分配通过甲方法定代表人个人向乙方支付,平账后按发票结算方式执行</div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import ProjectSettlementDetailsHeader from './ProjectSettlementDetailsHeader.vue' import ProjectSettlementDetailsHeader from './ProjectSettlementDetailsHeader.vue'
import { useTagsViewStore } from '@/store/modules/tagsView' import { useTagsViewStore } from '@/store/modules/tagsView'
import * as ProjectSettlementApi from '@/api/crm/project/settlement' import * as ProjectSettlementApi from '@/api/crm/project/settlement'
// partners
interface Partner {
userId: number;
userName?: string;
ratio: number;
shareAmount: number;
payableAmount: number;
lastUnbalancedAmount: number;
afterSettlementAmount: number;
}
interface ProjectSettlementWithPartners extends ProjectSettlementApi.ProjectSettlementVO {
partners?: Partner[];
}
import download from '@/utils/download' import download from '@/utils/download'
defineOptions({ name: 'CrmProjectSettlementDetail' }) defineOptions({ name: 'CrmProjectSettlementDetail' })
@ -52,7 +93,7 @@ const message = useMessage() // 消息弹窗
const exportLoading = ref(false) // const exportLoading = ref(false) //
const { delView } = useTagsViewStore() // const { delView } = useTagsViewStore() //
const { push, currentRoute } = useRouter() // const { push, currentRoute } = useRouter() //
const settlement = ref<ProjectSettlementApi.ProjectSettlementVO>({} as ProjectSettlementApi.ProjectSettlementVO) // const settlement = ref<ProjectSettlementWithPartners>({} as ProjectSettlementWithPartners) //
const close = () => { const close = () => {
delView(unref(currentRoute)) delView(unref(currentRoute))
@ -96,11 +137,25 @@ const handleExportDetailRow = async (row: ProjectSettlementApi.ProjectSettlement
return '' return ''
} }
const params = { const params = {
projectId: row.projectId, projectId: row.projectId,
returnTime: [formatDateTime(row.startDate), formatDateTime(row.endDate)] returnTime: [formatDateTime(row.startDate), formatDateTime(row.endDate)]
} }
const data = await ProjectSettlementApi.exportProjectSettlementDetail(params) const data = await ProjectSettlementApi.exportProjectSettlementDetail(params)
download.excel(data, `${row.projectName || '项目'}结算明细.xls`) download.excel(data, `${row.name || '项目'}结算明细.xls`)
} catch {
} finally {
exportLoading.value = false
}
}
/** 导出明细按钮操作 */
const handleExportDocxDetailRow = async (row: ProjectSettlementApi.ProjectSettlementVO) => {
try {
await message.exportConfirm()
exportLoading.value = true
const data = await ProjectSettlementApi.exportProjectSettlementDetailDocx(params)
download.word(data, `${row.name || '项目'}${row.period}期利润结算分配表.docx`)
} catch { } catch {
} finally { } finally {
exportLoading.value = false exportLoading.value = false
@ -113,10 +168,7 @@ const incomeTable = computed(() => [
{ name: '政府补贴收入', amount: settlement.value.governmentSubsidy, remark: '扣除应纳税额后的净补贴额' }, { name: '政府补贴收入', amount: settlement.value.governmentSubsidy, remark: '扣除应纳税额后的净补贴额' },
{ name: '其他收入', amount: settlement.value.otherIncome, remark: '如项目或设备转让、征用赔偿款等' }, { name: '其他收入', amount: settlement.value.otherIncome, remark: '如项目或设备转让、征用赔偿款等' },
]) ])
// // ...existing code...
const incomeRemarkTable = computed(() => [
{ name: settlement.value.incomeRemark ? settlement.value.incomeRemark : '无', remark: '其他需注明的内容' },
])
// //
const expenseTable = computed(() => [ const expenseTable = computed(() => [
{ name: '电费成本', amount: settlement.value.electricityCost, remark: '以电力公司账单为准' }, { name: '电费成本', amount: settlement.value.electricityCost, remark: '以电力公司账单为准' },
@ -134,8 +186,37 @@ const expenseTable = computed(() => [
// //
const profitTable = computed(() => [ const profitTable = computed(() => [
{ name: '当期利润/亏损', amount: settlement.value.grossProfit, remark: '收入合计-支出合计' }, { name: '当期利润/亏损', amount: settlement.value.grossProfit, remark: '收入合计-支出合计' },
{ name: '当期可分配利润', amount: settlement.value.shareAmount < 0 ? 0 : settlement.value.shareAmount, remark: '当期利润亏损时填0' }, { name: '当期可分配利润', amount: settlement.value.shareAmount && settlement.value.shareAmount < 0 ? 0 : (settlement.value.shareAmount ?? 0), remark: '当期利润亏损时填0' },
]) ])
const profitDistributionTable = computed(() => {
const partners = settlement.value.partners || [];
//
const totalPartnerRatio = partners.reduce((sum, p) => sum + (typeof p.ratio === 'number' ? p.ratio : Number(p.ratio) || 0), 0);
// = 100 -
const mainRatio = (100 - totalPartnerRatio).toFixed(2).replace(/\.00$/, '');
const table = [
{ name: '甲方', ratio: `${mainRatio}%`, shareAmount: '不参与分配', payableAmount: '——', method: '——' }
];
partners.forEach((p) => {
table.push({
name: p.userName ? `${p.userName}` : '',
ratio: p.ratio ? `${p.ratio}%` : '',
shareAmount: p.shareAmount?.toFixed(2) ?? '',
payableAmount: p.payableAmount?.toFixed(2) ?? '',
method: '往来款平账/发票结算'
});
});
return table;
});
const clearingDistributionTable = computed(() => {
const partners = settlement.value.partners || [];
return partners.map((p) => ({
name: p.userName ? `${p.userName}` : '',
lastUnbalancedAmount: p.lastUnbalancedAmount?.toFixed(2) ?? '',
afterSettlementAmount: p.afterSettlementAmount?.toFixed(2) ?? ''
}));
});
</script> </script>
<style scoped> <style scoped>

View File

@ -112,8 +112,22 @@
{{ scope.row.settlementDate ? formatDate(scope.row.settlementDate, 'YYYY-MM-DD') : '' }} {{ scope.row.settlementDate ? formatDate(scope.row.settlementDate, 'YYYY-MM-DD') : '' }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="结算状态" prop="settlementStatus" width="160">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CRM_PROJECT_SETTLEMENT_STATUS" :value="scope.row.settlementStatus" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="180px" fixed="right"> <el-table-column label="操作" align="center" width="180px" fixed="right">
<template #default="scope"> <template #default="scope">
<el-button
v-if="scope.row.settlementStatus === 0"
link
type="success"
@click="settelment(scope.row.id)"
v-hasPermi="['crm:project-settlement:update']"
>
结算
</el-button>
<el-button <el-button
link link
type="primary" type="primary"
@ -153,6 +167,7 @@ import * as ProjectSettlementApi from '@/api/crm/project/settlement'
import * as ProjectApi from '@/api/crm/project' import * as ProjectApi from '@/api/crm/project'
import ProjectSettlementForm from './ProjectSettlementForm.vue' import ProjectSettlementForm from './ProjectSettlementForm.vue'
import ElQuarterPicker from '@/components/ElQuarterPicker/index.vue' import ElQuarterPicker from '@/components/ElQuarterPicker/index.vue'
import { DICT_TYPE,getIntDictOptions } from '@/utils/dict'
// '2024-1' => '2024' // '2024-1' => '2024'
function formatQuarterPeriod(val?: string) { function formatQuarterPeriod(val?: string) {
@ -245,6 +260,18 @@ const handleExport = async () => {
} }
} }
//
const settelment = async (id: number) => {
try {
//
await message.confirm("是否发起结算?")
//
await ProjectSettlementApi.settelmentProjectSettlementDetail(id)
message.success(t('结算成功'))
//
await getList()
} catch {}
}
/** 跳转到项目结算明细页面 */ /** 跳转到项目结算明细页面 */
@ -262,4 +289,5 @@ onMounted(async () => {
// //
projectOptions.value = await ProjectApi.getProjectSimpleList(); projectOptions.value = await ProjectApi.getProjectSimpleList();
}) })
</script> </script>

View File

@ -62,13 +62,6 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
</el-row> </el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="缴存金额" prop="saveAmount">
<el-input v-model="formData.saveAmount" placeholder="请输入缴存金额" type="number" @input="onAmountInput('saveAmount')" />
</el-form-item>
</el-col>
</el-row>
</el-form> </el-form>
<template #footer> <template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button> <el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
@ -77,17 +70,7 @@
</Dialog> </Dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
const validateProjectRequired = (_: any, __: any, callback: any) => {
const userId = formData.value.userId;
const projectId = formData.value.projectId;
if (userId !== 1 && userId !== '1') {
if (projectId === undefined || projectId === '' || projectId === null) {
callback(new Error('请选择项目'));
return;
}
}
callback();
};
import * as ProjectTransactionDetailApi from '@/api/crm/project/transaction' import * as ProjectTransactionDetailApi from '@/api/crm/project/transaction'
import * as ProjectApi from '@/api/crm/project' import * as ProjectApi from '@/api/crm/project'
import * as UserApi from '@/api/system/user' import * as UserApi from '@/api/system/user'
@ -110,24 +93,12 @@ const formData = ref({
investmentAmount: undefined, investmentAmount: undefined,
saveAmount: undefined, saveAmount: undefined,
}) })
const validateAmountExclusive = (_: any, __: any, callback: any) => {
const investment = formData.value.investmentAmount;
const save = formData.value.saveAmount;
if ((investment === undefined || investment === '' || investment === null) && (save === undefined || save === '' || save === null)) {
callback(new Error('投资金额和缴存金额必须填写一个'));
} else if ((investment !== undefined && investment !== '' && investment !== null) && (save !== undefined && save !== '' && save !== null)) {
callback(new Error('投资金额和缴存金额只能填写一个'));
} else {
callback();
}
};
const formRules = reactive({ const formRules = reactive({
date: [{ required: true, message: '缴存日期不能为空', trigger: 'blur' }], date: [{ required: true, message: '缴存日期不能为空', trigger: 'blur' }],
userId: [{ required: true, message: '投资人不能为空', trigger: 'blur' }], userId: [{ required: true, message: '投资人不能为空', trigger: 'blur' }],
projectId: [{ validator: validateProjectRequired, trigger: 'blur' }], projectId: [{ required: true, message: '项目不能为空', trigger: 'blur' }],
investmentAmount: [{ validator: validateAmountExclusive, trigger: 'blur' }], investmentAmount: [{ required: true, message: '投资金额不能为空', trigger: 'blur' }],
saveAmount: [{ validator: validateAmountExclusive, trigger: 'blur' }],
}) })
const formRef = ref() // Ref const formRef = ref() // Ref
const projectOptions = ref<ProjectApi.ProjectVO[]>([]) const projectOptions = ref<ProjectApi.ProjectVO[]>([])

View File

@ -102,11 +102,12 @@
{{ scope.row.date ? formatDate(scope.row.date, 'YYYY-MM-DD') : '' }} {{ scope.row.date ? formatDate(scope.row.date, 'YYYY-MM-DD') : '' }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="投资人/代管人" align="center" prop="userName" width="300px"/> <el-table-column label="投资人/代管人" align="center" prop="userName" />
<el-table-column label="项目名称" align="center" prop="projectName" min-width="260px"/> <el-table-column label="项目名称" align="center" prop="projectName" min-width="260px"/>
<el-table-column label="投资款/分红" align="center" prop="investmentAmount" width="300px"/> <el-table-column label="投资款/分红" align="center" prop="investmentAmount" />
<el-table-column label="缴存/支取" align="center" prop="saveAmount" width="300px"/> <el-table-column label="缴存/支取" align="center" prop="saveAmount" />
<el-table-column label="相差数" align="center" prop="diff" width="300px"/> <el-table-column label="相差数" align="center" prop="diff" />
<el-table-column label="待还投资额" align="center" prop="remainAmount" />
<el-table-column label="操作" align="center" fixed="right" min-width="120px"> <el-table-column label="操作" align="center" fixed="right" min-width="120px">
<template #default="scope"> <template #default="scope">
<el-button <el-button