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:
2025-10-16 15:47:33 +08:00
parent cf0a7e18ea
commit 71a358fa77
28 changed files with 1274 additions and 40 deletions

View File

@@ -6,12 +6,12 @@
<parent>
<groupId>com.ecep.contract</groupId>
<artifactId>Contract-Manager</artifactId>
<version>0.0.102-SNAPSHOT</version>
<version>0.0.122-SNAPSHOT</version>
</parent>
<groupId>com.ecep.contract</groupId>
<artifactId>client</artifactId>
<version>0.0.102-SNAPSHOT</version>
<version>0.0.122-SNAPSHOT</version>
<properties>
<maven.compiler.source>${java.version}</maven.compiler.source>
@@ -22,7 +22,7 @@
<dependency>
<groupId>com.ecep.contract</groupId>
<artifactId>common</artifactId>
<version>0.0.102-SNAPSHOT</version>
<version>0.0.122-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>

View File

@@ -0,0 +1,24 @@
package com.ecep.contract.controller.contract;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ecep.contract.controller.AbstEntityManagerSkin;
import com.ecep.contract.vm.ContractInvoiceViewModel;
import com.ecep.contract.vo.ContractInvoiceVo;
public class ContractInvoiceManagerSkin extends
AbstEntityManagerSkin<ContractInvoiceVo, ContractInvoiceViewModel, ContractInvoiceManagerSkin, ContractInvoiceManagerWindowController> {
private static final Logger logger = LoggerFactory.getLogger(ContractInvoiceManagerSkin.class);
public ContractInvoiceManagerSkin(ContractInvoiceManagerWindowController controller) {
super(controller);
}
@Override
public void initializeTable() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'initializeTable'");
}
}

View File

@@ -0,0 +1,48 @@
package com.ecep.contract.controller.contract;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.ecep.contract.controller.AbstManagerWindowController;
import com.ecep.contract.service.ContractInvoiceService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vm.ContractInvoiceViewModel;
import com.ecep.contract.vo.ContractInvoiceVo;
import javafx.scene.control.TableColumn;
import javafx.stage.Stage;
@Lazy
@Scope("prototype")
@Component
@FxmlPath("/ui/contract/contract-invoice-manager.fxml")
public class ContractInvoiceManagerWindowController extends AbstManagerWindowController<ContractInvoiceVo, ContractInvoiceViewModel, ContractInvoiceManagerSkin> {
public TableColumn<ContractInvoiceViewModel, Number> idColumn;
public TableColumn<ContractInvoiceViewModel, String> codeColumn;
public TableColumn<ContractInvoiceViewModel, String> nameColumn;
public TableColumn<ContractInvoiceViewModel, Integer> contractColumn;
public TableColumn<ContractInvoiceViewModel, Integer> contractItemColumn;
@Autowired
private ContractInvoiceService contractInvoiceService;
@Override
public ContractInvoiceService getViewModelService() {
return contractInvoiceService;
}
@Override
protected ContractInvoiceManagerSkin createDefaultSkin() {
ContractInvoiceManagerSkin skin = new ContractInvoiceManagerSkin(this);
return skin;
}
@Override
public void show(Stage stage) {
super.show(stage);
getTitle().set("合同发票管理");
}
}

View File

@@ -0,0 +1,50 @@
package com.ecep.contract.controller.contract;
import org.springframework.stereotype.Component;
import com.ecep.contract.controller.AbstEntityController;
import com.ecep.contract.controller.tab.ContractInvoiceTabSkinBase;
import com.ecep.contract.service.ContractInvoiceService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vm.ContractInvoiceViewModel;
import com.ecep.contract.vo.ContractInvoiceVo;
import javafx.collections.ObservableList;
import javafx.scene.control.Button;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.stage.Window;
@FxmlPath("/ui/contract/contract-invoice.fxml")
@Component
public class ContractInvoiceWindowController
extends AbstEntityController<ContractInvoiceVo, ContractInvoiceViewModel> {
public static void show(ContractInvoiceViewModel viewModel, Window owner) {
show(ContractInvoiceWindowController.class, viewModel, owner);
}
public Tab baseInfoTab;
public TextField codeField;
public TextField nameField;
public TextField invoiceField;
public TextField amountField;
public TextArea remarkField;
public Button saveBtn;
public Button cancelBtn;
@Override
public ContractInvoiceService getViewModelService() {
return getBean(ContractInvoiceService.class);
}
@Override
protected void registerTabSkins() {
TabPane tabPane = baseInfoTab.getTabPane();
ObservableList<Tab> tabs = tabPane.getTabs();
registerTabSkin(baseInfoTab, tab -> new ContractInvoiceTabSkinBase(this));
}
}

View File

@@ -3,24 +3,21 @@ package com.ecep.contract.controller.contract;
import java.time.LocalDate;
import com.ecep.contract.ContractPayWay;
import com.ecep.contract.controller.contract.sale_order.SalesOrderWindowController;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.table.cell.CompanyTableCell;
import com.ecep.contract.controller.table.cell.EmployeeTableCell;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.controller.contract.sale_order.SalesOrderWindowController;
import com.ecep.contract.controller.table.cell.LocalDateFieldTableCell;
import com.ecep.contract.converter.EmployeeStringConverter;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.SaleOrdersService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vm.SalesOrderViewModel;
import com.ecep.contract.vo.SalesOrderVo;
import javafx.scene.control.Button;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import lombok.Setter;
/**
@@ -56,7 +53,6 @@ public class ContractTabSkinSaleOrders
public TableColumn<SalesOrderViewModel, LocalDate> verifierDateColumn;
public TableColumn<SalesOrderViewModel, Number> refIdColumn;
public TableColumn<SalesOrderViewModel, Number> taxRateColumn;
public TableColumn<SalesOrderViewModel, Integer> customerColumn;
public TableColumn<SalesOrderViewModel, String> customerAddressColumn;
/**
* 修改人, Employee
@@ -70,8 +66,6 @@ public class ContractTabSkinSaleOrders
public TableColumn<SalesOrderViewModel, LocalDate> closerDateColumn;
public TableColumn<SalesOrderViewModel, String> descriptionColumn;
public MenuItem subContractTable_menu_refresh;
public TextField contractSearchKeyField;
public Button searchBtn;
private Tab tab;
public ContractTabSkinSaleOrders(ContractWindowController controller, Tab tab) {
@@ -98,13 +92,7 @@ public class ContractTabSkinSaleOrders
@Override
public void initializeTab() {
contractSearchKeyField.setOnKeyReleased(event -> {
if (event.getCode() == KeyCode.ENTER) {
searchBtn.fire();
}
});
searchBtn.setOnAction(this::onTableRefreshAction);
super.initializeTab();
subContractTable_menu_refresh.setOnAction(this::onTableRefreshAction);
idColumn.setCellValueFactory(param -> param.getValue().getId());
@@ -123,8 +111,6 @@ public class ContractTabSkinSaleOrders
// 设置新增字段的单元格值工厂和工厂类
refIdColumn.setCellValueFactory(param -> param.getValue().getRefId());
taxRateColumn.setCellValueFactory(param -> param.getValue().getTaxRate());
customerColumn.setCellValueFactory(param -> param.getValue().getCustomer());
customerColumn.setCellFactory(CompanyTableCell.forTableColumn(getCompanyService()));
customerAddressColumn.setCellValueFactory(param -> param.getValue().getCustomerAddress());
modifierColumn.setCellValueFactory(param -> param.getValue().getModifier());
modifierColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
@@ -136,7 +122,7 @@ public class ContractTabSkinSaleOrders
closerDateColumn.setCellFactory(LocalDateFieldTableCell.forTableColumn());
descriptionColumn.setCellValueFactory(param -> param.getValue().getDescription());
super.initializeTab();
}
@Override

View File

@@ -13,6 +13,7 @@ import com.ecep.contract.controller.AbstEntityController;
import com.ecep.contract.controller.company.CompanyWindowController;
import com.ecep.contract.controller.tab.ContractTabSkinBase;
import com.ecep.contract.controller.tab.ContractTabSkinFiles;
import com.ecep.contract.controller.tab.ContractTabSkinInvoices;
import com.ecep.contract.controller.tab.ContractTabSkinItemsV2;
import com.ecep.contract.controller.tab.ContractTabSkinPayPlan;
import com.ecep.contract.controller.tab.ContractTabSkinSubContract;
@@ -154,7 +155,7 @@ public class ContractWindowController
registerTabSkin(saleOrderTab, tab -> new ContractTabSkinSaleOrders(this, tab));
Tab invoiceTab = new Tab("发票");
tabs.add(invoiceTab);
// registerTabSkin(invoiceTab, tab -> new ContractTabSkinInvoices(this, tab));
registerTabSkin(invoiceTab, tab -> new ContractTabSkinInvoices(this, tab));
tabs.add(new Tab("出库单"));
tabs.add(new Tab("发货单"));
tabs.add(new Tab("签收单"));

View File

@@ -0,0 +1,31 @@
package com.ecep.contract.controller.tab;
import com.ecep.contract.controller.contract.ContractInvoiceWindowController;
import com.ecep.contract.vm.ContractInvoiceViewModel;
import com.ecep.contract.vo.ContractInvoiceVo;
import javafx.scene.control.Tab;
/**
* 合同发票基础信息标签页皮肤
*/
public class ContractInvoiceTabSkinBase
extends AbstEntityBasedTabSkin<ContractInvoiceWindowController, ContractInvoiceVo, ContractInvoiceViewModel>
implements TabSkin {
public ContractInvoiceTabSkinBase(ContractInvoiceWindowController controller) {
super(controller);
}
@Override
public Tab getTab() {
return controller.baseInfoTab;
}
@Override
public void initializeTab() {
// 可以在这里进行标签页的初始化工作
controller.codeField.textProperty().bindBidirectional(viewModel.getCode());
controller.nameField.textProperty().bindBidirectional(viewModel.getName());
controller.remarkField.textProperty().bindBidirectional(viewModel.getRemark());
}
}

View File

@@ -0,0 +1,124 @@
package com.ecep.contract.controller.tab;
import java.time.LocalDate;
import java.util.Objects;
import org.controlsfx.glyphfont.Glyph;
import org.springframework.stereotype.Component;
import com.ecep.contract.controller.contract.AbstContractTableTabSkin;
import com.ecep.contract.controller.contract.ContractInvoiceWindowController;
import com.ecep.contract.controller.contract.ContractWindowController;
import com.ecep.contract.controller.table.EditableEntityTableTabSkin;
import com.ecep.contract.controller.table.cell.ContractInvoiceTableCell;
import com.ecep.contract.controller.table.cell.EmployeeTableCell;
import com.ecep.contract.controller.table.cell.NumberTableCell;
import com.ecep.contract.model.IdentityEntity;
import com.ecep.contract.service.ContractInvoiceService;
import com.ecep.contract.service.ContractService;
import com.ecep.contract.service.InvoiceService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.ContractInvoiceViewModel;
import com.ecep.contract.vo.ContractInvoiceVo;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleStringProperty;
import javafx.event.ActionEvent;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.Region;
import javafx.util.StringConverter;
import javafx.util.converter.CurrencyStringConverter;
@FxmlPath("/ui/contract/contract-tab-invoices.fxml")
public class ContractTabSkinInvoices
extends AbstContractTableTabSkin<ContractInvoiceVo, ContractInvoiceViewModel>
implements TabSkin, EditableEntityTableTabSkin<ContractInvoiceVo, ContractInvoiceViewModel> {
private Tab tab;
public TableColumn<ContractInvoiceViewModel, Number> idColumn;
public TableColumn<ContractInvoiceViewModel, String> codeColumn;
public TableColumn<ContractInvoiceViewModel, String> nameColumn;
public TableColumn<ContractInvoiceViewModel, Integer> invoiceColumn;
public TableColumn<ContractInvoiceViewModel, Number> amountColumn;
public TableColumn<ContractInvoiceViewModel, String> remarkColumn;
public TableColumn<ContractInvoiceViewModel, Integer> creatorColumn;
public TableColumn<ContractInvoiceViewModel, LocalDate> createDateColumn;
public TableColumn<ContractInvoiceViewModel, Integer> updaterColumn;
public TableColumn<ContractInvoiceViewModel, LocalDate> updateDateColumn;
public Label totalAmountLabel;
public ContractTabSkinInvoices(ContractWindowController controller, Tab tab) {
super(controller);
this.tab = tab;
}
@Override
public Tab getTab() {
return tab;
}
@Override
protected ContractInvoiceService getViewModelService() {
return getBean(ContractInvoiceService.class);
}
@Override
public void initializeTable() {
super.initializeTable();
getTableView().setEditable(true);
// 初始化列
idColumn.setCellValueFactory(new PropertyValueFactory<>("id"));
idColumn.setPrefWidth(50);
codeColumn.setCellValueFactory(new PropertyValueFactory<>("code"));
codeColumn.setPrefWidth(120);
codeColumn.setCellFactory(TextFieldTableCell.forTableColumn());
nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
nameColumn.setPrefWidth(200);
nameColumn.setCellFactory(TextFieldTableCell.forTableColumn());
// 发票列
invoiceColumn.setCellValueFactory(new PropertyValueFactory<>("invoice"));
invoiceColumn.setPrefWidth(100);
invoiceColumn.setCellFactory(ContractInvoiceTableCell.forTableColumn(getCachedBean(InvoiceService.class)));
// 金额列
amountColumn.setCellValueFactory(new PropertyValueFactory<>("amount"));
amountColumn.setPrefWidth(100);
amountColumn.setCellFactory(NumberTableCell.forTableColumn(new CurrencyStringConverter(getLocale())));
remarkColumn.setCellValueFactory(new PropertyValueFactory<>("remark"));
remarkColumn.setPrefWidth(150);
remarkColumn.setCellFactory(TextFieldTableCell.forTableColumn());
// 创建人列
creatorColumn.setCellValueFactory(new PropertyValueFactory<>("creator"));
creatorColumn.setPrefWidth(80);
creatorColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
createDateColumn.setCellValueFactory(new PropertyValueFactory<>("createDate"));
createDateColumn.setPrefWidth(120);
// 更新人列
updaterColumn.setCellValueFactory(new PropertyValueFactory<>("updater"));
updaterColumn.setPrefWidth(80);
updaterColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
updateDateColumn.setCellValueFactory(new PropertyValueFactory<>("updateDate"));
updateDateColumn.setPrefWidth(120);
}
}

View File

@@ -4,6 +4,8 @@ import java.text.NumberFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import javax.naming.Binding;
import org.springframework.data.domain.Pageable;
import com.ecep.contract.ContractPayWay;
@@ -28,6 +30,7 @@ import com.ecep.contract.vo.InventoryVo;
import com.ecep.contract.vo.PurchaseOrderItemVo;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab;
@@ -196,6 +199,8 @@ public class ContractTabSkinItemsV2
}
showInOwner(InventoryWindowController.class, InventoryViewModel.from(inventory));
});
showInventory.visibleProperty().bind(
Bindings.select(getTableView().getSelectionModel().selectedItemProperty(), "inventory", "value").isNotNull());
contextMenu.getItems().addAll(showInventory);
}

View File

@@ -0,0 +1,59 @@
package com.ecep.contract.controller.table.cell;
import static com.ecep.contract.SpringApp.getBean;
import com.ecep.contract.service.InvoiceService;
import com.ecep.contract.vo.InvoiceVo;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
import lombok.NoArgsConstructor;
/**
* 合同发票表格单元格
* 用于在表格中显示合同发票信息
*/
@NoArgsConstructor
public class ContractInvoiceTableCell<V> extends AsyncUpdateTableCell<V, Integer, InvoiceVo> {
/**
* 创建一个ContractInvoiceTableCell的TableCell工厂自动获取InvoiceService实例
*
* @param <S> 表格行类型
* @return TableCell工厂回调
*/
public static <S> Callback<TableColumn<S, Integer>, TableCell<S, Integer>> forTableColumn() {
return forTableColumn(getBean(InvoiceService.class));
}
/**
* 创建一个ContractInvoiceTableCell的TableCell工厂使用提供的InvoiceService实例
*
* @param <S> 表格行类型
* @param service InvoiceService实例
* @return TableCell工厂回调
*/
public static <S> Callback<TableColumn<S, Integer>, TableCell<S, Integer>> forTableColumn(InvoiceService service) {
return param -> new ContractInvoiceTableCell<S>(service);
}
/**
* 使用指定的InvoiceService创建ContractInvoiceTableCell
*
* @param invoiceService InvoiceService实例
*/
public ContractInvoiceTableCell(InvoiceService invoiceService) {
setService(invoiceService);
}
@Override
protected InvoiceService getServiceBean() {
return getBean(InvoiceService.class);
}
@Override
public String format(InvoiceVo entity) {
return entity.getCode();
}
}

View File

@@ -0,0 +1,67 @@
package com.ecep.contract.service;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import com.ecep.contract.vm.ContractInvoiceViewModel;
import com.ecep.contract.vo.ContractInvoiceVo;
@Service
@CacheConfig(cacheNames = "contract-invoice")
public class ContractInvoiceService extends QueryService<ContractInvoiceVo, ContractInvoiceViewModel> {
@Cacheable(key = "#p0")
@Override
public ContractInvoiceVo findById(Integer id) {
return super.findById(id);
}
@Caching(evict = {
@CacheEvict(key = "#p0.id"),
@CacheEvict(key = "'code-'+#p0.code")
})
public ContractInvoiceVo save(ContractInvoiceVo invoice) {
return super.save(invoice);
}
@Caching(evict = {
@CacheEvict(key = "#p0.id"),
@CacheEvict(key = "'code-'+#p0.code")
})
public void delete(ContractInvoiceVo invoice) {
super.delete(invoice);
}
@Cacheable(key = "'code-'+#p0")
public ContractInvoiceVo findByCode(String code) {
try {
return async("findByCode", code, String.class).handle((response, ex) -> {
if (ex != null) {
throw new RuntimeException("远程方法+findByCode+调用失败", ex);
}
if (response != null) {
return updateValue(createNewEntity(), response);
}
return null;
}).get();
} catch (Exception e) {
throw new RuntimeException("查找合同发票 " + code + " 时发生错误", e);
}
}
public ContractInvoiceVo findByContractId(Integer contractId) {
try {
return async("findByContractId", contractId, Integer.class).handle((response, ex) -> {
if (response != null) {
return updateValue(createNewEntity(), response);
}
return null;
}).get();
} catch (Exception e) {
throw new RuntimeException("查找合同ID为 " + contractId + " 的发票时发生错误", e);
}
}
}

View File

@@ -10,7 +10,6 @@ import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import com.ecep.contract.model.EmployeeRole;
import com.ecep.contract.vm.EmployeeViewModel;
import com.ecep.contract.vo.EmployeeRoleVo;
import com.ecep.contract.vo.EmployeeVo;

View File

@@ -0,0 +1,119 @@
package com.ecep.contract.vm;
import java.time.LocalDate;
import java.util.Objects;
import com.ecep.contract.util.NumberUtils;
import com.ecep.contract.vo.ContractInvoiceVo;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public class ContractInvoiceViewModel extends IdentityViewModel<ContractInvoiceVo> {
private final SimpleStringProperty code = new SimpleStringProperty();
private final SimpleStringProperty name = new SimpleStringProperty();
/**
* 关联的合同对象, Contract
*/
private final SimpleObjectProperty<Integer> contract = new SimpleObjectProperty<>();
/**
* 关联的发票对象, Invoice
*/
private final SimpleObjectProperty<Integer> invoice = new SimpleObjectProperty<>();
/**
* 发票金额
*/
private final SimpleObjectProperty<Double> amount = new SimpleObjectProperty<>();
/**
* 备注
*/
private final SimpleStringProperty remark = new SimpleStringProperty();
private final SimpleObjectProperty<LocalDate> createDate = new SimpleObjectProperty<>();
private final SimpleObjectProperty<LocalDate> updateDate = new SimpleObjectProperty<>();
private final SimpleObjectProperty<Integer> creator = new SimpleObjectProperty<>();
private final SimpleObjectProperty<Integer> updater = new SimpleObjectProperty<>();
@Override
protected void updateFrom(ContractInvoiceVo v) {
super.updateFrom(v);
code.set(v.getCode());
name.set(v.getName());
contract.set(v.getContractId());
invoice.set(v.getInvoiceId());
amount.set(v.getAmount() != null ? v.getAmount() : 0.0);
remark.set(v.getRemark());
updateDate.set(v.getUpdateDate());
creator.set(v.getSetupPersonId());
updater.set(v.getUpdatePersonId());
}
@Override
public boolean copyTo(ContractInvoiceVo v) {
boolean modified = super.copyTo(v);
if (!Objects.equals(code.get(), v.getCode())) {
v.setCode(code.get());
modified = true;
}
if (!Objects.equals(name.get(), v.getName())) {
v.setName(name.get());
modified = true;
}
if (!Objects.equals(contract.get(), v.getContractId())) {
v.setContractId(contract.get());
modified = true;
}
if (!Objects.equals(invoice.get(), v.getInvoiceId())) {
v.setInvoiceId(invoice.get());
modified = true;
}
if (!NumberUtils.equals(amount.get(), v.getAmount())) {
v.setAmount(amount.get());
modified = true;
}
if (!Objects.equals(remark.get(), v.getRemark())) {
v.setRemark(remark.get());
modified = true;
}
if (!Objects.equals(creator.get(), v.getSetupPersonId())) {
v.setSetupPersonId(creator.get());
modified = true;
}
if (!Objects.equals(updater.get(), v.getUpdatePersonId())) {
v.setUpdatePersonId(updater.get());
modified = true;
}
if (!Objects.equals(createDate.get(), v.getSetupDate())) {
v.setSetupDate(createDate.get());
modified = true;
}
if (!Objects.equals(updateDate.get(), v.getUpdateDate())) {
v.setUpdateDate(updateDate.get());
modified = true;
}
return modified;
}
/**
* 从VO对象创建ViewModel实例
*
* @param vo 发票VO对象
* @return 发票ViewModel实例
*/
public static ContractInvoiceViewModel from(ContractInvoiceVo vo) {
ContractInvoiceViewModel viewModel = new ContractInvoiceViewModel();
viewModel.updateFrom(vo);
return viewModel;
}
}

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<BorderPane id="root" fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0" prefWidth="900.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1">
<top>
<HBox alignment="CENTER_LEFT" prefHeight="40.0" prefWidth="900.0" spacing="10.0">
<Button mnemonicParsing="false" onAction="#onTableCreateNewAction" text="新建">
<HBox.margin>
<Insets left="10.0" />
</HBox.margin>
</Button>
<Button mnemonicParsing="false" onAction="#onTableRefreshAction" text="刷新">
<HBox.margin>
<Insets left="10.0" />
</HBox.margin>
</Button>
<Button mnemonicParsing="false" onAction="#onTableDeleteAction" text="删除">
<HBox.margin>
<Insets left="10.0" />
</HBox.margin>
</Button>
<TextField fx:id="searchField" promptText="搜索..." HBox.hgrow="ALWAYS">
<HBox.margin>
<Insets left="10.0" right="10.0" />
</HBox.margin>
</TextField>
</HBox>
</top>
<center>
<TableView fx:id="table" prefHeight="600.0" prefWidth="900.0">
<columns>
<TableColumn fx:id="idColumn" prefWidth="80.0" text="ID" />
<TableColumn fx:id="codeColumn" prefWidth="120.0" text="发票编号" />
<TableColumn fx:id="nameColumn" prefWidth="150.0" text="发票名称" />
<TableColumn fx:id="contractColumn" prefWidth="100.0" text="合同ID" />
<TableColumn fx:id="contractItemColumn" prefWidth="100.0" text="合同项目ID" />
</columns>
</TableView>
</center>
<bottom>
<HBox alignment="CENTER" prefHeight="40.0" prefWidth="900.0" spacing="10.0">
<Label fx:id="pageInfoLabel" text="第 1 页,共 1 页" />
<Button mnemonicParsing="false" onAction="#onPrevPageAction" text="上一页" />
<Button mnemonicParsing="false" onAction="#onNextPageAction" text="下一页" />
<Button mnemonicParsing="false" onAction="#onFirstPageAction" text="首页" />
<Button mnemonicParsing="false" onAction="#onLastPageAction" text="末页" />
<TextField fx:id="pageField" prefWidth="60.0" text="1" />
<Button mnemonicParsing="false" onAction="#onGotoPageAction" text="跳转" />
</HBox>
</bottom>
</BorderPane>

View File

@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.DatePicker?>
<?import javafx.scene.control.ComboBox?>
<BorderPane id="root" fx:id="root" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="700.0" prefWidth="900.0" xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.ecep.contract.controller.contract.ContractInvoiceWindowController">
<top>
<HBox alignment="CENTER_LEFT" prefHeight="40.0" prefWidth="900.0" spacing="10.0">
<Button fx:id="saveBtn" mnemonicParsing="false" text="保存">
<HBox.margin>
<Insets left="10.0" />
</HBox.margin>
</Button>
<Button mnemonicParsing="false" onAction="#refresh" text="刷新">
<HBox.margin>
<Insets left="10.0" />
</HBox.margin>
</Button>
<Label fx:id="statusLabel" prefWidth="200.0">
<HBox.margin>
<Insets left="10.0" />
</HBox.margin>
</Label>
</HBox>
</top>
<center>
<TabPane fx:id="tabPane" prefHeight="660.0" prefWidth="900.0" tabClosingPolicy="UNAVAILABLE">
<Tab fx:id="baseInfoTab" text="基本信息">
<ScrollPane fitToWidth="true">
<VBox maxWidth="800.0" prefWidth="800.0">
<GridPane hgap="10.0" vgap="10.0">
<columnConstraints>
<ColumnConstraints halignment="RIGHT" hgrow="NEVER" prefWidth="100.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="0.0" prefWidth="200.0" />
<ColumnConstraints halignment="RIGHT" hgrow="NEVER" prefWidth="100.0" />
<ColumnConstraints hgrow="ALWAYS" minWidth="0.0" prefWidth="200.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints prefHeight="30.0" vgrow="NEVER" />
<RowConstraints prefHeight="30.0" vgrow="NEVER" />
<RowConstraints prefHeight="30.0" vgrow="NEVER" />
<RowConstraints prefHeight="30.0" vgrow="NEVER" />
<RowConstraints prefHeight="30.0" vgrow="NEVER" />
<RowConstraints prefHeight="30.0" vgrow="NEVER" />
<RowConstraints prefHeight="100.0" vgrow="ALWAYS" />
</rowConstraints>
<Label text="ID:" GridPane.columnIndex="0" GridPane.rowIndex="0" />
<Label fx:id="idLabel" GridPane.columnIndex="1" GridPane.rowIndex="0" />
<Label text="发票编号:" GridPane.columnIndex="2" GridPane.rowIndex="0" />
<TextField fx:id="codeField" GridPane.columnIndex="3" GridPane.rowIndex="0" />
<Label text="发票名称:" GridPane.columnIndex="0" GridPane.rowIndex="1" />
<TextField fx:id="nameField" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.rowIndex="1" />
<Label text="合同ID:" GridPane.columnIndex="0" GridPane.rowIndex="2" />
<TextField fx:id="contractField" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<Label text="合同项目ID:" GridPane.columnIndex="2" GridPane.rowIndex="2" />
<TextField fx:id="contractItemField" GridPane.columnIndex="3" GridPane.rowIndex="2" />
<Label text="发票ID:" GridPane.columnIndex="0" GridPane.rowIndex="3" />
<TextField fx:id="invoiceField" GridPane.columnIndex="1" GridPane.rowIndex="3" />
<Label text="发票金额:" GridPane.columnIndex="2" GridPane.rowIndex="3" />
<TextField fx:id="amountField" GridPane.columnIndex="3" GridPane.rowIndex="3" />
<Label text="发票日期:" GridPane.columnIndex="0" GridPane.rowIndex="4" />
<DatePicker fx:id="invoiceDatePicker" GridPane.columnIndex="1" GridPane.rowIndex="4" />
<Label text="发票类型:" GridPane.columnIndex="2" GridPane.rowIndex="4" />
<ComboBox fx:id="invoiceTypeComboBox" GridPane.columnIndex="3" GridPane.rowIndex="4" />
<Label text="发票状态:" GridPane.columnIndex="0" GridPane.rowIndex="5" />
<ComboBox fx:id="statusComboBox" GridPane.columnIndex="1" GridPane.rowIndex="5" />
<Label text="用友ID:" GridPane.columnIndex="2" GridPane.rowIndex="5" />
<TextField fx:id="ufIdField" GridPane.columnIndex="3" GridPane.rowIndex="5" />
<Label text="开票单位:" GridPane.columnIndex="0" GridPane.rowIndex="6" />
<TextField fx:id="invoiceUnitField" GridPane.columnIndex="1" GridPane.rowIndex="6" />
<Label text="接收单位:" GridPane.columnIndex="2" GridPane.rowIndex="6" />
<TextField fx:id="receiveUnitField" GridPane.columnIndex="3" GridPane.rowIndex="6" />
<Label text="备注:" GridPane.columnIndex="0" GridPane.rowIndex="7" />
<TextArea fx:id="remarkTextArea" GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.rowIndex="7" />
</GridPane>
<VBox.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</VBox.margin>
</VBox>
</ScrollPane>
</Tab>
<Tab fx:id="entityTab" text="实体信息" />
</TabPane>
</center>
</BorderPane>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane xmlns="http://javafx.com/javafx/17" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.ecep.contract.controller.tab.ContractTabSkinInvoices">
<children>
<HBox spacing="3.0">
<children>
<TextField fx:id="searchKeyField" promptText="搜索..." />
<Button fx:id="searchBtn" text="搜索"/>
<Button fx:id="refreshBtn" text="刷新"/>
<Button fx:id="addBtn" text="新增发票"/>
</children>
<padding>
<Insets bottom="10.0" left="7.0" right="10.0" top="10.0"/>
</padding>
</HBox>
<TableView fx:id="table" AnchorPane.bottomAnchor="5.0" AnchorPane.leftAnchor="5.0" AnchorPane.rightAnchor="5.0"
AnchorPane.topAnchor="42.0">
<columns>
<TableColumn fx:id="idColumn" text="ID"/>
<TableColumn fx:id="codeColumn" text="编号"/>
<TableColumn fx:id="nameColumn" text="名称"/>
<TableColumn fx:id="invoiceColumn" text="发票"/>
<TableColumn fx:id="amountColumn" text="金额"/>
<TableColumn fx:id="remarkColumn" text="备注"/>
<TableColumn fx:id="creatorColumn" text="创建人"/>
<TableColumn fx:id="createDateColumn" text="创建日期"/>
<TableColumn fx:id="updaterColumn" text="更新人"/>
<TableColumn fx:id="updateDateColumn" text="更新日期"/>
</columns>
</TableView>
</children>
</AnchorPane>

View File

@@ -10,7 +10,7 @@
<children>
<HBox spacing="3.0">
<children>
<TextField fx:id="contractSearchKeyField" promptText="检索关键字"/>
<TextField fx:id="searchKeyField" promptText="检索关键字"/>
<Button fx:id="searchBtn" mnemonicParsing="false" text="检索"/>
</children>
<padding>
@@ -23,7 +23,6 @@
<columns>
<TableColumn fx:id="idColumn" prefWidth="75.0" text="ID"/>
<TableColumn fx:id="codeColumn" prefWidth="120" text="编号"/>
<TableColumn fx:id="customerColumn" prefWidth="100.0" text="客户"/>
<TableColumn fx:id="customerAddressColumn" prefWidth="150.0" text="客户地址"/>
<TableColumn fx:id="taxRateColumn" prefWidth="60.0" text="税率"/>
<TableColumn fx:id="refIdColumn" prefWidth="60.0" text="RefID"/>

View File

@@ -6,12 +6,12 @@
<parent>
<groupId>com.ecep.contract</groupId>
<artifactId>Contract-Manager</artifactId>
<version>0.0.102-SNAPSHOT</version>
<version>0.0.122-SNAPSHOT</version>
</parent>
<groupId>com.ecep.contract</groupId>
<artifactId>common</artifactId>
<version>0.0.102-SNAPSHOT</version>
<version>0.0.122-SNAPSHOT</version>
<properties>
<maven.compiler.source>${java.version}</maven.compiler.source>

View File

@@ -0,0 +1,50 @@
package com.ecep.contract.vo;
import java.io.Serializable;
import java.time.LocalDate;
import com.ecep.contract.model.IdentityEntity;
import com.ecep.contract.model.NamedEntity;
import lombok.Data;
@Data
public class ContractInvoiceVo implements IdentityEntity, NamedEntity, ContractBasedVo, Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String code;
private String name;
/**
* 关联的合同对象, Contract
*/
private Integer contractId;
/**
* 关联的发票对象, Invoice
*/
private Integer invoiceId;
/**
* 发票金额
*/
private Double amount;
/**
* 备注
*/
private String remark;
/**
* 创建人
*/
private Integer setupPersonId;
/**
* 提交日期
*/
private LocalDate setupDate;
/**
* 更新人
*/
private Integer updatePersonId;
/**
* 更新日期
*/
private LocalDate updateDate;
}

View File

@@ -25,6 +25,7 @@ public class SalesOrderVo implements IdentityEntity, ContractBasedVo, Serializab
private Integer refId;
private float taxRate;
private Integer customerId;
private Integer customerPersonId;
private String customerAddress;
private Integer modifierId;
private LocalDate modifierDate;

View File

@@ -0,0 +1,37 @@
CREATE TABLE supplier_ms.CONTRACT_INVOICE (
ID INT NOT NULL AUTO_INCREMENT,
CODE VARCHAR(50) NULL,
NAME VARCHAR(200) NULL,
CONTRACT_ID INT NULL,
INVOICE_ID INT NULL,
AMOUNT DOUBLE NULL,
SETUP_PERSON_ID INT NULL,
SETUP_DATE DATE NULL,
UPDATE_PERSON_ID INT NULL,
UPDATE_DATE DATE NULL,
REMARK VARCHAR(500) NULL,
PRIMARY KEY (ID),
INDEX FK_CONTRACT_INVOICE_CONTRACT_idx (CONTRACT_ID ASC) VISIBLE,
INDEX FK_CONTRACT_INVOICE_INVOICE_idx (INVOICE_ID ASC) VISIBLE,
INDEX FK_CONTRACT_INVOICE_SETUP_PERSON_idx (SETUP_PERSON_ID ASC) VISIBLE,
INDEX FK_CONTRACT_INVOICE_UPDATE_PERSON_idx (UPDATE_PERSON_ID ASC) VISIBLE,
CONSTRAINT FK_CONTRACT_INVOICE_CONTRACT
FOREIGN KEY (CONTRACT_ID)
REFERENCES supplier_ms.CONTRACT (ID)
ON DELETE CASCADE
ON UPDATE NO ACTION,
CONSTRAINT FK_CONTRACT_INVOICE_INVOICE
FOREIGN KEY (INVOICE_ID)
REFERENCES supplier_ms.INVOICE (ID)
ON DELETE SET NULL
ON UPDATE NO ACTION,
CONSTRAINT FK_CONTRACT_INVOICE_SETUP_PERSON
FOREIGN KEY (SETUP_PERSON_ID)
REFERENCES supplier_ms.EMPLOYEE (ID)
ON DELETE SET NULL
ON UPDATE NO ACTION,
CONSTRAINT FK_CONTRACT_INVOICE_UPDATE_PERSON
FOREIGN KEY (UPDATE_PERSON_ID)
REFERENCES supplier_ms.EMPLOYEE (ID)
ON DELETE SET NULL
ON UPDATE NO ACTION);

View File

@@ -0,0 +1,37 @@
CREATE TABLE supplier_ms.CONTRACT_SALES_ORDER (
ID INT NOT NULL AUTO_INCREMENT COMMENT '主键ID',
CONTRACT_ID INT NOT NULL COMMENT '合同ID',
EMPLOYEE_ID INT COMMENT '业务员ID',
CODE VARCHAR(50) COMMENT '单据编号',
REF_ID INT COMMENT '引用ID',
TAX_RATE FLOAT COMMENT '税率',
CUSTOMER_ID INT COMMENT '客户ID',
CUSTOMER_PERSON_ID INT COMMENT '客户联系人ID',
CUSTOMER_ADDRESS VARCHAR(255) COMMENT '客户地址',
MAKER_ID INT COMMENT '制单人ID',
MAKER_DATE DATE COMMENT '制单日期',
VERIFIER_ID INT COMMENT '审核人ID',
VERIFIED_DATE DATE COMMENT '审核日期',
MODIFIER_ID INT COMMENT '修改人ID',
MODIFIED_DATE DATE COMMENT '修改日期',
CLOSER_ID INT COMMENT '关闭人ID',
CLOSED_DATE DATE COMMENT '关闭日期',
DESCRIPTION TEXT COMMENT '备注',
PRIMARY KEY (ID),
INDEX IDX_SALES_ORDER_CONTRACT (CONTRACT_ID),
INDEX IDX_SALES_ORDER_EMPLOYEE (EMPLOYEE_ID),
INDEX IDX_SALES_ORDER_CUSTOMER (CUSTOMER_ID),
INDEX IDX_SALES_ORDER_CUSTOMER_PERSON (CUSTOMER_PERSON_ID),
INDEX IDX_SALES_ORDER_MAKER (MAKER_ID),
INDEX IDX_SALES_ORDER_VERIFIER (VERIFIER_ID),
INDEX IDX_SALES_ORDER_MODIFIER (MODIFIER_ID),
INDEX IDX_SALES_ORDER_CLOSER (CLOSER_ID),
CONSTRAINT FK_SALES_ORDER_CONTRACT FOREIGN KEY (CONTRACT_ID) REFERENCES supplier_ms.CONTRACT (ID) ON DELETE CASCADE ON UPDATE NO ACTION,
CONSTRAINT FK_SALES_ORDER_EMPLOYEE FOREIGN KEY (EMPLOYEE_ID) REFERENCES supplier_ms.EMPLOYEE (ID) ON DELETE SET NULL ON UPDATE NO ACTION,
CONSTRAINT FK_SALES_ORDER_CUSTOMER FOREIGN KEY (CUSTOMER_ID) REFERENCES supplier_ms.COMPANY (ID) ON DELETE SET NULL ON UPDATE NO ACTION,
CONSTRAINT FK_SALES_ORDER_CUSTOMER_PERSON FOREIGN KEY (CUSTOMER_PERSON_ID) REFERENCES supplier_ms.EMPLOYEE (ID) ON DELETE SET NULL ON UPDATE NO ACTION,
CONSTRAINT FK_SALES_ORDER_MAKER FOREIGN KEY (MAKER_ID) REFERENCES supplier_ms.EMPLOYEE (ID) ON DELETE SET NULL ON UPDATE NO ACTION,
CONSTRAINT FK_SALES_ORDER_VERIFIER FOREIGN KEY (VERIFIER_ID) REFERENCES supplier_ms.EMPLOYEE (ID) ON DELETE SET NULL ON UPDATE NO ACTION,
CONSTRAINT FK_SALES_ORDER_MODIFIER FOREIGN KEY (MODIFIER_ID) REFERENCES supplier_ms.EMPLOYEE (ID) ON DELETE SET NULL ON UPDATE NO ACTION,
CONSTRAINT FK_SALES_ORDER_CLOSER FOREIGN KEY (CLOSER_ID) REFERENCES supplier_ms.EMPLOYEE (ID) ON DELETE SET NULL ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='销售订单表';

View File

@@ -10,7 +10,7 @@
</parent>
<groupId>com.ecep.contract</groupId>
<artifactId>Contract-Manager</artifactId>
<version>0.0.102-SNAPSHOT</version>
<version>0.0.122-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>server</module>

View File

@@ -6,12 +6,12 @@
<parent>
<groupId>com.ecep.contract</groupId>
<artifactId>Contract-Manager</artifactId>
<version>0.0.102-SNAPSHOT</version>
<version>0.0.122-SNAPSHOT</version>
</parent>
<groupId>com.ecep.contract</groupId>
<artifactId>server</artifactId>
<version>0.0.102-SNAPSHOT</version>
<version>0.0.122-SNAPSHOT</version>
<properties>
<maven.compiler.source>${java.version}</maven.compiler.source>
@@ -22,7 +22,7 @@
<dependency>
<groupId>com.ecep.contract</groupId>
<artifactId>common</artifactId>
<version>0.0.102-SNAPSHOT</version>
<version>0.0.122-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

View File

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

View File

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

View File

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

View File

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