add 项目利润结算

pull/853/head
wersd 2025-08-28 00:12:52 +08:00
parent 6b0dd8728b
commit b90b6a1cb8
2 changed files with 233 additions and 0 deletions

View File

@ -0,0 +1,88 @@
<template>
<div v-loading="loading">
<div class="flex items-start justify-between">
<div>
<span class="text-md font-bold">{{ settlement.projectName || '' }}</span>
</div>
<div>
<slot></slot>
</div>
</div>
</div>
<ContentWrap class="mt-10px">
<el-descriptions :column="3" direction="horizontal" class="mb-10px">
<el-descriptions-item label="项目名称:">
{{ settlement.projectName || '' }}
</el-descriptions-item>
<el-descriptions-item label="枪数:">
{{ settlement.piles || '' }}
</el-descriptions-item>
<el-descriptions-item label="结算周期:">
{{ formatQuarterPeriod(settlement.settlementPeriod) }}
</el-descriptions-item>
<el-descriptions-item label="总期数:">
{{ settlement.period ?? '' }}
</el-descriptions-item>
<el-descriptions-item label="结算开始时间:">
{{ formatYMD(settlement.startDate) }}
</el-descriptions-item>
<el-descriptions-item label="结算结束时间:">
{{ formatYMD(settlement.endDate) }}
</el-descriptions-item>
<el-descriptions-item label="结算日期:">
{{ formatDateAuto(settlement.settlementDate) }}
</el-descriptions-item>
</el-descriptions>
</ContentWrap>
</template>
<script lang="ts" setup>
import { DICT_TYPE } from '@/utils/dict'
import * as ProjectSettlementApi from '@/api/crm/project/settlement'
import { formatDate } from '@/utils/formatTime'
defineOptions({ name: 'CrmProjectSettlementDetailsHeader' })
const props = defineProps<{
settlement: ProjectSettlementApi.ProjectSettlementVO //
loading: boolean //
}>()
// '2025-2' => '2025'
function formatQuarterPeriod(val?: string) {
if (!val) return ''
const match = val.match(/^(\d{4})-(\d)$/)
if (!match) return val
const year = match[1]
const quarter = match[2]
const quarterMap = { '1': '第一季度', '2': '第二季度', '3': '第三季度', '4': '第四季度' }
return `${year}${quarterMap[quarter] || quarter}`
}
// [2025,4,1] => '2025-04-01'
function formatYMD(val?: number[] | string) {
if (!val) return ''
if (typeof val === 'string') return val
if (Array.isArray(val) && val.length === 3) {
const [y, m, d] = val
return `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`
}
return ''
}
//
function formatAmount(val: any) {
if (val === null || val === undefined) return ''
return Number(val).toFixed(2)
}
// Date
function formatDateAuto(val: any) {
if (!val) return ''
if (typeof val === 'number') {
return formatDate(new Date(val), 'YYYY-MM-DD')
}
if (typeof val === 'string' || val instanceof Date) {
return formatDate(val, 'YYYY-MM-DD')
}
return ''
}
</script>

View File

@ -0,0 +1,145 @@
<template>
<ProjectSettlementDetailsHeader :settlement="settlement" :loading="loading" >
<el-button
type="primary"
:loading="exportLoading"
@click="handleExportDetailRow(settlement)"
v-hasPermi="['crm:project-settlement:export']"
>
导出明细
</el-button>
</ProjectSettlementDetailsHeader>
<div class="settlement-detail-section">
<h2>收入项</h2>
<el-table :data="incomeTable" border style="width: 100%; margin-bottom: 16px;">
<el-table-column prop="name" label="科目名称" />
<el-table-column prop="amount" label="金额/元" />
<el-table-column prop="remark" label="说明" />
</el-table>
<div v-if="settlement.incomeRemark">{{ settlement.incomeRemark }}</div>
<div style="margin-bottom: 12px;">收入合计<b>{{ settlement.totalIncome?.toFixed(2) ?? '--' }}</b></div>
<h2 style="margin-top: 32px;">支出项</h2>
<el-table :data="expenseTable" border style="width: 100%; margin-bottom: 16px;">
<el-table-column prop="name" label="科目名称" />
<el-table-column prop="amount" label="金额/元" />
<el-table-column prop="remark" label="说明" />
</el-table>
<div v-if="settlement.expenseRemark">{{ settlement.expenseRemark }}</div>
<div style="margin-bottom: 12px;">支出合计<b>{{ settlement.totalExpenses?.toFixed(2) ?? '--' }}</b></div>
<h2 style="margin-top: 32px;">利润计算</h2>
<el-table :data="profitTable" border style="width: 100%; margin-bottom: 16px;">
<el-table-column prop="name" label="科目名称" />
<el-table-column prop="amount" label="金额/元" />
<el-table-column prop="remark" label="说明" />
</el-table>
<div>备注当期利润如有亏损则自动结转至下一季度支出项</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'
import download from '@/utils/download'
defineOptions({ name: 'CrmProjectSettlementDetail' })
const settlementId = ref(0) //
const loading = ref(true) //
const message = useMessage() //
const exportLoading = ref(false) //
const { delView } = useTagsViewStore() //
const { push, currentRoute } = useRouter() //
const settlement = ref<ProjectSettlementApi.ProjectSettlementVO>({} as ProjectSettlementApi.ProjectSettlementVO) //
const close = () => {
delView(unref(currentRoute))
push({ name: 'CrmProjectSettlement' })
}
const getSettlement = async () => {
loading.value = true
try {
settlement.value = await ProjectSettlementApi.getProjectSettlement(settlementId.value)
} finally {
loading.value = false
}
}
/** 初始化 */
const { params } = useRoute()
onMounted(() => {
if (!params.id) {
message.warning('参数错误,项目结算编号不能为空!')
close()
return
}
settlementId.value = Number(params.id)
getSettlement()
})
/** 导出明细按钮操作 */
const handleExportDetailRow = async (row: ProjectSettlementApi.ProjectSettlementVO) => {
try {
await message.exportConfirm()
exportLoading.value = true
// 'YYYY-MM-DD HH:mm:ss'
const formatDateTime = (d: any) => {
if (!d) return ''
if (typeof d === 'string') return d.length > 10 ? d : d + ' 00:00:00'
if (typeof d === 'number') {
const date = new Date(d)
return `${date.getFullYear()}-${String(date.getMonth()+1).padStart(2,'0')}-${String(date.getDate()).padStart(2,'0')} 00:00:00`
}
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`)
} catch {
} finally {
exportLoading.value = false
}
}
//
const incomeTable = computed(() => [
{ name: '实际营收', amount: settlement.value.actualRevenue, remark: '扣除平台管理费后的净收入' },
{ name: '政府补贴收入', amount: settlement.value.governmentSubsidy, remark: '扣除应纳税额后的净补贴额' },
{ name: '其他收入', amount: settlement.value.otherIncome, remark: '如项目或设备转让、征用赔偿款等' },
])
//
const incomeRemarkTable = computed(() => [
{ name: settlement.value.incomeRemark ? settlement.value.incomeRemark : '无', remark: '其他需注明的内容' },
])
//
const expenseTable = computed(() => [
{ name: '电费成本', amount: settlement.value.electricityCost, remark: '以电力公司账单为准' },
{ name: '场地租金', amount: settlement.value.venueRent, remark: '按《场地租赁协议》约定' },
{ name: '场地押金', amount: settlement.value.venueDeposit, remark: '按《场地租赁协议》约定' },
{ name: '项目推介费', amount: settlement.value.projectPromotion, remark: '付给项目推荐人' },
{ name: '设备更新购置费', amount: settlement.value.equipmentUpgrade, remark: '同一场站设备更新' },
{ name: '管理费用', amount: settlement.value.managementCost, remark: '按120元/抢/月计算(用于不可分割的公摊费用)' },
{ name: '税收', amount: settlement.value.taxCost, remark: '电费进项与销项差额的13%为增值税,设备税金抵所得税' },
{ name: '上期亏损结转', amount: settlement.value.previousLossCarryover, remark: '上期未弥补亏损金额(如有)' },
{ name: '其他支出', amount: settlement.value.otherExpenses, remark: '如有请备注' },
])
//
//
const profitTable = computed(() => [
{ name: '当期利润/亏损', amount: settlement.value.grossProfit, remark: '收入合计-支出合计' },
{ name: '当期可分配利润', amount: settlement.value.shareAmount < 0 ? 0 : settlement.value.shareAmount, remark: '当期利润亏损时填0' },
])
</script>
<style scoped>
.settlement-detail-section {
margin: 24px 0 0 0;
}
</style>