|
|
|
|
@ -29,9 +29,9 @@
|
|
|
|
|
<el-table-column label="基本信息" align="center">
|
|
|
|
|
<el-table-column type="selection" width="55" align="center" fixed="left" />
|
|
|
|
|
<el-table-column label="项目编号" prop="projectCode" width="140" align="center" fixed="left" show-overflow-tooltip />
|
|
|
|
|
<el-table-column label="客户名称" width="150" align="center" show-overflow-tooltip>
|
|
|
|
|
<el-table-column label="客户名称" prop="customerName" width="150" align="center" show-overflow-tooltip>
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
{{ scope.row._customerName || '-' }}
|
|
|
|
|
{{ scope.row.customerName || '-' }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="项目名称" prop="projectName" width="200" align="center" show-overflow-tooltip />
|
|
|
|
|
@ -43,12 +43,12 @@
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="产品数量" width="100" align="center">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
{{ scope.row._productAmount != null ? scope.row._productAmount : '-' }}
|
|
|
|
|
{{ scope.row.productAmount != null ? scope.row.productAmount : '-' }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="项目阶段" width="100" align="center">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
<dict-tag v-if="scope.row._projectPhases" :options="project_phases" :value="scope.row._projectPhases" />
|
|
|
|
|
<dict-tag v-if="scope.row.projectPhases" :options="project_phases" :value="scope.row.projectPhases" />
|
|
|
|
|
<span v-else>-</span>
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
@ -65,7 +65,7 @@
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="实际验收时间" width="120" align="center">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
{{ scope.row._acceptanceDate || '-' }}
|
|
|
|
|
{{ scope.row.acceptanceDate ? scope.row.acceptanceDate.substring(0, 10) : '-' }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="是否异常启动" width="120" align="center">
|
|
|
|
|
@ -80,17 +80,17 @@
|
|
|
|
|
<el-table-column label="合同信息" align="center">
|
|
|
|
|
<el-table-column label="签订时间" width="110" align="center">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
{{ scope.row._contractDate || '-' }}
|
|
|
|
|
{{ scope.row.contractDate ? scope.row.contractDate.substring(0, 10) : '-' }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="合同额" width="120" align="center">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
{{ scope.row._contractAmount != null ? formatNumber(scope.row._contractAmount) : '-' }}
|
|
|
|
|
{{ scope.row.contractAmount != null ? formatNumber(scope.row.contractAmount) : '-' }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="客户经理" width="90" align="center">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
{{ scope.row._contractManagerName || '-' }}
|
|
|
|
|
{{ scope.row.contractManagerName || '-' }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="付款方式" prop="paymentMethod" width="110" align="center" show-overflow-tooltip />
|
|
|
|
|
@ -100,27 +100,27 @@
|
|
|
|
|
<el-table-column label="预算及成本" align="center">
|
|
|
|
|
<el-table-column label="预算" width="120" align="center">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
{{ scope.row._budgetCost != null ? formatNumber(scope.row._budgetCost) : '-' }}
|
|
|
|
|
{{ scope.row.budgetCost != null ? formatNumber(scope.row.budgetCost) : '-' }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="预算毛利率" width="100" align="center">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
{{ scope.row._budgetRate != null ? scope.row._budgetRate + '%' : '-' }}
|
|
|
|
|
{{ scope.row.budgetRate != null ? scope.row.budgetRate + '%' : '-' }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="降成本后预算" width="130" align="center">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
{{ scope.row._reduceBudgetCost != null ? formatNumber(scope.row._reduceBudgetCost) : '-' }}
|
|
|
|
|
{{ scope.row.reduceBudgetCost != null ? formatNumber(scope.row.reduceBudgetCost) : '-' }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="降成本后预算毛利率" width="150" align="center">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
{{ scope.row._reduceBudgetRate != null ? scope.row._reduceBudgetRate + '%' : '-' }}
|
|
|
|
|
{{ scope.row.reduceBudgetRate != null ? scope.row.reduceBudgetRate + '%' : '-' }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="累计工时" width="100" align="center">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
{{ scope.row._totalHours != null ? scope.row._totalHours : '-' }}
|
|
|
|
|
{{ scope.row.totalHours != null ? scope.row.totalHours : '-' }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="已发生成本" width="110" align="center">
|
|
|
|
|
@ -128,7 +128,7 @@
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="收入(合同额/1.13)" width="120" align="center">
|
|
|
|
|
<template #default="scope">
|
|
|
|
|
{{ scope.row._revenue != null ? formatNumber(scope.row._revenue) : '-' }}
|
|
|
|
|
{{ scope.row.revenue != null ? formatNumber(scope.row.revenue) : '-' }}
|
|
|
|
|
</template>
|
|
|
|
|
</el-table-column>
|
|
|
|
|
<el-table-column label="已发生成本占收入比例" width="160" align="center">
|
|
|
|
|
@ -142,13 +142,7 @@
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts" name="ProjectLedgerReport">
|
|
|
|
|
import { listProjectInfo } from '@/api/oa/erp/projectInfo';
|
|
|
|
|
import { ProjectInfoVO, ProjectInfoQuery } from '@/api/oa/erp/projectInfo/types';
|
|
|
|
|
import { listErpBudgetInfo } from '@/api/oa/erp/budgetInfo';
|
|
|
|
|
import { listContractInfo } from '@/api/oa/erp/contractInfo';
|
|
|
|
|
import { listProjectAcceptance } from '@/api/oa/erp/projectAcceptance';
|
|
|
|
|
import { listProjectManHourReport } from '@/api/oa/erp/timesheetReport';
|
|
|
|
|
import { listContractMaterial } from '@/api/oa/erp/contractMaterial';
|
|
|
|
|
import { listProjectLedgerReport, ProjectLedgerReportQuery, ProjectLedgerReportVO } from '@/api/oa/erp/projectLedgerReport';
|
|
|
|
|
|
|
|
|
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
|
|
|
const { business_direction, project_status, project_phases } = toRefs<any>(proxy?.useDict('business_direction', 'project_status', 'project_phases'));
|
|
|
|
|
@ -156,18 +150,17 @@ const { business_direction, project_status, project_phases } = toRefs<any>(proxy
|
|
|
|
|
const loading = ref(true);
|
|
|
|
|
const showSearch = ref(true);
|
|
|
|
|
const total = ref(0);
|
|
|
|
|
const reportList = ref<any[]>([]);
|
|
|
|
|
const selectedRows = ref<any[]>([]);
|
|
|
|
|
const reportList = ref<ProjectLedgerReportVO[]>([]);
|
|
|
|
|
const selectedRows = ref<ProjectLedgerReportVO[]>([]);
|
|
|
|
|
const queryFormRef = ref<ElFormInstance>();
|
|
|
|
|
|
|
|
|
|
const queryParams = reactive<ProjectInfoQuery>({
|
|
|
|
|
const queryParams = reactive<ProjectLedgerReportQuery>({
|
|
|
|
|
pageNum: 1,
|
|
|
|
|
pageSize: 20,
|
|
|
|
|
projectCode: undefined,
|
|
|
|
|
projectName: undefined,
|
|
|
|
|
businessDirection: undefined,
|
|
|
|
|
projectStatus: undefined,
|
|
|
|
|
params: {}
|
|
|
|
|
projectStatus: undefined
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/** 格式化数字 */
|
|
|
|
|
@ -179,115 +172,9 @@ const formatNumber = (num: number) => {
|
|
|
|
|
const getList = async () => {
|
|
|
|
|
loading.value = true;
|
|
|
|
|
try {
|
|
|
|
|
// 1. 加载项目列表(排除合同订单类别 9)
|
|
|
|
|
const res = await listProjectInfo(queryParams);
|
|
|
|
|
const projects: any[] = (res.rows || []).filter((p: ProjectInfoVO) => p.projectCategory !== '9');
|
|
|
|
|
const res = await listProjectLedgerReport(queryParams);
|
|
|
|
|
reportList.value = res.rows || [];
|
|
|
|
|
total.value = res.total;
|
|
|
|
|
|
|
|
|
|
// 初始化扩展字段
|
|
|
|
|
projects.forEach((p) => {
|
|
|
|
|
p._customerName = '';
|
|
|
|
|
p._contractDate = '';
|
|
|
|
|
p._contractAmount = null;
|
|
|
|
|
p._contractManagerName = '';
|
|
|
|
|
p._budgetCost = null;
|
|
|
|
|
p._budgetRate = null;
|
|
|
|
|
p._reduceBudgetCost = null;
|
|
|
|
|
p._reduceBudgetRate = null;
|
|
|
|
|
p._budgetContractAmount = null;
|
|
|
|
|
p._revenue = null;
|
|
|
|
|
p._acceptanceDate = '';
|
|
|
|
|
p._projectPhases = '';
|
|
|
|
|
p._totalHours = null;
|
|
|
|
|
p._productAmount = null;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 2. 并行加载关联数据
|
|
|
|
|
const [budgetRes, acceptanceRes, contractRes, manHourRes, materialRes] = await Promise.all([
|
|
|
|
|
listErpBudgetInfo({ pageNum: 1, pageSize: 9999, budgetStatus: '3' } as any).catch(() => ({ rows: [] })),
|
|
|
|
|
listProjectAcceptance({ pageNum: 1, pageSize: 9999 } as any).catch(() => ({ rows: [] })),
|
|
|
|
|
listContractInfo({ pageNum: 1, pageSize: 9999 } as any).catch(() => ({ rows: [] })),
|
|
|
|
|
listProjectManHourReport({ pageNum: 1, pageSize: 9999 } as any).catch(() => ({ data: [] })),
|
|
|
|
|
listContractMaterial({ pageNum: 1, pageSize: 9999 } as any).catch(() => ({ rows: [] }))
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// 构建映射
|
|
|
|
|
const budgetMap = new Map<string, any>();
|
|
|
|
|
((budgetRes as any).rows || []).forEach((b: any) => {
|
|
|
|
|
if (b.projectId) budgetMap.set(String(b.projectId), b);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const acceptanceMap = new Map<string, any>();
|
|
|
|
|
((acceptanceRes as any).rows || []).forEach((a: any) => {
|
|
|
|
|
if (a.projectId && a.flowStatus === 'finish') {
|
|
|
|
|
acceptanceMap.set(String(a.projectId), a);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const contractMap = new Map<string, any>();
|
|
|
|
|
((contractRes as any).rows || []).forEach((c: any) => {
|
|
|
|
|
if (c.contractId) contractMap.set(String(c.contractId), c);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const manHourMap = new Map<string, number>();
|
|
|
|
|
const manHourData = (manHourRes as any).data || (manHourRes as any).rows || [];
|
|
|
|
|
manHourData.forEach((m: any) => {
|
|
|
|
|
if (m.projectId) {
|
|
|
|
|
const pid = String(m.projectId);
|
|
|
|
|
manHourMap.set(pid, (manHourMap.get(pid) || 0) + Number(m.totalHours || 0));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 合同物料数量映射(按 contractId 聚合 amount)
|
|
|
|
|
const materialAmountMap = new Map<string, number>();
|
|
|
|
|
((materialRes as any).rows || []).forEach((m: any) => {
|
|
|
|
|
if (m.contractId) {
|
|
|
|
|
const cid = String(m.contractId);
|
|
|
|
|
materialAmountMap.set(cid, (materialAmountMap.get(cid) || 0) + Number(m.amount || 0));
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 3. 合并数据
|
|
|
|
|
projects.forEach((p) => {
|
|
|
|
|
const pid = String(p.projectId);
|
|
|
|
|
// 合同信息
|
|
|
|
|
if (p.contractId) {
|
|
|
|
|
const contract = contractMap.get(String(p.contractId));
|
|
|
|
|
if (contract) {
|
|
|
|
|
p._customerName = contract.oneCustomerName || '';
|
|
|
|
|
p._contractDate = contract.contractDate || '';
|
|
|
|
|
p._contractManagerName = contract.contractManagerName || '';
|
|
|
|
|
}
|
|
|
|
|
// 产品数量
|
|
|
|
|
const amt = materialAmountMap.get(String(p.contractId));
|
|
|
|
|
if (amt != null) {
|
|
|
|
|
p._productAmount = amt;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 预算信息
|
|
|
|
|
const budget = budgetMap.get(pid);
|
|
|
|
|
if (budget) {
|
|
|
|
|
p._budgetCost = budget.budgetCost;
|
|
|
|
|
p._budgetRate = budget.budgetRate;
|
|
|
|
|
p._reduceBudgetCost = budget.reduceBudgetCost;
|
|
|
|
|
p._reduceBudgetRate = budget.reduceBudgetRate;
|
|
|
|
|
p._budgetContractAmount = budget.contractAmount;
|
|
|
|
|
p._revenue = budget.contractAmount != null ? budget.contractAmount / 1.13 : null;
|
|
|
|
|
p._contractAmount = budget.contractAmount;
|
|
|
|
|
}
|
|
|
|
|
// 验收信息
|
|
|
|
|
const acceptance = acceptanceMap.get(pid);
|
|
|
|
|
if (acceptance) {
|
|
|
|
|
p._acceptanceDate = acceptance.acceptanceDate || '';
|
|
|
|
|
}
|
|
|
|
|
// 工时信息
|
|
|
|
|
const hours = manHourMap.get(pid);
|
|
|
|
|
if (hours != null) {
|
|
|
|
|
p._totalHours = hours;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
reportList.value = projects;
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false;
|
|
|
|
|
}
|
|
|
|
|
@ -306,91 +193,20 @@ const resetQuery = () => {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 多选框选中数据 */
|
|
|
|
|
const handleSelectionChange = (selection: any[]) => {
|
|
|
|
|
const handleSelectionChange = (selection: ProjectLedgerReportVO[]) => {
|
|
|
|
|
selectedRows.value = selection;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 获取字典标签 */
|
|
|
|
|
const getDictLabel = (dictOptions: any, value: string | number | undefined) => {
|
|
|
|
|
if (value == null || value === '') return '-';
|
|
|
|
|
const opts = dictOptions?.value || dictOptions || [];
|
|
|
|
|
const item = opts.find((d: any) => String(d.value) === String(value));
|
|
|
|
|
return item?.label || '-';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/** 导出按钮操作 */
|
|
|
|
|
const handleExport = () => {
|
|
|
|
|
const data = selectedRows.value.length > 0 ? selectedRows.value : reportList.value;
|
|
|
|
|
if (!data || data.length === 0) {
|
|
|
|
|
proxy?.$modal.msgWarning('没有可导出的数据');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 构建 HTML table
|
|
|
|
|
const html = `
|
|
|
|
|
<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40">
|
|
|
|
|
<head><meta charset="UTF-8"><!--[if gte mso 9]><xml><x:ExcelWorkbook><x:ExcelWorksheets><x:ExcelWorksheet>
|
|
|
|
|
<x:Name>项目台账报表</x:Name><x:WorksheetOptions><x:DisplayGridlines/></x:WorksheetOptions>
|
|
|
|
|
</x:ExcelWorksheet></x:ExcelWorksheets></x:ExcelWorkbook></xml><![endif]--></head><body>
|
|
|
|
|
<table border="1" cellspacing="0" cellpadding="4" style="border-collapse:collapse; font-size:11px;">
|
|
|
|
|
<tr>
|
|
|
|
|
<td colspan="16" align="center" style="font-weight:bold; background:#CCE5FF;">基本信息</td>
|
|
|
|
|
<td colspan="4" align="center" style="font-weight:bold; background:#FFCCCC;">合同信息</td>
|
|
|
|
|
<td colspan="8" align="center" style="font-weight:bold; background:#CCFFCC;">预算及成本</td>
|
|
|
|
|
</tr>
|
|
|
|
|
<tr style="font-weight:bold; font-size:10px;">
|
|
|
|
|
<td>序号</td><td>项目编号</td><td>客户名称</td><td>项目名称</td><td>项目经理</td>
|
|
|
|
|
<td>部门</td><td>项目类型</td><td>产品型号</td><td>产品数量</td><td>项目阶段</td><td>情况说明</td>
|
|
|
|
|
<td>状态</td><td>预计验收时间</td><td>实际验收时间</td>
|
|
|
|
|
<td>是否异常启动</td><td>异常启动原因</td>
|
|
|
|
|
<td>签订时间</td><td>合同额</td><td>客户经理</td><td>付款方式</td>
|
|
|
|
|
<td>预算</td><td>预算毛利率</td><td>降成本后预算</td>
|
|
|
|
|
<td>降成本后预算毛利率</td><td>已发生成本</td><td>收入(合同额/1.13)</td><td>已发生成本占收入比例</td><td>累计工时</td>
|
|
|
|
|
</tr>
|
|
|
|
|
${data
|
|
|
|
|
.map(
|
|
|
|
|
(row: any, idx: number) => `<tr>
|
|
|
|
|
<td>${idx + 1}</td>
|
|
|
|
|
<td>${row.projectCode || '-'}</td>
|
|
|
|
|
<td>${row._customerName || '-'}</td>
|
|
|
|
|
<td>${row.projectName || '-'}</td>
|
|
|
|
|
<td>${row.managerName || '-'}</td>
|
|
|
|
|
<td>${row.deptName || '-'}</td>
|
|
|
|
|
<td>${row.typeName || '-'}</td>
|
|
|
|
|
<td>-</td>
|
|
|
|
|
<td>${row._productAmount != null ? row._productAmount : '-'}</td>
|
|
|
|
|
<td>${row._projectPhases ? getDictLabel(project_phases, row._projectPhases) : '-'}</td>
|
|
|
|
|
<td>-</td>
|
|
|
|
|
<td>${getDictLabel(project_status, row.projectStatus)}</td>
|
|
|
|
|
<td>-</td>
|
|
|
|
|
<td>${row._acceptanceDate || '-'}</td>
|
|
|
|
|
<td>-</td>
|
|
|
|
|
<td>-</td>
|
|
|
|
|
<td>${row._contractDate || '-'}</td>
|
|
|
|
|
<td>${row._contractAmount != null ? formatNumber(row._contractAmount) : '-'}</td>
|
|
|
|
|
<td>${row._contractManagerName || '-'}</td>
|
|
|
|
|
<td>${row.paymentMethod || '-'}</td>
|
|
|
|
|
<td>${row._budgetCost != null ? formatNumber(row._budgetCost) : '-'}</td>
|
|
|
|
|
<td>${row._budgetRate != null ? row._budgetRate + '%' : '-'}</td>
|
|
|
|
|
<td>${row._reduceBudgetCost != null ? formatNumber(row._reduceBudgetCost) : '-'}</td>
|
|
|
|
|
<td>${row._reduceBudgetRate != null ? row._reduceBudgetRate + '%' : '-'}</td>
|
|
|
|
|
<td>-</td>
|
|
|
|
|
<td>${row._revenue != null ? formatNumber(row._revenue) : '-'}</td>
|
|
|
|
|
<td>-</td>
|
|
|
|
|
<td>${row._totalHours != null ? row._totalHours : '-'}</td>
|
|
|
|
|
</tr>`
|
|
|
|
|
)
|
|
|
|
|
.join('')}
|
|
|
|
|
</table></body></html>`;
|
|
|
|
|
const exportParams: any = { ...queryParams };
|
|
|
|
|
|
|
|
|
|
const blob = new Blob([html], { type: 'application/vnd.ms-excel;charset=utf-8' });
|
|
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
|
const a = document.createElement('a');
|
|
|
|
|
a.href = url;
|
|
|
|
|
a.download = `项目台账报表_${new Date().getTime()}.xls`;
|
|
|
|
|
document.body.appendChild(a);
|
|
|
|
|
a.click();
|
|
|
|
|
document.body.removeChild(a);
|
|
|
|
|
URL.revokeObjectURL(url);
|
|
|
|
|
// 只导出选中的数据,传递到后端的 params.projectIds 中
|
|
|
|
|
if (selectedRows.value.length > 0) {
|
|
|
|
|
exportParams['params[projectIds]'] = selectedRows.value.map((row) => row.projectId).join(',');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
proxy?.download('/oa/erp/projectLedgerReport/export', exportParams, `项目台账报表_${new Date().getTime()}.xlsx`);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|