在原来的合同台账系统中,我其实是在合同和明细记录中,设计了收款结算和付款结算两个东西,用于统计项目的收付款。但是我现在发现,比如一次收款没有收完,下一次又收了款,就不好对应到上一次收款的余额中去,所以这一次我决定修改一下记录,即在合同下面挂三种东西,一个是收款结算,一个是付款结算,一个是明细记录,这三个东西之前不再有外键关联,而是通过业务计算,来实时的反映累计的结算与累计的收款之间的差额,然后单独统计收款结算、付款结算中其他的内容。
这样就可以继续编写了,还是先来设计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
类的其他方法都省略了,包括创建时间和自动更新还有equals
和hashcode
之类,依然是使用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
,增删改查的路径设计如下:
pms/receipt/contract/{cid}/list
,某个项目下的所有收款结算记录pms/receipt/contract/{cid}/add
,添加某个合同下的收款结算记录pms/receipt/detail/{id}
,收款结算记录的详情页pms/receipt/{cid}/edit/{id}
,修改收款结算记录的页面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> 新增收款结算</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()}"> <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>
后记
相比合同和预算的多对多关系,这里又恢复到一对多关系,编写起来比较简单。付款结算的编写与收款记录如出一辙,就不再单独放了。