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) => {
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 // 投资人名称
investmentAmount: 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_AUXILIARY_TYPE = 'crm_finance_invoice_auxiliary_type', // 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_AUDIT_STATUS = 'erp_audit_status', // ERP 审批状态

View File

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

View File

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

View File

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

View File

@ -126,8 +126,8 @@
:show-overflow-tooltip="true"
@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="statMonth" 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
type="primary"
:loading="exportLoading"
@click="handleExportDocxDetailRow(settlement)"
v-hasPermi="['crm:project-settlement:export']"
>
导出文档
</el-button>
</ProjectSettlementDetailsHeader>
<div class="settlement-detail-section">
@ -37,12 +45,45 @@
<el-table-column prop="remark" label="说明" />
</el-table>
<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>
</template>
<script lang="ts" setup>
import ProjectSettlementDetailsHeader from './ProjectSettlementDetailsHeader.vue'
import { useTagsViewStore } from '@/store/modules/tagsView'
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'
defineOptions({ name: 'CrmProjectSettlementDetail' })
@ -52,7 +93,7 @@ const message = useMessage() // 消息弹窗
const exportLoading = ref(false) //
const { delView } = useTagsViewStore() //
const { push, currentRoute } = useRouter() //
const settlement = ref<ProjectSettlementApi.ProjectSettlementVO>({} as ProjectSettlementApi.ProjectSettlementVO) //
const settlement = ref<ProjectSettlementWithPartners>({} as ProjectSettlementWithPartners) //
const close = () => {
delView(unref(currentRoute))
@ -96,11 +137,25 @@ const handleExportDetailRow = async (row: ProjectSettlementApi.ProjectSettlement
return ''
}
const params = {
projectId: row.projectId,
returnTime: [formatDateTime(row.startDate), formatDateTime(row.endDate)]
}
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 {
} finally {
exportLoading.value = false
@ -113,10 +168,7 @@ const incomeTable = computed(() => [
{ name: '政府补贴收入', amount: settlement.value.governmentSubsidy, remark: '扣除应纳税额后的净补贴额' },
{ name: '其他收入', amount: settlement.value.otherIncome, remark: '如项目或设备转让、征用赔偿款等' },
])
//
const incomeRemarkTable = computed(() => [
{ name: settlement.value.incomeRemark ? settlement.value.incomeRemark : '无', remark: '其他需注明的内容' },
])
// ...existing code...
//
const expenseTable = computed(() => [
{ name: '电费成本', amount: settlement.value.electricityCost, remark: '以电力公司账单为准' },
@ -134,8 +186,37 @@ const expenseTable = computed(() => [
//
const profitTable = computed(() => [
{ 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>
<style scoped>

View File

@ -112,8 +112,22 @@
{{ scope.row.settlementDate ? formatDate(scope.row.settlementDate, 'YYYY-MM-DD') : '' }}
</template>
</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">
<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
link
type="primary"
@ -153,6 +167,7 @@ import * as ProjectSettlementApi from '@/api/crm/project/settlement'
import * as ProjectApi from '@/api/crm/project'
import ProjectSettlementForm from './ProjectSettlementForm.vue'
import ElQuarterPicker from '@/components/ElQuarterPicker/index.vue'
import { DICT_TYPE,getIntDictOptions } from '@/utils/dict'
// '2024-1' => '2024'
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();
})
</script>

View File

@ -62,13 +62,6 @@
</el-form-item>
</el-col>
</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>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading"> </el-button>
@ -77,17 +70,7 @@
</Dialog>
</template>
<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 ProjectApi from '@/api/crm/project'
import * as UserApi from '@/api/system/user'
@ -110,24 +93,12 @@ const formData = ref({
investmentAmount: 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({
date: [{ required: true, message: '缴存日期不能为空', trigger: 'blur' }],
userId: [{ required: true, message: '投资人不能为空', trigger: 'blur' }],
projectId: [{ validator: validateProjectRequired, trigger: 'blur' }],
investmentAmount: [{ validator: validateAmountExclusive, trigger: 'blur' }],
saveAmount: [{ validator: validateAmountExclusive, trigger: 'blur' }],
projectId: [{ required: true, message: '项目不能为空', trigger: 'blur' }],
investmentAmount: [{ required: true, message: '投资金额不能为空', trigger: 'blur' }],
})
const formRef = ref() // Ref
const projectOptions = ref<ProjectApi.ProjectVO[]>([])

View File

@ -102,11 +102,12 @@
{{ scope.row.date ? formatDate(scope.row.date, 'YYYY-MM-DD') : '' }}
</template>
</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="investmentAmount" width="300px"/>
<el-table-column label="缴存/支取" align="center" prop="saveAmount" width="300px"/>
<el-table-column label="相差数" align="center" prop="diff" width="300px"/>
<el-table-column label="投资款/分红" align="center" prop="investmentAmount" />
<el-table-column label="缴存/支取" align="center" prop="saveAmount" />
<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">
<template #default="scope">
<el-button