快接近完工了,给预算对象和项目加上汇总就可以了。
Budget
类的汇总方法
对于每一个预算条目,我关心的是对应的成本发生,资金支付,以及成本是否超过了预算的成本+费用,这些就不用数据库来操作了,直接给Budget
类添加方法即可。
// 合计收入
public BigDecimal totalRevenue() {
BigDecimal result = new BigDecimal("0.00");
for (Journal journal : journals) {
result = result.add(journal.getRevenuePL());
}
return result;
}
// 履约成本
public BigDecimal totalContractCost() {
BigDecimal result = new BigDecimal("0.00");
for (Journal journal : journals) {
result = result.add(journal.getContractCost());
}
return result;
}
// 资金支付
public BigDecimal totalCashflow() {
BigDecimal result = new BigDecimal("0.00");
for (Journal journal : journals) {
result = result.add(journal.getCash());
}
return result;
}
// 是否成本超支
public boolean isCostOverflow() {
return netCost.add(expenditure).compareTo(totalContractCost()) < 0;
}
其实到了这里就会发现,本质上和Django
项目编写的方法也是类似的,使用了ORM
之后确实比较方便,当然这里也可以写实际的SQL
语句。
剩下就是展示一下了,也比较简单:
<table class="table table-hover table-responsive" th:if="${count != 0}">
<caption class="text-primary h5"><br>净利润:<span class="text-danger"
th:text="${#numbers.formatDecimal(budgetData.getNetProfit(),0,'COMMA',2,'POINT')}"></span><br><br>净利润率:
<span class="text-danger"
th:text="${#numbers.formatDecimal(budgetData.getProfitRate(),1,'COMMA',2,'POINT') + '%'}"></span>
</caption>
<thead>
<tr>
<th>序号</th>
<th>预算名称</th>
<th>预算收入</th>
<th>预算成本</th>
<th>预算费用</th>
<th>实际成本</th>
<th>资金收付</th>
<th>虚拟条目</th>
</tr>
</thead>
<tbody>
<tr th:each="budget, control:${budgetData.getBudgets()}">
<td th:text="${control.count}"></td>
<td><a th:href="${'/pms/budget/' + project.getId() +'/detail/'+ budget.getId()}">[[${budget.budgetName}]]</a></td>
<td th:text="${#numbers.formatDecimal(budget.netIncome,1,'COMMA',2,'POINT')}"></td>
<td th:text="${#numbers.formatDecimal(budget.netCost,1,'COMMA',2,'POINT')}"></td>
<td th:text="${#numbers.formatDecimal(budget.expenditure,1,'COMMA',2,'POINT')}"></td>
<td>[[${#numbers.formatDecimal(budget.totalContractCost(),1,'COMMA',2,'POINT')}]] <span th:if="${budget.isCostOverflow()}"><i class="fas fa-exclamation-triangle text-danger"></i></span></td>
<td th:text="${#numbers.formatDecimal(budget.totalCashflow(),1,'COMMA',2,'POINT')}"></td>
<td th:if="${budget.isVirtual()}"><i class="fa fa-check text-danger"></i></td>
<td th:if="${!budget.isVirtual()}"><i class="fa fa-times text-success"></i></td>
</tr>
<tr>
<td class="text-center text-primary" colspan="2"><strong>合计</strong></td>
<td class="text-primary"
th:text="${#numbers.formatDecimal(budgetData.getTotalNetIncome(),1,'COMMA',2,'POINT')}"></td>
<td class="text-primary"
th:text="${#numbers.formatDecimal(budgetData.getTotalNetCost(),1,'COMMA',2,'POINT')}"></td>
<td class="text-primary"
th:text="${#numbers.formatDecimal(budgetData.getTotalExpenditure(),1,'COMMA',2,'POINT')}"></td>
<td></td>
</tr>
</tbody>
</table>
添加了两列用来展示成本和资金支付
BudgetData
类添加计算汇总的方法
预算列表页,我们还需要进行对成本和资金支付的汇总,这就要来修改BudgetData
类,其实方法也很简单,如下:
private BigDecimal totalContractCost = new BigDecimal("0.00");
private BigDecimal totalCashflow = new BigDecimal("0.00");
public BigDecimal getTotalContractCost() {
return totalContractCost;
}
public void setTotalContractCost(BigDecimal totalContractCost) {
this.totalContractCost = totalContractCost;
}
public BigDecimal getTotalCashflow() {
return totalCashflow;
}
public void setTotalCashflow(BigDecimal totalCashflow) {
this.totalCashflow = totalCashflow;
}
之后在业务类组装BudgetData
的时候,来设置这两个域的值:
budgets.forEach(budget -> {
result.setTotalContractCost(result.getTotalContractCost().add(budget.totalContractCost()));
});
budgets.forEach(budget -> {
result.setTotalCashflow(result.getTotalCashflow().add(budget.totalCashflow()));
});
这样就组装了一个带有这两个合计数的BudgetData
,在预算列表页中将其渲染出来即可:
<table class="table table-hover table-responsive" th:if="${count != 0}">
<thead>
<tr>
<th>序号</th>
<th>结算说明</th>
<th>凭证号</th>
<th>净现金流</th>
<th>确认收入</th>
<th>履约成本</th>
</tr>
</thead>
<tbody>
<tr th:each="journal, control:${journals}">
<td th:text="${control.count}"></td>
<td><a href="#" th:href="${journal.absoluteUrl()}" th:text="${journal.getDescription()}"></a></td>
<td th:text="${#dates.format(journal.getNoteDate(), 'yyyy') +'-'+ #dates.format(journal.getNoteDate(), 'MM') + '-' + #numbers.formatDecimal(journal.getNoteNumber(),4,0,'POINT')}"></td>
<td th:text="${#numbers.formatDecimal(journal.getCash(),1,'COMMA',2,'POINT')}"></td>
<td th:text="${#numbers.formatDecimal(journal.getRevenuePL(),1,'COMMA',2,'POINT')}"></td>
<td><span th:text="${#numbers.formatDecimal(journal.getContractCost(),1,'COMMA',2,'POINT')}"></td>
</tr>
<tr>
<td class="text-center" th:text="合计" colspan="3"></td>
<td class="text-primary"
th:text="${#numbers.formatDecimal(contractData.totalCash,1,'COMMA',2,'POINT')}"></td>
<td class="text-primary"
th:text="${#numbers.formatDecimal(contractData.totalRevenue,1,'COMMA',2,'POINT')}"></td>
<td class="text-primary"
th:text="${#numbers.formatDecimal(contractData.totalContractCost,1,'COMMA',2,'POINT')}"></td>
</tr>
</tbody>
</table>
这样就编写完了预算的功能,最后来编写项目的合计数。
项目汇总功能
我在Journal
字段中加了一个保存项目id
的内容,现在就要靠这个东西来查了。
之所以加这个功能,实际上就是想直接把内容查出来,而不需要再去一层一层的查预算和合同。
为此,我们需要一个新的VO
类ProjectData
,然后在业务层中组装这个数据,用于在项目详情页中展示汇总数据。
ProjectData
类
这个类就是套路了,纯粹的装载数据,如下:
public class ProjectData {
private Project project;
private Set<Journal> journals;
// 收入
private BigDecimal totalRevenue = new BigDecimal("0.00");
// 成本
private BigDecimal totalCost = new BigDecimal("0.00");
// 合同履约成本
private BigDecimal totalContractCost = new BigDecimal("0.00");
// 资金
private BigDecimal totalCashflow = new BigDecimal("0.00");
// 应收账款
private BigDecimal totalAccountReceivable = new BigDecimal("0.00");
// 合同资产
private BigDecimal totalContractAsset = new BigDecimal("0.00");
// 进项税金
private BigDecimal totalInputVAT = new BigDecimal("0.00");
// 合同负债
private BigDecimal totalContractLiability = new BigDecimal("0.00");
// 应付账款
private BigDecimal totalAccountPayable = new BigDecimal("0.00");
// 销项税金
private BigDecimal totalOutputVat = new BigDecimal("0.00");
public Project getProject() {
return project;
}
public void setProject(Project project) {
this.project = project;
}
public BigDecimal getTotalRevenue() {
return totalRevenue;
}
public void setTotalRevenue(BigDecimal totalRevenue) {
this.totalRevenue = totalRevenue;
}
public BigDecimal getTotalCost() {
return totalCost;
}
public void setTotalCost(BigDecimal totalCost) {
this.totalCost = totalCost;
}
public BigDecimal getTotalContractCost() {
return totalContractCost;
}
public void setTotalContractCost(BigDecimal totalContractCost) {
this.totalContractCost = totalContractCost;
}
public BigDecimal getTotalCashflow() {
return totalCashflow;
}
public void setTotalCashflow(BigDecimal totalCashflow) {
this.totalCashflow = totalCashflow;
}
public BigDecimal getTotalAccountReceivable() {
return totalAccountReceivable;
}
public void setTotalAccountReceivable(BigDecimal totalAccountReceivable) {
this.totalAccountReceivable = totalAccountReceivable;
}
public BigDecimal getTotalContractAsset() {
return totalContractAsset;
}
public void setTotalContractAsset(BigDecimal totalContractAsset) {
this.totalContractAsset = totalContractAsset;
}
public BigDecimal getTotalInputVAT() {
return totalInputVAT;
}
public void setTotalInputVAT(BigDecimal totalInputVAT) {
this.totalInputVAT = totalInputVAT;
}
public BigDecimal getTotalContractLiability() {
return totalContractLiability;
}
public void setTotalContractLiability(BigDecimal totalContractLiability) {
this.totalContractLiability = totalContractLiability;
}
public BigDecimal getTotalAccountPayable() {
return totalAccountPayable;
}
public void setTotalAccountPayable(BigDecimal totalAccountPayable) {
this.totalAccountPayable = totalAccountPayable;
}
public BigDecimal getTotalOutputVat() {
return totalOutputVat;
}
public void setTotalOutputVat(BigDecimal totalOutputVat) {
this.totalOutputVat = totalOutputVat;
}
public Set<Journal> getJournals() {
return journals;
}
public void setJournals(Set<Journal> journals) {
this.journals = journals;
}
}
剩下的重型逻辑都在业务代码里
编写DAO
中的查询和业务类的组装
这里遇到一个问题,我原来在Journal
类中把项目id
的名称定义为project_id
,结果被Hibernate
解析为与Project
对象的关联,导致在DAO
中无法识别,所以就将其修改成了:
// 手工设置上项目的id
private int projectId;
之后在JournalDao
中添加一个方法:
Set<Journal> findAllByProjectId(int id);
这样就可以按照项目的id
来查出所有对应的Journal
并且组装数据对象,修改ProjectService
:
private final ProjectDao projectDao;
private final JournalDao journalDao;
@Autowired
public ProjectService(ProjectDao projectDao, JournalDao journalDao) {
this.projectDao = projectDao;
this.journalDao = journalDao;
}
@Transactional
public ProjectData getProjectDataById(int id) {
ProjectData result = new ProjectData();
Project project = findById(id);
result.setProject(project);
Set<Journal> journals = journalDao.findAllByProjectId(id);
result.setJournals(journals);
// 收入
journals.forEach(journal -> {
result.setTotalRevenue(result.getTotalRevenue().add(journal.getRevenuePL()));
});
// 成本
journals.forEach(journal -> {
result.setTotalCost(result.getTotalCost().add(journal.getCostPL()));
});
// 合同履约成本
journals.forEach(journal -> {
result.setTotalContractCost(result.getTotalContractCost().add(journal.getContractCost()));
});
// 资金
journals.forEach(journal -> {
result.setTotalCashflow(result.getTotalCashflow().add(journal.getCash()));
});
// 应收账款
journals.forEach(journal -> {
result.setTotalAccountReceivable(result.getTotalAccountReceivable().add(journal.getAccountReceivable()));
});
// 合同资产
journals.forEach(journal -> {
result.setTotalContractAsset(result.getTotalContractAsset().add(journal.getContractAsset()));
});
// 进项税金
journals.forEach(journal -> {
result.setTotalInputVAT(result.getTotalInputVAT().add(journal.getInputVAT()));
});
// 合同负债
journals.forEach(journal -> {
result.setTotalContractLiability(result.getTotalContractLiability().add(journal.getContractLiability()));
});
// 应付账款
journals.forEach(journal -> {
result.setTotalAccountPayable(result.getTotalAccountPayable().add(journal.getAccountPayable()));
});
// 销项税金
journals.forEach(journal -> {
result.setTotalOutputVat(result.getTotalOutputVat().add(journal.getOutputVAT()));
});
return result;
}
这里就是查到项目对应的所有明细记录之后,简单粗暴的返回聚合了上述结果的VO
,之后就是来修改控制器。
控制器修改
这个也很简单,就在ProjectController
的详情页控制器中补充返回刚刚组装的ProjectData
对象:
model.addAttribute("projectData", projectService.getProjectDataById(id));
页面修改
就快结束了,在页面上把这些内容都展示出来吧:
<h2 th:if="${projectData.getJournals().size()>0}">项目财务数据</h2>
<table th:if="${projectData.getJournals().size()>0}" class="table table-sm table-striped">
<thead>
<tr>
<th>内容</th>
<th>金额</th>
</tr>
</thead>
<tbody>
<tr>
<td>累计营业收入</td>
<td th:text="${#numbers.formatDecimal(projectData.totalRevenue,1,'COMMA',2,'POINT')}"></td>
</tr>
<tr>
<td>累计营业成本</td>
<td th:text="${#numbers.formatDecimal(projectData.totalCost,1,'COMMA',2,'POINT')}"></td>
</tr>
<tr>
<td>资金情况</td>
<td th:text="${#numbers.formatDecimal(projectData.totalCashflow,1,'COMMA',2,'POINT')}"></td>
</tr>
<tr>
<td>合同履约成本</td>
<td th:text="${#numbers.formatDecimal(projectData.totalContractCost,1,'COMMA',2,'POINT')}"></td>
</tr>
<tr>
<td>合同资产</td>
<td th:text="${#numbers.formatDecimal(projectData.totalContractAsset,1,'COMMA',2,'POINT')}"></td>
</tr>
<tr>
<td>应收账款</td>
<td th:text="${#numbers.formatDecimal(projectData.totalAccountReceivable,1,'COMMA',2,'POINT')}"></td>
</tr>
<tr>
<td>进项税金</td>
<td th:text="${#numbers.formatDecimal(projectData.totalInputVAT,1,'COMMA',2,'POINT')}"></td>
</tr>
<tr>
<td>合同负债</td>
<td th:text="${#numbers.formatDecimal(projectData.totalContractLiability,1,'COMMA',2,'POINT')}"></td>
</tr>
<tr>
<td>应付账款</td>
<td th:text="${#numbers.formatDecimal(projectData.totalAccountPayable,1,'COMMA',2,'POINT')}"></td>
</tr>
<tr>
<td>销项税金</td>
<td th:text="${#numbers.formatDecimal(projectData.totalOutputVat,1,'COMMA',2,'POINT')}"></td>
</tr>
</tbody>
</table>
上边的这个表格加在显示预算表格的下边就可以了,突然发现好久没有放图了,放一张现在的项目详情页:
至此就编写完了这个系统的主要功能。
接下来是最后一块内容,就是查询了。