Fms-Java版开发实录:26 结算与明细记录的重新设计

Fms-Java版开发实录:26 结算与明细记录的重新设计

这里把结算记录重新设计了一下,和明细记录都挂在合同下边。

在原来的合同台账系统中,我其实是在合同和明细记录中,设计了收款结算和付款结算两个东西,用于统计项目的收付款。但是我现在发现,比如一次收款没有收完,下一次又收了款,就不好对应到上一次收款的余额中去,所以这一次我决定修改一下记录,即在合同下面挂三种东西,一个是收款结算,一个是付款结算,一个是明细记录,这三个东西之前不再有外键关联,而是通过业务计算,来实时的反映累计的结算与累计的收款之间的差额,然后单独统计收款结算、付款结算中其他的内容。

这样就可以继续编写了,还是先来设计PO类,由于之前已经有了经验,所以会比较快。

收款结算与付款结算设计

收款结算记录和付款结算记录中的字段沿用原来Django中的设计,之后的增删改查也是套路,由于大部分是数字,编写起来还算简单。

收款结算PO

这个依然放在cc.conyli.fms.dataobject.pms.entity下边:

@Entity
@Table(name = "receipt")
public class ReceiptSettlement implements Serializable {

    private static final long serialVersionUID = 10L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;

    @NotEmpty(message = "结算说明不能为空")
    @Length(max = 255, message = "长度不能超过255个字符")
    @Column(name = "description", nullable = false)
    private String description;

    // 应收结算款-预付款项
    @Digits(integer = 14, fraction = 2)
    private BigDecimal prepaidReceivable;

    // 应收结算款-设计服务
    @Digits(integer = 14, fraction = 2)
    private BigDecimal designReceivable;

    // 应收结算款-管理服务
    @Digits(integer = 14, fraction = 2)
    private BigDecimal managementReceivable;

    // 应收结算款-货物销售
    @Digits(integer = 14, fraction = 2)
    private BigDecimal salesReceivable;

    // 应收结算款-工程服务
    @Digits(integer = 14, fraction = 2)
    private BigDecimal engineeringReceivable;

    // 扣款-预付款项
    @Digits(integer = 14, fraction = 2)
    private BigDecimal prepaidDeduction;

    // 扣款-质保金
    @Digits(integer = 14, fraction = 2)
    private BigDecimal warrantyDeduction;

    // 扣款-违约金
    @Digits(integer = 14, fraction = 2)
    private BigDecimal damageDeduction;

    // 扣款-其他扣款
    @Digits(integer = 14, fraction = 2)
    private BigDecimal otherDeduction;

    // 实际收款-预收款
    @Digits(integer = 14, fraction = 2)
    private BigDecimal prepaidReceived;

    // 实际收款-进度款
    @Digits(integer = 14, fraction = 2)
    private BigDecimal normalReceived;

    // 实际收款-质保金
    @Digits(integer = 14, fraction = 2)
    private BigDecimal warrantyReceived;

    // 实际收款-奖励款
    @Digits(integer = 14, fraction = 2)
    private BigDecimal bonusReceived;

    // 实际收款-其他
    @Digits(integer = 14, fraction = 2)
    private BigDecimal otherReceived;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "contract_id")
    private Contract contract;
    
    // 计算总应收
    public BigDecimal totalReceivable() {
        return prepaidReceivable
                .add(designReceivable)
                .add(managementReceivable)
                .add(salesReceivable)
                .add(engineeringReceivable);
    }
    
    // 计算总扣款
    public BigDecimal totalDeduction() {
        return prepaidDeduction
                .add(warrantyDeduction)
                .add(damageDeduction)
                .add(otherDeduction);
    }
    
    // 计算总实际收款
    public BigDecimal totalReceived() {
        return prepaidReceived
                .add(normalReceived)
                .add(warrantyReceived)
                .add(bonusReceived)
                .add(otherReceived);
    }

    // 计算此次结算是否全部收到款项
    public boolean isSettled() {
        return totalReceivable().subtract(totalDeduction()).equals(totalReceived());
    }
    // 返回详情页地址
    public String absoluteUrl() {
        return "/pms/receipt/detail/" + id;
    }
    // 返回编辑页地址
    public String editUrl() {
        return "/pms/receipt/" + contract.getId() + "/edit/" + id;
    }
    // 将所有字段如果为null,设置成0,用于接受前端数据时候避免出现null字段
    public void normalize() {
        if (prepaidReceivable == null) {
            prepaidReceivable = new BigDecimal("0.00");
        }
        if (designReceivable == null) {
            designReceivable = new BigDecimal("0.00");
        }
        if (managementReceivable == null) {
            managementReceivable = new BigDecimal("0.00");
        }
        if (salesReceivable == null) {
            salesReceivable = new BigDecimal("0.00");
        }
        if (engineeringReceivable == null) {
            engineeringReceivable = new BigDecimal("0.00");
        }

        if (prepaidDeduction == null) {
            prepaidDeduction = new BigDecimal("0.00");
        }

        if (warrantyDeduction == null) {
            warrantyDeduction = new BigDecimal("0.00");
        }

        if (damageDeduction == null) {
            damageDeduction = new BigDecimal("0.00");
        }

        if (otherDeduction == null) {
            otherDeduction = new BigDecimal("0.00");
        }

        if (prepaidReceived == null) {
            prepaidReceived = new BigDecimal("0.00");
        }

        if (normalReceived == null) {
            normalReceived = new BigDecimal("0.00");
        }

        if (warrantyReceived == null) {
            warrantyReceived = new BigDecimal("0.00");
        }

        if (bonusReceived == null) {
            bonusReceived = new BigDecimal("0.00");
        }

        if (otherReceived == null) {
            otherReceived = new BigDecimal("0.00");
        }
    }

}

这个类的逻辑很简单,就是一堆数字,然后取三个汇总数,收款结算,付款结算和明细记录是目前最末端的数据,所以编写一些计算合计的方法,之后要层层汇总上去。

付款结算PO

这个和收款结算是一样的,只是字段有所变化:

@Entity
@Table(name = "payment")
public class PaymentSettlement implements Serializable {

    private static final long serialVersionUID = 11L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;

    @NotEmpty(message = "结算说明不能为空")
    @Length(max = 255, message = "长度不能超过255个字符")
    @Column(name = "description", nullable = false)
    private String description;

    // 应付结算款-预付款项
    @Digits(integer = 14, fraction = 2)
    private BigDecimal prepaidPayable;

    // 应付结算款-合同款
    @Digits(integer = 14, fraction = 2)
    private BigDecimal normalPayable;

    // 应付结算款-其他
    @Digits(integer = 14, fraction = 2)
    private BigDecimal otherPayable;

    // 扣款-预付款项
    @Digits(integer = 14, fraction = 2)
    private BigDecimal prepaidDeduction;

    // 扣款-质保金
    @Digits(integer = 14, fraction = 2)
    private BigDecimal warrantyDeduction;

    // 扣款-违约金
    @Digits(integer = 14, fraction = 2)
    private BigDecimal damageDeduction;

    // 扣款-其他扣款
    @Digits(integer = 14, fraction = 2)
    private BigDecimal otherDeduction;

    // 实际付款-预收款
    @Digits(integer = 14, fraction = 2)
    private BigDecimal prepaidPaid;

    // 实际付款-工程款
    @Digits(integer = 14, fraction = 2)
    private BigDecimal normalPaid;

    // 实际付款-材料款
    @Digits(integer = 14, fraction = 2)
    private BigDecimal salesPaid;

    // 实际付款-租赁款
    @Digits(integer = 14, fraction = 2)
    private BigDecimal rentPaid;

    // 实际付款-设计费
    @Digits(integer = 14, fraction = 2)
    private BigDecimal designPaid;

    // 实际付款-其他
    @Digits(integer = 14, fraction = 2)
    private BigDecimal otherPaid;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "contract_id")
    private Contract contract;

    // 计算总应付
    public BigDecimal totalPayable() {
        return prepaidPayable
                .add(normalPayable)
                .add(otherPayable);
    }

    // 计算总扣款
    public BigDecimal totalDeduction() {
        return prepaidDeduction
                .add(warrantyDeduction)
                .add(damageDeduction)
                .add(otherDeduction);
    }

    // 计算总实际付款
    public BigDecimal totalPaid() {
        return prepaidPaid
                .add(normalPaid)
                .add(salesPaid)
                .add(rentPaid)
                .add(designPaid)
                .add(otherPaid);
    }
}

PaymentSettlement类的其他方法都省略了,包括创建时间和自动更新还有equalshashcode之类,依然是使用id来判断是否equal
之后还需要为Contract类来添加两个关系映射:

@OneToMany(mappedBy = "contract", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<ReceiptSettlement> receiptSettlements    

@OneToMany(mappedBy = "contract", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<PaymentSettlement> paymentSettlements;

这样就准备好了PO类,下边又是套路化的增删改查。

URL设计

两个记录的编写方法其实非常相似,我这里就用其中的一个就可以。
由于结算记录对应的只是合同,即知道合同的id就足够,剩下的预算列表以及项目id都可以从这个合同中获取,因此URL中核心变量就是合同的id,增删改查的路径设计如下:

  1. pms/receipt/contract/{cid}/list,某个项目下的所有收款结算记录
  2. pms/receipt/contract/{cid}/add,添加某个合同下的收款结算记录
  3. pms/receipt/detail/{id},收款结算记录的详情页
  4. pms/receipt/{cid}/edit/{id},修改收款结算记录的页面
  5. pms/receipt/delete,删除收款结算记录

都是老套路了,下边就来逐步实现,依然是先编写添加,然后是列表,之后是修改和删除。

新增收款结算记录

新增主要就是编写表单花点时间,控制器的套路已经非常熟悉了:

    // 新增页面
    @GetMapping("/contract/{cid}/add")
    public String addReceiptPage(@PathVariable("cid") int cid,
                                 @ModelAttribute("receipt") ReceiptSettlement receiptSettlement,
                                 Model model) {

        Contract contract = contractService.getContractById(cid);
        Budget budget = (Budget) (contract.getBudgets().toArray()[0]);
        Project project = budget.getProject();

        model.addAttribute("project", project);
        model.addAttribute("contract", contract);

        return "pms/receipt/receiptAdd";
    }

    @PostMapping("/contract/{cid}/add")
    public String addReceipt(@Valid @ModelAttribute("receipt") ReceiptSettlement receiptSettlement,
                             BindingResult rs,
                             @PathVariable("cid") int cid, Model model,
                             RedirectAttributes redirectAttributes) {
        Contract contract = contractService.getContractById(cid);
        Budget budget = (Budget) (contract.getBudgets().toArray()[0]);
        Project project = budget.getProject();

        model.addAttribute("project", project);
        model.addAttribute("contract", contract);

        if (rs.hasErrors()) {
            return "pms/receipt/receiptAdd";
        }
        receiptSettlement.normalize();
        receiptSettlement.setContract(contract);
        receiptService.save(receiptSettlement);

        redirectAttributes.addFlashAttribute("created", "成功新增收款结算:" + receiptSettlement.getDescription());
        return "redirect:" + contract.receiptListUrl();
    }

表单也不复杂:

<main class="container">
    <nav style="--bs-breadcrumb-divider: '>';" aria-label="breadcrumb" class="mt-3">
        <ol class="breadcrumb">
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/project/list" th:href="@{/pms/project/list}"
                                                               class="link-secondary">项目管理</a></li>
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/project/list"
                                                               th:href="${project.absoluteUrl()}"
                                                               class="link-secondary">[[${project.projectName}]]</a>
            </li>
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/project/list"
                                                               th:href="${contract.absoluteUrl()}"
                                                               class="link-secondary">[[${contract.name}]]</a>
            </li>
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/project/list"
                                                               th:href="${contract.addReceiptUrl()}"
                                                               class="link-secondary">新增收款结算</a>
            </li>
        </ol>
    </nav>
    <div class="py-1 text-center">
        <h2>新增收款结算</h2>

    </div>

    <div class="row g-5">
        <div class="col-2"></div>
        <div class="col-md-8 col-lg-8">
            <p class="text-secondary">不需填写的数值请保留0.00字样,不要删除</p>
            <form class="needs-validation" action="/pms/project/add" th:action="${contract.addReceiptUrl()}" method="post"
                  th:object="${receipt}">
                <div class="row g-3 mb-3">
                    <h4 class="mb-1">应收结算款</h4>
                    <div class="col-sm-6">
                        <label for="description" class="form-label">结算说明</label>
                        <input type="text" class="form-control" id="description" th:field="*{description}"
                               th:classappend="${#fields.hasErrors('description')}? 'is-invalid'" required>
                        <div id="descriptionError" th:if="${#fields.hasErrors('description')}"
                             th:errors="*{description}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="prepaidReceivable" class="form-label">应收结算款-预付款项</label>
                        <input type="number" step="0.01" class="form-control" id="prepaidReceivable" th:field="*{prepaidReceivable}"
                               th:classappend="${#fields.hasErrors('prepaidReceivable')}? 'is-invalid'" required>
                        <div id="prepaidReceivableError" th:if="${#fields.hasErrors('prepaidReceivable')}"
                             th:errors="*{prepaidReceivable}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="designReceivable" class="form-label">应收结算款-设计款</label>
                        <input type="number" step="0.01" class="form-control" id="designReceivable" th:field="*{designReceivable}"
                               th:classappend="${#fields.hasErrors('designReceivable')}? 'is-invalid'" required>
                        <div id="designReceivableError" th:if="${#fields.hasErrors('designReceivable')}"
                             th:errors="*{designReceivable}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="managementReceivable" class="form-label">应收结算款-管理费</label>
                        <input type="number" step="0.01" class="form-control" id="managementReceivable" th:field="*{managementReceivable}"
                               th:classappend="${#fields.hasErrors('managementReceivable')}? 'is-invalid'" required>
                        <div id="managementReceivableError" th:if="${#fields.hasErrors('managementReceivable')}"
                             th:errors="*{managementReceivable}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="salesReceivable" class="form-label">应收结算款-货物销售</label>
                        <input type="number" step="0.01" class="form-control" id="salesReceivable" th:field="*{salesReceivable}"
                               th:classappend="${#fields.hasErrors('salesReceivable')}? 'is-invalid'" required>
                        <div id="salesReceivableError" th:if="${#fields.hasErrors('salesReceivable')}"
                             th:errors="*{salesReceivable}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="engineeringReceivable" class="form-label">应收结算款-工程服务</label>
                        <input type="number" step="0.01" class="form-control" id="engineeringReceivable" th:field="*{engineeringReceivable}"
                               th:classappend="${#fields.hasErrors('engineeringReceivable')}? 'is-invalid'" required>
                        <div id="engineeringReceivableError" th:if="${#fields.hasErrors('engineeringReceivable')}"
                             th:errors="*{engineeringReceivable}"
                             class="invalid-feedback"></div>
                    </div>
                    <hr>
                    <h4 class="mb-1">扣款</h4>
                    <div class="col-sm-6">
                        <label for="prepaidDeduction" class="form-label">扣款-预付款</label>
                        <input type="number" step="0.01" class="form-control" id="prepaidDeduction" th:field="*{prepaidDeduction}"
                               th:classappend="${#fields.hasErrors('prepaidDeduction')}? 'is-invalid'" required>
                        <div id="prepaidDeductionError" th:if="${#fields.hasErrors('prepaidDeduction')}"
                             th:errors="*{prepaidDeduction}"
                             class="invalid-feedback"></div>
                    </div>

                    <div class="col-sm-6">
                        <label for="warrantyDeduction" class="form-label">扣款-质保金</label>
                        <input type="number" step="0.01" class="form-control" id="warrantyDeduction" th:field="*{warrantyDeduction}"
                               th:classappend="${#fields.hasErrors('warrantyDeduction')}? 'is-invalid'" required>
                        <div id="warrantyDeductionError" th:if="${#fields.hasErrors('warrantyDeduction')}"
                             th:errors="*{warrantyDeduction}"
                             class="invalid-feedback"></div>
                    </div>

                    <div class="col-sm-6">
                        <label for="damageDeduction" class="form-label">扣款-违约金</label>
                        <input type="number" step="0.01" class="form-control" id="damageDeduction" th:field="*{damageDeduction}"
                               th:classappend="${#fields.hasErrors('damageDeduction')}? 'is-invalid'" required>
                        <div id="damageDeductionError" th:if="${#fields.hasErrors('damageDeduction')}"
                             th:errors="*{damageDeduction}"
                             class="invalid-feedback"></div>
                    </div>

                    <div class="col-sm-6">
                        <label for="otherDeduction" class="form-label">扣款-其他</label>
                        <input type="number" step="0.01" class="form-control" id="otherDeduction" th:field="*{otherDeduction}"
                               th:classappend="${#fields.hasErrors('otherDeduction')}? 'is-invalid'" required>
                        <div id="otherDeductionError" th:if="${#fields.hasErrors('otherDeduction')}"
                             th:errors="*{otherDeduction}"
                             class="invalid-feedback"></div>
                    </div>
                    <hr>
                    <h4 class="mb-1">实际收款</h4>
                    <div class="col-sm-6">
                        <label for="prepaidReceived" class="form-label">实际收款-预收款</label>
                        <input type="number" step="0.01" class="form-control" id="prepaidReceived" th:field="*{prepaidReceived}"
                               th:classappend="${#fields.hasErrors('prepaidReceived')}? 'is-invalid'" required>
                        <div id="prepaidReceivedError" th:if="${#fields.hasErrors('prepaidReceived')}"
                             th:errors="*{prepaidReceived}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="normalReceived" class="form-label">实际收款-合同款</label>
                        <input type="number" step="0.01" class="form-control" id="normalReceived" th:field="*{normalReceived}"
                               th:classappend="${#fields.hasErrors('normalReceived')}? 'is-invalid'" required>
                        <div id="normalReceivedError" th:if="${#fields.hasErrors('normalReceived')}"
                             th:errors="*{normalReceived}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="warrantyReceived" class="form-label">实际收款-质保金</label>
                        <input type="number" step="0.01" class="form-control" id="warrantyReceived" th:field="*{warrantyReceived}"
                               th:classappend="${#fields.hasErrors('warrantyReceived')}? 'is-invalid'" required>
                        <div id="warrantyReceivedError" th:if="${#fields.hasErrors('warrantyReceived')}"
                             th:errors="*{warrantyReceived}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="bonusReceived" class="form-label">实际收款-奖励款</label>
                        <input type="number" step="0.01" class="form-control" id="bonusReceived" th:field="*{bonusReceived}"
                               th:classappend="${#fields.hasErrors('bonusReceived')}? 'is-invalid'" required>
                        <div id="bonusReceivedError" th:if="${#fields.hasErrors('bonusReceived')}"
                             th:errors="*{bonusReceived}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="otherReceived" class="form-label">实际收款-其他</label>
                        <input type="number" step="0.01" class="form-control" id="otherReceived" th:field="*{otherReceived}"
                               th:classappend="${#fields.hasErrors('otherReceived')}? 'is-invalid'" required>
                        <div id="otherReceivedError" th:if="${#fields.hasErrors('otherReceived')}"
                             th:errors="*{otherReceived}"
                             class="invalid-feedback"></div>
                    </div>
                </div>
                <div class="text-center mb-4">
                    <button class="btn btn-primary btn-lg" type="submit"><i class="fas fa-check"></i> 提交</button>
                    <a class="btn btn-secondary btn-lg" th:href="${contract.receiptListUrl()}" href="/pms/project/list"><i class="fas fa-undo-alt"></i> 返回</a>
                </div>
            </form>
        </div>
        <div class="col-2"></div>
    </div>

</main>

结算记录列表

直接放控制器和页面了:

    //收款结算列表页
    @GetMapping("/contract/{cid}/list")
    public String receiptListPage(@PathVariable("cid") int cid, Model model) {
        Contract contract = contractService.getContractById(cid);
        Budget budget = (Budget) (contract.getBudgets().toArray()[0]);
        Project project = budget.getProject();
        model.addAttribute("project", project);
        model.addAttribute("contract", contract);
        model.addAttribute("receipts", contract.getReceiptSettlements());
        model.addAttribute("count", contract.getReceiptSettlements().size());
        return "pms/receipt/receiptList";
    }

页面如下:

<main class="container">
    <nav style="--bs-breadcrumb-divider: '>';" aria-label="breadcrumb" class="mt-3">
        <ol class="breadcrumb">
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/project/list" th:href="@{/pms/project/list}"
                                                               class="link-secondary">项目管理</a></li>
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/project/list"
                                                               th:href="${project.absoluteUrl()}"
                                                               class="link-secondary">[[${project.projectName}]]</a>
            </li>
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/project/list"
                                                               th:href="${project.contractListUrl()}"
                                                               class="link-secondary">合同列表</a>
            </li>
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/project/list"
                                                               th:href="${contract.absoluteUrl()}"
                                                               class="link-secondary">[[${contract.name}]]</a>
            </li>
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/project/list"
                                                               th:href="${contract.receiptListUrl()}"
                                                               class="link-secondary">收款结算列表</a>
            </li>
        </ol>
    </nav>
    <div class="row py-1">
        <div class="col-sm-4"></div>
        <div th:if="${created}" class="text-center alert alert-success col-12 col-sm-4" role="alert">
            [[${created}]]
        </div>
        <div th:if="${updated}" class="text-center alert alert-success col-12 col-sm-4" role="alert">
            [[${updated}]]
        </div>
        <div th:if="${deleted}" class="text-center alert alert-success col-12 col-sm-4" role="alert">
            [[${deleted}]]
        </div>
        <div class="col-sm-4"></div>
    </div>
    <p>
        <a class="btn btn-primary" href="pms/budget/add" th:href="${contract.addReceiptUrl()}"><i class="fas fa-plus"></i>&nbsp;新增收款结算</a>
    </p>
    <table class="table table-hover table-responsive" th:if="${count != 0}">
        <thead>
        <tr>
            <th>序号</th>
            <th>结算说明</th>
            <th>合计应收</th>
            <th>合计扣款</th>
            <th>实际收款合计</th>
        </tr>
        </thead>
        <tbody>
        <tr th:each="receipt, control:${receipts}">
            <td th:text="${control.count}"></td>
            <td><a href="#" th:href="${receipt.absoluteUrl()}" th:text="${receipt.getDescription()}"></a></td>
            <td th:text="${#numbers.formatDecimal(receipt.totalReceivable(),1,'COMMA',2,'POINT')}"></td>
            <td th:text="${#numbers.formatDecimal(receipt.totalDeduction(),1,'COMMA',2,'POINT')}"></td>
            <td><span th:text="${#numbers.formatDecimal(receipt.totalReceived(),1,'COMMA',2,'POINT')}"></span><span th:if="${!receipt.isSettled()}">&nbsp;<i  class="fas fa-exclamation text-danger"></i></span></td>

        </tr>
        </tbody>
    </table>

</main>

详情页与修改和删除

控制器直接把剩下的都放出来

    //详情页
    @GetMapping("/detail/{id}")
    public String detailPage(@PathVariable("id") int rid, Model model) {
        ReceiptSettlement receiptSettlement = receiptService.findById(rid);
        Contract contract = receiptSettlement.getContract();
        Budget budget = (Budget) (contract.getBudgets().toArray()[0]);
        Project project = budget.getProject();
        model.addAttribute("project", project);
        model.addAttribute("contract", contract);
        model.addAttribute("receipt", receiptSettlement);

        return "pms/receipt/receiptDetail";
    }

    // 显示编辑页面
    @GetMapping("/{cid}/edit/{id}")
    public String editReceiptPage(@PathVariable("cid") int cid,
                                  @PathVariable("id") int rid, Model model) {
        Contract contract = contractService.getContractById(cid);
        ReceiptSettlement receiptSettlement = receiptService.findById(rid);
        Budget budget = (Budget) (contract.getBudgets().toArray()[0]);
        Project project = budget.getProject();
        model.addAttribute("project", project);
        model.addAttribute("contract", contract);
        model.addAttribute("receipt", receiptSettlement);

        return "pms/receipt/receiptEdit";
    }

    // 接受编辑修改
    @PostMapping("/{cid}/edit/{id}")
    public String editReceipt(
            @Valid @ModelAttribute("receipt") ReceiptSettlement receiptSettlement,
            BindingResult rs, @PathVariable("cid") int cid,
            @PathVariable("id") int rid, Model model,
            RedirectAttributes attributes) {
        Contract contract = contractService.getContractById(cid);
        Budget budget = (Budget) (contract.getBudgets().toArray()[0]);
        Project project = budget.getProject();
        model.addAttribute("project", project);
        model.addAttribute("contract", contract);
        model.addAttribute("receipt", receiptSettlement);

        if (rs.hasErrors()) {
            return "pms/receipt/receiptEdit";
        }

        receiptSettlement.setId(rid);
        receiptSettlement.setContract(contract);
        receiptSettlement.normalize();
        receiptService.save(receiptSettlement);
        attributes.addFlashAttribute("updated", "成功更新结算记录:" + receiptSettlement.getDescription());
        return "redirect:" + receiptSettlement.absoluteUrl();
    }

    // 删除收款结算
    @PostMapping("/delete")
    public String deleteReceipt(@RequestParam("deleteId")int rid,
                                @RequestParam("name")String name,
                                @RequestParam("contract")int cid,
                                RedirectAttributes attributes) {
        receiptService.deleteReceipt(rid);
        Contract contract = contractService.getContractById(cid);
        attributes.addFlashAttribute("deleted", "成功删除收款记录:" + name);

        return "redirect:" + contract.receiptListUrl();
    }

详情页使用了一个表格,中间分割成三部分来展示:

<main class="col-lg-8 mx-auto container">
    <nav style="--bs-breadcrumb-divider: '>';" aria-label="breadcrumb" class="mt-3">
        <ol class="breadcrumb">
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/project/list" th:href="@{/pms/project/list}"
                                                               class="link-secondary">项目管理</a></li>
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/project/list"
                                                               th:href="${project.absoluteUrl()}"
                                                               class="link-secondary">[[${project.projectName}]]</a>
            </li>
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/project/list"
                                                               th:href="${project.contractListUrl()}"
                                                               class="link-secondary">合同列表</a>
            </li>
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/project/list"
                                                               th:href="${contract.absoluteUrl()}"
                                                               class="link-secondary">[[${contract.name}]]</a>
            </li>
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/project/list"
                                                               th:href="${contract.receiptListUrl()}"
                                                               class="link-secondary">收款结算列表</a>
            </li>
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/project/list"
                                                               th:href="${receipt.absoluteUrl()}"
                                                               class="link-secondary">[[${receipt.description}]]</a>
            </li>
        </ol>
    </nav>
    <h1 th:text="${receipt.description}">结算说明</h1>
    <p class="text-danger" th:if="${!receipt.isSettled()}">该结算未完全收款,请注意。</p>
    <div class="row py-1" >
        <div class="col-sm-4"></div>
        <div th:if="${updated}" class="text-center alert alert-success col-12 col-sm-4" role="alert">
            [[${updated}]]
        </div>
        <div class="col-sm-4"></div>
    </div>
    <hr>

    <div class="row g-5">
        <div class="col-md-9">
            <table class="table table-sm" th:object="${receipt}">
                <thead>
                <tr>
                    <th>属性</th>
                    <th>内容</th>
                </tr>
                </thead>
                <tbody>
                <tr>
                    <td>结算说明</td>
                    <td th:text="*{description}"></td>
                </tr>
                <tr>
                    <td>应收结算款-预付款项</td>
                    <td th:text="${#numbers.formatDecimal(receipt.prepaidReceivable,1,'COMMA',2,'POINT')}"></td>
                </tr>
                <tr>
                    <td>应收结算款-设计款</td>
                    <td th:text="${#numbers.formatDecimal(receipt.designReceivable,1,'COMMA',2,'POINT')}"></td>
                </tr>
                <tr>
                    <td>应收结算款-管理费</td>
                    <td th:text="${#numbers.formatDecimal(receipt.managementReceivable,1,'COMMA',2,'POINT')}"></td>
                </tr>
                <tr>
                    <td>应收结算款-货物销售</td>
                    <td th:text="${#numbers.formatDecimal(receipt.salesReceivable,1,'COMMA',2,'POINT')}"></td>
                </tr>
                <tr>
                    <td>应收结算款-工程服务</td>
                    <td th:text="${#numbers.formatDecimal(receipt.engineeringReceivable,1,'COMMA',2,'POINT')}"></td>
                </tr>
                <tr class="text-primary bg-light">
                    <td>应收结算款合计</td>
                    <td th:text="${#numbers.formatDecimal(receipt.totalReceivable(),1,'COMMA',2,'POINT')}"></td>
                </tr>
                <tr>
                    <td></td>
                    <td></td>
                </tr>
                <tr>
                    <td>扣款-预付款</td>
                    <td th:text="${#numbers.formatDecimal(receipt.prepaidDeduction,1,'COMMA',2,'POINT')}"></td>
                </tr>
                <tr>
                    <td>扣款-质保金</td>
                    <td th:text="${#numbers.formatDecimal(receipt.warrantyDeduction,1,'COMMA',2,'POINT')}"></td>
                </tr>
                <tr>
                    <td>扣款-违约金</td>
                    <td th:text="${#numbers.formatDecimal(receipt.damageDeduction,1,'COMMA',2,'POINT')}"></td>
                </tr>
                <tr>
                    <td>扣款-其他</td>
                    <td th:text="${#numbers.formatDecimal(receipt.otherDeduction,1,'COMMA',2,'POINT')}"></td>
                </tr>
                <tr class="text-primary bg-light">
                    <td>扣款合计</td>
                    <td th:text="${#numbers.formatDecimal(receipt.totalDeduction(),1,'COMMA',2,'POINT')}"></td>
                </tr>
                <tr>
                    <td></td>
                    <td></td>
                </tr>

                <tr>
                    <td>实际收款-预收款</td>
                    <td th:text="${#numbers.formatDecimal(receipt.prepaidReceived,1,'COMMA',2,'POINT')}"></td>
                </tr>

                <tr>
                    <td>实际收款-合同款</td>
                    <td th:text="${#numbers.formatDecimal(receipt.normalReceived,1,'COMMA',2,'POINT')}"></td>
                </tr>

                <tr>
                    <td>实际收款-质保金</td>
                    <td th:text="${#numbers.formatDecimal(receipt.warrantyReceived,1,'COMMA',2,'POINT')}"></td>
                </tr>

                <tr>
                    <td>实际收款-奖励款</td>
                    <td th:text="${#numbers.formatDecimal(receipt.bonusReceived,1,'COMMA',2,'POINT')}"></td>
                </tr>

                <tr>
                    <td>实际收款-其他</td>
                    <td th:text="${#numbers.formatDecimal(receipt.otherReceived,1,'COMMA',2,'POINT')}"></td>
                </tr>

                <tr class="text-primary bg-light">
                    <td>实际收款合计</td>
                    <td th:text="${#numbers.formatDecimal(receipt.totalReceived(),1,'COMMA',2,'POINT')}"></td>
                </tr>

                </tbody>
            </table>
        </div>

        <div class="col-md-3">
            <h2>功能</h2>
            <ul class="list-group list-group-flush">
                <li class="list-group-item"><a href="/pms/project/detail" th:href="${receipt.editUrl()}"><i
                        class="fas fa-pencil-alt"></i> 编辑本记录</a></li>
                <li class="list-group-item"><a href="#" class="link-danger" data-bs-toggle="modal"
                                               data-bs-target="#deleteModal"><i class="fas fa-trash-alt"></i> 删除本记录</a>
                </li>
            </ul>
        </div>
    </div>

    <div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteLabel" aria-hidden="true">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title" id="deleteLabel">删除收款结算记录</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                </div>
                <div class="modal-body">
                    将删除[[${receipt.description}]]。
                </div>
                <form class="modal-footer" method="post" action="/pms/project/delete" th:action="@{/pms/receipt/delete}">
                    <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"><i class="fas fa-undo"></i>
                        返回
                    </button>
                    <input type="hidden" name="deleteId" th:value="${receipt.getId()}">
                    <input type="hidden" name="contract" th:value="${contract.id}">
                    <input type="hidden" name="name" th:value="${receipt.description}">
                    <button type="submit" class="btn btn-danger"><i class="fas fa-info-circle"></i>
                        确定删除
                    </button>
                </form>
            </div>
        </div>
    </div>
</main>

修改页依然使用了原来的表单,全都是套路:

<main class="container">
    <nav style="--bs-breadcrumb-divider: '>';" aria-label="breadcrumb" class="mt-3">
        <ol class="breadcrumb">
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/project/list" th:href="@{/pms/project/list}"
                                                               class="link-secondary">项目管理</a></li>
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/project/list"
                                                               th:href="${project.absoluteUrl()}"
                                                               class="link-secondary">[[${project.projectName}]]</a>
            </li>
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/project/list"
                                                               th:href="${contract.absoluteUrl()}"
                                                               class="link-secondary">[[${contract.name}]]</a>
            </li>
            <li class="breadcrumb-item" aria-current="page"><a href="/pms/project/list"
                                                               th:href="${receipt.editUrl()}"
                                                               class="link-secondary">编辑收款结算</a>
            </li>
        </ol>
    </nav>
    <div class="py-1 text-center">
        <h2>编辑收款结算</h2>
    </div>

    <div class="row g-5">
        <div class="col-2"></div>
        <div class="col-md-8 col-lg-8">
            <p class="text-secondary">不需填写的数值请保留0.00字样,不要删除</p>
            <form class="needs-validation" action="/pms/project/add" th:action="${receipt.editUrl()}" method="post"
                  th:object="${receipt}">
                <div class="row g-3 mb-3">
                    <h4 class="mb-1">应收结算款</h4>
                    <div class="col-sm-6">
                        <label for="description" class="form-label">结算说明</label>
                        <input type="text" class="form-control" id="description" th:field="*{description}"
                               th:classappend="${#fields.hasErrors('description')}? 'is-invalid'" required>
                        <div id="descriptionError" th:if="${#fields.hasErrors('description')}"
                             th:errors="*{description}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="prepaidReceivable" class="form-label">应收结算款-预付款项</label>
                        <input type="number" step="0.01" class="form-control" id="prepaidReceivable" th:field="*{prepaidReceivable}"
                               th:classappend="${#fields.hasErrors('prepaidReceivable')}? 'is-invalid'" required>
                        <div id="prepaidReceivableError" th:if="${#fields.hasErrors('prepaidReceivable')}"
                             th:errors="*{prepaidReceivable}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="designReceivable" class="form-label">应收结算款-设计款</label>
                        <input type="number" step="0.01" class="form-control" id="designReceivable" th:field="*{designReceivable}"
                               th:classappend="${#fields.hasErrors('designReceivable')}? 'is-invalid'" required>
                        <div id="designReceivableError" th:if="${#fields.hasErrors('designReceivable')}"
                             th:errors="*{designReceivable}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="managementReceivable" class="form-label">应收结算款-管理费</label>
                        <input type="number" step="0.01" class="form-control" id="managementReceivable" th:field="*{managementReceivable}"
                               th:classappend="${#fields.hasErrors('managementReceivable')}? 'is-invalid'" required>
                        <div id="managementReceivableError" th:if="${#fields.hasErrors('managementReceivable')}"
                             th:errors="*{managementReceivable}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="salesReceivable" class="form-label">应收结算款-货物销售</label>
                        <input type="number" step="0.01" class="form-control" id="salesReceivable" th:field="*{salesReceivable}"
                               th:classappend="${#fields.hasErrors('salesReceivable')}? 'is-invalid'" required>
                        <div id="salesReceivableError" th:if="${#fields.hasErrors('salesReceivable')}"
                             th:errors="*{salesReceivable}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="engineeringReceivable" class="form-label">应收结算款-工程服务</label>
                        <input type="number" step="0.01" class="form-control" id="engineeringReceivable" th:field="*{engineeringReceivable}"
                               th:classappend="${#fields.hasErrors('engineeringReceivable')}? 'is-invalid'" required>
                        <div id="engineeringReceivableError" th:if="${#fields.hasErrors('engineeringReceivable')}"
                             th:errors="*{engineeringReceivable}"
                             class="invalid-feedback"></div>
                    </div>
                    <hr>
                    <h4 class="mb-1">扣款</h4>
                    <div class="col-sm-6">
                        <label for="prepaidDeduction" class="form-label">扣款-预付款</label>
                        <input type="number" step="0.01" class="form-control" id="prepaidDeduction" th:field="*{prepaidDeduction}"
                               th:classappend="${#fields.hasErrors('prepaidDeduction')}? 'is-invalid'" required>
                        <div id="prepaidDeductionError" th:if="${#fields.hasErrors('prepaidDeduction')}"
                             th:errors="*{prepaidDeduction}"
                             class="invalid-feedback"></div>
                    </div>

                    <div class="col-sm-6">
                        <label for="warrantyDeduction" class="form-label">扣款-质保金</label>
                        <input type="number" step="0.01" class="form-control" id="warrantyDeduction" th:field="*{warrantyDeduction}"
                               th:classappend="${#fields.hasErrors('warrantyDeduction')}? 'is-invalid'" required>
                        <div id="warrantyDeductionError" th:if="${#fields.hasErrors('warrantyDeduction')}"
                             th:errors="*{warrantyDeduction}"
                             class="invalid-feedback"></div>
                    </div>

                    <div class="col-sm-6">
                        <label for="damageDeduction" class="form-label">扣款-违约金</label>
                        <input type="number" step="0.01" class="form-control" id="damageDeduction" th:field="*{damageDeduction}"
                               th:classappend="${#fields.hasErrors('damageDeduction')}? 'is-invalid'" required>
                        <div id="damageDeductionError" th:if="${#fields.hasErrors('damageDeduction')}"
                             th:errors="*{damageDeduction}"
                             class="invalid-feedback"></div>
                    </div>

                    <div class="col-sm-6">
                        <label for="otherDeduction" class="form-label">扣款-其他</label>
                        <input type="number" step="0.01" class="form-control" id="otherDeduction" th:field="*{otherDeduction}"
                               th:classappend="${#fields.hasErrors('otherDeduction')}? 'is-invalid'" required>
                        <div id="otherDeductionError" th:if="${#fields.hasErrors('otherDeduction')}"
                             th:errors="*{otherDeduction}"
                             class="invalid-feedback"></div>
                    </div>
                    <hr>
                    <h4 class="mb-1">实际收款</h4>
                    <div class="col-sm-6">
                        <label for="prepaidReceived" class="form-label">实际收款-预收款</label>
                        <input type="number" step="0.01" class="form-control" id="prepaidReceived" th:field="*{prepaidReceived}"
                               th:classappend="${#fields.hasErrors('prepaidReceived')}? 'is-invalid'" required>
                        <div id="prepaidReceivedError" th:if="${#fields.hasErrors('prepaidReceived')}"
                             th:errors="*{prepaidReceived}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="normalReceived" class="form-label">实际收款-合同款</label>
                        <input type="number" step="0.01" class="form-control" id="normalReceived" th:field="*{normalReceived}"
                               th:classappend="${#fields.hasErrors('normalReceived')}? 'is-invalid'" required>
                        <div id="normalReceivedError" th:if="${#fields.hasErrors('normalReceived')}"
                             th:errors="*{normalReceived}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="warrantyReceived" class="form-label">实际收款-质保金</label>
                        <input type="number" step="0.01" class="form-control" id="warrantyReceived" th:field="*{warrantyReceived}"
                               th:classappend="${#fields.hasErrors('warrantyReceived')}? 'is-invalid'" required>
                        <div id="warrantyReceivedError" th:if="${#fields.hasErrors('warrantyReceived')}"
                             th:errors="*{warrantyReceived}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="bonusReceived" class="form-label">实际收款-奖励款</label>
                        <input type="number" step="0.01" class="form-control" id="bonusReceived" th:field="*{bonusReceived}"
                               th:classappend="${#fields.hasErrors('bonusReceived')}? 'is-invalid'" required>
                        <div id="bonusReceivedError" th:if="${#fields.hasErrors('bonusReceived')}"
                             th:errors="*{bonusReceived}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="otherReceived" class="form-label">实际收款-其他</label>
                        <input type="number" step="0.01" class="form-control" id="otherReceived" th:field="*{otherReceived}"
                               th:classappend="${#fields.hasErrors('otherReceived')}? 'is-invalid'" required>
                        <div id="otherReceivedError" th:if="${#fields.hasErrors('otherReceived')}"
                             th:errors="*{otherReceived}"
                             class="invalid-feedback"></div>
                    </div>
                </div>
                <div class="text-center mb-4">
                    <button class="btn btn-primary btn-lg" type="submit"><i class="fas fa-check"></i> 提交</button>
                    <a class="btn btn-secondary btn-lg" th:href="${receipt.absoluteUrl()}" href="/pms/project/list"><i class="fas fa-undo-alt"></i> 返回</a>
                </div>
            </form>
        </div>
        <div class="col-2"></div>
    </div>

</main>

后记

相比合同和预算的多对多关系,这里又恢复到一对多关系,编写起来比较简单。付款结算的编写与收款记录如出一辙,就不再单独放了。

LICENSED UNDER CC BY-NC-SA 4.0
Comment