feat(contract): 新增合同发票管理功能
实现合同发票的增删改查功能,包括: 1. 添加ContractInvoiceVo实体类及相关ViewModel 2. 创建合同发票数据库表CONTRACT_INVOICE 3. 实现前后端发票管理服务ContractInvoiceService 4. 开发发票管理界面及标签页 5. 添加发票表格单元格组件 6. 完善销售订单表结构,增加客户联系人字段 7. 更新pom.xml版本至0.0.122-SNAPSHOT 修复销售订单界面搜索字段命名不一致问题
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
package com.ecep.contract.ds.contract.model;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.ecep.contract.ds.other.model.Invoice;
|
||||
import com.ecep.contract.model.Employee;
|
||||
import com.ecep.contract.model.IdentityEntity;
|
||||
import com.ecep.contract.model.NamedEntity;
|
||||
import com.ecep.contract.model.Voable;
|
||||
import com.ecep.contract.vo.ContractInvoiceVo;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import org.hibernate.annotations.OnDelete;
|
||||
import org.hibernate.annotations.OnDeleteAction;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* 合同发票实体类
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "CONTRACT_INVOICE", schema = "supplier_ms")
|
||||
@ToString
|
||||
public class ContractInvoice implements IdentityEntity, NamedEntity, Voable<ContractInvoiceVo> {
|
||||
|
||||
@Id
|
||||
@Column(name = "ID", nullable = false)
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Integer id;
|
||||
|
||||
@Column(name = "CODE", length = 50)
|
||||
private String code;
|
||||
|
||||
@Column(name = "NAME", length = 200)
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 关联的合同对象
|
||||
*/
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "CONTRACT_ID", foreignKey = @jakarta.persistence.ForeignKey(name = "FK_CONTRACT_INVOICE_CONTRACT"))
|
||||
@OnDelete(action = OnDeleteAction.CASCADE)
|
||||
@ToString.Exclude
|
||||
private Contract contract;
|
||||
|
||||
/**
|
||||
* 关联的发票对象
|
||||
*/
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "INVOICE_ID", foreignKey = @jakarta.persistence.ForeignKey(name = "FK_CONTRACT_INVOICE_INVOICE"))
|
||||
@OnDelete(action = OnDeleteAction.SET_NULL)
|
||||
@ToString.Exclude
|
||||
private Invoice invoice;
|
||||
|
||||
/**
|
||||
* 发票金额
|
||||
*/
|
||||
@Column(name = "AMOUNT")
|
||||
private Double amount;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "SETUP_PERSON_ID", foreignKey = @jakarta.persistence.ForeignKey(name = "FK_CONTRACT_INVOICE_SETUP_PERSON"))
|
||||
@OnDelete(action = OnDeleteAction.SET_NULL)
|
||||
@ToString.Exclude
|
||||
private Employee setupPerson;
|
||||
/**
|
||||
* 提交日期
|
||||
*/
|
||||
@Column(name = "SETUP_DATE")
|
||||
private LocalDate setupDate;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "UPDATE_PERSON_ID", foreignKey = @jakarta.persistence.ForeignKey(name = "FK_CONTRACT_INVOICE_UPDATE_PERSON"))
|
||||
@OnDelete(action = OnDeleteAction.SET_NULL)
|
||||
@ToString.Exclude
|
||||
private Employee updatePerson;
|
||||
/**
|
||||
* 更新日期
|
||||
*/
|
||||
@Column(name = "UPDATE_DATE")
|
||||
private LocalDate updateDate;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@Column(name = "REMARK", length = 500)
|
||||
private String remark;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
ContractInvoice that = (ContractInvoice) o;
|
||||
return Objects.equals(id, that.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContractInvoiceVo toVo() {
|
||||
ContractInvoiceVo vo = new ContractInvoiceVo();
|
||||
vo.setId(id);
|
||||
vo.setCode(code);
|
||||
vo.setName(name);
|
||||
vo.setContractId(contract != null ? contract.getId() : null);
|
||||
vo.setInvoiceId(invoice != null ? invoice.getId() : null);
|
||||
vo.setAmount(amount);
|
||||
vo.setRemark(remark);
|
||||
vo.setSetupPersonId(setupPerson != null ? setupPerson.getId() : null);
|
||||
vo.setUpdatePersonId(updatePerson != null ? updatePerson.getId() : null);
|
||||
vo.setSetupDate(setupDate);
|
||||
vo.setUpdateDate(updateDate);
|
||||
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.ecep.contract.ds.contract.repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import com.ecep.contract.ds.MyRepository;
|
||||
import com.ecep.contract.ds.contract.model.ContractInvoice;
|
||||
|
||||
@Repository
|
||||
public interface ContractInvoiceRepository extends MyRepository<ContractInvoice, Integer> {
|
||||
|
||||
/**
|
||||
* 根据GUID查找发票的方法
|
||||
*
|
||||
* @param code 发票代码
|
||||
* @return 返回一个Optional<ContractInvoice>对象,可能包含找到的发票,也可能为空
|
||||
*/
|
||||
Optional<ContractInvoice> findByCode(String code);
|
||||
|
||||
/**
|
||||
* 根据合同ID查找发票列表
|
||||
*
|
||||
* @param contractId 合同ID
|
||||
* @return 发票列表
|
||||
*/
|
||||
List<ContractInvoice> findByContractId(Integer contractId);
|
||||
|
||||
/**
|
||||
* 根据发票ID查找合同发票
|
||||
*
|
||||
* @param invoiceId 发票ID
|
||||
* @return 合同发票实体
|
||||
*/
|
||||
Optional<ContractInvoice> findByInvoiceId(Integer invoiceId);
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package com.ecep.contract.ds.contract.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.CacheConfig;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.ecep.contract.IEntityService;
|
||||
import com.ecep.contract.QueryService;
|
||||
import com.ecep.contract.SpringApp;
|
||||
import com.ecep.contract.ds.company.service.InvoiceService;
|
||||
import com.ecep.contract.ds.contract.model.ContractInvoice;
|
||||
import com.ecep.contract.ds.contract.repository.ContractInvoiceRepository;
|
||||
import com.ecep.contract.ds.other.service.EmployeeService;
|
||||
import com.ecep.contract.service.VoableService;
|
||||
import com.ecep.contract.util.SpecificationUtils;
|
||||
import com.ecep.contract.vo.ContractInvoiceVo;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@CacheConfig(cacheNames = "contract-invoice")
|
||||
public class ContractInvoiceService implements IEntityService<ContractInvoice>, QueryService<ContractInvoiceVo>,
|
||||
VoableService<ContractInvoice, ContractInvoiceVo> {
|
||||
|
||||
@Autowired
|
||||
private ContractInvoiceRepository repository;
|
||||
|
||||
@Cacheable(key = "#p0")
|
||||
public ContractInvoiceVo findById(Integer id) {
|
||||
return repository.findById(id).map(ContractInvoice::toVo).orElse(null);
|
||||
}
|
||||
|
||||
@Cacheable(key = "'code-'+#p0")
|
||||
public ContractInvoiceVo findByCode(String code) {
|
||||
return repository.findByCode(code).map(ContractInvoice::toVo).orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据合同ID查找发票列表
|
||||
*
|
||||
* @param contractId 合同ID
|
||||
* @return 发票VO列表
|
||||
*/
|
||||
public List<ContractInvoiceVo> findByContractId(Integer contractId) {
|
||||
return repository.findByContractId(contractId)
|
||||
.stream()
|
||||
.map(ContractInvoice::toVo)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据发票ID查找合同发票
|
||||
*
|
||||
* @param invoiceId 发票ID
|
||||
* @return 合同发票VO
|
||||
*/
|
||||
public ContractInvoiceVo findByInvoiceId(Integer invoiceId) {
|
||||
return repository.findByInvoiceId(invoiceId)
|
||||
.map(ContractInvoice::toVo)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存发票
|
||||
*
|
||||
* @param invoice 发票实体
|
||||
* @return 保存后的发票VO
|
||||
*/
|
||||
@Override
|
||||
public ContractInvoice save(ContractInvoice invoice) {
|
||||
try {
|
||||
ContractInvoice saved = repository.save(invoice);
|
||||
return saved;
|
||||
} catch (Exception e) {
|
||||
log.error("保存发票失败", e);
|
||||
throw new RuntimeException("保存发票失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateByVo(ContractInvoice entity, ContractInvoiceVo vo) {
|
||||
if (entity == null || vo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
entity.setCode(vo.getCode());
|
||||
entity.setName(vo.getName());
|
||||
entity.setAmount(vo.getAmount());
|
||||
entity.setRemark(vo.getRemark());
|
||||
|
||||
// 处理关联实体 - 合同
|
||||
if (vo.getContractId() == null) {
|
||||
entity.setContract(null);
|
||||
} else {
|
||||
ContractService contractService = SpringApp.getBean(ContractService.class);
|
||||
if (entity.getContract() == null || !entity.getContract().getId().equals(vo.getContractId())) {
|
||||
entity.setContract(contractService.getById(vo.getContractId()));
|
||||
}
|
||||
}
|
||||
|
||||
// 处理关联实体 - 发票
|
||||
if (vo.getInvoiceId() == null) {
|
||||
entity.setInvoice(null);
|
||||
} else {
|
||||
InvoiceService invoiceService = SpringApp.getBean(InvoiceService.class);
|
||||
if (entity.getInvoice() == null || !entity.getInvoice().getId().equals(vo.getInvoiceId())) {
|
||||
entity.setInvoice(invoiceService.getById(vo.getInvoiceId()));
|
||||
}
|
||||
}
|
||||
|
||||
// 处理关联实体 - 发票
|
||||
EmployeeService personService = SpringApp.getBean(EmployeeService.class);
|
||||
if (vo.getSetupPersonId() == null) {
|
||||
entity.setSetupPerson(null);
|
||||
} else {
|
||||
if (entity.getSetupPerson() == null || !entity.getSetupPerson().getId().equals(vo.getSetupPersonId())) {
|
||||
entity.setSetupPerson(personService.getById(vo.getSetupPersonId()));
|
||||
}
|
||||
}
|
||||
if (vo.getUpdatePersonId() == null) {
|
||||
entity.setUpdatePerson(null);
|
||||
} else {
|
||||
if (entity.getUpdatePerson() == null || !entity.getUpdatePerson().getId().equals(vo.getUpdatePersonId())) {
|
||||
entity.setUpdatePerson(personService.getById(vo.getUpdatePersonId()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<ContractInvoiceVo> findAll(JsonNode paramsNode, Pageable pageable) {
|
||||
Specification<ContractInvoice> spec = buildParameterSpecification(paramsNode);
|
||||
Page<ContractInvoice> page = repository.findAll(spec, pageable);
|
||||
return page.map(ContractInvoice::toVo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建参数规范
|
||||
*
|
||||
* @param paramsNode 参数节点
|
||||
* @return 规范对象
|
||||
*/
|
||||
protected Specification<ContractInvoice> buildParameterSpecification(JsonNode paramsNode) {
|
||||
Specification<ContractInvoice> spec = null;
|
||||
|
||||
// 处理基本字段参数
|
||||
spec = SpecificationUtils.andFieldEqualParam(spec, paramsNode, "name", "code");
|
||||
|
||||
// 处理关联实体参数
|
||||
spec = SpecificationUtils.andParam(spec, paramsNode, "contract", "invoice", "setupPerson", "updatePerson");
|
||||
|
||||
// 处理搜索文本
|
||||
if (paramsNode.has("searchText")) {
|
||||
String searchText = paramsNode.get("searchText").asText();
|
||||
if (searchText != null && !searchText.isEmpty()) {
|
||||
spec = SpecificationUtils.and(spec, getSpecification(searchText));
|
||||
}
|
||||
}
|
||||
|
||||
return spec;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContractInvoice getById(Integer id) {
|
||||
return repository.findById(id).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<ContractInvoice> findAll(Specification<ContractInvoice> spec, Pageable pageable) {
|
||||
return repository.findAll(spec, pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Specification<ContractInvoice> getSpecification(String searchText) {
|
||||
return (root, query, builder) -> {
|
||||
return builder.or(
|
||||
builder.like(root.get("name"), "%" + searchText + "%"),
|
||||
builder.like(root.get("code"), "%" + searchText + "%"),
|
||||
builder.like(root.get("remark"), "%" + searchText + "%"));
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(ContractInvoice entity) {
|
||||
try {
|
||||
repository.delete(entity);
|
||||
} catch (Exception e) {
|
||||
log.error("删除合同发票失败", e);
|
||||
throw new RuntimeException("删除合同发票失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,9 @@ package com.ecep.contract.ds.customer.model;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.hibernate.annotations.OnDelete;
|
||||
import org.hibernate.annotations.OnDeleteAction;
|
||||
|
||||
import com.ecep.contract.ds.company.model.Company;
|
||||
import com.ecep.contract.ds.contract.model.Contract;
|
||||
import com.ecep.contract.ds.contract.model.ContractBasedEntity;
|
||||
@@ -22,6 +25,7 @@ import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.ForeignKey;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
@@ -42,7 +46,8 @@ public class SalesOrder implements IdentityEntity, BasedEntity, ContractBasedEnt
|
||||
private Integer id;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "CONTRACT_ID")
|
||||
@JoinColumn(name = "CONTRACT_ID", foreignKey = @ForeignKey(name = "FK_SALES_ORDER_CONTRACT"))
|
||||
@OnDelete(action = OnDeleteAction.CASCADE)
|
||||
@ToString.Exclude
|
||||
private Contract contract;
|
||||
|
||||
@@ -50,7 +55,8 @@ public class SalesOrder implements IdentityEntity, BasedEntity, ContractBasedEnt
|
||||
* 业务员
|
||||
*/
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "EMPLOYEE_ID")
|
||||
@JoinColumn(name = "EMPLOYEE_ID", foreignKey = @ForeignKey(name = "FK_SALES_ORDER_EMPLOYEE"))
|
||||
@OnDelete(action = OnDeleteAction.SET_NULL)
|
||||
@ToString.Exclude
|
||||
private Employee employee;
|
||||
|
||||
@@ -73,10 +79,20 @@ public class SalesOrder implements IdentityEntity, BasedEntity, ContractBasedEnt
|
||||
* 客户
|
||||
*/
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "CUSTOMER_ID")
|
||||
@JoinColumn(name = "CUSTOMER_ID", foreignKey = @ForeignKey(name = "FK_SALES_ORDER_CUSTOMER"))
|
||||
@OnDelete(action = OnDeleteAction.SET_NULL)
|
||||
@ToString.Exclude
|
||||
private Company customer;
|
||||
|
||||
/**
|
||||
* 客户联系人
|
||||
*/
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "CUSTOMER_PERSON_ID", foreignKey = @ForeignKey(name = "FK_SALES_ORDER_CUSTOMER_PERSON"))
|
||||
@OnDelete(action = OnDeleteAction.SET_NULL)
|
||||
@ToString.Exclude
|
||||
private Employee customerPerson;
|
||||
|
||||
/**
|
||||
* 客户地址
|
||||
*/
|
||||
@@ -87,7 +103,8 @@ public class SalesOrder implements IdentityEntity, BasedEntity, ContractBasedEnt
|
||||
* 制单人
|
||||
*/
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "MAKER_ID")
|
||||
@JoinColumn(name = "MAKER_ID", foreignKey = @ForeignKey(name = "FK_SALES_ORDER_MAKER"))
|
||||
@OnDelete(action = OnDeleteAction.SET_NULL)
|
||||
@ToString.Exclude
|
||||
private Employee maker;
|
||||
|
||||
@@ -100,7 +117,8 @@ public class SalesOrder implements IdentityEntity, BasedEntity, ContractBasedEnt
|
||||
* 审核人
|
||||
*/
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "VERIFIER_ID")
|
||||
@JoinColumn(name = "VERIFIER_ID", foreignKey = @ForeignKey(name = "FK_SALES_ORDER_VERIFIER"))
|
||||
@OnDelete(action = OnDeleteAction.SET_NULL)
|
||||
@ToString.Exclude
|
||||
private Employee verifier;
|
||||
/**
|
||||
@@ -109,8 +127,12 @@ public class SalesOrder implements IdentityEntity, BasedEntity, ContractBasedEnt
|
||||
@Column(name = "VERIFIED_DATE")
|
||||
private LocalDate verifierDate;
|
||||
|
||||
/**
|
||||
* 修改人
|
||||
*/
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "MODIFIER_ID")
|
||||
@JoinColumn(name = "MODIFIER_ID", foreignKey = @ForeignKey(name = "FK_SALES_ORDER_MODIFIER"))
|
||||
@OnDelete(action = OnDeleteAction.SET_NULL)
|
||||
@ToString.Exclude
|
||||
private Employee modifier;
|
||||
|
||||
@@ -121,7 +143,8 @@ public class SalesOrder implements IdentityEntity, BasedEntity, ContractBasedEnt
|
||||
* 关闭人
|
||||
*/
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "CLOSER_ID")
|
||||
@JoinColumn(name = "CLOSER_ID", foreignKey = @ForeignKey(name = "FK_SALES_ORDER_CLOSER"))
|
||||
@OnDelete(action = OnDeleteAction.SET_NULL)
|
||||
@ToString.Exclude
|
||||
private Employee closer;
|
||||
|
||||
@@ -170,7 +193,7 @@ public class SalesOrder implements IdentityEntity, BasedEntity, ContractBasedEnt
|
||||
vo.setModifierDate(modifierDate);
|
||||
vo.setCloserDate(closerDate);
|
||||
vo.setDescription(description);
|
||||
|
||||
|
||||
// 关联对象ID映射
|
||||
if (contract != null) {
|
||||
vo.setContractId(contract.getId());
|
||||
|
||||
Reference in New Issue
Block a user