Fms-Java版开发实录:28 Journal类的设计

Fms-Java版开发实录:28 Journal类的设计

终于到了目前我打算编写的最后一个Entity类了,也就是一个会计凭证类

终于到了目前我打算编写的最后一个Entity类了,也就是一个会计凭证类。老样子,先来设计一下。

Journal

这里设计分为两部分,一部分是关系映射,一部分是基础字段

关系映射

这里最先要解决的问题,就是Journal究竟要挂在什么东西下边,这里我想了一下,首先毫无疑问的,Journal应该外键关联到合同,由于合同和预算是多对多的关系,但Journal不行,一条只能归属于一个预算,所以在新增的时候,需要将这个合同对应的预算都列出来供选择。只要对应了预算,也就等于对应了项目。这里我打算用一点稍微非规范化的设计,就是再添加一个int的字段,手动保存上对应的项目即可,那么一条Journal记录实际上同时对应某个合同,某个预算和项目。

至于修改页面,我暂时不打算让用户去修改这三个对应关系,仅仅只能修改数字,不像合同那么灵活。如果输入错误,就删除了再来吧,这里就做的死板一些,不要太灵活。

根据上边的设计,类的关系映射设计如下:

@Entity
@Table(name = "journal")
public class Journal implements Serializable {

    private static final long serialVersionUID = 11L;

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

    // 关联到合同的外键
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "contract_id", nullable = false)
    private Contract contract;

    // 关联到预算的外键
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "budget_id", nullable = false)
    private Budget budget;

    // 手工设置上项目的id
    private int project_id;
}

这里最后一个非规范化的project_id,将来可以快速的找到对应的项目。

还需要在预算和合同类中设置上相应的关联映射,这里代码就省略了。

普通字段映射

这里我基本上就参考原来Django中的编写,把字段都设置好,从目前的应用来看,原来的设计足够,还是突出那些关键的科目字段。

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

    // 凭证日期
    @Temporal(TemporalType.DATE)
    @Column(name = "start_time")
    @DateTimeFormat(pattern = "yyyy-MM-dd", iso = DateTimeFormat.ISO.DATE)
    private Date noteDate;

    // 凭证号
    @Digits(integer = 4, fraction = 0,message = "必须为0-9999的整数")
    private int noteNumber;

    //会计字段部分

    // 损益-收入
    @NotNull(message = "收入不能为空")
    @Digits(integer = 14, fraction = 2)
    private BigDecimal revenuePL = new BigDecimal("0.00");

    // 损益-成本
    @NotNull(message = "成本不能为空")
    @Digits(integer = 14, fraction = 2)
    private BigDecimal costPL = new BigDecimal("0.00");

    // 损益-其他损益
    @NotNull(message = "成本不能为空")
    @Digits(integer = 14, fraction = 2)
    private BigDecimal otherPL = new BigDecimal("0.00");

    // 资产-货币资金
    @NotNull(message = "不能为空")
    @Digits(integer = 14, fraction = 2)
    private BigDecimal cash = new BigDecimal("0.00");

    // 资产-合同履约成本
    @NotNull(message = "不能为空")
    @Digits(integer = 14, fraction = 2)
    private BigDecimal contractCost = new BigDecimal("0.00");

    // 资产-合同资产
    @NotNull(message = "不能为空")
    @Digits(integer = 14, fraction = 2)
    private BigDecimal contractAsset = new BigDecimal("0.00");

    // 资产-合同取得成本
    @NotNull(message = "不能为空")
    @Digits(integer = 14, fraction = 2)
    private BigDecimal contractAcquireCost = new BigDecimal("0.00");

    // 资产-应收账款
    @NotNull(message = "不能为空")
    @Digits(integer = 14, fraction = 2)
    private BigDecimal accountReceivable = new BigDecimal("0.00");

    // 资产-预付账款
    @NotNull(message = "不能为空")
    @Digits(integer = 14, fraction = 2)
    private BigDecimal prepaid = new BigDecimal("0.00");

    // 资产-进项税金
    @NotNull(message = "不能为空")
    @Digits(integer = 14, fraction = 2)
    private BigDecimal inputVAT = new BigDecimal("0.00");

    // 资产-其他资产
    @NotNull(message = "不能为空")
    @Digits(integer = 14, fraction = 2)
    private BigDecimal otherAsset = new BigDecimal("0.00");

    // 负债-合同负债
    @NotNull(message = "不能为空")
    @Digits(integer = 14, fraction = 2)
    private BigDecimal contractLiability = new BigDecimal("0.00");

    // 负债-应付账款
    @NotNull(message = "不能为空")
    @Digits(integer = 14, fraction = 2)
    private BigDecimal accountPayable = new BigDecimal("0.00");

    // 负债-销项税金
    @NotNull(message = "不能为空")
    @Digits(integer = 14, fraction = 2)
    private BigDecimal outputVAT = new BigDecimal("0.00");

    // 负债-其他负债
    @NotNull(message = "不能为空")
    @Digits(integer = 14, fraction = 2)
    private BigDecimal otherLiability = new BigDecimal("0.00");

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "create_time", updatable = false)
    @CreationTimestamp
    private Date createTime;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "update_time")
    @UpdateTimestamp
    protected Date updateTime;

编写自检方法

我们要根据会计的借贷是否相等,来判断这个类的数据是不是有效,所以需要编写几个方法:

    public BigDecimal totalDebitSide() {
        return costPL
                .add(otherPL)
                .add(cash)
                .add(contractCost)
                .add(contractAsset)
                .add(contractAcquireCost)
                .add(accountReceivable)
                .add(prepaid)
                .add(inputVAT)
                .add(otherAsset);
    }

    public BigDecimal totalCreditSide() {
        return revenuePL
                .add(contractLiability)
                .add(accountPayable)
                .add(outputVAT)
                .add(otherLiability);
    }

    public boolean isValid() {
        return totalDebitSide().equals(totalCreditSide());
    }
    

之后把DAOService类也写了,这里就不放具体代码了。

URL设计

例行的还是要来设计一下URL,由于这个类的上一级就是Contract,所以URL也很好设计,如下:

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

新增Journal

这里就是要编写一个表单,用来新增Journal类,这里需要获取合同对应的预算,让用户只能选择其中的一个预算项目。
先来编写表单:

<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.addJournalUrl()}"
                                                               class="link-secondary">新增明细记录</a>
            </li>
        </ol>
    </nav>
    <div class="py-1 text-center">
        <h2>新增明细记录</h2>

    </div>
    <div class="row py-1">
        <div class="col-sm-4"></div>
        <div th:if="${numberError}" class="text-center alert alert-danger col-12 col-sm-4" role="alert">
            [[${numberError}]]
        </div>
        <div class="col-sm-4"></div>
    </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/journal/add" th:action="${contract.addJournalUrl()}" method="post"
                  th:object="${journal}">
                <div class="row g-3 mb-3">
                    <h4 class="mb-1">基础信息</h4>

                    <div class="col-sm-6">
                        <p class="mb-0">所属预算条目</p>
                        <select class="form-select mt-2" aria-label="budget" name="budget" th:field="*{budget}">
                            <option th:each="budget, control:${budgets}" th:value="${budget.getId()}" >
                                [[${budget.budgetName}]]
                            </option>
                        </select>
                    </div>
                    
                    <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-6">
                        <label for="noteDate" class="form-label">凭证日期</label>
                        <input type="date" class="form-control" id="noteDate" th:field="*{noteDate}"
                               th:classappend="${#fields.hasErrors('noteDate')}? 'is-invalid'" required>
                        <div id="noteDateError" th:if="${#fields.hasErrors('noteDate')}"
                             th:errors="*{noteDate}"
                             class="invalid-feedback"></div>
                    </div>
                    
                    <div class="col-sm-6">
                        <label for="noteNumber" class="form-label">凭证号</label>
                        <input type="number" step="1" class="form-control" id="noteNumber" th:field="*{noteNumber}"
                               th:classappend="${#fields.hasErrors('noteNumber')}? 'is-invalid'" required>
                        <div id="noteNumberError" th:if="${#fields.hasErrors('noteNumber')}"
                             th:errors="*{noteNumber}"
                             class="invalid-feedback"></div>
                    </div>
                    <hr>
                    <h4 class="mb-1">损益</h4>

                    <div class="col-sm-6">
                        <label for="revenuePL" class="form-label">营业收入</label>
                        <input type="number" step="0.01" class="form-control" id="revenuePL" th:field="*{revenuePL}"
                               th:classappend="${#fields.hasErrors('revenuePL')}? 'is-invalid'" required>
                        <div id="revenuePLError" th:if="${#fields.hasErrors('revenuePL')}"
                             th:errors="*{revenuePL}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="costPL" class="form-label">营业成本</label>
                        <input type="number" step="0.01" class="form-control" id="costPL" th:field="*{costPL}"
                               th:classappend="${#fields.hasErrors('costPL')}? 'is-invalid'" required>
                        <div id="costPLError" th:if="${#fields.hasErrors('costPL')}"
                             th:errors="*{costPL}"
                             class="invalid-feedback"></div>
                    </div>

                    <div class="col-sm-6">
                        <label for="otherPL" class="form-label">其他损益</label>
                        <input type="number" step="0.01" class="form-control" id="otherPL" th:field="*{otherPL}"
                               th:classappend="${#fields.hasErrors('otherPL')}? 'is-invalid'" required>
                        <div id="otherPLError" th:if="${#fields.hasErrors('otherPL')}"
                             th:errors="*{otherPL}"
                             class="invalid-feedback"></div>
                    </div>
               
                    <hr>
                    <h4 class="mb-1">资产</h4>
                    <div class="col-sm-6">
                        <label for="cash" class="form-label">货币资金</label>
                        <input type="number" step="0.01" class="form-control" id="cash" th:field="*{cash}"
                               th:classappend="${#fields.hasErrors('cash')}? 'is-invalid'" required>
                        <div id="cashError" th:if="${#fields.hasErrors('cash')}"
                             th:errors="*{cash}"
                             class="invalid-feedback"></div>
                    </div>

                    <div class="col-sm-6">
                        <label for="contractAsset" class="form-label">合同资产</label>
                        <input type="number" step="0.01" class="form-control" id="contractAsset" th:field="*{contractAsset}"
                               th:classappend="${#fields.hasErrors('contractAsset')}? 'is-invalid'" required>
                        <div id="contractAssetError" th:if="${#fields.hasErrors('contractAsset')}"
                             th:errors="*{contractAsset}"
                             class="invalid-feedback"></div>
                    </div>

                    <div class="col-sm-6">
                        <label for="contractCost" class="form-label">合同履约成本</label>
                        <input type="number" step="0.01" class="form-control" id="contractCost" th:field="*{contractCost}"
                               th:classappend="${#fields.hasErrors('contractCost')}? 'is-invalid'" required>
                        <div id="contractCostError" th:if="${#fields.hasErrors('contractCost')}"
                             th:errors="*{contractCost}"
                             class="invalid-feedback"></div>
                    </div>

                    <div class="col-sm-6">
                        <label for="contractAcquireCost" class="form-label">合同取得成本</label>
                        <input type="number" step="0.01" class="form-control" id="contractAcquireCost" th:field="*{contractAcquireCost}"
                               th:classappend="${#fields.hasErrors('contractAcquireCost')}? 'is-invalid'" required>
                        <div id="contractAcquireCostError" th:if="${#fields.hasErrors('contractAcquireCost')}"
                             th:errors="*{contractAcquireCost}"
                             class="invalid-feedback"></div>
                    </div>

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

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

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

                    <div class="col-sm-6">
                        <label for="otherAsset" class="form-label">其他资产</label>
                        <input type="number" step="0.01" class="form-control" id="otherAsset" th:field="*{otherAsset}"
                               th:classappend="${#fields.hasErrors('otherAsset')}? 'is-invalid'" required>
                        <div id="otherAssetError" th:if="${#fields.hasErrors('otherAsset')}"
                             th:errors="*{otherAsset}"
                             class="invalid-feedback"></div>
                    </div>

                    <hr>
                    <h4 class="mb-1">负债</h4>
                    <div class="col-sm-6">
                        <label for="contractLiability" class="form-label">合同负债</label>
                        <input type="number" step="0.01" class="form-control" id="contractLiability" th:field="*{contractLiability}"
                               th:classappend="${#fields.hasErrors('contractLiability')}? 'is-invalid'" required>
                        <div id="contractLiabilityError" th:if="${#fields.hasErrors('contractLiability')}"
                             th:errors="*{contractLiability}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="accountPayable" class="form-label">应付账款</label>
                        <input type="number" step="0.01" class="form-control" id="accountPayable" th:field="*{accountPayable}"
                               th:classappend="${#fields.hasErrors('accountPayable')}? 'is-invalid'" required>
                        <div id="accountPayableError" th:if="${#fields.hasErrors('accountPayable')}"
                             th:errors="*{accountPayable}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="outputVAT" class="form-label">销项税金</label>
                        <input type="number" step="0.01" class="form-control" id="outputVAT" th:field="*{outputVAT}"
                               th:classappend="${#fields.hasErrors('outputVAT')}? 'is-invalid'" required>
                        <div id="outputVATError" th:if="${#fields.hasErrors('outputVAT')}"
                             th:errors="*{outputVAT}"
                             class="invalid-feedback"></div>
                    </div>
                    <div class="col-sm-6">
                        <label for="otherLiability" class="form-label">其他负债</label>
                        <input type="number" step="0.01" class="form-control" id="otherLiability" th:field="*{otherLiability}"
                               th:classappend="${#fields.hasErrors('otherLiability')}? 'is-invalid'" required>
                        <div id="otherLiabilityError" th:if="${#fields.hasErrors('otherLiability')}"
                             th:errors="*{otherLiability}"
                             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.journalListUrl()}" href="/pms/project/list"><i class="fas fa-undo-alt"></i> 返回</a>
                </div>
            </form>
        </div>
        <div class="col-2"></div>
    </div>

</main>

这里要给Contract类添加几个生成URL的方法:

    public String addJournalUrl() {
        return "/pms/journal/contract/" + id + "/add";
    }

    public String journalListUrl() {
        return "/pms/journal/contract/" + id + "/list";
    }

之后是控制器,也是之前编写过的套路,要注意手动设置上关联的合同和项目的id

// 新增Journal页面
@GetMapping("/contract/{cid}/add")
public String addJournalPage(@PathVariable("cid") int cid,
                             @ModelAttribute("journal") Journal journal, Model model) {
    Contract contract = contractService.getContractById(cid);
    Set<Budget> budgets = contract.getBudgets();
    Budget budget = (Budget) (budgets.toArray()[0]);
    Project project = budget.getProject();

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

    return "pms/journal/journalAdd";
}


// 接受修改
@PostMapping("/contract/{cid}/add")
public String addJournal(@Valid @ModelAttribute("journal") Journal journal, BindingResult rs,
                         Model model,
                         @PathVariable("cid") int cid,
                         RedirectAttributes attributes) {
    Contract contract = contractService.getContractById(cid);
    Set<Budget> budgets = contract.getBudgets();
    Budget budget = (Budget) (budgets.toArray()[0]);
    Project project = budget.getProject();
    model.addAttribute("project", project);
    model.addAttribute("contract", contract);
    model.addAttribute("budgets", budgets);

    if (rs.hasErrors()) {
        return "pms/journal/journalAdd";
    }

    System.out.println(journal);
    if (!journal.isValid()) {
        model.addAttribute("numberError", "借贷方不平,请检查数字。");
        return "pms/journal/journalAdd";
    }

    journal.setContract(contract);
    journal.setProject_id(project.getId());
    journalService.save(journal);
    attributes.addFlashAttribute("created", "成功新增明细记录");
    return "redirect:" + contract.journalListUrl();
}

这样就编写好了新增功能,剩下的功能下一篇文章中编写。

LICENSED UNDER CC BY-NC-SA 4.0
Comment