Fms-Java版开发实录:34 重构数据结构

Fms-Java版开发实录:34 重构数据结构

作为重构的第一步,就是要把各个Entity重新整理并且做好关联映射。

作为重构的第一步,就是要把各个Entity重新整理并且做好关联映射。

由于是重构,结构更清晰,这一次就从最底层的Journal类开始做起。

Journal类重构

Journal类相比原来工程项目简化了不少,主要是没有了利润的烦恼。使用的主要科目如下:

  1. 货币资金
  2. 开发成本
  3. 预付账款
  4. 进项税金
  5. 其他资产
  6. 应付账款
  7. 其他应付款
  8. 其他负债
  9. 其他损益

可以看到,其实核心的就是开发成本,进项税金和货币资金了,毕竟房地产的成本归集还是比较简单的。

至于类的方法,依然保留有检查借贷方合计是否相等的方法,至于查询URL,我打算把数据结构重新整理之后,写一个服务用来生成对应的URL,这样就可以把原来分散在各个entity类中的方法聚合起来,让整个程序的结构互相分离的更加明显。

重构之后的类如下:

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

    private static final long serialVersionUID = 7L;

    @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;

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

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

    // 以下为会计科目部分


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

    // 资产-开发成本
    @NotNull(message = "不能为空")
    @Digits(integer = 14, fraction = 2)
    private BigDecimal developmentCost = 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 accountPayable = new BigDecimal("0.00");

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

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

    // 损益-其他损益
    @NotNull(message = "成本不能为空")
    @Digits(integer = 14, fraction = 2)
    private BigDecimal otherPL = 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 cash
                .add(developmentCost)
                .add(prepaid)
                .add(inputVAT)
                .add(otherAsset);
    }

    //计算合计的贷方金额
    public BigDecimal totalCreditSide() {
        return accountPayable
                .add(otherLiability)
                .add(otherPayable)
                .add(otherPL);
    }

    // 当前记录是否有效,即总借方是否等于总贷方
    public boolean isValid() {
        return totalDebitSide().equals(totalCreditSide());
    }
}

常见的equalshashcode还有getter settertoString方法就都省略了。而且对于id是不设置setter的。

很显然,目前还没有设置关系映射,没有关系,马上就开始下一个层级的数据,也就是合同类Contract

Contract类重构

合同所需要的信息倒是与原来差不多,因为很多合同也涉及到结算金额,只不过我们是甲方,没有那么复杂,但是为了清晰起见,还是把基本信息都列出来:

  1. 必填 - 合同编号,这个需要唯一,我就打算用用印单的号码即可。
  2. 必填 - 合同名称
  3. 必填 - 合同主要条款
  4. 必填 - 合同主要对手方
  5. 必填 - 合同总价
  6. 必填 - 开口或闭口合同
  7. 选填 - 不含税价格
  8. 选填 - 增值税额
  9. 选填 - 结算价格
  10. 选填 - 签订时间
  11. 选填 - 终止时间
  12. 选填 - 次要对手方1
  13. 选填 - 次要对手方2
  14. 选填 - 次要对手方3
  15. 选填 - 合同标的
  16. 选填 - 联系人
  17. 选填 - 联系电话

目前重构的类如下:

@Entity
@Table(name = "contract")
public class Contract implements Serializable {

    private static final long serialVersionUID = 9L;

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

    // 合同编号
    @NotEmpty(message = "合同编号不能为空")
    @Length(max = 255, message = "合同编号最长255个字符")
    @Column(name = "number", nullable = false, unique = true)
    private String number;


    //关联到印花税条目的外键,每个合同只对应一种印花税
    @ManyToOne(optional = false, fetch = FetchType.LAZY)
    @JoinColumn(name = "stamp_id")
    private Stamp stamp;

    // 合同名称
    @NotEmpty(message = "合同名称不能为空")
    @Length(max = 255, message = "合同名称最长255个字符")
    @Column(name = "name", nullable = false)
    private String name;

    // 合同主要条款
    @NotEmpty(message = "主要条款不能为空")
    @Length(max = 1000, message = "长度不能超过1000个字符")
    @Column(name = "terms", nullable = false, length = 1000)
    private String terms;

    // 主要合同方
    @NotEmpty(message = "主要合同方不能为空")
    @Length(max = 255, message = "长度不能超过255个字符")
    @Column(name = "supplier", nullable = false)
    private String supplier;

    // 合同总价
    @NotNull(message = "合同总价不能为空")
    @Digits(integer = 14, fraction = 2)
    private BigDecimal totalPrice;

    //是否单价合同或开口合同
    @Column(name = "is_open")
    private boolean isOpen;

    //以上为必填项目,以下为可留空项目

    // 不含税价格
    @Digits(integer = 14, fraction = 2)
    private BigDecimal priceWithoutVAT;

    // 增值税额
    @Digits(integer = 14, fraction = 2)
    private BigDecimal VAT;

    // 结算价格
    @Digits(integer = 14, fraction = 2)
    private BigDecimal finalPrice;

    // 签订时间
    @Temporal(TemporalType.DATE)
    @Column(name = "sign_date")
    @DateTimeFormat(pattern = "yyyy-MM-dd", iso = DateTimeFormat.ISO.DATE)
    private Date signDate;

    // 终止时间
    @Temporal(TemporalType.DATE)
    @Column(name = "end_date")
    @DateTimeFormat(pattern = "yyyy-MM-dd", iso = DateTimeFormat.ISO.DATE)
    private Date endDate;

    // 次要供应商1
    @Length(max = 255, message = "长度不能超过255个字符")
    @Column(name = "secondary_supplier1")
    private String secondarySupplier1;

    // 次要供应商2
    @Length(max = 255, message = "长度不能超过255个字符")
    @Column(name = "secondary_supplier2")
    private String secondarySupplier2;

    // 次要供应商3
    @Length(max = 255, message = "长度不能超过255个字符")
    @Column(name = "secondary_supplier3")
    private String secondarySupplier3;

    // 合同标的
    @Length(max = 255, message = "长度不能超过255个字符")
    @Column(name = "target")
    private String target;

    // 联系人
    @Length(max = 255, message = "长度不能超过255个字符")
    @Column(name = "contact")
    private String contact;

    // 联系电话
    @Length(max = 255, message = "长度不能超过255个字符")
    @Column(name = "phone")
    private String phone;

    // 以下为时间戳

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

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

    // 以下为关系映射

    // 对应到Journal类的关联关系
    @OneToMany(mappedBy = "contract", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private Set<Journal> journals;

}

由于设置了关系映射,所以在Journal类中添加一条对应的:

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

接下来继续编写合同类型的类,以及项目类。

Budget重构为Subject

现在的预算就不叫预算了,实际上就是类似于开发成本下边的大类一样,将合同进行分类。
其实这里的类型,还可以再进行区分,比如审计事务所的合同等等,这些进管理费用的合同,我这里都不纳入范围内,仅仅只是开发项目的合同台账。其他那些合同,其实就直接装在凭证后边就可以。

根据我原来的经验和科目设置,要分类为如下的类型:

  1. 土地征用及拆迁补偿
  2. 前期工程
  3. 基础设施
  4. 建筑安装
  5. 公共配套
  6. 资金成本
  7. 开发间接费

基本上前边这些科目,都有着对应的科目。当然这些分类比科目还是要少的,科目里还会有暂估和结转,但是不会有对应的合同分类。合同分类也是在建立科目的时候就要想好,否则一旦更改起来就比较麻烦。

这个类非常简单了,在我的规划里,其实就包含一个约束unique的名称就行了。

@Entity
@Table(name = "subject")
public class Subject implements Serializable {

    private static final long serialVersionUID = 9L;

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

    @NotEmpty(message = "合同类型名称不能为空")
    @Length(max = 255, message = "长度不能超过255个字符")
    @Column(name = "name", nullable = false)
    private String budgetName;

    // 是否虚拟类别,用于进行展示
    @Column(name = "virtual")
    @ColumnDefault("false")
    private boolean virtual;

    // 备注
    @Length(max = 255, message = "备注最长255个字符")
    @Column(name = "description")
    private String description;

    // 时间戳类型
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "create_time", updatable = false)
    @CreationTimestamp
    private Date createTime;

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

    //关联关系映射

    //关联到合同的多对多映射
    @OneToMany(mappedBy = "subject",cascade = CascadeType.REMOVE)
    private Set<Contract> contracts;
}

当然,还需要在Contract类中添加对应的映射:

    // 对应到Subject类的关联关系
    @ManyToOne(optional = false, fetch = FetchType.EAGER)
    @JoinColumn(name = "subject_id", nullable = false)
    private Subject subject;

Project类重构

这个类就简单多了,也不存在项目经理这种说法了,基本就只剩下项目名称,地址,和简要描述,关联关系就是关联到Subject类的一对多关系,类的代码如下:

@Entity
@Table(name = "project")
public class Project 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 = "name", nullable = false)
    private String projectName;

    @NotEmpty(message = "地址不能为空")
    @Length(max = 255, message = "长度不能超过255个字符")
    @Column(name = "address", nullable = false)
    private String projectAddress;

    @NotEmpty(message = "项目描述不能为空")
    @Length(max = 1000, message = "长度不能超过1000个字符")
    @Column(name = "description", nullable = false, length = 1000)
    private String projectDescription;

    // 时间戳部分
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "create_time", updatable = false)
    @CreationTimestamp
    private Date createTime;

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

    // 关联关系

    // 映射到Subject类的一对多
    @OneToMany(mappedBy = "project", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private Set<Subject> subjects;
}

这样四个PO类就搞定了,接下来设计一下BO类,用于装载一个PO数据和对应的汇总计算数据。之后就是慢慢改写页面了。现在工作节奏也不快,合同也比较少,慢慢写吧。

LICENSED UNDER CC BY-NC-SA 4.0
Comment