Fms-Java版开发实录:31 项目和预算汇总明细

Fms-Java版开发实录:31 项目和预算汇总明细

这次是预算和项目分别要汇总其所属的明细记录,和合同一样如法炮制......

快接近完工了,给预算对象和项目加上汇总就可以了。

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的内容,现在就要靠这个东西来查了。
之所以加这个功能,实际上就是想直接把内容查出来,而不需要再去一层一层的查预算和合同。
为此,我们需要一个新的VOProjectData,然后在业务层中组装这个数据,用于在项目详情页中展示汇总数据。

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>

上边的这个表格加在显示预算表格的下边就可以了,突然发现好久没有放图了,放一张现在的项目详情页:
fms-projectdetail

至此就编写完了这个系统的主要功能。

接下来是最后一块内容,就是查询了。

LICENSED UNDER CC BY-NC-SA 4.0
Comment