Compare commits

..

35 Commits

Author SHA1 Message Date
643338f4b0 refactor(WebSocketServerCallbackManager): 使用常量替换硬编码的方法名
将硬编码的方法名替换为ServiceConstant中定义的常量,提高代码可维护性和可读性
2025-12-17 17:01:08 +08:00
880671a5a9 refactor: 使用常量替换硬编码方法名并优化参数构建
- 在ServiceConstant中添加常用方法名常量
- 使用ParamUtils重构多处参数构建逻辑
- 统一QueryService中的方法名调用为常量
- 修复CompanyOldNameService中的字段别名问题
2025-12-17 16:56:45 +08:00
4e738bea3c refactor: 移除冗余方法并优化查询逻辑
移除多个服务类中重复的findAll方法实现,统一使用父类方法
优化QueryService的findOneByProperty实现,提高可读性
为EntityService添加aliasFor方法支持字段别名
修复ContractVerifyWindowController的消息处理逻辑
添加查看验证状态的功能菜单项
2025-12-15 00:04:32 +08:00
3cf3a717be refactor(service): 移除冗余的getById方法
多个服务类中移除了重复的getById方法实现
2025-12-14 17:12:36 +08:00
e8c8305f40 refactor(vendor): 移除冗余方法并统一继承EntityService
重构供应商相关服务类,移除重复的getById等方法,统一继承EntityService基类
更新文档中的服务类检查状态
2025-12-14 16:36:11 +08:00
be63ff62a4 refactor(service): 重构多个服务类继承EntityService基类
重构多个服务类使其继承EntityService基类,统一实现基础CRUD操作
移除重复的findAll、getById等方法实现
添加缓存注解确保一致性
更新服务类文档说明继承关系
2025-12-14 15:54:21 +08:00
18057a657e refactor(service): 重构服务层代码,统一继承EntityService基类
refactor(repository): 将JpaRepository替换为自定义的MyRepository接口
refactor(tasker): 将findById方法更名为getById以提高一致性
docs: 更新server_entity_services.md文档中的服务实现状态
2025-12-14 14:47:51 +08:00
db07befffe refactor: 统一方法命名,提高代码一致性
将 newContractGroup 重命名为 createNewEntity 以符合命名规范
将 findById 重命名为 getById 以保持方法命名一致性
2025-12-14 13:14:19 +08:00
94030a5a39 Merge branch 'main' of http://gitea.ecctrl.com/songqq/contract-manager 2025-12-14 12:59:24 +08:00
7e59f2c17e refactor(服务层): 重构多个服务类继承EntityService基类
统一服务层实现,将PurchaseBillVoucherItemService、PurchaseOrdersService等服务类重构为继承EntityService基类
移除重复代码,实现通用CRUD操作
更新文档标记服务可用性
优化查询规范和缓存配置
2025-12-14 12:58:56 +08:00
c8b0d57f22 feat(EntityService): 实现 LIKE 和小于操作符的查询逻辑
添加 LIKE 操作符支持包含、开头和结尾匹配模式
实现小于操作符的比较逻辑,支持嵌套属性路径
2025-12-13 21:17:54 +08:00
6eebdb1744 refactor(service): 重构服务类继承EntityService基类
重构多个服务类使其继承EntityService基类,统一实现通用CRUD操作
移除重复代码,提取公共逻辑到基类中
更新缓存注解和查询规范实现
添加必要的重写方法如getRepository和createNewEntity
2025-12-13 20:32:06 +08:00
9dc90507cb refactor(vendor): 重构服务类继承EntityService基类
将多个vendor相关的服务类重构为继承自EntityService基类,统一实现通用CRUD操作
移除重复代码,简化各服务类的实现
添加必要的重写方法如getRepository和createNewEntity
2025-12-13 18:54:15 +08:00
0c26d329c6 refactor(service): 重构服务类继承EntityService以统一实现
将BankService、EmployeeService、CustomerCatalogService、InventoryService和DepartmentService重构为继承EntityService基类,统一实现通用CRUD操作和查询逻辑。移除重复代码,提高代码复用性和可维护性。
2025-12-13 18:17:53 +08:00
e3661630fe refactor: 将getSpecification重命名为getSearchSpecification以提高方法命名清晰度 2025-12-13 17:59:16 +08:00
5d6fb961b6 feat(查询): 实现通用过滤条件构建与解析功能
新增参数常量定义和查询条件构建工具,支持复杂条件组合
重构EntityService基类以支持通用过滤条件解析
优化SpecificationUtils工具类,增加搜索文本处理方法
2025-12-13 16:48:54 +08:00
72edb07798 feat: 添加合同全量同步任务和合格供方名录路径生成功能
refactor: 重构查询服务使用ParamConstant替换ServiceConstant
style: 清理无用代码和注释
fix: 修复CompanyCustomerEvaluationFormFileService查询方法
docs: 更新CloudYuVo和CompanyBankAccountVo字段注释
2025-12-13 11:11:37 +08:00
330418cfd6 feat(contract): 添加ContractFileTypeGroup枚举和ParamConstant类
添加合同模块的基础类型定义,为后续功能开发提供支持
2025-12-13 11:10:59 +08:00
c10bd369c0 up 2025-11-26 16:10:17 +08:00
f0e85c5a18 docs: 添加项目文档和架构设计文件
删除旧的package.json文件
添加天眼查下载信用报告文档
添加项目文档总览、架构设计、API文档、开发指南和数据库设计文档
2025-11-26 16:10:01 +08:00
a784438e97 feat: 实现员工同步任务的WebSocket支持及合同名称锁定功能
- 为EmployeesSyncTask添加WebSocket客户端和服务端支持,实现实时任务进度反馈
- 新增合同名称锁定功能,防止误修改重要合同名称
- 优化SmbFileService的连接异常处理,提高稳定性
- 重构ContractFilesRebuildTasker的任务执行逻辑,改进错误处理
- 更新tasker_mapper.json注册EmployeesSyncTask
- 添加相关任务文档和验收报告

修复WebSocketClientSession的任务完成状态处理问题
改进UITools中任务执行的线程管理
优化DepartmentService的findByCode方法返回类型
2025-11-20 16:26:34 +08:00
02afa189f8 feat(contract): 新增合同余额功能及重构文件管理
重构合同文件管理逻辑,增加错误处理和日志记录
新增ContractBalance实体、Repository和VO类
完善Voable接口文档和实现规范
更新项目架构文档和数据库设计
修复SmbFileService的连接问题
移动合同相关TabSkin类到contract包
添加合同文件重建任务的WebSocket支持
2025-11-19 00:50:16 +08:00
87290f15b0 feat(SMB): 重构SMB文件服务支持多服务器配置和连接池优化
重构SmbFileService以支持多服务器配置,引入连接池和会话池管理机制。主要变更包括:
1. 实现基于主机的多服务器认证配置
2. 新增连接池和会话池管理,提高连接复用率
3. 添加定时清理空闲连接和会话的功能
4. 优化异常处理和重试机制
5. 改进日志记录和资源释放

同时更新相关配置文件和应用属性以支持新功能:
1. 修改application.properties支持多服务器SMB配置
2. 增强SmbConfig类以管理多服务器配置
3. 添加任务映射到tasker_mapper.json
4. 新增客户端和服务端任务规则文档
2025-11-17 12:55:31 +08:00
e761990ebf feat: 实现SMB文件服务并优化合同文件管理
- 新增SmbFileService服务类,支持SMB/CIFS协议的文件操作
- 修改合同文件管理逻辑,支持SMB路径检查与目录创建
- 优化BankTableCell实现工厂模式并更新相关文档
- 调整Redis配置并添加连接测试
- 修复合同发票视图模型的时间处理问题
- 更新项目版本至0.0.134-SNAPSHOT
2025-11-12 16:32:03 +08:00
1cb0edbd07 build: 添加客户端应用配置文件 2025-11-12 16:31:35 +08:00
dd49c3927a feat(客户评估): 实现客户评估表单的搜索和显示功能
添加客户评估表单文件的搜索功能,支持按公司ID和搜索文本查询
新增CompanyCustomerEvaluationFormFileStringConverter用于文件显示转换
优化自动完成功能,支持自定义搜索逻辑
移除不必要的文件路径属性绑定
重构客户评估表单窗口控制器继承结构
2025-10-18 01:36:58 +08:00
7d4961dae4 feat: 更新项目版本至0.0.129-SNAPSHOT并修复多个问题
fix(ProjectIndustryService): 添加getStringConverter方法
fix(AbstContractRepairTasker): 初始化repository修复同步问题
fix(ProjectTabSkinBase): 处理saleTypeField空值情况
refactor(ProjectTypeService): 优化stringConverter初始化
feat(ProjectTypeStringConverter): 修改toString方法显示编码和名称
fix(CustomerCtx): 初始化repository修复客户更新问题
feat(ProjectCostWindowController): 根据版本控制审批标签显示
fix(DeliverySignMethodService): 添加saleType查询条件
feat: 添加DeliverySignMethodStringConverter
fix(SalesOrderCtx): 使用getRepository修复查询问题
fix(VendorCtx): 初始化repository修复供应商更新问题
refactor(CompanyTabSkinContract): 优化员工表格单元格创建
feat(DeliverySignMethodService): 添加findByCode和findByName方法
refactor(AbstEntityController): 优化视图模型更新逻辑
style(project.fxml): 调整界面布局和字段显示
2025-10-17 15:39:41 +08:00
235269f86f refactor(converter): 重构StringConverter实现并统一处理null值
重构各实体类的StringConverter实现,移除EntityStringConverter基类,改为直接实现StringConverter接口
在各Service中提供对应的StringConverter实例,统一处理null值情况
更新ComboBoxUtils使用Service提供的StringConverter
2025-10-17 00:27:01 +08:00
0b45f6eef2 refactor: 优化上下文对象初始化方式
将InventoryCtx、VendorCtx和CustomerCtx的初始化方式改为直接通过构造函数传入上下文对象,简化代码并提高可读性
2025-10-16 23:53:35 +08:00
22ab2c7bdf refactor(u8上下文): 重构上下文类初始化方式,使用BeanContext传递依赖
移除from方法,改为通过构造函数注入BeanContext
统一使用getCachedBean获取服务实例
添加无参构造函数和带BeanContext参数的构造函数
优化代码结构,移除无用导入
2025-10-16 18:52:25 +08:00
eea4d93ae1 feat(资金计划): 完善资金计划功能并优化界面显示
- 在ProjectFundPlanService中添加payWay和payDate字段映射
- 为资金计划添加项目关联处理
- 优化付款计划表格的列名和显示
- 重构ProjectTabSkinFundPlan的service获取逻辑
2025-10-16 18:25:28 +08:00
71a358fa77 feat(contract): 新增合同发票管理功能
实现合同发票的增删改查功能,包括:
1. 添加ContractInvoiceVo实体类及相关ViewModel
2. 创建合同发票数据库表CONTRACT_INVOICE
3. 实现前后端发票管理服务ContractInvoiceService
4. 开发发票管理界面及标签页
5. 添加发票表格单元格组件
6. 完善销售订单表结构,增加客户联系人字段
7. 更新pom.xml版本至0.0.122-SNAPSHOT

修复销售订单界面搜索字段命名不一致问题
2025-10-16 15:47:33 +08:00
cf0a7e18ea feat(contract): 新增合同内容管理功能
- 添加合同内容窗口控制器及相关视图模型
- 实现合同内容基础信息标签页
- 添加金额计算功能
- 完善合同内容相关字段绑定
- 优化合同内容界面布局
2025-10-16 00:09:01 +08:00
d2e0dc4555 refactor(controller): 移除冗余的service注入,使用BeanContext统一管理
feat(sales-order): 补充SalesOrderVo缺失字段并完善相关功能
feat(sales-order): 添加客户、税率等字段到销售订单界面
refactor(tab-skin): 重构TabSkin基类,统一bean获取方式
fix(fxml): 修正controller包路径和字段绑定
feat(repository): 添加findAllByOrder方法优化查询
2025-10-15 00:40:02 +08:00
1c1ff678a5 feat(contract): 添加采购订单和销售订单的合同标签页实现
实现采购订单和销售订单的合同标签页功能,包括表格列定义、单元格渲染和基本操作。采购订单标签页在合同为付款方式时启用,销售订单标签页在合同为收款方式时启用。两个标签页都支持双击行查看详情和刷新操作,销售订单标签页额外提供搜索功能。
2025-10-14 17:36:55 +08:00
378 changed files with 17789 additions and 5570 deletions

1
.env Normal file
View File

@@ -0,0 +1 @@
PLAYWRIGHT_MCP_EXTENSION_TOKEN=TB7T39NhEbruyS7L-E7RXIGYk39PVK7eu1h-WP8M1Cg

4
.gitignore vendored
View File

@@ -35,4 +35,6 @@ build/
### VS Code ### ### VS Code ###
.vscode/ .vscode/
/config.properties /config.properties
node_modules
node_modules

View File

@@ -154,4 +154,70 @@ public class [业务]WindowController extends AbstEntityController<[Vo类型], [
// 窗口显示后的逻辑 // 窗口显示后的逻辑
} }
} }
``` ```
## 13. TableCell 工厂模式规范
### 13.1 工厂方法命名
- TableCell类应提供静态工厂方法`forTableColumn`,用于创建单元格工厂
- 方法命名必须为`forTableColumn`,保持一致性
### 13.2 工厂方法参数
- 工厂方法应接收服务层参数,如`BankService`
- 参数类型应与TableCell构造函数所需的服务类型一致
### 13.3 工厂方法返回类型
- 返回类型应为`Callback<javafx.scene.control.TableColumn<V, T>, javafx.scene.control.TableCell<V, T>>`
- 其中`V`为ViewModel类型`T`为单元格值类型
### 13.4 工厂方法实现
- 在工厂方法内部创建并返回TableCell实例
- 使用泛型参数确保类型安全
### 13.5 使用方式
- 在设置表格列的单元格工厂时应调用TableCell的静态工厂方法
- 避免直接使用`new TableCell<>(service)`的方式创建实例
### 13.6 示例代码
```java
/**
* 银行单元格
*/
@NoArgsConstructor
public class BankTableCell<T> extends AsyncUpdateTableCell<T, Integer, BankVo> {
/**
* 创建单元格工厂
*
* @param bankService 银行服务
* @return 单元格工厂
*/
public static <V> Callback<javafx.scene.control.TableColumn<V, Integer>, javafx.scene.control.TableCell<V, Integer>> forTableColumn(
BankService bankService) {
return param -> new BankTableCell<V>(bankService);
}
public BankTableCell(BankService service) {
setService(service);
}
@Override
protected BankService getServiceBean() {
return getBean(BankService.class);
}
}
```
### 13.7 使用示例
```java
// 推荐方式:使用工厂方法
column.setCellFactory(BankTableCell.forTableColumn(getBankService()));
// 不推荐方式:直接实例化
// column.setCellFactory(param -> new BankTableCell<>(getBankService()));

View File

@@ -1,83 +1,133 @@
# 客户端 Service 类规则 # Client Service 实现编写指南
## 1. 目录结构 ## 📋 概述
- 所有客户端 Service 类位于 `client/src/main/java/com/ecep/contract/service/` 目录下
- 按业务领域组织,直接放置在 service 包下,不进行子包划分
- 服务类命名与实体类一一对应
## 2. 命名规范 本指南总结 Client 模块 Service 层的实现经验,用于指导后续 Service 的编写。本指南基于 Contract-Manager 项目中已实现的 Service 模式整理。
- 服务类命名格式为:`[实体名称]Service.java`
- 例如:`CompanyService.java``ContractService.java``ProjectService.java`
- 基础服务接口命名为:`IEntityService.java``ViewModelService.java`
- 泛型基础服务类命名为:`QueryService.java`
## 3. 继承关系 ---
- 业务服务类通常继承自泛型基础服务类 `QueryService<T, TV>`
- `T` 表示 VO 类型(实现了 IdentityEntity 接口) ## 🏗️ 基础架构
- `TV` 表示 ViewModel 类型(实现了 IdentityViewModel<T> 接口)
- `QueryService` 实现了 `ViewModelService<T, TV>` 接口 ### 1. 继承层次结构
- `ViewModelService` 继承了 `IEntityService<T>` 接口
- 特定场景下可以不继承 `QueryService`,直接实现所需接口或创建独立服务类
```java ```java
// Service 接口定义
public interface IEntityService<T> {
T findById(Integer id);
T save(T entity);
void delete(T entity);
List<T> findAll();
Page<T> findAll(Map<String, Object> params, Pageable pageable);
StringConverter<T> getStringConverter();
}
// 基础 Service 实现
public abstract class QueryService<T extends IdentityEntity, TV extends IdentityViewModel<T>>
implements ViewModelService<T, TV> {
// 核心实现
}
// 具体业务 Service
@Service @Service
@CacheConfig(cacheNames = "company") @CacheConfig(cacheNames = "business")
public class CompanyService extends QueryService<CompanyVo, CompanyViewModel> { public class XxxService extends QueryService<XxxVo, XxxViewModel> {
// 业务方法实现 // 业务特定实现
} }
``` ```
## 4. 注解使用 ### 2. 核心特性
- **@Service**:标记为 Spring 服务组件,使其可被自动发现和注入
- **@CacheConfig**:配置缓存名称,通常与服务类名对应 - **泛型支持**`QueryService` 使用泛型处理不同类型的 Vo 和 ViewModel
- **@Cacheable**标记方法结果可缓存需指定缓存键key - **WebSocket 通信**:通过 `WebSocketClientService` 与 Server 端通信
- **@CacheEvict**:标记方法执行后清除缓存,可指定缓存键或清除所有 - **异步处理**:使用 `CompletableFuture` 实现异步操作
- **@Caching**:组合多个缓存操作(如同时清除多个缓存条目) - **缓存机制**:集成 Spring Cache 支持多级缓存
- **@Autowired**:用于自动注入依赖的其他服务 - **错误处理**:统一的异常处理和日志记录
---
## 📝 Service 编写规范
### 1. 类声明和注解
```java ```java
@Service @Service // Spring 组件注解
@CacheConfig(cacheNames = "contract") @CacheConfig(cacheNames = "xxx") // 缓存配置xxx为业务域名称
public class ContractService extends QueryService<ContractVo, ContractViewModel> { public class XxxService extends QueryService<XxxVo, XxxViewModel> {
@Autowired // Service 实现
private SysConfService confService;
@Cacheable(key = "#p0")
public ContractVo findById(Integer id) {
return super.findById(id);
}
@Caching(evict = {
@CacheEvict(key = "#p0.id"),
@CacheEvict(key = "'code-'+#p0.code")
})
public ContractVo save(ContractVo contract) {
return super.save(contract);
}
} }
``` ```
## 5. 缓存机制 ### 2. 缓存策略
- 每个服务类应有独立的缓存名称空间
- 缓存键key应具有唯一性通常使用 ID、代码或名称等唯一标识
- 保存和删除操作时应清除相关缓存,保持数据一致性
- 可使用 SpEL 表达式动态生成缓存键
- 频繁查询的数据应考虑缓存,提高性能
## 6. 异步调用机制 #### 缓存注解使用
- 使用 `async()` 方法进行异步远程调用 ```java
- 方法参数通常包括:方法名、参数值、参数类型列表 @Cacheable(key = "#p0") // 按ID缓存
- 使用 `CompletableFuture` 处理异步结果 @Cacheable(key = "'code-'+#p0") // 按代码缓存
- 使用 `handle()` 方法处理响应和异常 @Cacheable(key = "'name-'+#p0") // 按名称缓存
- 远程调用异常应包装为 RuntimeException 并提供详细错误信息
@CacheEvict(key = "#p0.id") // 删除时清除ID缓存
@CacheEvict(key = "'code-'+#p0.code") // 清除代码缓存
@Caching(evict = {
@CacheEvict(key = "#p0.id"),
@CacheEvict(key = "'code-'+#p0.code")
}) // 批量清除缓存
```
#### 缓存键设计原则
- **ID 缓存**`#p0`第一个参数通常是ID
- **代码缓存**`'code-'+#p0`(业务代码的缓存)
- **名称缓存**`'name-'+#p0`(业务名称的缓存)
- **关联缓存**`'company-'+#p0.id`(关联实体的缓存)
### 3. 核心方法实现
#### findById 方法
```java
@Cacheable(key = "#p0")
@Override
public XxxVo findById(Integer id) {
return super.findById(id); // 调用父类方法
}
```
#### save 方法(带缓存清除)
```java
@Caching(evict = {
@CacheEvict(key = "#p0.id"),
@CacheEvict(key = "'code-'+#p0.code")
})
@Override
public XxxVo save(XxxVo entity) {
return super.save(entity);
}
```
#### delete 方法(带缓存清除)
```java
@Caching(evict = {
@CacheEvict(key = "#p0.id"),
@CacheEvict(key = "'code-'+#p0.code")
})
@Override
public void delete(XxxVo entity) {
super.delete(entity);
}
```
---
## 🔄 异步通信模式
### 1. 基本异步调用
```java ```java
@Cacheable(key = "'code-'+#p0") // 异步调用示例
public ContractVo findByCode(String code) { public XxxVo findByCode(String code) {
try { try {
return async("findByCode", code, String.class).handle((response, ex) -> { return async("findByCode", code, String.class).handle((response, ex) -> {
if (ex != null) { if (ex != null) {
throw new RuntimeException("远程方法+findByCode+调用失败", ex); throw new RuntimeException("远程方法 findByCode 调用失败", ex);
} }
if (response != null) { if (response != null) {
return updateValue(createNewEntity(), response); return updateValue(createNewEntity(), response);
@@ -85,62 +135,374 @@ public ContractVo findByCode(String code) {
return null; return null;
}).get(); }).get();
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("查失败: " + code, e); throw new RuntimeException("查找实体失败: " + code, e);
} }
} }
``` ```
## 7. 基础方法实现 ### 2. 复杂对象处理
- 应实现 `IEntityService<T>` 接口定义的核心方法:
- `findById(Integer id)`:根据 ID 查询实体
- `save(T entity)`:保存实体
- `delete(T entity)`:删除实体
- `findAll()`:查询所有实体
- `findAll(Map<String, Object> params, Pageable pageable)`:条件分页查询
- `getStringConverter()`:获取类型转换器
- 通常通过继承 `QueryService` 来复用这些基础方法的实现
- 可根据业务需求重写或扩展基础方法
## 8. 业务方法规范
- 业务方法应与服务端对应,保持方法名和参数一致
- 方法命名应清晰表达其功能,如 `findByName`, `findByCode`
- 复杂业务逻辑应封装为独立方法
- 参数校验应在方法开始处进行
- 返回值类型应明确,避免使用过于泛化的类型
## 9. 类型转换器
- 实现 `getStringConverter()` 方法,返回对应的 StringConverter 实例
- 通常创建专用的 Converter 类,如 `CustomerCatalogStringConverter`
- 转换器实例应作为服务类的成员变量,避免重复创建
```java ```java
public class CustomerCatalogService extends QueryService<CustomerCatalogVo, CustomerCatalogViewModel> { public List<XxxDetailVo> findDetailsByXxxId(Integer xxxId) {
private final CustomerCatalogStringConverter stringConverter = new CustomerCatalogStringConverter(this); try {
return async("findDetailsByXxxId", List.of(xxxId), List.of(Integer.class))
@Override .handle((response, ex) -> {
public StringConverter<CustomerCatalogVo> getStringConverter() { if (ex != null) {
return stringConverter; throw new RuntimeException("远程方法调用失败", ex);
}
if (response != null) {
try {
List<XxxDetailVo> content = new ArrayList<>();
for (JsonNode node : response) {
XxxDetailVo newEntity = new XxxDetailVo();
objectMapper.updateValue(newEntity, node);
content.add(newEntity);
}
return content;
} catch (Exception e) {
throw new RuntimeException(response.toString(), e);
}
}
return null;
}).get();
} catch (Exception e) {
throw new RuntimeException(e);
} }
} }
``` ```
## 10. 错误处理 ---
- 远程调用异常应捕获并包装为更具描述性的 RuntimeException
- 提供详细的错误信息,包括调用的方法名和参数
- 对于可预期的业务异常,可添加专门的处理逻辑
- 不推荐使用 printStackTrace(),应使用日志记录异常
## 11. 工具方法和辅助功能 ## 💼 业务逻辑模式
- 通用功能可封装为工具方法
- 配置相关操作可通过 `SysConfService` 实现
- 文件路径处理应使用 `File` 类和相关工具方法
- 日期时间处理应使用 Java 8+ 的日期时间 API
## 12. 最佳实践 ### 1. 文件系统集成
- 遵循单一职责原则,每个服务类专注于一个业务领域
- 优先使用继承和接口实现来复用代码 #### 路径管理
- 合理使用缓存提高性能,但注意缓存一致性 ```java
- 异步调用应正确处理异常和超时情况 @Autowired
- 服务类之间的依赖应通过 `@Autowired` 注入,避免硬编码 private SysConfService confService;
- 方法实现应简洁明了,复杂逻辑应拆分
- 为重要方法添加 JavaDoc 注释,说明其功能和参数含义 private File basePath;
public File getBasePath() {
if (basePath == null) {
basePath = new File(confService.getString(Constant.KEY_BASE_PATH));
}
return basePath;
}
// 验证路径是否在基础目录内
public boolean checkXxxPathInBasePath(XxxVo xxx) {
if (!existsXxxPath(xxx)) {
return false;
}
File basePath = getBasePath();
if (basePath == null || !basePath.exists()) {
throw new IllegalArgumentException("基础目录不存在");
}
File path = new File(xxx.getPath());
return path.getAbsolutePath().startsWith(basePath.getAbsolutePath());
}
// 检查路径是否存在
public boolean existsXxxPath(XxxVo xxx) {
if (!StringUtils.hasText(xxx.getPath())) {
return false;
}
File path = new File(xxx.getPath());
return path.exists();
}
```
#### 目录创建
```java
public File makePath(XxxVo xxx) {
File basePath = getBasePath();
if (!basePath.exists()) {
holder.error("存储目录不存在:" + basePath.getAbsolutePath());
return null;
}
// 构建目录路径逻辑
String fileName = FileUtils.escapeFileName(xxx.getName());
File dir = new File(basePath, fileName);
if (!dir.exists()) {
if (!dir.mkdir()) {
holder.error("创建目录失败:" + dir.getAbsolutePath());
return null;
}
}
return dir;
}
```
### 2. 业务验证模式
#### 数据完整性验证
```java
public void verifyXxx(XxxVo xxx, LocalDate verifyDate, MessageHolder holder) {
// 检查关键字段
if (!StringUtils.hasText(xxx.getCode())) {
holder.error("编号异常:未设置");
return;
}
// 检查状态字段
String status = xxx.getStatus();
if (StringUtils.hasText(status) && status.contains("无效")) {
LocalDate end = xxx.getEndDate();
LocalDate begin = xxx.getBeginDate();
if (begin == null || end == null) {
holder.error("状态异常:" + status);
} else {
if (!MyDateTimeUtils.dateValidFilter(verifyDate, begin, end, 0)) {
holder.error("状态异常:" + status);
}
}
} else {
holder.error("状态异常:未设置");
}
}
```
#### 关联实体验证
```java
public boolean verifyAsXxxType(XxxVo xxx, LocalDate verifyDate, MessageHolder holder) {
boolean valid = false;
// 检查关联实体
RelatedVo related = relatedService.findById(xxx.getRelatedId());
if (related == null) {
holder.error("关联实体不存在");
valid = true;
}
// 检查关联数据
if (!StringUtils.hasText(xxx.getRelatedField())) {
holder.error("关联字段未设置");
valid = true;
}
// 检查业务规则
if (xxx.getStatus() == XxxStatus.INACTIVE) {
holder.error("业务状态异常:已停用");
valid = true;
}
return valid;
}
```
### 3. 复杂业务查询
#### 分页查询
```java
public List<XxxVo> findAllByXxxCondition(XxxCondition condition, LocalDate beginDate, LocalDate endDate) {
return findAll(ParamUtils.builder()
.equals("field1", condition.getField1())
.between("createDate", beginDate, endDate)
.equals("status", "ACTIVE")
.build(), Pageable.unpaged()).getContent();
}
```
#### 组合条件查询
```java
public Page<XxxVo> findAllWithComplexCondition(XxxQueryParam param) {
ParamUtils.ParamBuilder builder = ParamUtils.builder()
.equals("category", param.getCategory());
if (StringUtils.hasText(param.getName())) {
builder.like("name", "%" + param.getName() + "%");
}
if (param.getDateRange() != null) {
builder.between("createDate", param.getDateRange().getStart(),
param.getDateRange().getEnd());
}
return findAll(builder.build(), Pageable.ofSize(param.getPageSize()));
}
```
---
## 🔧 工具类和依赖注入
### 1. 常用工具类依赖
```java
// 常用注入
@Autowired
private SysConfService confService; // 系统配置服务
@Autowired
private RelatedService relatedService; // 关联实体服务
// 静态工具类使用
import com.ecep.contract.util.FileUtils; // 文件工具
import com.ecep.contract.util.ParamUtils; // 参数工具
import com.ecep.contract.util.MyStringUtils; // 字符串工具
```
### 2. 工具类使用示例
#### ParamUtils 构建查询条件
```java
// 构建复杂查询条件
Map<String, Object> params = ParamUtils.builder()
.equals("field1", value1)
.equals("field2", value2)
.like("name", "%" + keyword + "%")
.between("date", startDate, endDate)
.in("status", List.of("ACTIVE", "PENDING"))
.orderBy("createTime", "desc")
.build();
```
#### FileUtils 处理文件路径
```java
// 文件名转义
String safeFileName = FileUtils.escapeFileName(companyName);
// 获取父级前缀
String parentPrefix = FileUtils.getParentPrefixByDistrict(district);
```
---
## 📊 错误处理和日志
### 1. 异常处理模式
```java
// 查询异常处理
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 boolean businessMethod(XxxVo xxx) {
if (xxx == null) {
return false;
}
if (!xxx.isValid()) {
throw new IllegalArgumentException("实体数据无效");
}
return true;
}
```
### 2. 日志记录
```java
// 在 QueryService 中已集成日志
private static final Logger logger = LoggerFactory.getLogger(QueryService.class);
// 在业务方法中使用
public void deleteXxx(XxxVo xxx) {
try {
super.delete(xxx);
logger.info("删除实体成功 #{}", xxx.getId());
} catch (Exception e) {
logger.error("删除实体失败 #{}", xxx.getId(), e);
throw new RuntimeException("删除实体失败", e);
}
}
```
---
## 🧪 测试和验证
### 1. 方法验证检查点
- **输入参数验证**:检查空值、格式、范围
- **业务逻辑验证**:检查状态、关联、权限
- **文件系统验证**:检查路径、权限、空间
- **数据库验证**:检查连接、事务、一致性
### 2. 常见测试场景
```java
// 测试用例示例
@Test
public void testFindById() {
// 正常情况
XxxVo result = xxxService.findById(1);
assertNotNull(result);
// 异常情况
assertThrows(RuntimeException.class, () -> {
xxxService.findById(-1);
});
}
@Test
public void testSaveWithCache() {
XxxVo xxx = createTestXxx();
xxx.setCode("TEST001");
XxxVo saved = xxxService.save(xxx);
assertNotNull(saved.getId());
// 验证缓存
XxxVo cached = xxxService.findById(saved.getId());
assertEquals(saved.getId(), cached.getId());
}
```
---
## 🎯 最佳实践
### 1. 代码组织原则
- **单一职责**:每个 Service 专注特定的业务域
- **依赖注入**:合理使用 `@Autowired` 注入依赖
- **缓存策略**:为高频查询字段配置缓存
- **异常处理**:统一处理业务异常和系统异常
### 2. 性能优化建议
- **缓存配置**:合理设置缓存过期时间
- **分页查询**:避免大数据量一次性查询
- **异步处理**:使用异步调用提升响应速度
- **批量操作**:考虑批量处理减少网络开销
### 3. 可维护性提升
- **命名规范**:遵循项目统一的命名约定
- **注释文档**:为复杂业务逻辑添加注释
- **代码复用**:提取公共逻辑到工具类
- **版本兼容**:考虑前后端版本兼容性
---
## 📚 相关规范
- **Controller 规范**[client_controller_rules.md](.trae/rules/client_controller_rules.md)
- **转换器规范**[client_converter_rules.md](.trae/rules/client_converter_rules.md)
- **Task 规范**[client_task_rules.md](.trae/rules/client_task_rules.md)
- **Entity 规范**[entity_rules.md](.trae/rules/entity_rules.md)
- **VO 规范**[vo_rules.md](.trae/rules/vo_rules.md)
---
*本文档基于 Contract-Manager 项目现有 Service 实现模式总结,遵循项目既定的技术架构和编程规范。*
**文档版本**: v1.0.0
**最后更新**: 2024-12-19
**维护团队**: Contract Manager Development Team

View File

@@ -0,0 +1,419 @@
# 客户端Tasker实现WebSocketClientTasker接口规范
## 概述
本文档基于 `ContractRepairAllTasker` 实现 `WebSocketClientTasker` 接口的经验总结了客户端Tasker类升级为支持WebSocket通信的最佳实践和规范。
## WebSocketClientTasker接口介绍
`WebSocketClientTasker` 接口定义了通过WebSocket与服务器通信的任务的通用方法包括任务名称、消息更新、进度更新等核心功能。
### 核心方法
1. **getTaskName()** - 获取任务名称用于在WebSocket通信中标识任务
2. **updateMessage(Level, String)** - 更新任务执行过程中的消息
3. **updateTitle(String)** - 更新任务标题
4. **updateProgress(long, long)** - 更新任务进度
5. **cancelTask()** - 取消任务执行(默认实现为空)
6. **callRemoteTask(MessageHolder, Locale, Object...)** - 调用远程WebSocket任务
7. **callRemoteTaskAsync(MessageHolder, Locale, Object...)** - 异步调用远程WebSocket任务
8. **generateTaskId()** - 生成唯一的任务ID
### 典型实现模式概览
通过分析项目中的17个实现类我们发现了以下典型实现模式
1. **标准实现**继承Tasker<Object>并实现WebSocketClientTasker
2. **属性注入**:使用@Setter注解或手动设置属性传递任务参数
3. **Spring Bean获取**通过SpringApp.getBean()获取服务实例
4. **消息更新**:简洁的消息更新方式
5. **参数传递**通过callRemoteTask的可变参数传递任务所需数据
## Tasker实现WebSocketClientTasker最佳实践
### 1. 类定义和继承
```java
/**
* 任务类描述
* 用于通过WebSocket与服务器通信执行具体操作
*/
public class 任务类名 extends Tasker<Object> implements WebSocketClientTasker {
private static final Logger logger = LoggerFactory.getLogger(任务类名.class);
// 实现方法
}
```
### 2. 参数传递模式
#### 2.1 使用@Setter注解注入参数
```java
/**
* 更新供应商评价表任务
*/
public class CompanyVendorEvaluationFormUpdateTask extends Tasker<Object> implements WebSocketClientTasker {
private static final Logger logger = LoggerFactory.getLogger(CompanyVendorEvaluationFormUpdateTask.class);
@Setter
private VendorVo vendor;
@Override
protected Object execute(MessageHolder holder) throws Exception {
updateTitle("更新供应商评价表");
return callRemoteTask(holder, getLocale(), vendor.getId());
}
}
```
#### 2.2 使用@Getter和@Setter注解
```java
/**
* 客户文件重建任务类
*/
public class CustomerRebuildFilesTasker extends Tasker<Object> implements WebSocketClientTasker {
@Getter
@Setter
private CustomerVo companyCustomer;
@Override
protected Object execute(MessageHolder holder) throws Exception {
updateTitle("重建客户文件");
return callRemoteTask(holder, getLocale(), companyCustomer.getId());
}
}
```
### 2. 必要方法实现
#### 2.1 getTaskName()
```java
@Override
public String getTaskName() {
return "Task名称"; // 必须与服务器端对应Tasker类名匹配
}
```
**注意事项**
- 任务名称必须与服务器端对应的Tasker注册名tasker_mapper.json中的key保持一致
- 名称应简洁明了,反映任务的核心功能
#### 2.2 updateProgress()
updateProgress 方法重载为public用于外部调用更新进度
```java
@Override
public void updateProgress(long current, long total) {
super.updateProgress(current, total); // 调用父类方法更新进度
}
```
#### 2.3 updateTitle()
```java
@Override
public void updateTitle(String title) {
super.updateTitle(title); // 使用Tasker的updateTitle方法更新标题
}
```
#### 2.4 execute() 方法重写
**标准实现**
```java
@Override
protected Object execute(MessageHolder holder) throws Exception {
logger.info("开始执行任务描述");
updateTitle("任务标题");
// 调用远程任务,可选传入参数
Object result = callRemoteTask(holder, getLocale(), 可选参数...);
logger.info("任务执行完成");
return result;
}
```
**简洁实现**(适用于简单任务):
```java
@Override
protected Object execute(MessageHolder holder) throws Exception {
updateTitle("更新供应商评价表");
return callRemoteTask(holder, getLocale(), vendor.getId());
}
```
**带消息更新的实现**
```java
@Override
protected Object execute(MessageHolder holder) throws Exception {
// 设置任务标题
updateTitle("全量库存同步任务");
// 更新任务消息
updateMessage("开始执行全量库存同步...");
// 调用远程WebSocket任务
return callRemoteTask(holder, getLocale());
}
```
**关键步骤**
1. 记录任务开始日志(可选)
2. 设置任务标题
3. 可选:添加任务开始消息
4. 调用远程任务执行核心逻辑,传入必要参数
5. 记录任务完成日志(可选)
6. 返回执行结果
### 3. 日志记录
```java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// 在类中定义
private static final Logger logger = LoggerFactory.getLogger(任务类名.class);
// 在关键位置使用日志
logger.info("任务开始");
logger.warn("警告信息");
logger.error("错误信息", exception);
```
**日志使用建议**
- 复杂任务建议记录详细日志
- 简单任务可以简化或省略日志记录
- 确保异常情况下有适当的错误日志记录
### 4. 异常处理
`execute`方法中应妥善处理可能的异常并通过MessageHolder通知用户
```java
@Override
protected Object execute(MessageHolder holder) throws Exception {
try {
// 任务执行逻辑
} catch (Exception e) {
logger.error("任务执行失败", e);
holder.addMessage(Level.SEVERE, "任务执行失败: " + e.getMessage());
throw e; // 向上抛出异常,让框架处理
}
}
```
**异常处理策略**
- 对于简单任务,可以依赖框架的异常处理机制
- 对于复杂任务,建议添加自定义的异常处理逻辑
- 确保异常信息对用户友好且具有足够的调试信息
## 完整实现示例
### 示例1简单任务实现
```java
package com.ecep.contract.task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.WebSocketClientTasker;
/**
* 合同修复任务类
* 用于通过WebSocket与服务器通信执行合同数据修复操作
*/
public class ContractRepairAllTasker extends Tasker<Object> implements WebSocketClientTasker {
private static final Logger logger = LoggerFactory.getLogger(ContractRepairAllTasker.class);
@Override
public void updateProgress(long current, long total) {
super.updateProgress(current, total);
}
@Override
public void updateTitle(String title) {
// 使用Tasker的updateTitle方法更新标题
super.updateTitle(title);
}
@Override
public String getTaskName() {
return "ContractRepairAllTask"; // 与服务器端对应Tasker类名匹配
}
@Override
protected Object execute(MessageHolder holder) throws Exception {
logger.info("开始执行合同修复任务");
updateTitle("合同数据修复");
Object result = callRemoteTask(holder, getLocale());
logger.info("合同修复任务执行完成");
return result;
}
}
```
### 示例2带参数的任务实现
```java
package com.ecep.contract.task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.WebSocketClientTasker;
import com.ecep.contract.vo.VendorVo;
import lombok.Setter;
/**
* 更新供应商评价表
*/
public class CompanyVendorEvaluationFormUpdateTask extends Tasker<Object> implements WebSocketClientTasker {
private static final Logger logger = LoggerFactory.getLogger(CompanyVendorEvaluationFormUpdateTask.class);
@Setter
private VendorVo vendor;
@Override
public void updateProgress(long current, long total) {
super.updateProgress(current, total);
}
@Override
public String getTaskName() {
return "CompanyVendorEvaluationFormUpdateTask"; // 与服务器端对应Tasker类名匹配
}
@Override
protected Object execute(MessageHolder holder) throws Exception {
updateTitle("更新供应商评价表");
return callRemoteTask(holder, getLocale(), vendor.getId());
}
}
```
### 示例3使用Spring Bean的任务实现
```java
package com.ecep.contract.task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.SpringApp;
import com.ecep.contract.WebSocketClientTasker;
import com.ecep.contract.service.YongYouU8Service;
/**
* 合同同步任务
*/
public class ContractSyncTask extends Tasker<Object> implements WebSocketClientTasker {
private static final Logger logger = LoggerFactory.getLogger(ContractSyncTask.class);
private YongYouU8Service yongYouU8Service;
private YongYouU8Service getYongYouU8Service() {
if (yongYouU8Service == null) {
yongYouU8Service = SpringApp.getBean(YongYouU8Service.class);
}
return yongYouU8Service;
}
public String getTaskName() {
return "ContractSyncTask"; // 与服务器端对应Tasker类名匹配
}
@Override
public void updateProgress(long current, long total) {
super.updateProgress(current, total);
}
@Override
protected Object execute(MessageHolder holder) throws Exception {
updateTitle("用友U8系统-同步合同");
return callRemoteTask(holder, getLocale());
}
}
```
## 注意事项和最佳实践
### 1. 命名规范
- 任务类名应采用驼峰命名法,以`Tasker`结尾或描述性名称如`Task`
- getTaskName()返回的名称应与服务器端对应Tasker类名完全匹配
- 类注释应清晰描述任务的功能和用途
### 2. 继承关系
- 必须同时继承Tasker类并实现WebSocketClientTasker接口
- Tasker泛型参数通常为Object
- 确保正确导入所有必要的包
### 3. 参数处理
- 对于需要参数的任务,使用@Setter注解简化属性设置
- 对于需要在多处使用的参数,考虑添加@Getter注解
- 确保参数验证(如果必要)
### 4. Spring Bean获取
- 使用SpringApp.getBean()获取所需的服务实例
- 考虑使用懒加载模式避免不必要的Bean初始化
### 5. 消息和进度更新
- 使用updateTitle()设置有意义的任务标题
- 通过MessageHolder或updateMessage()记录详细的执行消息
- 确保进度更新反映真实的执行进度
### 6. 异常处理
- 在关键操作处添加try-catch块
- 记录异常日志并通知用户
- 适当向上抛出异常以确保框架能正确处理
### 7. 日志级别使用
- INFO: 记录正常的操作流程
- WARNING: 记录可能的问题,但不影响继续执行
- ERROR: 记录严重错误,通常会终止执行
### 8. 远程调用参数
- 确保传入的参数类型与服务器端Tasker期望的一致
- 对于不需要参数的任务,可以不传入额外参数
- 对于需要多个参数的任务,确保参数顺序正确
### 9. 代码风格
- 保持代码简洁明了
- 遵循项目的代码格式化规范
- 添加必要的注释说明核心逻辑
### 10. 实现策略选择
- 简单任务:使用简洁的实现方式,省略不必要的日志
- 复杂任务:添加详细的日志记录和异常处理
- 有特定需求的任务:根据需要重写接口中的其他方法
## 与服务器端交互流程
1. 客户端Tasker通过callRemoteTask()方法提交任务
2. WebSocketClientService负责建立与服务器的连接并发送任务信息
3. 服务器接收到任务后创建对应的Tasker实例并执行
4. 执行过程中的消息、进度等通过WebSocket实时返回给客户端
5. 客户端Tasker通过updateMessage()、updateProgress()等方法更新UI
## 扩展和自定义
如需为特定任务提供自定义功能,可以:
1. 重写cancelTask()方法实现任务取消逻辑
2. 根据需要添加额外的字段和方法
3. 扩展execute()方法实现更复杂的任务流程
## 总结
通过分析项目中的17个WebSocketClientTasker实现类我们总结了客户端Tasker实现的多种模式和最佳实践。这些实现从简单到复杂涵盖了各种使用场景为后续Tasker的编写提供了全面的参考。
客户端Tasker类实现WebSocketClientTasker接口是实现与服务器实时通信的关键步骤。通过遵循本文档中的规范和最佳实践可以确保任务执行的可靠性、进度的实时更新和良好的用户体验。
在实际开发中,应根据任务的复杂度和具体需求,选择合适的实现模式和策略,同时保持代码的一致性和可维护性。

View File

@@ -0,0 +1,374 @@
# Repository一致性分析 - 综合报告
## 📊 执行摘要
### 分析概览
- **总Repository数量**: 22个
- **涉及业务域**: contract, company, customer, project, vendor
- **分析时间**: 2024年项目全面分析
- **分析范围**: 覆盖server模块下所有5个业务域的Repository实现
### 目录覆盖情况
`server/src/main/java/com/ecep/contract/ds/contract/repository/` (13个)
`server/src/main/java/com/ecep/contract/ds/company/repository/` (3个)
`server/src/main/java/com/ecep/contract/ds/customer/repository/` (2个)
`server/src/main/java/com/ecep/contract/ds/project/repository/` (3个)
`server/src/main/java/com/ecep/contract/ds/vendor/repository/` (2个)
## 🔍 关键发现与评估
### 🔴 严重问题继承层次不一致5个
#### 1. 直接继承JpaRepository4个
- `ContractFileRepository` - 直接继承`JpaRepository``JpaSpecificationExecutor`
- `ContractItemRepository` - 直接继承`JpaRepository``JpaSpecificationExecutor`
- `VendorFileRepository` - 直接继承`JpaRepository``JpaSpecificationExecutor`
- `ProjectQuotationRepository` - 直接继承`JpaRepository``JpaSpecificationExecutor`
#### 2. 继承多个冗余接口1个
- `PurchaseOrderRepository` - 同时继承了4个接口`CrudRepository`, `PagingAndSortingRepository`, `JpaRepository`, `JpaSpecificationExecutor`
### 🟡 中等问题6个
#### 注解缺失2个
- `CompanyFileRepository` - 缺少`@Repository`注解
- `ProjectFileRepository` - 缺少`@Repository`注解
#### 文档注释缺失4个
- `CompanyFileRepository` - 缺少JavaDoc注释
- `ProjectFileRepository` - 缺少JavaDoc注释
- `VendorFileRepository` - 缺少JavaDoc注释
- `ProjectQuotationRepository` - 缺少JavaDoc注释
### 📈 整体质量评估
#### 正确实现统计
-**17个Repository正确继承MyRepository** (77%)
-**5个Repository继承层次错误** (23%)
- 🟡 **2个Repository缺少注解** (9%)
- 🟡 **4个Repository文档不完整** (18%)
#### 质量分级分布
- **A级完全正确**: 13个 (59%)
- **B级轻微问题**: 6个 (27%)
- **C级严重错误**: 3个 (14%)
---
# 详细技术分析
## 继承一致性详细分析
### ✅ 正确继承MyRepository的Repository17个
**contract业务域7个**
- ContractRepository
- ContractBalanceRepository
- SalesOrderRepository
- ContractTypeRepository
- ContractKindRepository
- ContractInvoiceRepository
**company业务域3个**
- CompanyRepository
- CompanyFileRepository (有注解问题)
- CompanyCustomerRepository
**customer业务域2个**
- CustomerCatalogRepository
**project业务域2个**
- ProjectRepository
- ProjectFileRepository (有注解问题)
**vendor业务域1个**
- VendorRepository
### ❌ 继承层次错误的Repository5个
#### Contract业务域3个
1. **ContractFileRepository**
2. **ContractItemRepository**
3. **PurchaseOrderRepository**
#### Project业务域1个
4. **ProjectQuotationRepository**
#### Vendor业务域1个
5. **VendorFileRepository**
## 问题影响分析
### 技术影响
- ❌ 违反了统一的架构设计原则
- ❌ 失去了MyRepository提供的统一功能增强
- ❌ 代码风格不一致,影响可维护性
- ❌ 开发团队需要维护多种不同的实现模式
- ❌ 增加了代码维护成本和技术债务
### 业务影响
- ❌ 代码可维护性降低
- ❌ 新团队成员学习成本增加
- ❌ 代码审查复杂度提高
- ❌ 系统稳定性潜在风险增加
## 详细修复方案
### 🔴 优先级1修复继承层次错误
#### 1. ContractFileRepository
```java
// ❌ 当前错误实现
public interface ContractFileRepository
extends JpaRepository<ContractFile, Integer>, JpaSpecificationExecutor<ContractFile> {
// ✅ 正确修复
@Repository
public interface ContractFileRepository extends MyRepository<ContractFile, Integer> {
// 自动获得MyRepository提供的所有功能
}
```
#### 2. ContractItemRepository
```java
// ❌ 当前错误实现
public interface ContractItemRepository
extends JpaRepository<ContractItem, Integer>, JpaSpecificationExecutor<ContractItem> {
// ✅ 正确修复
@Repository
public interface ContractItemRepository extends MyRepository<ContractItem, Integer> {
// 统一继承结构,简化维护
}
```
#### 3. VendorFileRepository
```java
// ❌ 当前错误实现
@Repository
public interface VendorFileRepository
extends JpaRepository<VendorFile, Integer>, JpaSpecificationExecutor<VendorFile> {
// ✅ 正确修复
@Repository
public interface VendorFileRepository extends MyRepository<VendorFile, Integer> {
// 保持注解的同时修正继承结构
}
```
#### 4. ProjectQuotationRepository
```java
// ❌ 当前错误实现
@Repository
public interface ProjectQuotationRepository
extends JpaRepository<ProjectQuotation, Integer>, JpaSpecificationExecutor<ProjectQuotation> {
// ✅ 正确修复
@Repository
public interface ProjectQuotationRepository extends MyRepository<ProjectQuotation, Integer> {
// 获得MyRepository的所有增强功能
}
```
#### 5. PurchaseOrderRepository
```java
// ❌ 当前错误实现继承4个冗余接口
public interface PurchaseOrderRepository extends
CrudRepository<PurchaseOrder, Integer>,
PagingAndSortingRepository<PurchaseOrder, Integer>,
JpaRepository<PurchaseOrder, Integer>,
JpaSpecificationExecutor<PurchaseOrder> {
// ✅ 正确修复
@Repository
public interface PurchaseOrderRepository extends MyRepository<PurchaseOrder, Integer> {
// 单一继承,清晰简洁
}
```
### 🟡 优先级2修复注解缺失
#### CompanyFileRepository
```java
// ❌ 当前缺少注解
public interface CompanyFileRepository extends MyRepository<CompanyFile, Integer> {
// ✅ 添加注解
@Repository
public interface CompanyFileRepository extends MyRepository<CompanyFile, Integer> {
// 添加完整的JavaDoc注释
/**
* 公司文件数据访问接口
* 提供公司相关文件的CRUD操作和业务查询功能
*/
}
```
#### ProjectFileRepository
```java
// ❌ 当前缺少注解
public interface ProjectFileRepository extends MyRepository<ProjectFile, Integer> {
// ✅ 添加注解
@Repository
public interface ProjectFileRepository extends MyRepository<ProjectFile, Integer> {
/**
* 项目文件数据访问接口
* 提供项目文件的管理和查询功能
*/
}
```
### 🟡 优先级3完善文档注释
为以下Repository补充完整的JavaDoc注释
- CompanyFileRepository
- ProjectFileRepository
- VendorFileRepository
- ProjectQuotationRepository
标准JavaDoc格式示例
```java
/**
* [功能描述]数据访问接口
*
* 提供[业务描述]的CRUD操作和业务查询功能
*
* @author [作者]
* @since [版本]
*/
```
## 实施计划
### 阶段1立即修复1-2天
**目标**: 解决所有严重的继承层次错误
**执行步骤**:
1. 备份当前的Repository实现
2. 逐一修复5个继承层次错误的Repository
3. 添加缺失的@Repository注解
4. 运行编译测试确保无破坏性变更
5. 执行基本功能验证测试
**验收标准**:
- 所有Repository都继承MyRepository
- 编译通过,无语法错误
- 基本CRUD功能正常
### 阶段2文档完善1天
**目标**: 统一JavaDoc注释规范
**执行步骤**:
1. 为4个Repository补充JavaDoc注释
2. 检查并更新相关单元测试
3. 验证文档格式规范性
4. 代码审查确认
**验收标准**:
- 所有Repository都有完整的JavaDoc注释
- 测试用例覆盖主要功能
- 代码审查通过
### 阶段3质量保证半天
**目标**: 确保修改不影响系统稳定性
**执行步骤**:
1. 全面编译测试
2. 运行相关单元测试套件
3. 执行集成测试验证
4. 更新相关文档
5. 最终验收确认
**验收标准**:
- 所有测试通过
- 系统功能完整
- 性能无明显影响
## 对规范文档的影响
### ✅ 已更新的规范文档
- **`server_repository_rules.md`** - 已包含错误示例和正确实现指南
- 新增"2.2 重要错误示例"章节
- 新增"2.3 当前项目错误统计"章节
- 新增"2.5 MyRepository基类能力"章节
### 📋 需要更新的检查清单
1. **继承层次检查** - 确保所有Repository继承MyRepository
2. **注解完整性检查** - 验证所有Repository都有@Repository注解
3. **文档规范性检查** - 确认JavaDoc注释完整性
4. **代码风格一致性检查** - 验证命名规范和代码格式
## 🎯 预期收益
### 技术收益
-**统一架构设计** - 所有Repository遵循一致的设计模式
-**代码一致性提升** - 消除实现差异,提高代码质量
-**维护成本降低** - 单一继承结构,简化维护工作
-**团队开发效率提升** - 统一规范,降低学习成本
### 质量收益
-**减少代码冗余** - 消除重复的接口继承
-**提高代码可读性** - 统一模式,易于理解
-**降低技术债务** - 减少不一致实现的技术负担
-**增强系统稳定性** - 统一模式,减少潜在风险
## 📅 后续行动
### 立即行动(本周)
1. 🔧 修复5个继承层次错误的Repository
2. 🔧 添加缺失的@Repository注解
3. 🔧 编译测试验证功能完整性
### 短期完善(下周)
1. 📝 补充JavaDoc文档注释
2. 🧪 运行完整的单元测试套件
3. 👥 代码审查确认实现质量
### 长期维护
1. 📋 建立Repository开发规范检查清单
2. 🔍 定期进行Repository实现一致性检查
3. 🎓 团队培训和规范宣贯
4. 🤖 建立CI/CD流程中的自动检查机制
## 📞 技术支持
如需技术支持或有任何疑问,请参考:
### 相关文档
- **详细技术分析**: `repository_analysis_report.md`
- **实施规范指导**: `server_repository_rules.md`
- **项目整体规范**: `.trae/rules/` 目录
### 联系方式
- 技术问题: 参考详细分析报告中的修复代码示例
- 规范疑问: 查看server_repository_rules.md文档
- 实施支持: 遵循本报告的分阶段实施计划
---
## 结论
通过对项目中22个Repository的全面分析我们发现了明显的实现不一致性问题特别是23%的Repository存在继承层次错误。这些问题严重影响了代码的可维护性和架构的统一性。
**关键统计数据**:
- 17个Repository正确实现 (77%)
- 5个继承层次错误 (23%)
- 2个注解缺失 (9%)
- 4个文档不完整 (18%)
**修复优先级**:
1. **高优先级**: 修复5个继承层次错误1-2天
2. **中优先级**: 补充2个缺失注解半天
3. **低优先级**: 完善4个文档注释1天
建议按照本报告提出的分阶段实施计划逐步改进所有Repository实现确保项目达到统一的架构设计标准。通过这些改进将显著提升代码质量、降低维护成本并为团队的长期发展奠定坚实基础。
**分析状态**: ✅ 完成
**下一步**: 执行分阶段修复计划
**预计完成时间**: 3-4个工作日
---
**报告生成时间**: 2024年
**分析深度**: 全面技术分析
**实施可行性**: 高(详细修复方案已提供)

View File

@@ -0,0 +1,460 @@
# Server模块 Repository 实现经验总结
## 1. Repository 基本架构
### 1.1 接口设计原则
- Repository接口必须继承`MyRepository<T, ID>`基类
- 使用`@Repository`注解标记为Spring组件
- 遵循接口隔离原则,按业务功能组织方法
### 1.2 包结构规范
```
com.ecep.contract.ds.{business}.repository.{EntityName}Repository.java
```
示例:`ContractBalanceRepository`位于`com.ecep.contract.ds.contract.repository`
## 2. 接口继承层次
### 2.1 基础接口结构
```java
@Repository
public interface ContractBalanceRepository extends MyRepository<ContractBalance, Integer> {
// 自定义查询方法
}
```
### 2.2 ⚠️ 重要错误示例 - 避免这些实现方式
**❌ 错误示例1直接继承JpaRepository**
```java
// ContractFileRepository, ContractItemRepository, VendorFileRepository, ProjectQuotationRepository
@Repository
public interface ContractFileRepository
extends JpaRepository<ContractFile, Integer>, JpaSpecificationExecutor<ContractFile> {
// 错误不应该直接继承JpaRepository
List<ContractFile> findByContractId(Integer contractId);
}
```
**❌ 错误示例2继承多个冗余接口**
```java
// PurchaseOrderRepository
public interface PurchaseOrderRepository extends
CrudRepository<PurchaseOrder, Integer>,
PagingAndSortingRepository<PurchaseOrder, Integer>,
JpaRepository<PurchaseOrder, Integer>,
JpaSpecificationExecutor<PurchaseOrder> {
// 错误冗余接口继承应该只继承MyRepository
}
```
**❌ 错误示例3缺少@Repository注解**
```java
// CompanyFileRepository, ProjectFileRepository
public interface CompanyFileRepository extends MyRepository<CompanyFile, Integer> {
// 错误:缺少@Repository注解
List<CompanyFile> findByCompany(Company company);
}
```
**❌ 错误示例4缺少JavaDoc注释**
```java
// 多个Repository存在此问题
public interface VendorFileRepository extends MyRepository<VendorFile, Integer> {
// 错误缺少JavaDoc注释说明用途和方法
List<VendorFile> findAllByVendorId(int vendorId);
}
```
**✅ 正确实现示例**
```java
/**
* 合同Repository - 提供合同相关的数据库访问操作
*/
@Repository
public interface ContractRepository extends MyRepository<Contract, Integer> {
// 正确统一继承MyRepository有完整的JavaDoc
/**
* 根据合同代码查询合同
* @param code 合同代码
* @return 合同 Optional
*/
Optional<Contract> findByCode(String code);
/**
* 根据状态查询合同列表
* @param status 合同状态
* @return 合同列表
*/
List<Contract> findByStatus(ContractStatus status);
}
```
### 2.3 当前项目错误统计
基于对全项目22个Repository的全面分析发现以下问题分布
**🔴 继承层次错误5个**
- `ContractFileRepository` - 直接继承JpaRepository
- `ContractItemRepository` - 直接继承JpaRepository
- `VendorFileRepository` - 直接继承JpaRepository
- `ProjectQuotationRepository` - 直接继承JpaRepository
- `PurchaseOrderRepository` - 继承4个冗余接口
**🟡 注解缺失2个**
- `CompanyFileRepository` - 缺少@Repository注解
- `ProjectFileRepository` - 缺少@Repository注解
**🟡 文档注释不完整4个**
- `CompanyFileRepository` - 缺少JavaDoc注释
- `ProjectFileRepository` - 缺少JavaDoc注释
- `VendorFileRepository` - 缺少JavaDoc注释
- `ProjectQuotationRepository` - 缺少JavaDoc注释
**✅ 正确实现17个**
- contract: ContractRepository, ContractBalanceRepository, SalesOrderRepository, ContractTypeRepository, ContractKindRepository, ContractInvoiceRepository
- company: CompanyRepository
- customer: CompanyCustomerRepository, CustomerCatalogRepository
- project: ProjectRepository
- vendor: VendorRepository
### 2.5 MyRepository基类能力
`MyRepository`接口继承结构:
- `JpaRepository`提供CRUD操作
- 继承`JpaSpecificationExecutor`提供条件查询能力
- 继承`QueryByExampleExecutor`提供示例查询能力
## 3. 查询方法设计
### 3.1 方法命名规范
使用Spring Data JPA的查询方法命名约定
#### 基本查询方法
- `findBy{属性名}` - 根据属性查找
- `findBy{属性名}And{属性名}` - 多条件AND查询
- `findBy{属性名}Or{属性名}` - 多条件OR查询
- `findBy{属性名}OrderBy{排序属性}` - 带排序查询
#### 关联对象查询
- `findBy{关联对象属性名}.{关联对象属性}` - 嵌套属性查询
- 支持多级嵌套,如:`findByContractCompanyName`
#### 特殊查询类型
```java
// 根据业务ID查找
ContractBalance findByGuid(UUID guid);
// 根据合同ID查找带分页支持
Page<ContractBalance> findByContractId(Integer contractId, Pageable pageable);
// 根据多个条件组合查询
List<ContractBalance> findByContractIdAndRefId(Integer contractId, String refId);
```
### 3.2 自定义查询实现
当方法命名无法满足需求时,使用`@Query`注解:
```java
@Query("SELECT cb FROM ContractBalance cb WHERE cb.contract.id = :contractId " +
"AND cb.guid = :guid AND cb.setupDate >= :startDate")
Page<ContractBalance> findByContractAndGuidAndDateRange(
@Param("contractId") Integer contractId,
@Param("guid") UUID guid,
@Param("startDate") LocalDateTime startDate,
Pageable pageable
);
```
## 4. 分页和排序支持
### 4.1 分页查询
```java
// 在Repository接口中定义
Page<ContractBalance> findByContractId(Integer contractId, Pageable pageable);
// 在Service中调用
Page<ContractBalance> result = repository.findByContractId(contractId,
PageRequest.of(page, size, Sort.by("setupDate").descending()));
```
### 4.2 排序规范
- 默认按主键ID降序排列
- 业务相关字段按创建时间降序
- 支持多字段排序:`Sort.by("field1").ascending().and(Sort.by("field2").descending())`
## 5. 统计和聚合查询
### 5.1 基础统计
```java
@Query("SELECT COUNT(cb) FROM ContractBalance cb WHERE cb.contract.id = :contractId")
Long countByContractId(@Param("contractId") Integer contractId);
@Query("SELECT SUM(cb.balanceAmount) FROM ContractBalance cb WHERE cb.contract.id = :contractId")
BigDecimal sumBalanceByContractId(@Param("contractId") Integer contractId);
```
### 5.2 复杂聚合
```java
@Query("SELECT new com.ecep.contract.vo.ContractBalanceStatsVO(" +
"cb.contract.id, COUNT(cb), SUM(cb.balanceAmount)) " +
"FROM ContractBalance cb GROUP BY cb.contract.id")
List<ContractBalanceStatsVO> getBalanceStatsByContract();
```
## 6. 缓存策略
### 6.1 缓存注解使用
虽然Repository本身不直接使用缓存但需要考虑Service层的缓存需求
```java
// 在Service层配合使用
@Cacheable(key = "#p0")
ContractBalance findById(Integer id);
@CacheEvict(key = "#p0.id")
ContractBalance save(ContractBalance contractBalance);
```
### 6.2 缓存键设计原则
- 单一记录:使用主键作为键,如`"balance-" + id`
- 按业务维度缓存:使用业务标识,如`"contract-" + contractId`
- 避免缓存键冲突
## 7. 事务管理
### 7.1 事务传播级别
- 查询方法:默认`REQUIRED`
- 写操作:明确指定`@Transactional(propagation = Propagation.REQUIRED)`
### 7.2 异常处理
```java
@Transactional
public ContractBalance saveWithRetry(ContractBalance balance) {
try {
return repository.save(balance);
} catch (DataIntegrityViolationException e) {
// 处理数据完整性异常
throw new BusinessException("数据重复或违反约束", e);
}
}
```
## 8. 性能优化
### 8.1 懒加载优化
```java
// 在查询时指定抓取策略
@Query("SELECT cb FROM ContractBalance cb LEFT JOIN FETCH cb.contract " +
"LEFT JOIN FETCH cb.employee WHERE cb.id = :id")
Optional<ContractBalance> findByIdWithRelations(@Param("id") Integer id);
```
### 8.2 批量操作优化
```java
// 批量插入
@Modifying
@Query("DELETE FROM ContractBalance cb WHERE cb.contract.id IN :contractIds")
void deleteByContractIds(@Param("contractIds") List<Integer> contractIds);
```
## 9. 错误处理和调试
### 9.1 常见异常类型
- `DataAccessException` - 数据访问异常
- `DataIntegrityViolationException` - 数据完整性异常
- `EmptyResultDataAccessException` - 空结果异常
### 9.2 日志记录
```java
@Repository
@Slf4j
public class ContractBalanceRepository {
@Query("...")
public List<ContractBalance> findComplexQuery(...) {
log.debug("执行复杂查询: {}", jpql);
// 执行查询
}
}
```
## 10. 测试策略
### 10.1 Repository测试
```java
@DataJpaTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ContractBalanceRepositoryTest {
@Autowired
private TestEntityManager em;
@Autowired
private ContractBalanceRepository repository;
@Test
void shouldFindByContractId() {
// 测试数据准备
ContractBalance balance = createTestBalance();
// 执行测试
Page<ContractBalance> result = repository.findByContractId(
balance.getContract().getId(),
PageRequest.of(0, 10)
);
// 断言验证
assertThat(result.getContent()).hasSize(1);
assertThat(result.getContent().get(0)).isEqualTo(balance);
}
}
```
### 10.2 性能测试
```java
@Test
@RepeatedTest(100)
@Timeout(value = 5, unit = TimeUnit.SECONDS)
void performanceTest() {
// 性能测试实现
}
```
## 11. 最佳实践总结
### 11.1 设计原则
1. **单一职责**每个Repository只负责一个实体类型
2. **接口隔离**:根据客户端需求设计专用查询方法
3. **命名清晰**:方法名应能直观表达查询意图
4. **性能优先**:合理使用索引和查询优化
### 11.2 代码规范
1. **注解完整**:正确使用`@Repository``@Query``@Param`
2. **参数校验**对传入参数进行null和有效性检查
3. **异常处理**:捕获并处理数据库访问异常
4. **日志记录**:记录关键查询操作和性能指标
### 11.3 维护性
1. **版本兼容**:考虑数据库模式变更的影响
2. **向后兼容**:新增方法不影响现有功能
3. **文档完整**复杂查询添加JavaDoc说明
4. **测试覆盖**:确保关键查询逻辑有测试覆盖
## 12. 常见问题和解决方案
### 12.1 N+1查询问题
**问题**查询列表时触发N+1次关联查询
**解决**:使用`JOIN FETCH``@EntityGraph`
### 12.2 性能瓶颈
**问题**:复杂查询导致响应慢
**解决**
1. 添加数据库索引
2. 优化查询语句
3. 使用分页限制结果集
4. 考虑使用缓存
### 12.3 事务死锁
**问题**:并发操作导致事务死锁
**解决**
1. 合理设计事务边界
2. 调整事务隔离级别
3. 实现重试机制
## 13. 扩展建议
### 13.1 动态查询支持
考虑使用`Querydsl``Specification`支持动态查询构建。
### 13.2 读写分离
实现主从数据库分离,提升查询性能。
### 13.3 分布式缓存
结合Redis等分布式缓存提升查询性能。
---
## 14. 实施检查清单
### 14.1 Repository实现后验证步骤
在每个Repository实现完成后请按以下清单进行验证
#### ✅ 基础架构检查
- [ ] Repository接口继承了 `MyRepository<T, ID>`
- [ ] 使用了 `@Repository` 注解
- [ ] 包路径符合规范:`com.ecep.contract.ds.{business}.repository`
- [ ] 类名符合命名规范:`{EntityName}Repository`
#### ✅ 方法设计检查
- [ ] 遵循Spring Data JPA命名约定
- [ ] 包含必要的业务查询方法
- [ ] 复杂查询使用 `@Query` 注解
- [ ] 分页方法支持 `Pageable` 参数
#### ✅ 文档和注释检查
- [ ] 类级别JavaDoc注释完整
- [ ] 关键方法有JavaDoc说明
- [ ] 参数和返回值有明确说明
#### ✅ 性能和质量检查
- [ ] 避免N+1查询问题
- [ ] 适当使用索引提示
- [ ] 包含必要的单元测试
- [ ] 异常处理考虑周全
### 14.2 常见错误检查
在提交代码前,特别检查是否犯了以下常见错误:
#### ❌ 继承层次错误
```java
// 错误示例 - 不要这样做
public interface SomeRepository extends JpaRepository<Entity, Integer>
public interface AnotherRepository extends CrudRepository<Entity, Integer>
// 正确做法 - 统一使用MyRepository
public interface SomeRepository extends MyRepository<Entity, Integer>
```
#### ❌ 方法命名不规范
```java
// 错误示例
List<Entity> getDataByCondition(String condition) // 使用get前缀而非find
// 正确做法
List<Entity> findByCondition(String condition) // 使用find前缀
```
#### ❌ 缺少必要文档
```java
// 错误示例 - 无注释
List<Entity> findByStatus(String status);
// 正确做法 - 完整注释
/**
* 根据状态查询实体列表
*
* @param status 状态值
* @return 符合状态的实体列表
*/
List<Entity> findByStatus(String status);
```
### 14.3 架构一致性验证
确保新实现的Repository与项目中其他Repository保持一致
1. **继承模式统一**所有Repository都必须继承MyRepository
2. **注解使用统一**:统一使用@Repository注解
3. **命名约定统一**遵循Spring Data JPA命名规范
4. **文档风格统一**保持JavaDoc注释风格一致
### 14.4 后续维护注意事项
- 新增查询方法时遵循现有命名规范
- 修改现有方法时保持向后兼容性
- 定期审查Repository方法的性能和合理性
- 及时更新相关文档和测试用例
---
*本文档基于ContractBalanceRepository实现经验总结结合项目实际情况分析其他Repository实现应参考此文档规范。特别注意避免文档中提到的常见错误。*

View File

@@ -1,260 +1,435 @@
# 服务器端 Service 类规则文档 # 服务器端Service设计规范
## 1. 概述 ## 目录结构
本规则文档定义了 Contract-Manager 项目服务器端server模块Service 类的设计规范、实现标准和最佳实践。所有服务器端 Service 类必须严格遵循本规则,以确保代码的一致性、可维护性和性能。 每个业务域下的service目录结构示例
```
ds/
├── company/service/
│ ├── CompanyService.java # 主业务服务
│ ├── CompanyContactService.java # 联系人服务
│ ├── CompanyFileService.java # 文件管理服务
│ ├── CompanyOldNameService.java # 曾用名服务
│ └── ...
├── contract/service/
│ ├── ContractService.java # 主业务服务
│ ├── ContractCatalogService.java # 分类目录服务
│ └── ...
├── customer/service/
│ ├── CustomerService.java # 主业务服务继承CompanyBasicService
│ └── ...
├── project/service/
│ ├── ProjectService.java # 主业务服务
│ ├── ProjectFileService.java # 文件管理服务
│ └── ...
└── vendor/service/
├── VendorService.java # 主业务服务继承CompanyBasicService
└── ...
```
## 2. 目录结构 ## 核心基类和接口体系
Service 类按业务领域组织,位于 `server/src/main/java/com/ecep/contract/ds/{业务领域}/service/` 目录下。其中 `{业务领域}` 对应具体的业务模块,如 `customer``contract``company``project``other` 等。 ### 主要基类
- **EntityService<M, Vo, ID>**: 通用实体服务基类提供CRUD操作的标准实现
- **CompanyBasicService**: 专门处理公司相关业务的基础服务类,支持公司关联查询
**示例:** ### 核心接口
- 客户分类服务:`server/src/main/java/com/ecep/contract/ds/customer/service/CustomerCatalogService.java` - **IEntityService<T>**: 实体基本操作接口
- 员工服务:`server/src/main/java/com/ecep/contract/ds/other/service/EmployeeService.java` - **QueryService<Vo>**: 查询服务接口
- **VoableService<M, Vo>**: 实体与视图对象转换服务接口
## 3. 命名规范 ## 注解使用规范
- **类名**:采用驼峰命名法,首字母大写,以 `Service` 结尾,表示这是一个服务类。 ### 类级别注解
**示例**`CustomerCatalogService``EmployeeService` ```java
@Lazy // 延迟加载,避免循环依赖
@Service // Spring服务组件
@CacheConfig(cacheNames = "业务缓存名称") // 缓存配置
public class CompanyService extends EntityService<Company, CompanyVo, Integer>
implements IEntityService<Company>, QueryService<CompanyVo>, VoableService<Company, CompanyVo> {
// 实现代码
}
```
## 4. 接口实现 ### 方法级别注解
```java
// 查询方法缓存 - 使用参数作为缓存键
@Cacheable(key = "#p0") // ID查询
public CompanyVo findById(Integer id) {
return repository.findById(id).map(Company::toVo).orElse(null);
}
所有业务领域的 Service 类必须实现以下三个核心接口: @Cacheable(key = "'name-'+#p0") // 名称查询,带前缀
public CompanyVo findByName(String name) {
return repository.findFirstByName(name).map(Company::toVo).orElse(null);
}
### 4.1 IEntityService<T> // 修改方法缓存清理 - 清理所有相关缓存
@Caching(evict = {
@CacheEvict(key = "#p0.id"),
@CacheEvict(key = "'name-'+#p0.name"),
@CacheEvict(key = "'code-'+#p0.code")
})
public Contract save(Contract contract) {
return contractRepository.save(contract);
}
```
提供实体类的基本 CRUD 操作。泛型 `T` 表示实体类类型。 ## 依赖注入规范
**主要方法:** ### Repository注入
- `T getById(Integer id)`:根据 ID 查询实体对象 ```java
- `Page<T> findAll(Specification<T> spec, Pageable pageable)`:根据条件和分页参数查询实体列表 @Lazy
- `Specification<T> getSpecification(String searchText)`:构建搜索条件 @Autowired
- `void delete(T entity)`:删除实体 private CompanyRepository repository;
- `T save(T entity)`:保存实体 ```
### 4.2 QueryService<Vo> ### Service间依赖注入
```java
@Lazy
@Autowired
private ContractService contractService;
提供 VO 对象的查询能力。泛型 `Vo` 表示视图对象类型。 @Lazy
@Autowired
private VendorService vendorService;
**主要方法:** @Lazy
- `Vo findById(Integer id)`:根据 ID 查询 VO 对象 @Autowired
- `Page<Vo> findAll(JsonNode paramsNode, Pageable pageable)`:根据 JSON 查询参数和分页条件查询 VO 列表 private CompanyContactService companyContactService;
- `default long count(JsonNode paramsNode)`:根据查询参数统计数据总数 ```
### 4.3 VoableService<M, Vo> ### 外部服务依赖注入
```java
@Lazy
@Autowired
private CloudRkService cloudRkService;
提供从 VO 对象更新实体对象的能力。泛型 `M` 表示实体类类型,`Vo` 表示视图对象类型。 @Lazy
@Autowired
private CloudTycService cloudTycService;
**主要方法:** @Autowired(required = false) // 可选依赖
- `void updateByVo(M model, Vo vo)`:根据 VO 对象更新实体对象 private YongYouU8Service yongYouU8Service;
```
**实现示例:** ## 查询实现模式
### 标准查询实现
```java
@Override
public Page<CompanyVo> findAll(JsonNode paramsNode, Pageable pageable) {
Specification<Company> spec = null;
// 搜索文本查询
if (paramsNode.has("searchText")) {
spec = getSpecification(paramsNode.get("searchText").asText());
}
// 字段等值查询
spec = SpecificationUtils.andFieldEqualParam(spec, paramsNode, "name", "uniscid", "abbName");
// 分页查询并转换为VO
return findAll(spec, pageable).map(Company::toVo);
}
```
### 复杂查询条件构建
```java
@Override
public Specification<Company> getSpecification(String searchText) {
if (!StringUtils.hasText(searchText)) {
return null;
}
return (root, query, builder) -> {
return builder.or(
builder.like(root.get("name"), "%" + searchText + "%"),
builder.like(root.get("code"), "%" + searchText + "%"),
builder.like(root.get("description"), "%" + searchText + "%"));
};
}
```
### CompanyBasicService继承模式
继承CompanyBasicService的Service如CustomerService、VendorService提供公司关联查询
```java
@Override
public Specification<Vendor> getSpecification(String searchText) {
if (!StringUtils.hasText(searchText)) {
return null;
}
// 使用公司关联查询
Specification<Vendor> nameSpec = (root, query, builder) -> {
Path<Company> company = root.get("company");
return companyService.buildSearchPredicate(searchText, company, query, builder);
};
// 数字ID查询
if (MyStringUtils.isAllDigit(searchText)) {
try {
int id = Integer.parseInt(searchText);
nameSpec = SpecificationUtils.or(nameSpec, (root, query, builder) -> {
return builder.equal(root.get("id"), id);
});
} catch (Exception ignored) {
}
}
// 实体搜索
List<VendorEntity> searched = vendorEntityService.search(searchText);
if (!searched.isEmpty()) {
nameSpec = SpecificationUtils.or(nameSpec, (root, query, builder) -> {
return builder.in(root.get("id")).value(searched.stream()
.map(VendorEntity::getVendor)
.filter(Objects::nonNull)
.map(Vendor::getId)
.collect(Collectors.toSet()));
});
}
return nameSpec;
}
```
## 业务逻辑实现模式
### 1. 主业务Service继承EntityService
```java ```java
@Lazy @Lazy
@Service @Service
@CacheConfig(cacheNames = "customer-catalog") @CacheConfig(cacheNames = "contract")
public class CustomerCatalogService implements IEntityService<CustomerCatalog>, QueryService<CustomerCatalogVo>, public class ContractService extends EntityService<Contract, ContractVo, Integer>
VoableService<CustomerCatalog, CustomerCatalogVo> { implements IEntityService<Contract>, QueryService<ContractVo>, VoableService<Contract, ContractVo> {
// 实现方法...
@Override
protected ContractRepository getRepository() {
return contractRepository;
}
@Cacheable(key = "#p0")
public ContractVo findById(Integer id) {
return getRepository().findById(id).map(Contract::toVo).orElse(null);
}
// 业务特定方法
public List<Contract> findAllByCompany(Company company) {
return contractRepository.findAllByCompanyId(company.getId());
}
public File getContractCatalogPath(ContractCatalogVo catalog, ContractVo contract) {
// 文件路径处理逻辑
String parent = catalog.getParent();
File dir = getBasePath();
// ... 路径构建逻辑
return dir;
}
} }
``` ```
## 5. 注解规范 ### 2. 继承CompanyBasicService的Service
Service 类必须使用以下注解:
### 5.1 类级别注解
- `@Service`:标记这是一个 Spring 服务类,使其能够被自动扫描和管理
- `@Lazy`:延迟加载服务类,提高应用启动性能
- `@CacheConfig(cacheNames = "缓存名称")`:配置缓存名称,缓存名称通常与业务领域相关
**示例**`@CacheConfig(cacheNames = "customer-catalog")``@CacheConfig(cacheNames = "employee")`
### 5.2 方法级别注解
#### 5.2.1 查询方法注解
- `@Cacheable(key = "缓存键")`:将查询结果缓存起来,下次相同查询可以直接从缓存获取
**示例**`@Cacheable(key = "#p0")`(使用方法第一个参数作为缓存键)、`@Cacheable(key = "'code-'+#p0")`(使用前缀+参数值作为缓存键)
#### 5.2.2 数据修改方法注解
- `@Caching(evict = { ... })`:在保存或删除操作时清除相关缓存
**示例**
```java
@Caching(evict = {
@CacheEvict(key = "#p0.id"),
@CacheEvict(key = "'code-'+#p0.code"),
@CacheEvict(key = "'name-'+#p0.name"),
@CacheEvict(key = "'all'")
})
```
- `@Transactional`:标记方法需要在事务中执行,确保数据一致性
**示例**:用于包含多个数据操作的复杂业务方法
## 6. 缓存策略
Service 类必须遵循以下缓存策略:
### 6.1 查询缓存
- 所有 `findById`、`findByCode`、`findByName` 等单条查询方法都应使用 `@Cacheable` 注解缓存结果
- 缓存键设计应具有唯一性和可读性,通常包含参数值和适当的前缀
- 列表查询(如 `findAll`)可以考虑使用 `@Cacheable`,但需谨慎管理缓存失效
### 6.2 缓存清理
- 所有 `save`、`delete` 等修改数据的方法都应使用 `@Caching` 和 `@CacheEvict` 注解清理相关缓存
- 清理缓存时应考虑所有可能影响的查询,确保缓存数据的一致性
## 7. 方法实现规范
### 7.1 IEntityService 方法实现
- `getById`:直接调用 Repository 的 `findById` 方法,返回实体对象
```java
@Override
public CustomerCatalog getById(Integer id) {
return repository.findById(id).orElse(null);
}
```
- `findAll(Specification<T>, Pageable)`:直接调用 Repository 的 `findAll` 方法,返回实体分页对象
```java
@Override
public Page<CustomerCatalog> findAll(Specification<CustomerCatalog> spec, Pageable pageable) {
return repository.findAll(spec, pageable);
}
```
- `save/delete`:调用 Repository 的相应方法,并添加缓存清理注解
```java
@Caching(evict = { ... })
@Override
public CustomerCatalog save(CustomerCatalog catalog) {
return repository.save(catalog);
}
```
### 7.2 QueryService 方法实现
- `findById`:调用 Repository 的 `findById` 方法,然后调用实体类的 `toVo` 方法转换为 VO 对象
```java
@Cacheable(key = "#p0")
@Override
public CustomerCatalogVo findById(Integer id) {
return repository.findById(id).map(CustomerCatalog::toVo).orElse(null);
}
```
- `findAll(JsonNode, Pageable)`:构建 Specification调用 IEntityService 的 `findAll` 方法,然后使用 Stream API 的 `map` 方法将结果转换为 VO 对象
```java
@Override
public Page<CustomerCatalogVo> findAll(JsonNode paramsNode, Pageable pageable) {
Specification<CustomerCatalog> spec = null;
if (paramsNode.has(ServiceConstant.KEY_SEARCH_TEXT)) {
spec = getSpecification(paramsNode.get(ServiceConstant.KEY_SEARCH_TEXT).asText());
}
// 字段等值查询
spec = SpecificationUtils.andFieldEqualParam(spec, paramsNode, "code", "name", "description");
return repository.findAll(spec, pageable).map(CustomerCatalog::toVo);
}
```
### 7.3 VoableService 方法实现
- `updateByVo`:将 VO 对象的属性逐个复制到实体对象,实现 VO 到实体的转换
```java
@Override
public void updateByVo(CustomerCatalog model, CustomerCatalogVo vo) {
// 参数校验
if (model == null) {
throw new ServiceException("实体对象不能为空");
}
if (vo == null) {
throw new ServiceException("VO对象不能为空");
}
// 映射基本属性
model.setCode(vo.getCode());
model.setName(vo.getName());
model.setDescription(vo.getDescription());
}
```
## 8. 依赖注入规范
- Service 类应使用 `@Autowired` 注解注入所需的 Repository 和其他依赖
- 为了避免循环依赖,所有注入的依赖都应使用 `@Lazy` 注解标记为延迟加载
```java
@Lazy
@Autowired
private CustomerCatalogRepository repository;
```
## 9. 查询条件构建
- 使用 `SpecificationUtils` 工具类辅助构建查询条件
- 实现 `getSpecification(String searchText)` 方法,提供基于搜索文本的模糊查询能力
```java
@Override
public Specification<CustomerCatalog> getSpecification(String searchText) {
if (!StringUtils.hasText(searchText)) {
return null;
}
String likeText = "%" + searchText + "%";
return (root, query, builder) -> {
return builder.or(
builder.like(root.get("code"), likeText),
builder.like(root.get("name"), likeText),
builder.like(root.get("description"), likeText));
};
}
```
## 10. 异常处理
- Service 类应适当处理异常,特别是对输入参数的校验
- 对于业务逻辑异常,应抛出 `ServiceException`
```java
if (model == null) {
throw new ServiceException("实体对象不能为空");
}
```
## 11. 最佳实践
1. **VO优先原则**QueryService 接口应使用 VO 类型作为泛型参数,返回 VO 对象而不是实体对象
2. **缓存粒度**:缓存键应设计得足够细粒度,避免缓存过大或频繁失效
3. **事务管理**:包含多个数据操作的方法应使用 `@Transactional` 注解确保事务一致性
4. **延迟加载**:所有依赖都应使用 `@Lazy` 注解,避免循环依赖问题
5. **参数校验**:方法开始时应进行参数校验,确保输入数据的合法性
6. **文档注释**:关键方法应有清晰的 JavaDoc 注释,说明其功能、参数和返回值
7. **代码复用**:尽量使用 SpecificationUtils 等工具类辅助构建查询条件,提高代码复用性
## 12. 不符合规范的Service类示例
以下是不符合规范的Service类实现应避免
```java ```java
// 错误QueryService泛型参数使用实体类型而非VO类型 @Lazy
@Service @Service
@CacheConfig(cacheNames = "company") @CacheConfig(cacheNames = "company-customer")
public class CompanyService extends EntityService<Company, Integer> public class CustomerService extends CompanyBasicService
implements IEntityService<Company>, QueryService<Company>, VoableService<Company, CompanyVo> { implements IEntityService<CompanyCustomer>, QueryService<CustomerVo>,
// 实现方法... VoableService<CompanyCustomer, CustomerVo> {
}
// 提供公司关联查询
// 错误:未使用缓存注解 public CompanyCustomer findByCompany(Company company) {
@Service return repository.findByCompany(company).orElse(null);
public class ExampleService implements IEntityService<Example>, QueryService<ExampleVo>, }
VoableService<Example, ExampleVo> {
// 未使用@Cacheable、@CacheEvict等缓存注解 public CustomerVo findByCompany(CompanyVo company) {
return repository.findByCompanyId(company.getId()).map(CompanyCustomer::toVo).orElse(null);
}
// 文件重建业务逻辑
public boolean reBuildingFiles(CompanyCustomer companyCustomer, MessageHolder holder) {
List<CompanyCustomerFile> dbFiles = companyCustomerFileService.findAllByCustomer(companyCustomer);
Map<String, CompanyCustomerFile> map = new HashMap<>();
boolean modified = fetchDbFiles(dbFiles, map, holder::info);
List<File> needMoveToCompanyPath = new ArrayList<>();
fetchFiles(companyCustomer.getPath(), needMoveToCompanyPath, retrieveFiles, map, holder::info);
moveFileToCompany(companyCustomer.getCompany(), needMoveToCompanyPath);
holder.info("导入 " + retrieveFiles.size() + " 个文件");
if (!retrieveFiles.isEmpty()) {
retrieveFiles.forEach(v -> v.setCustomer(companyCustomer));
companyCustomerFileService.saveAll(retrieveFiles);
modified = true;
}
return modified;
}
} }
``` ```
## 13. 总结 ## 文件管理Service实现模式
本规则文档定义了服务器端 Service 类的完整规范,包括目录结构、命名规范、接口实现、注解规范、缓存策略、方法实现、依赖注入等方面。所有服务器端开发人员都必须严格遵循这些规则,以确保代码的一致性、可维护性和性能。 ### 文件路径管理
```java
public File getBasePath() {
return new File(confService.getString(ContractConstant.KEY_BASE_PATH));
}
public File getContractCatalogPath(ContractCatalogVo catalog, ContractVo contract) {
String parent = catalog.getParent();
File dir = getBasePath();
if (StringUtils.hasText(parent)) {
dir = new File(dir, parent);
if (!dir.exists() && !dir.mkdir()) {
System.out.println("unable make directory = " + dir.getAbsolutePath());
return null;
}
}
dir = new File(dir, catalog.getPath());
if (!dir.exists() && !dir.mkdir()) {
System.out.println("unable make directory = " + dir.getAbsolutePath());
return null;
}
if (catalog.isUseYear()) {
String code = contract.getCode();
String catalogCode = catalog.getCode();
int length = catalogCode.length();
String yearCode = code.substring(length, length + 2);
dir = new File(dir, "20" + yearCode);
if (!dir.exists() && !dir.mkdir()) {
System.out.println("unable make directory = " + dir.getAbsolutePath());
return null;
}
}
return dir;
}
```
## 缓存策略最佳实践
### 缓存键设计原则
- **ID查询**: `@Cacheable(key = "#p0")`
- **字符串查询**: `@Cacheable(key = "'name-'+#p0")`
- **复合键查询**: `@Cacheable(key = "'code-year-'+#p0+'-'+#p1")`
### 缓存清理策略
```java
@Caching(evict = {
@CacheEvict(key = "#p0.id"), // 主键缓存
@CacheEvict(key = "'name-'+#p0.name"), // 名称缓存
@CacheEvict(key = "'code-'+#p0.code"), // 编码缓存
@CacheEvict(key = "'guid-'+#p0.guid") // GUID缓存
})
public Contract save(Contract contract) {
return contractRepository.save(contract);
}
```
## 性能优化建议
### 1. 延迟加载
所有Service依赖都应使用@Lazy注解,避免循环依赖和启动时的性能问题。
### 2. 缓存粒度
- 单条查询使用细粒度缓存键
- 避免缓存大量数据的列表查询
- 合理设置缓存过期策略
### 3. 分页查询
使用Page<T>进行分页查询,避免一次性加载大量数据:
```java
public Page<Project> findAll(Specification<Project> spec, Pageable pageable) {
return projectRepository.findAll(spec, pageable);
}
```
### 4. 批量操作
对于大量数据操作,考虑使用批量处理:
```java
public void saveAll(List<CompanyCustomerFile> files) {
companyCustomerFileService.saveAll(retrieveFiles);
}
```
## 异常处理规范
### 参数校验
```java
public void updateByVo(CustomerCatalog model, CustomerCatalogVo vo) {
if (model == null) {
throw new ServiceException("实体对象不能为空");
}
if (vo == null) {
throw new ServiceException("VO对象不能为空");
}
// ... 业务逻辑
}
```
### 业务异常处理
```java
public Company findAndRemoveDuplicateCompanyByUniscid(String uniscid) {
List<Company> companies = repository.findAllByUniscid(uniscid);
if (companies.isEmpty()) {
return null;
}
if (companies.size() == 1) {
return companies.getFirst();
} else {
List<Company> result = removeDuplicatesByUniscid(companies);
if (!result.isEmpty()) {
return result.getFirst();
}
}
return null;
}
```
## 工具类使用规范
### SpecificationUtils使用
```java
// 字段等值查询
spec = SpecificationUtils.andFieldEqualParam(spec, paramsNode, "name", "code", "status");
// 参数关联查询
spec = SpecificationUtils.andParam(spec, paramsNode, "company", "catalog", "type");
// 搜索文本组合
spec = SpecificationUtils.andWith(searchText, this::buildSearchSpecification);
```
### 字符串工具使用CompanyBasicService
```java
// 全数字判断
if (MyStringUtils.isAllDigit(searchText)) {
// 数字处理逻辑
}
// 空值检查
if (!StringUtils.hasText(searchText)) {
return null;
}
```
## 最佳实践总结
1. **接口实现完整性**: 所有Service应实现三个核心接口确保功能一致性
2. **缓存策略一致性**: 遵循统一的缓存键设计和清理策略
3. **依赖注入规范**: 使用@Lazy避免循环依赖清晰管理Service间依赖关系
4. **查询性能优化**: 合理使用SpecificationUtils构建查询条件支持分页查询
5. **异常处理统一**: 统一的异常处理方式,提高代码健壮性
6. **代码复用**: 继承合适的基类,复用通用逻辑
7. **文档注释**: 关键方法和复杂业务逻辑应有清晰的JavaDoc注释
8. **性能监控**: 关注缓存命中率,适时调整缓存策略
这套规范确保了Service层的代码质量、性能和可维护性为整个系统的稳定运行提供了坚实基础。

View File

@@ -0,0 +1,460 @@
# WebSocketServerTasker 接口实现规范
## 1. 概述
本文档总结了实现 `WebSocketServerTasker` 接口的标准做法和最佳实践基于对项目中已有实现类的分析。该接口用于服务器端实现支持WebSocket通信的任务处理器实现异步任务的执行和状态监控。
## 2. WebSocketServerTasker 接口介绍
`WebSocketServerTasker` 接口位于 `com.ecep.contract.service.tasker` 包下,继承自 `java.util.concurrent.Callable<Object>`,定义了以下核心方法:
```java
public interface WebSocketServerTasker extends Callable<Object> {
// 初始化任务,处理传入的参数
void init(JsonNode argsNode);
// 设置会话信息(默认实现为空)
default void setSession(SessionInfo session) {}
// 设置消息处理函数
void setMessageHandler(Predicate<Message> messageHandler);
// 设置标题处理函数
void setTitleHandler(Predicate<String> titleHandler);
// 设置属性处理函数
void setPropertyHandler(BiConsumer<String, Object> propertyHandler);
// 设置进度处理函数
void setProgressHandler(BiConsumer<Long, Long> progressHandler);
}
```
## 3. 基础实现模式
### 3.1 推荐实现方式
通过分析项目中的实现类,推荐以下标准实现模式:
1. **继承 Tasker<Object> 并实现 WebSocketServerTasker 接口**
```java
public class YourTasker extends Tasker<Object> implements WebSocketServerTasker {
// 实现代码
}
```
2. **在 Tasker 基类中已实现的方法**
`Tasker` 类已经提供了 `WebSocketServerTasker` 接口中大部分方法的实现,包括:
- `setMessageHandler`
- `setTitleHandler`
- `setPropertyHandler`
- `setProgressHandler`
- `call()` 方法(实现了 `Callable` 接口)
这使得实现类只需要关注特定业务逻辑的实现。
## 3.2 WebSocketServerTasker 注册配置
要使WebSocketServerTasker能够被客户端通过WebSocket调用必须在`tasker_mapper.json`配置文件中进行注册。
### 3.2.1 注册配置文件
配置文件位置:`server/src/main/resources/tasker_mapper.json`
### 3.2.2 注册格式
注册格式为JSON对象包含一个`tasks`字段,该字段是一个键值对映射,其中:
- **键**:任务名称(客户端通过此名称调用任务)
- **值**:任务类的完全限定名
```json
{
"tasks": {
"任务名称": "任务类的完全限定名",
"ContractVerifyTasker": "com.ecep.contract.service.tasker.ContractVerifyTasker"
},
"descriptions": "任务注册信息, 客户端的任务可以通过 WebSocket 调用"
}
```
### 3.2.3 注册示例
假设我们创建了一个名为`CustomTasker`的新任务类,其完全限定名为`com.ecep.contract.service.tasker.CustomTasker`,则注册方式如下:
```json
{
"tasks": {
"CustomTasker": "com.ecep.contract.service.tasker.CustomTasker",
"OtherTasker": "..."
},
"descriptions": "任务注册信息, 客户端的任务可以通过 WebSocket 调用"
}
```
### 3.2.4 注册机制说明
WebSocketServerTaskManager类在启动时会读取`tasker_mapper.json`文件,初始化任务类映射表。当客户端请求执行任务时,系统会:
1. 根据客户端提供的任务名称从映射表中查找对应的任务类
2. 使用反射机制实例化任务对象
3. 设置WebSocket会话和各类处理器
4. 执行任务并通过WebSocket返回结果
## 4. ContractRepairAllTasker 升级经验
通过分析 `ContractRepairAllTasker` 的实现,我们总结了以下升级经验:
### 4.1 升级步骤
1. **继承适当的基础类**:根据业务需求选择继承 `Tasker<Object>` 或其他特定的抽象类(如 `AbstContractRepairTasker`
2. **实现 WebSocketServerTasker 接口**:声明实现该接口,自动继承接口定义的方法
3. **实现 init 方法**:处理任务初始化和参数解析
```java
@Override
public void init(JsonNode argsNode) {
// 解析参数或初始化任务状态
// 如果 Client 没有传递参数,就不做处理
// do nothing
// 如果有参数,正确做法:检查参数有效性并安全解析
if (argsNode != null && argsNode.size() > 0) {
ContractService contractService = getCachedBean(ContractService.class);
int contractId = argsNode.get(0).asInt();
this.contract = contractService.findById(contractId);
}
}
```
4. **实现或重写 execute 方法**:提供具体的业务逻辑实现
```java
@Override
protected Object execute(MessageHolder holder) throws Exception {
// 调用父类初始化
super.execute(holder);
updateTitle("同步修复所有合同");
// 执行具体业务逻辑
repair(holder);
return null; // 通常返回 null 或处理结果
}
```
5. **使用更新方法提供状态反馈**:在执行过程中使用 `updateTitle`、`updateProgress` 等方法提供实时反馈
```java
updateProgress(counter.incrementAndGet(), total);
```
6. **支持任务取消**:定期检查 `isCancelled()` 方法,支持任务的取消操作
```java
if (isCancelled()) {
break;
}
```
### 4.2 关键经验
1. **消息处理**:通过 `MessageHolder` 处理和记录执行过程中的消息,方便调试和错误追踪
2. **进度更新**:对于批量操作,使用计数器和总数定期更新进度,提供良好的用户体验
3. **异常处理**:在循环处理中捕获异常并记录,确保单个项目的失败不会导致整个任务失败
4. **分页处理**:对于大量数据的处理,使用分页查询避免内存溢出
## 5. 共同模式和最佳实践
通过分析项目中的17个实现类我们总结了以下共同模式和最佳实践
### 5.1 共同模式
1. **继承结构**:所有实现类都继承 `Tasker<Object>` 并实现 `WebSocketServerTasker` 接口
2. **核心方法实现**
- 实现 `init(JsonNode argsNode)` 方法处理参数
- 实现或重写 `execute(MessageHolder holder)` 方法实现业务逻辑
3. **状态更新**:使用 `updateTitle`、`updateMessage`、`updateProgress` 等方法更新任务状态
4. **Bean获取**:使用 `getCachedBean` 方法获取Spring管理的Bean
```java
ContractService contractService = getCachedBean(ContractService.class);
```
### 5.2 最佳实践
1. **参数验证**:在 `init` 方法中验证和转换输入参数
2. **进度反馈**:对于长时间运行的任务,提供合理的进度更新
3. **异常处理**:捕获并处理特定异常,提供有意义的错误信息
4. **取消支持**:实现任务取消机制,定期检查 `isCancelled()` 状态
5. **资源清理**:在任务结束或取消时清理资源
6. **日志记录**:使用 `slf4j` 记录关键操作和异常信息
7. **属性传递**:使用 `updateProperty` 方法传递任务执行结果或状态
```java
updateProperty("passed", passed);
```
## 6. 完整实现示例
### 6.1 简单任务实现示例
```java
package com.ecep.contract.service.tasker;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.ds.contract.service.ContractService;
import com.ecep.contract.ui.Tasker;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter;
import lombok.Setter;
public class SimpleTasker extends Tasker<Object> implements WebSocketServerTasker {
@Getter
@Setter
private int entityId;
@Getter
@Setter
private boolean success = false;
@Override
public void init(JsonNode argsNode) {
// 解析参数
this.entityId = argsNode.get(0).asInt();
}
@Override
protected Object execute(MessageHolder holder) throws Exception {
// 更新标题
updateTitle("执行简单任务:" + entityId);
try {
// 更新进度
updateProgress(1, 3);
// 执行业务逻辑
ContractService service = getCachedBean(ContractService.class);
updateProgress(2, 3);
// 处理结果
success = true;
holder.info("任务执行成功");
// 更新最终进度
updateProgress(3, 3);
// 更新结果属性
updateProperty("success", success);
} catch (Exception e) {
holder.error("任务执行失败: " + e.getMessage());
success = false;
updateProperty("success", success);
throw e;
}
return null;
}
}
```
### 6.2 批量处理任务实现示例
```java
package com.ecep.contract.service.tasker;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.ds.contract.service.ContractService;
import com.ecep.contract.ds.contract.model.Contract;
import com.ecep.contract.ui.Tasker;
import com.fasterxml.jackson.databind.JsonNode;
public class BatchProcessTasker extends Tasker<Object> implements WebSocketServerTasker {
private ContractService contractService;
private List<Integer> entityIds;
@Override
public void init(JsonNode argsNode) {
// 初始化服务引用
this.contractService = getCachedBean(ContractService.class);
// 解析实体ID列表
entityIds = new ArrayList<>();
for (JsonNode node : argsNode) {
entityIds.add(node.asInt());
}
}
@Override
protected Object execute(MessageHolder holder) throws Exception {
updateTitle("批量处理实体任务");
AtomicInteger counter = new AtomicInteger(0);
long total = entityIds.size();
for (Integer id : entityIds) {
// 检查是否取消
if (isCancelled()) {
holder.info("任务已取消");
break;
}
try {
MessageHolder subHolder = holder.sub("处理 ID: " + id + " > ");
// 执行业务逻辑
Contract entity = contractService.getById(id);
// 处理实体...
subHolder.info("处理成功");
} catch (Exception e) {
holder.error("处理 ID: " + id + " 失败: " + e.getMessage());
// 记录异常但继续处理下一个
}
// 更新进度
updateProgress(counter.incrementAndGet(), total);
}
updateTitle("批量处理完成");
return null;
}
}
```
### 6.3 带有依赖的任务实现示例
```java
package com.ecep.contract.service.tasker;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.ds.contract.service.ContractService;
import com.ecep.contract.ds.customer.service.CustomerService;
import com.ecep.contract.ui.Tasker;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter;
import lombok.Setter;
public class DependentTasker extends Tasker<Object> implements WebSocketServerTasker {
@Getter
@Setter
private int contractId;
@Getter
@Setter
private boolean processed = false;
private ContractService contractService;
private CustomerService customerService;
@Override
public void init(JsonNode argsNode) {
// 解析参数
this.contractId = argsNode.get(0).asInt();
// 获取服务依赖
this.contractService = getCachedBean(ContractService.class);
this.customerService = getCachedBean(CustomerService.class);
}
@Override
protected Object execute(MessageHolder holder) throws Exception {
updateTitle("执行带有服务依赖的任务");
updateProgress(1, 5);
// 使用多个服务协同完成任务
try {
// 步骤1: 获取合同信息
var contract = contractService.getById(contractId);
updateProgress(2, 5);
// 步骤2: 获取相关客户信息
var customer = customerService.getById(contract.getCustomerId());
updateProgress(3, 5);
// 步骤3: 执行业务逻辑
// ...
updateProgress(4, 5);
processed = true;
holder.info("任务处理成功");
// 更新结果属性
updateProperty("processed", processed);
} catch (Exception e) {
holder.error("任务处理失败: " + e.getMessage());
throw e;
}
updateProgress(5, 5);
return null;
}
}
```
## 7. 最佳实践总结
1. **继承 Tasker<Object> 并实现 WebSocketServerTasker 接口**:利用已有的基础实现
2. **合理实现 init 方法**:处理参数初始化,避免在 execute 中重复解析
3. **实现清晰的 execute 逻辑**
- 开始时设置任务标题
- 过程中定期更新进度
- 提供详细的消息反馈
- 结束时更新最终状态
4. **使用 Spring Bean**:通过 `getCachedBean` 获取需要的服务依赖
5. **支持任务取消**:在关键循环中检查 `isCancelled()` 状态
6. **异常处理**:捕获异常并记录,提供有意义的错误信息
7. **进度反馈**:对于长时间运行的任务,提供合理的进度更新频率
8. **属性传递**:使用 `updateProperty` 方法传递任务结果给调用方
9. **日志记录**:使用 slf4j 记录关键操作和异常
10. **资源管理**:确保在任务完成或取消时释放相关资源
## 8. 注意事项
1. **不要在构造函数中获取 Spring Bean**:使用 `init` 方法或懒加载方式获取
2. **避免阻塞操作**:长时间阻塞的操作应考虑异步处理
3. **线程安全**:注意多线程环境下的并发访问问题
4. **内存管理**对于处理大量数据的任务注意内存使用避免OOM
5. **超时处理**:考虑设置合理的超时机制
6. **事务管理**:根据需要考虑事务的范围和传播行为
7. **任务注册**创建新的WebSocketServerTasker后必须在`tasker_mapper.json`文件中注册,否则客户端将无法调用
8. **任务名称唯一性**:确保任务名称在`tasker_mapper.json`中唯一,避免名称冲突
9. **类路径正确**:注册时确保使用正确的类完全限定名,否则会导致实例化失败
10. **重启服务**:修改`tasker_mapper.json`后,需要重启服务才能使新的注册生效
通过遵循这些规范和最佳实践,可以确保实现的 `WebSocketServerTasker` 类具有良好的可维护性、可扩展性和用户体验。

View File

@@ -0,0 +1,182 @@
# Tasker实现规范指南
## 1. Tasker架构概述
Tasker是系统中用于执行异步任务的核心框架主要用于处理耗时操作并提供实时进度反馈。Tasker架构包括
- **Tasker基类**:提供任务执行的基础框架和通信功能
- **WebSocketServerTasker接口**定义WebSocket通信任务的规范
- **MessageHolder**:用于在任务执行过程中传递消息和更新进度
## 2. 服务器端Tasker实现规范
### 2.1 基本结构
服务器端Tasker实现应遵循以下结构
```java
@Data
@EqualsAndHashCode(callSuper = true)
public class YourTaskNameTask extends Tasker<Object> implements WebSocketServerTasker {
// 服务依赖
@Setter
private YourService service;
@Override
public void init(JsonNode argsNode) {
// 初始化任务标题
updateTitle("任务标题");
// 初始化参数
}
@Override
protected Object execute(MessageHolder holder) throws Exception {
// 实现业务逻辑
// 使用holder.info()、holder.error()等方法反馈消息
return null;
}
}
```
### 2.2 关键注意事项
1. **继承与实现**
- 必须继承`Tasker<Object>`基类
- 必须实现`WebSocketServerTasker`接口
2. **方法实现**
- **不要重复实现**Tasker基类已提供的handler设置方法`setMessageHandler``setTitleHandler``setPropertyHandler``setProgressHandler`
- 必须实现`init`方法,设置任务标题和初始化参数
- 必须实现`execute`方法,实现具体业务逻辑
3. **进度和消息反馈**
- 使用`updateProgress`方法提供进度更新
- 使用`MessageHolder``info``error``debug`等方法提供消息反馈
4. **任务取消**
- 在适当的地方检查`isCancelled()`状态,支持任务取消
5. **注册要求**
- 任务类必须在`tasker_mapper.json`中注册
## 3. 客户端Tasker实现规范
客户端Tasker实现应遵循
```java
public class YourTaskNameTask extends Tasker<Object> {
@Override
protected Object execute(MessageHolder holder) throws Exception {
// 客户端任务逻辑
return null;
}
}
```
## 4. 通信规范
### 4.1 命名规范
- 客户端和服务器端对应的Tasker类应使用**相同的类名**
- 示例:客户端的`ContractSyncAllTask`对应服务器端的`ContractSyncAllTask`
### 4.2 消息传递
- 使用`MessageHolder`进行消息传递
- 支持不同级别的消息INFO、ERROR、DEBUG
## 5. 最佳实践
1. **任务拆分**
- 将大型任务拆分为多个小任务,提高可维护性
2. **异常处理**
- 捕获并正确处理异常,使用`holder.error()`提供详细错误信息
3. **资源管理**
- 确保及时释放资源,特别是在任务取消的情况下
4. **日志记录**
- 使用`holder.debug()`记录调试信息
- 使用`holder.info()`记录进度信息
5. **状态检查**
- 定期检查`isCancelled()`状态,确保任务可以被及时取消
## 6. 代码示例
### 6.1 服务器端Tasker示例
```java
@Data
@EqualsAndHashCode(callSuper = true)
public class ExampleSyncTask extends Tasker<Object> implements WebSocketServerTasker {
@Setter
private ExampleService exampleService;
@Override
public void init(JsonNode argsNode) {
updateTitle("示例同步任务");
// 从argsNode初始化参数
}
@Override
protected Object execute(MessageHolder holder) throws Exception {
try {
// 获取总记录数
long total = exampleService.getTotalCount();
holder.info("开始同步数据,共" + total + "条记录");
// 分批处理
long processed = 0;
List<ExampleEntity> entities = exampleService.getAllEntities();
for (ExampleEntity entity : entities) {
// 检查是否取消
if (isCancelled()) {
holder.info("任务已取消");
return null;
}
// 处理单个实体
exampleService.processEntity(entity);
processed++;
// 更新进度
updateProgress(processed, total);
if (processed % 100 == 0) {
holder.info("已处理" + processed + "/" + total + "条记录");
}
}
holder.info("同步完成,共处理" + processed + "条记录");
return processed;
} catch (Exception e) {
holder.error("同步失败: " + e.getMessage());
throw e;
}
}
}
```
## 7. 常见错误和避免方法
1. **重复实现handler方法**
- 错误在子类中重复实现setMessageHandler等方法
- 解决删除子类中的这些方法直接使用Tasker基类提供的实现
2. **缺少任务注册**
- 错误任务类没有在tasker_mapper.json中注册
- 解决确保所有服务器端Tasker类都在配置文件中正确注册
3. **类名不匹配**
- 错误客户端和服务器端Tasker类名不一致
- 解决:确保两端使用相同的类名
4. **进度更新不正确**
- 错误:进度计算错误或没有更新
- 解决:正确计算和更新进度,提高用户体验
5. **资源泄漏**
- 错误:未及时关闭数据库连接等资源
- 解决使用try-with-resources或在finally块中关闭资源

View File

@@ -178,8 +178,11 @@ Contract-Manager/
2. 配置连接到服务端的WebSocket地址 2. 配置连接到服务端的WebSocket地址
3. 打包为可执行jar或使用jpackage创建安装包 3. 打包为可执行jar或使用jpackage创建安装包
## 开发环境要求 ## Tasker实现规范指南
有关Tasker框架的详细实现规范请参阅 [Tasker实现规范指南](docs/task/tasker_implementation_guide.md)。
## 开发环境要求
- JDK 21 - JDK 21
- Maven 3.6+ - Maven 3.6+
- MySQL 8.0+ - MySQL 8.0+
@@ -192,7 +195,4 @@ Contract-Manager/
2. 优化数据库查询性能 2. 优化数据库查询性能
3. 增强系统安全机制 3. 增强系统安全机制
4. 改进用户界面体验 4. 改进用户界面体验
5. 扩展更多云端服务集成 5. 扩展更多云端服务集成

View File

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

View File

@@ -53,7 +53,6 @@ public class WebSocketClientService {
@Getter @Getter
@Setter @Setter
private long readTimeout = 30000; private long readTimeout = 30000;
private String webSocketUrl = "ws://localhost:8080/ws";
private boolean isActive = false; // 标记连接是否活跃 private boolean isActive = false; // 标记连接是否活跃
private ScheduledFuture<?> heartbeatTask; // 心跳任务 private ScheduledFuture<?> heartbeatTask; // 心跳任务
private ScheduledFuture<?> reconnectFuture; // 修改类型为CompletableFuture<Void> private ScheduledFuture<?> reconnectFuture; // 修改类型为CompletableFuture<Void>
@@ -207,6 +206,12 @@ public class WebSocketClientService {
send(objectMapper.writeValueAsString(message)); send(objectMapper.writeValueAsString(message));
} }
/**
* WebSocketServerCallbackManage#onMessage 负责接收处理
*
* @param msg
* @return
*/
public CompletableFuture<JsonNode> send(SimpleMessage msg) { public CompletableFuture<JsonNode> send(SimpleMessage msg) {
CompletableFuture<JsonNode> future = new CompletableFuture<>(); CompletableFuture<JsonNode> future = new CompletableFuture<>();
try { try {
@@ -248,6 +253,8 @@ public class WebSocketClientService {
try { try {
// 构建WebSocket请求包含认证信息 // 构建WebSocket请求包含认证信息
var myProperties = SpringApp.getBean(MyProperties.class);
String webSocketUrl = "ws://" + myProperties.getServerHost() + ":" + myProperties.getServerPort() + "/ws";
Request request = new Request.Builder() Request request = new Request.Builder()
.url(webSocketUrl) .url(webSocketUrl)
.build(); .build();
@@ -352,14 +359,6 @@ public class WebSocketClientService {
return online; return online;
} }
public void withSession(Consumer<WebSocketClientSession> sessionConsumer) {
WebSocketClientSession session = createSession();
try {
sessionConsumer.accept(session);
} finally {
// closeSession(session);vvvv
}
}
public void closeSession(WebSocketClientSession session) { public void closeSession(WebSocketClientSession session) {
if (session != null) { if (session != null) {

View File

@@ -1,22 +1,19 @@
package com.ecep.contract; package com.ecep.contract;
import com.ecep.contract.constant.WebSocketConstant;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import java.beans.PropertyDescriptor; import java.beans.PropertyDescriptor;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.logging.Level; import java.util.logging.Level;
import javafx.application.Platform;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import com.ecep.contract.constant.WebSocketConstant;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter;
/** /**
* *
*/ */
@@ -27,6 +24,8 @@ public class WebSocketClientSession {
*/ */
@Getter @Getter
private final String sessionId = UUID.randomUUID().toString(); private final String sessionId = UUID.randomUUID().toString();
@Getter
private boolean done = false;
private WebSocketClientTasker tasker; private WebSocketClientTasker tasker;
@@ -69,6 +68,8 @@ public class WebSocketClientSession {
handleAsStart(args); handleAsStart(args);
} else if (type.equals("done")) { } else if (type.equals("done")) {
handleAsDone(args); handleAsDone(args);
done = true;
close();
} else { } else {
tasker.updateMessage(java.util.logging.Level.INFO, "未知的消息类型: " + node.toString()); tasker.updateMessage(java.util.logging.Level.INFO, "未知的消息类型: " + node.toString());
} }
@@ -83,7 +84,6 @@ public class WebSocketClientSession {
private void handleAsDone(JsonNode args) { private void handleAsDone(JsonNode args) {
tasker.updateMessage(java.util.logging.Level.INFO, "任务完成"); tasker.updateMessage(java.util.logging.Level.INFO, "任务完成");
close();
} }
private void handleAsProgress(JsonNode args) { private void handleAsProgress(JsonNode args) {

View File

@@ -5,39 +5,47 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.Locale; import java.util.Locale;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.logging.Level;
/** /**
* WebSocket客户端任务接口 * WebSocket客户端任务接口
* 定义了所有通过WebSocket与服务器通信的任务的通用方法 * 定义了所有通过WebSocket与服务器通信的任务的通用方法
* 包括任务名称、更新消息、更新标题、更新进度等操作 * 包括任务名称、更新消息、更新标题、更新进度等操作
* <p>
* 所有通过WebSocket与服务器通信的任务类都应实现此接口, 文档参考 .trace/rules/client_task_rules.md
*/ */
public interface WebSocketClientTasker { public interface WebSocketClientTasker {
/** /**
* s
* 获取任务名称 * 获取任务名称
* * 任务名称用于唯一标识任务, 服务器端会根据任务名称来调用对应的任务处理函数
*
* @return 任务名称 * @return 任务名称
*/ */
String getTaskName(); String getTaskName();
/** /**
* 更新任务执行过程中的消息 * 更新任务执行过程中的消息
* * 客户端可以通过此方法向用户展示任务执行过程中的重要信息或错误提示
* @param level 消息级别 *
* @param message 消息内容 * @param level 消息级别, 用于区分不同类型的消息, 如INFO, WARNING, SEVERE等
* @param message 消息内容, 可以是任意字符串, 用于展示给用户
*/ */
void updateMessage(Level level, String message); void updateMessage(Level level, String message);
/** /**
* 更新任务标题 * 更新任务标题
* * 客户端可以通过此方法向用户展示任务的当前执行状态或重要信息
*
* @param title 任务标题 * @param title 任务标题
*/ */
void updateTitle(String title); void updateTitle(String title);
/** /**
* 更新任务进度 * 更新任务进度
* * 客户端可以通过此方法向用户展示任务的执行进度, 如文件上传进度、数据库操作进度等
*
* @param current 当前进度 * @param current 当前进度
* @param total 总进度 * @param total 总进度
*/ */
@@ -49,11 +57,15 @@ public interface WebSocketClientTasker {
*/ */
default void cancelTask() { default void cancelTask() {
// 默认实现为空,由具体任务重写 // 默认实现为空,由具体任务重写
// 需要获取到 session
// 发送 cancel 指令
// 关闭 session.close()
} }
/** /**
* 调用远程WebSocket任务 * 调用远程WebSocket任务
* * 客户端可以通过此方法向服务器提交任务, 并等待服务器返回任务执行结果
*
* @param holder 消息持有者,用于记录任务执行过程中的消息 * @param holder 消息持有者,用于记录任务执行过程中的消息
* @param locale 语言环境 * @param locale 语言环境
* @param args 任务参数 * @param args 任务参数
@@ -69,22 +81,31 @@ public interface WebSocketClientTasker {
return null; return null;
} }
webSocketService.withSession(session -> { WebSocketClientSession session = webSocketService.createSession();
try { try {
session.submitTask(this, locale, args); session.submitTask(this, locale, args);
holder.info("已提交任务到服务器: " + getTaskName()); holder.info("已提交任务到服务器: " + getTaskName());
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
String errorMsg = "任务提交失败: " + e.getMessage(); String errorMsg = "任务提交失败: " + e.getMessage();
holder.warn(errorMsg); holder.warn(errorMsg);
throw new RuntimeException("任务提交失败: " + e.getMessage(), e); throw new RuntimeException("任务提交失败: " + e.getMessage(), e);
} }
});
// while (!session.isDone()) {
// // 使用TimeUnit
// try {
// TimeUnit.SECONDS.sleep(1);
// } catch (InterruptedException e) {
// Thread.currentThread().interrupt();
// }
// }
return null; return null;
} }
/** /**
* 异步调用远程WebSocket任务 * 异步调用远程WebSocket任务
* *
* @param holder 消息持有者,用于记录任务执行过程中的消息 * @param holder 消息持有者,用于记录任务执行过程中的消息
* @param locale 语言环境 * @param locale 语言环境
* @param args 任务参数 * @param args 任务参数
@@ -106,7 +127,7 @@ public interface WebSocketClientTasker {
/** /**
* 生成唯一的任务ID * 生成唯一的任务ID
* *
* @return 唯一的任务ID * @return 唯一的任务ID
*/ */
default String generateTaskId() { default String generateTaskId() {

View File

@@ -45,48 +45,55 @@ public abstract class AbstEntityController<T extends IdentityEntity, TV extends
protected CompletableFuture<T> loadedFuture; protected CompletableFuture<T> loadedFuture;
private final ObservableList<TabSkin> tabSkins = FXCollections.observableArrayList(); private final ObservableList<TabSkin> tabSkins = FXCollections.observableArrayList();
@Override @Override
public void onShown(WindowEvent windowEvent) { public void onShown(WindowEvent windowEvent) {
super.onShown(windowEvent); super.onShown(windowEvent);
// 载数据
initializeData();
// 注册 skin
registerTabSkins();
// 初始化保存按钮
initializeSaveBtn();
// 安装 skin
installTabSkins();
}
protected void initializeSaveBtn() {
if (saveBtn != null) {
saveBtn.disableProperty().bind(createTabSkinChangedBindings().not());
saveBtn.setOnAction(event -> saveTabSkins());
}
}
protected void initializeData() {
ViewModelService<T, TV> service = getViewModelService(); ViewModelService<T, TV> service = getViewModelService();
if (service instanceof QueryService<T, TV> queryService) { if (service instanceof QueryService<T, TV> queryService) {
setStatus("读取..."); setStatus("读取...");
loadedFuture = queryService.asyncFindById(viewModel.getId().get()); loadedFuture = queryService.asyncFindById(viewModel.getId().get());
loadedFuture.thenAccept(entity -> {
// fixed, bind change if new view model create
if (viewModel != null) {
viewModel.bindListener();
}
setStatus();
// BaseViewModel.updateInFxApplicationThread(entity, viewModel);
});
loadedFuture.exceptionally(ex -> {
handleException("载入失败,#" + viewModel.getId().get(), ex);
return null;
});
} else { } else {
loadedFuture = CompletableFuture.supplyAsync(() -> { loadedFuture = CompletableFuture.supplyAsync(() -> {
T entity = getViewModelService().findById(viewModel.getId().get()); return getViewModelService().findById(viewModel.getId().get());
if (entity == null) {
return null;
}
Platform.runLater(() -> {
setStatus();
viewModel.update(entity);
});
viewModel.bindListener();
return entity;
}); });
} }
registerTabSkins(); loadedFuture.thenAccept(entity -> {
if (saveBtn != null) { // fixed, bind change if new view model create
saveBtn.disableProperty().bind(createTabSkinChangedBindings().not()); if (viewModel != null) {
saveBtn.setOnAction(event -> saveTabSkins()); updateViewModel(entity);
} viewModel.bindListener();
}
setStatus();
});
loadedFuture.exceptionally(ex -> {
handleException("载入失败,#" + viewModel.getId().get(), ex);
return null;
});
}
installTabSkins(); protected void updateViewModel(T entity) {
BaseViewModel.updateInFxApplicationThread(entity, viewModel);
} }
public T getEntity() { public T getEntity() {
@@ -108,6 +115,19 @@ public abstract class AbstEntityController<T extends IdentityEntity, TV extends
return saved; return saved;
} }
public void save() {
T entity = getEntity();
if (entity == null) {
return;
}
if (viewModel.copyTo(entity)) {
save(entity);
}
}
/**
* 注册 skin
*/
protected void registerTabSkins() { protected void registerTabSkins() {
} }
@@ -119,6 +139,9 @@ public abstract class AbstEntityController<T extends IdentityEntity, TV extends
return f; return f;
} }
/**
* 安装 skin
*/
protected void installTabSkins() { protected void installTabSkins() {
for (TabSkin tabSkin : tabSkins) { for (TabSkin tabSkin : tabSkins) {
try { try {
@@ -229,10 +252,10 @@ public abstract class AbstEntityController<T extends IdentityEntity, TV extends
} }
return CompletableFuture.allOf(list return CompletableFuture.allOf(list
.stream() .stream()
.map(RefreshableSkin::refresh) .map(RefreshableSkin::refresh)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.toArray(CompletableFuture<?>[]::new)) .toArray(CompletableFuture<?>[]::new))
.whenComplete((v, ex) -> { .whenComplete((v, ex) -> {
if (ex != null) { if (ex != null) {
future.completeExceptionally(ex); future.completeExceptionally(ex);

View File

@@ -10,6 +10,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer; import java.util.function.Consumer;
import com.ecep.contract.util.BeanContext; import com.ecep.contract.util.BeanContext;
import com.ecep.contract.util.DefaultBeanContext;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.FxmlUtils; import com.ecep.contract.util.FxmlUtils;
import com.ecep.contract.vo.EmployeeVo; import com.ecep.contract.vo.EmployeeVo;
@@ -51,7 +52,7 @@ public class BaseController implements BeanContext {
} }
public static <T extends BaseController> CompletableFuture<Void> show(Class<T> clz, Window owner, public static <T extends BaseController> CompletableFuture<Void> show(Class<T> clz, Window owner,
Consumer<T> consumer) { Consumer<T> consumer) {
String key = clz.getName(); String key = clz.getName();
if (toFront(key)) { if (toFront(key)) {
return null; return null;
@@ -188,20 +189,13 @@ public class BaseController implements BeanContext {
public Label rightStatusLabel; public Label rightStatusLabel;
private EmployeeVo currentUser; private EmployeeVo currentUser;
private HashMap<Class<?>, Object> cachedBeans = new HashMap<>(); private BeanContext beanContext = new DefaultBeanContext();
public <T> T getBean(Class<T> requiredType) throws BeansException { public <T> T getBean(Class<T> requiredType) throws BeansException {
return SpringApp.getBean(requiredType); return beanContext.getBean(requiredType);
} }
@SuppressWarnings("unchecked")
public <T> T getCachedBean(Class<T> requiredType) throws BeansException { public <T> T getCachedBean(Class<T> requiredType) throws BeansException {
Object object = cachedBeans.get(requiredType); return beanContext.getCachedBean(requiredType);
if (object == null) {
object = getBean(requiredType);
cachedBeans.put(requiredType, object);
}
return (T) object;
} }
public SysConfService getConfService() { public SysConfService getConfService() {

View File

@@ -1,5 +1,6 @@
package com.ecep.contract.controller.bank; package com.ecep.contract.controller.bank;
import org.controlsfx.control.SearchableComboBox;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
@@ -34,7 +35,6 @@ public class BankManagerWindowController
} }
public void onCreateNewAction(ActionEvent event) { public void onCreateNewAction(ActionEvent event) {
} }
public void onReBuildFilesAction(ActionEvent event) { public void onReBuildFilesAction(ActionEvent event) {

View File

@@ -4,6 +4,7 @@ import com.ecep.contract.controller.AbstEntityManagerSkin;
import com.ecep.contract.controller.ManagerSkin; import com.ecep.contract.controller.ManagerSkin;
import com.ecep.contract.controller.company.CompanyWindowController; import com.ecep.contract.controller.company.CompanyWindowController;
import com.ecep.contract.controller.table.cell.CompanyTableCell; import com.ecep.contract.controller.table.cell.CompanyTableCell;
import com.ecep.contract.controller.table.cell.LocalDateFieldTableCell;
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell; import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
import com.ecep.contract.service.CompanyService; import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.YongYouU8Service; import com.ecep.contract.service.YongYouU8Service;
@@ -15,6 +16,7 @@ import javafx.collections.ObservableList;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.scene.control.ContextMenu; import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
import javafx.scene.control.cell.CheckBoxTableCell;
public class YongYouU8ManagerSkin public class YongYouU8ManagerSkin
extends extends
@@ -36,10 +38,9 @@ public class YongYouU8ManagerSkin
@Override @Override
public void initializeTable() { public void initializeTable() {
controller.idColumn.setCellValueFactory(param -> param.getValue().getId()); controller.idColumn.setCellValueFactory(param -> param.getValue().getId());
controller.companyColumn.setCellValueFactory(param -> param.getValue().getCompany());
controller.companyColumn.setCellFactory(param -> new CompanyTableCell<>(getCompanyService()));
controller.cloudIdColumn.setCellValueFactory(param -> param.getValue().getCloudId()); controller.companyColumn.setCellValueFactory(param -> param.getValue().getCompany());
controller.companyColumn.setCellFactory( CompanyTableCell.forTableColumn(getCompanyService()));
controller.latestUpdateColumn.setCellValueFactory(param -> param.getValue().getLatestUpdate()); controller.latestUpdateColumn.setCellValueFactory(param -> param.getValue().getLatestUpdate());
controller.latestUpdateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); controller.latestUpdateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
@@ -47,7 +48,14 @@ public class YongYouU8ManagerSkin
controller.cloudLatestColumn.setCellValueFactory(param -> param.getValue().getCloudLatest()); controller.cloudLatestColumn.setCellValueFactory(param -> param.getValue().getCloudLatest());
controller.cloudLatestColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); controller.cloudLatestColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
controller.descriptionColumn.setCellValueFactory(param -> param.getValue().getVendorCode()); controller.cloudVendorUpdateDateColumn.setCellValueFactory(param -> param.getValue().getVendorUpdateDate());
controller.cloudCustomerUpdateDateColumn.setCellValueFactory(param -> param.getValue().getCustomerUpdateDate());
controller.activeColumn.setCellValueFactory(param -> param.getValue().getActive());
controller.activeColumn.setCellFactory(CheckBoxTableCell.forTableColumn(controller.activeColumn));
controller.descriptionColumn.setCellValueFactory(param -> param.getValue().getExceptionMessage());
} }
@Override @Override
@@ -73,7 +81,7 @@ public class YongYouU8ManagerSkin
} }
for (CloudYuInfoViewModel selectedItem : selectedItems) { for (CloudYuInfoViewModel selectedItem : selectedItems) {
CloudYuVo yongYouU8Vo = getU8Service().findById(selectedItem.getId().get()); CloudYuVo yongYouU8Vo = getU8Service().findById(selectedItem.getId().get());
selectedItem.getCustomerCode().set(""); selectedItem.getExceptionMessage().set("");
if (selectedItem.copyTo(yongYouU8Vo)) { if (selectedItem.copyTo(yongYouU8Vo)) {
CloudYuVo saved = getU8Service().save(yongYouU8Vo); CloudYuVo saved = getU8Service().save(yongYouU8Vo);
selectedItem.update(saved); selectedItem.update(saved);

View File

@@ -37,8 +37,10 @@ public class YongYouU8ManagerWindowController
public TableColumn<CloudYuInfoViewModel, Number> idColumn; public TableColumn<CloudYuInfoViewModel, Number> idColumn;
public TableColumn<CloudYuInfoViewModel, LocalDateTime> latestUpdateColumn; public TableColumn<CloudYuInfoViewModel, LocalDateTime> latestUpdateColumn;
public TableColumn<CloudYuInfoViewModel, Integer> companyColumn; public TableColumn<CloudYuInfoViewModel, Integer> companyColumn;
public TableColumn<CloudYuInfoViewModel, String> cloudIdColumn;
public TableColumn<CloudYuInfoViewModel, LocalDateTime> cloudLatestColumn; public TableColumn<CloudYuInfoViewModel, LocalDateTime> cloudLatestColumn;
public TableColumn<CloudYuInfoViewModel, java.time.LocalDate> cloudVendorUpdateDateColumn;
public TableColumn<CloudYuInfoViewModel, java.time.LocalDate> cloudCustomerUpdateDateColumn;
public TableColumn<CloudYuInfoViewModel, Boolean> activeColumn;
public TableColumn<CloudYuInfoViewModel, String> descriptionColumn; public TableColumn<CloudYuInfoViewModel, String> descriptionColumn;
@Override @Override
@@ -91,27 +93,32 @@ public class YongYouU8ManagerWindowController
public void onContractGroupSyncAction(ActionEvent event) { public void onContractGroupSyncAction(ActionEvent event) {
ContractGroupSyncTask task = new ContractGroupSyncTask(); ContractGroupSyncTask task = new ContractGroupSyncTask();
Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task); UITools.showTaskDialogAndWait("合同组数据同步", task, null);
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
} }
public void onContractTypeSyncAction(ActionEvent event) { public void onContractTypeSyncAction(ActionEvent event) {
ContractTypeSyncTask task = new ContractTypeSyncTask(); ContractTypeSyncTask task = new ContractTypeSyncTask();
Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task); UITools.showTaskDialogAndWait("合同类型数据同步", task, null);
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
} }
public void onContractKindSyncAction(ActionEvent event) { public void onContractKindSyncAction(ActionEvent event) {
ContractKindSyncTask task = new ContractKindSyncTask(); ContractKindSyncTask task = new ContractKindSyncTask();
Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task); UITools.showTaskDialogAndWait("合同类型数据同步", task, null);
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
} }
public void onVendorClassSyncAction(ActionEvent event) { public void onVendorClassSyncAction(ActionEvent event) {
VendorClassSyncTask task = new VendorClassSyncTask(); VendorClassSyncTask task = new VendorClassSyncTask();
Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task); UITools.showTaskDialogAndWait("客户分类数据同步", task, null);
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
} }
public void onCustomerClassSyncAction(ActionEvent event) { public void onCustomerClassSyncAction(ActionEvent event) {
CustomerClassSyncTask task = new CustomerClassSyncTask(); CustomerClassSyncTask task = new CustomerClassSyncTask();
Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task); UITools.showTaskDialogAndWait("客户分类数据同步", task, null);
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
} }
} }

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

@@ -0,0 +1,140 @@
package com.ecep.contract.controller.contract;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.ecep.contract.controller.AbstEntityController;
import com.ecep.contract.controller.inventory.InventoryWindowController;
import com.ecep.contract.controller.tab.ContractItemTabSkinBase;
import com.ecep.contract.service.ContractItemService;
import com.ecep.contract.service.InventoryService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.ContractItemViewModel;
import com.ecep.contract.vm.InventoryViewModel;
import com.ecep.contract.vo.ContractItemVo;
import com.ecep.contract.vo.InventoryVo;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.scene.control.Button;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
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.stage.Window;
import javafx.stage.WindowEvent;
@Lazy
@Scope("prototype")
@Component
@FxmlPath("/ui/contract/contract-item.fxml")
public class ContractItemWindowController
extends AbstEntityController<ContractItemVo, ContractItemViewModel> {
public static void show(ContractItemVo contractItem, Window owner) {
show(ContractItemViewModel.from(contractItem), owner);
}
/**
* 显示界面
*/
public static void show(ContractItemViewModel viewModel, Window window) {
show(ContractItemWindowController.class, viewModel, window);
}
public BorderPane root;
public Tab baseInfoTab;
// 基本信息字段
public Label idLabel;
public TextField codeField;
public TextField titleField;
public TextField specificationField;
public TextField unitField;
public TextField inventoryField;
public Button openInventoryBtn;
// 财务信息字段
public TextField exclusiveTaxPriceField;
public TextField taxRateField;
public TextField taxPriceField;
public TextField quantityField;
public TextField taxAmountField;
public TextField exclusiveTaxAmountField;
// 日期信息字段
public DatePicker startDateField;
public DatePicker endDateField;
public TextField createDateLabel;
public TextField updateDateLabel;
public TextField creatorLabel;
public TextField updaterLabel;
// 其他信息
public TextField refIdField;
public TextArea remarkField;
@Override
public ContractItemService getViewModelService() {
return getCachedBean(ContractItemService.class);
}
public InventoryService getInventoryService() {
return getCachedBean(InventoryService.class);
}
public void onShown(WindowEvent windowEvent) {
super.onShown(windowEvent);
getTitle().bind(viewModel.getTitle()
.map(title -> "[" + viewModel.getId() + "] " + title + " 合同内容详情"));
root.getScene().getStylesheets().add("/ui/contract/contract.css");
}
@Override
protected void registerTabSkins() {
super.registerTabSkins();
TabPane tabPane = baseInfoTab.getTabPane();
ObservableList<Tab> tabs = tabPane.getTabs();
registerTabSkin(baseInfoTab, tab -> new ContractItemTabSkinBase(this));
}
/**
* 打开关联的存货信息
*/
public void onOpenInventoryAction(ActionEvent event) {
try {
if (viewModel.getInventory() == null) {
UITools.showAlertAndWait("没有关联的存货信息");
return;
}
Integer inventoryId = viewModel.getInventory().get();
InventoryVo inventory = getInventoryService().findById(inventoryId);
if (inventory != null) {
InventoryWindowController.show(InventoryViewModel.from(inventory), root.getScene().getWindow());
} else {
UITools.showAlertAndWait("未找到关联的存货信息");
}
} catch (Exception e) {
UITools.showAlertAndWait("打开存货信息失败: " + e.getMessage());
}
}
/**
* 计算税额和金额
*/
public void onCalculateAmountsAction(ActionEvent event) {
try {
viewModel.calculateAmounts();
UITools.showAlertAndWait("金额计算完成");
} catch (Exception e) {
UITools.showAlertAndWait("计算失败: " + e.getMessage());
}
}
}

View File

@@ -1,13 +1,13 @@
package com.ecep.contract.controller.tab; package com.ecep.contract.controller.contract;
import com.ecep.contract.*; import com.ecep.contract.*;
import com.ecep.contract.constant.ContractConstant; import com.ecep.contract.constant.ContractConstant;
import com.ecep.contract.controller.contract.AbstContractTableTabSkin; import com.ecep.contract.controller.tab.ContractFilesRebuildTasker;
import com.ecep.contract.controller.contract.ContractWindowController; import com.ecep.contract.controller.tab.CustomerContractCostFormUpdateTask;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.table.EditableEntityTableTabSkin; import com.ecep.contract.controller.table.EditableEntityTableTabSkin;
import com.ecep.contract.controller.table.cell.ContractFileTypeTableCell; import com.ecep.contract.controller.table.cell.ContractFileTypeTableCell;
import com.ecep.contract.controller.table.cell.LocalDateFieldTableCell; import com.ecep.contract.controller.table.cell.LocalDateFieldTableCell;
import com.ecep.contract.model.ContractFileTypeLocal;
import com.ecep.contract.service.ContractFileService; import com.ecep.contract.service.ContractFileService;
import com.ecep.contract.service.ContractFileTypeService; import com.ecep.contract.service.ContractFileTypeService;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
@@ -21,7 +21,6 @@ import javafx.event.ActionEvent;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.control.cell.TextFieldTableCell; import javafx.scene.control.cell.TextFieldTableCell;
import javafx.stage.WindowEvent; import javafx.stage.WindowEvent;
import javafx.util.StringConverter;
import lombok.Setter; import lombok.Setter;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.Loader;
import org.apache.pdfbox.io.IOUtils; import org.apache.pdfbox.io.IOUtils;
@@ -94,21 +93,6 @@ public class ContractTabSkinFiles
return controller.fileTab; return controller.fileTab;
} }
static class ContractFileTypeLocalStringConverter extends StringConverter<ContractFileTypeLocal> {
@Override
public String toString(ContractFileTypeLocal local) {
if (local == null) {
return "-";
}
return local.getValue();
}
@Override
public ContractFileTypeLocal fromString(String string) {
return null;
}
}
@Override @Override
public void initializeTab() { public void initializeTab() {
TableView<ContractFileViewModel> table = getTableView(); TableView<ContractFileViewModel> table = getTableView();
@@ -134,6 +118,7 @@ public class ContractTabSkinFiles
.setCellFactory(ContractFileTypeTableCell.forTableColumn(getCachedBean(ContractFileTypeService.class))); .setCellFactory(ContractFileTypeTableCell.forTableColumn(getCachedBean(ContractFileTypeService.class)));
fileTable_typeColumn.setEditable(false); fileTable_typeColumn.setEditable(false);
/* 文件名编辑器 */ /* 文件名编辑器 */
fileTable_filePathColumn.setCellValueFactory(param -> param.getValue().getFileName()); fileTable_filePathColumn.setCellValueFactory(param -> param.getValue().getFileName());
fileTable_filePathColumn.setCellFactory(TextFieldTableCell.forTableColumn()); fileTable_filePathColumn.setCellFactory(TextFieldTableCell.forTableColumn());
@@ -193,6 +178,34 @@ public class ContractTabSkinFiles
createVendorContractRequestByTemplateUpdateMenuItem(), createVendorContractRequestByTemplateUpdateMenuItem(),
createVendorContractApplyByTemplateUpdateMenuItem()); createVendorContractApplyByTemplateUpdateMenuItem());
runAsync(() -> {
getCachedBean(ContractFileTypeService.class).findAll(getLocale()).forEach((k, v) -> {
if (isCustomer && !k.isSupportCustomer()) {
return;
}
if (isVendor && !k.isSupportVendor()) {
return;
}
{
MenuItem item = new MenuItem();
item.setText(v.getValue());
item.getProperties().put("typeLocal", v);
item.setOnAction(this::onFileTableContextMenuChangeTypeAndNameAction);
fileTable_menu_change_type.getItems().add(item);
}
if (StringUtils.hasText(v.getSuggestFileName())) {
MenuItem item = new MenuItem();
item.setText(v.getValue());
item.getProperties().put("typeLocal", v);
item.getProperties().put("rename", true);
item.setOnAction(this::onFileTableContextMenuChangeTypeAndNameAction);
fileTable_menu_change_type_and_name.getItems().add(item);
}
});
});
super.initializeTab(); super.initializeTab();
} }
@@ -540,13 +553,13 @@ public class ContractTabSkinFiles
// //
fileTable_menu_change_type.setVisible(true); fileTable_menu_change_type.setVisible(true);
for (MenuItem item : fileTable_menu_change_type.getItems()) { for (MenuItem item : fileTable_menu_change_type.getItems()) {
ContractFileTypeLocal typeLocal = (ContractFileTypeLocal) item.getProperties().get("typeLocal"); ContractFileTypeLocalVo typeLocal = (ContractFileTypeLocalVo) item.getProperties().get("typeLocal");
item.setVisible(typeLocal.getType() != selectedItem.getType().get()); item.setVisible(typeLocal.getType() != selectedItem.getType().get());
} }
fileTable_menu_change_type_and_name.setVisible(true); fileTable_menu_change_type_and_name.setVisible(true);
for (MenuItem item : fileTable_menu_change_type.getItems()) { for (MenuItem item : fileTable_menu_change_type.getItems()) {
ContractFileTypeLocal typeLocal = (ContractFileTypeLocal) item.getProperties().get("typeLocal"); ContractFileTypeLocalVo typeLocal = (ContractFileTypeLocalVo) item.getProperties().get("typeLocal");
item.setVisible(typeLocal.getType() != selectedItem.getType().get()); item.setVisible(typeLocal.getType() != selectedItem.getType().get());
} }
@@ -559,7 +572,7 @@ public class ContractTabSkinFiles
if (selectedItems == null || selectedItems.isEmpty()) { if (selectedItems == null || selectedItems.isEmpty()) {
return; return;
} }
ContractFileTypeLocal typeLocal = (ContractFileTypeLocal) item.getProperties().get("typeLocal"); ContractFileTypeLocalVo typeLocal = (ContractFileTypeLocalVo) item.getProperties().get("typeLocal");
if (typeLocal == null) { if (typeLocal == null) {
return; return;
} }

View File

@@ -1,7 +1,5 @@
package com.ecep.contract.controller.tab; package com.ecep.contract.controller.contract;
import com.ecep.contract.controller.contract.AbstContractTableTabSkin;
import com.ecep.contract.controller.contract.ContractWindowController;
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell; import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
import com.ecep.contract.service.ContractPayPlanService; import com.ecep.contract.service.ContractPayPlanService;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;

View File

@@ -1,19 +1,17 @@
package com.ecep.contract.controller.tab; package com.ecep.contract.controller.contract;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import com.ecep.contract.ContractPayWay; import com.ecep.contract.ContractPayWay;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.table.cell.EmployeeTableCell; import com.ecep.contract.controller.table.cell.EmployeeTableCell;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.controller.contract.AbstContractTableTabSkin;
import com.ecep.contract.controller.contract.ContractWindowController;
import com.ecep.contract.controller.vendor.purchase.order.PurchaseOrderWindowController; import com.ecep.contract.controller.vendor.purchase.order.PurchaseOrderWindowController;
import com.ecep.contract.converter.EmployeeStringConverter; import com.ecep.contract.converter.EmployeeStringConverter;
import com.ecep.contract.service.CompanyService; import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.PurchaseOrdersService; import com.ecep.contract.service.PurchaseOrdersService;
import com.ecep.contract.vm.PurchaseOrderViewModel; import com.ecep.contract.vm.PurchaseOrderViewModel;
import com.ecep.contract.vo.PurchaseOrderVo; import com.ecep.contract.vo.PurchaseOrderVo;
import com.ecep.contract.controller.table.cell.CompanyTableCell;
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell; import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;

View File

@@ -1,25 +1,23 @@
package com.ecep.contract.controller.tab; package com.ecep.contract.controller.contract;
import java.time.LocalDate; import java.time.LocalDate;
import com.ecep.contract.ContractPayWay; import com.ecep.contract.ContractPayWay;
import com.ecep.contract.controller.table.cell.EmployeeTableCell;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.controller.contract.AbstContractTableTabSkin;
import com.ecep.contract.controller.contract.ContractWindowController;
import com.ecep.contract.controller.contract.sale_order.SalesOrderWindowController; 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.controller.table.cell.LocalDateFieldTableCell; import com.ecep.contract.controller.table.cell.LocalDateFieldTableCell;
import com.ecep.contract.converter.EmployeeStringConverter; import com.ecep.contract.converter.EmployeeStringConverter;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.SaleOrdersService; import com.ecep.contract.service.SaleOrdersService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vm.SalesOrderViewModel; import com.ecep.contract.vm.SalesOrderViewModel;
import com.ecep.contract.vo.SalesOrderVo; import com.ecep.contract.vo.SalesOrderVo;
import javafx.scene.control.Button;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
import javafx.scene.control.TableColumn; import javafx.scene.control.TableColumn;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import lombok.Setter; import lombok.Setter;
/** /**
@@ -32,6 +30,8 @@ public class ContractTabSkinSaleOrders
@Setter @Setter
private SaleOrdersService saleOrdersService; private SaleOrdersService saleOrdersService;
private CompanyService companyService;
@Setter @Setter
private EmployeeStringConverter employeeStringConverter; private EmployeeStringConverter employeeStringConverter;
@@ -51,10 +51,21 @@ public class ContractTabSkinSaleOrders
*/ */
public TableColumn<SalesOrderViewModel, Integer> verifierColumn; public TableColumn<SalesOrderViewModel, Integer> verifierColumn;
public TableColumn<SalesOrderViewModel, LocalDate> verifierDateColumn; public TableColumn<SalesOrderViewModel, LocalDate> verifierDateColumn;
public TableColumn<SalesOrderViewModel, Number> refIdColumn;
public TableColumn<SalesOrderViewModel, Number> taxRateColumn;
public TableColumn<SalesOrderViewModel, String> customerAddressColumn;
/**
* 修改人, Employee
*/
public TableColumn<SalesOrderViewModel, Integer> modifierColumn;
public TableColumn<SalesOrderViewModel, LocalDate> modifierDateColumn;
/**
* 关闭人, Employee
*/
public TableColumn<SalesOrderViewModel, Integer> closerColumn;
public TableColumn<SalesOrderViewModel, LocalDate> closerDateColumn;
public TableColumn<SalesOrderViewModel, String> descriptionColumn; public TableColumn<SalesOrderViewModel, String> descriptionColumn;
public MenuItem subContractTable_menu_refresh; public MenuItem subContractTable_menu_refresh;
public TextField contractSearchKeyField;
public Button searchBtn;
private Tab tab; private Tab tab;
public ContractTabSkinSaleOrders(ContractWindowController controller, Tab tab) { public ContractTabSkinSaleOrders(ContractWindowController controller, Tab tab) {
@@ -81,31 +92,37 @@ public class ContractTabSkinSaleOrders
@Override @Override
public void initializeTab() { public void initializeTab() {
contractSearchKeyField.setOnKeyReleased(event -> { super.initializeTab();
if (event.getCode() == KeyCode.ENTER) {
searchBtn.fire();
}
});
searchBtn.setOnAction(this::onTableRefreshAction);
subContractTable_menu_refresh.setOnAction(this::onTableRefreshAction); subContractTable_menu_refresh.setOnAction(this::onTableRefreshAction);
idColumn.setCellValueFactory(param -> param.getValue().getId()); idColumn.setCellValueFactory(param -> param.getValue().getId());
codeColumn.setCellValueFactory(param -> param.getValue().getCode()); codeColumn.setCellValueFactory(param -> param.getValue().getCode());
employeeColumn.setCellValueFactory(param -> param.getValue().getEmployee()); employeeColumn.setCellValueFactory(param -> param.getValue().getEmployee());
employeeColumn.setCellFactory(param -> new EmployeeTableCell<>(getEmployeeService())); employeeColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
makerColumn.setCellValueFactory(param -> param.getValue().getMaker()); makerColumn.setCellValueFactory(param -> param.getValue().getMaker());
makerColumn.setCellFactory(param -> new EmployeeTableCell<>(getEmployeeService())); makerColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
makerDateColumn.setCellValueFactory(param -> param.getValue().getMakerDate()); makerDateColumn.setCellValueFactory(param -> param.getValue().getMakerDate());
makerDateColumn.setCellFactory(LocalDateFieldTableCell.forTableColumn()); makerDateColumn.setCellFactory(LocalDateFieldTableCell.forTableColumn());
verifierColumn.setCellValueFactory(param -> param.getValue().getVerifier()); verifierColumn.setCellValueFactory(param -> param.getValue().getVerifier());
verifierColumn.setCellFactory(param -> new EmployeeTableCell<>(getEmployeeService())); verifierColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
verifierDateColumn.setCellValueFactory(param -> param.getValue().getVerifierDate()); verifierDateColumn.setCellValueFactory(param -> param.getValue().getVerifierDate());
verifierDateColumn.setCellFactory(LocalDateFieldTableCell.forTableColumn()); verifierDateColumn.setCellFactory(LocalDateFieldTableCell.forTableColumn());
// 设置新增字段的单元格值工厂和工厂类
refIdColumn.setCellValueFactory(param -> param.getValue().getRefId());
taxRateColumn.setCellValueFactory(param -> param.getValue().getTaxRate());
customerAddressColumn.setCellValueFactory(param -> param.getValue().getCustomerAddress());
modifierColumn.setCellValueFactory(param -> param.getValue().getModifier());
modifierColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
modifierDateColumn.setCellValueFactory(param -> param.getValue().getModifierDate());
modifierDateColumn.setCellFactory(LocalDateFieldTableCell.forTableColumn());
closerColumn.setCellValueFactory(param -> param.getValue().getCloser());
closerColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
closerDateColumn.setCellValueFactory(param -> param.getValue().getCloserDate());
closerDateColumn.setCellFactory(LocalDateFieldTableCell.forTableColumn());
descriptionColumn.setCellValueFactory(param -> param.getValue().getDescription()); descriptionColumn.setCellValueFactory(param -> param.getValue().getDescription());
super.initializeTab();
} }
@Override @Override
@@ -113,17 +130,17 @@ public class ContractTabSkinSaleOrders
SalesOrderWindowController.show(item, getTableView().getScene().getWindow()); SalesOrderWindowController.show(item, getTableView().getScene().getWindow());
} }
private EmployeeStringConverter getEmployeeStringConverter() {
if (employeeStringConverter == null) {
employeeStringConverter = getBean(EmployeeStringConverter.class);
}
return employeeStringConverter;
}
SaleOrdersService getSaleOrdersService() { SaleOrdersService getSaleOrdersService() {
if (saleOrdersService == null) { if (saleOrdersService == null) {
saleOrdersService = getBean(SaleOrdersService.class); saleOrdersService = getBean(SaleOrdersService.class);
} }
return saleOrdersService; return saleOrdersService;
} }
CompanyService getCompanyService() {
if (companyService == null) {
companyService = getBean(CompanyService.class);
}
return companyService;
}
} }

View File

@@ -1,10 +1,11 @@
package com.ecep.contract.controller.tab; package com.ecep.contract.controller.contract;
import java.time.LocalDate; import java.time.LocalDate;
import com.ecep.contract.ContractPayWay; import com.ecep.contract.ContractPayWay;
import com.ecep.contract.controller.contract.AbstContractTableTabSkin; import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.contract.ContractWindowController; import com.ecep.contract.controller.table.cell.CompanyTableCell;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.ContractService; import com.ecep.contract.service.ContractService;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.ParamUtils; import com.ecep.contract.util.ParamUtils;
@@ -34,6 +35,7 @@ public class ContractTabSkinSubContract
public TableColumn<ContractViewModel, LocalDate> subContractTable_inureDateColumn; public TableColumn<ContractViewModel, LocalDate> subContractTable_inureDateColumn;
public TableColumn<ContractViewModel, LocalDate> subContractTable_orderDateColumn; public TableColumn<ContractViewModel, LocalDate> subContractTable_orderDateColumn;
public TableColumn<ContractViewModel, LocalDate> subContractTable_varyDateColumn; public TableColumn<ContractViewModel, LocalDate> subContractTable_varyDateColumn;
public TableColumn<ContractViewModel, Integer> subContractTable_companyColumn;
public MenuItem subContractTable_menu_refresh; public MenuItem subContractTable_menu_refresh;
public TextField contractSearchKeyField; public TextField contractSearchKeyField;
public Button contractSearchBtn; public Button contractSearchBtn;
@@ -86,6 +88,8 @@ public class ContractTabSkinSubContract
subContractTable_inureDateColumn.setCellValueFactory(param -> param.getValue().getInureDate()); subContractTable_inureDateColumn.setCellValueFactory(param -> param.getValue().getInureDate());
subContractTable_orderDateColumn.setCellValueFactory(param -> param.getValue().getOrderDate()); subContractTable_orderDateColumn.setCellValueFactory(param -> param.getValue().getOrderDate());
subContractTable_varyDateColumn.setCellValueFactory(param -> param.getValue().getVaryDate()); subContractTable_varyDateColumn.setCellValueFactory(param -> param.getValue().getVaryDate());
subContractTable_companyColumn.setCellValueFactory(param -> param.getValue().getCompany());
subContractTable_companyColumn.setCellFactory(CompanyTableCell.forTableColumn(getCachedBean(CompanyService.class)));
Platform.runLater(() -> { Platform.runLater(() -> {
getTableView().getSortOrder().add(subContractTable_codeColumn); getTableView().getSortOrder().add(subContractTable_codeColumn);

View File

@@ -1,4 +1,4 @@
package com.ecep.contract.controller.tab; package com.ecep.contract.controller.contract;
import java.util.List; import java.util.List;
@@ -7,8 +7,7 @@ import org.controlsfx.control.textfield.TextFields;
import com.ecep.contract.ContractPayWay; import com.ecep.contract.ContractPayWay;
import com.ecep.contract.SpringApp; import com.ecep.contract.SpringApp;
import com.ecep.contract.controller.contract.AbstContractTableTabSkin; import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.contract.ContractWindowController;
import com.ecep.contract.controller.table.EditableEntityTableTabSkin; import com.ecep.contract.controller.table.EditableEntityTableTabSkin;
import com.ecep.contract.controller.table.cell.CompanyTableCell; import com.ecep.contract.controller.table.cell.CompanyTableCell;
import com.ecep.contract.controller.table.cell.ContractFileTableCell; import com.ecep.contract.controller.table.cell.ContractFileTableCell;

View File

@@ -12,6 +12,9 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level; import java.util.logging.Level;
import javafx.scene.Node;
import javafx.scene.control.*;
import org.controlsfx.control.PopOver;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -50,13 +53,6 @@ import javafx.collections.ObservableList;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.control.CheckMenuItem;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import javafx.stage.Modality; import javafx.stage.Modality;
@@ -79,6 +75,7 @@ public class ContractVerifyWindowController extends BaseController {
return super.show(loader, owner, modality); return super.show(loader, owner, modality);
} }
@Setter @Setter
@Getter @Getter
public static class MessageExt extends Message { public static class MessageExt extends Message {
@@ -90,12 +87,16 @@ public class ContractVerifyWindowController extends BaseController {
} }
@Data @Data
public static class Model implements MessageHolder { public static class Model {
private SimpleStringProperty code = new SimpleStringProperty(); private SimpleStringProperty code = new SimpleStringProperty();
private SimpleStringProperty name = new SimpleStringProperty(); private SimpleStringProperty name = new SimpleStringProperty();
private SimpleObjectProperty<Integer> employee = new SimpleObjectProperty<>(); private SimpleObjectProperty<Integer> employee = new SimpleObjectProperty<>();
private SimpleObjectProperty<LocalDate> setupDate = new SimpleObjectProperty<>(); private SimpleObjectProperty<LocalDate> setupDate = new SimpleObjectProperty<>();
private SimpleListProperty<MessageExt> messages = new SimpleListProperty<>(FXCollections.observableArrayList()); private SimpleListProperty<MessageExt> messages = new SimpleListProperty<>(FXCollections.observableArrayList());
}
static class MessageHolderImpl implements MessageHolder {
List<MessageExt> messages = new ArrayList<>();
@Override @Override
public void addMessage(Level level, String message) { public void addMessage(Level level, String message) {
@@ -261,6 +262,8 @@ public class ContractVerifyWindowController extends BaseController {
long total = contractService.count(params); long total = contractService.count(params);
setStatus("合同:" + total + ""); setStatus("合同:" + total + "");
MessageHolderImpl messageHolder = new MessageHolderImpl();
while (true) { while (true) {
if (isCloseRequested()) { if (isCloseRequested()) {
break; break;
@@ -268,6 +271,7 @@ public class ContractVerifyWindowController extends BaseController {
Page<ContractVo> page = contractService.findAll(params, pageRequest); Page<ContractVo> page = contractService.findAll(params, pageRequest);
for (ContractVo contract : page) { for (ContractVo contract : page) {
messageHolder.messages.clear();
if (isCloseRequested()) { if (isCloseRequested()) {
break; break;
} }
@@ -285,11 +289,11 @@ public class ContractVerifyWindowController extends BaseController {
model.getName().set(contract.getName()); model.getName().set(contract.getName());
model.getSetupDate().set(contract.getSetupDate()); model.getSetupDate().set(contract.getSetupDate());
comm.verify(contract, model); comm.verify(contract, messageHolder);
setStatus("合同验证进度:" + counter.get() + " / " + total); setStatus("合同验证进度:" + counter.get() + " / " + total);
// 移除中间消息 // 移除中间消息
if (!model.getMessages().isEmpty()) { if (!messageHolder.messages.isEmpty()) {
model.getMessages().removeIf(msg -> msg.getLevel().intValue() <= Level.INFO.intValue()); model.getMessages().setAll(messageHolder.messages.stream().filter(msg -> msg.getLevel().intValue() > Level.INFO.intValue()).limit(50).toList());
} }
if (model.getMessages().isEmpty()) { if (model.getMessages().isEmpty()) {
if (onlyShowVerifiedChecker.isSelected()) { if (onlyShowVerifiedChecker.isSelected()) {
@@ -325,6 +329,7 @@ public class ContractVerifyWindowController extends BaseController {
} }
runAsync(() -> { runAsync(() -> {
ContractVo contract = null; ContractVo contract = null;
MessageHolderImpl messageHolder = new MessageHolderImpl();
try { try {
contract = contractService.findByCode(contractCode); contract = contractService.findByCode(contractCode);
} catch (Exception e) { } catch (Exception e) {
@@ -336,17 +341,18 @@ public class ContractVerifyWindowController extends BaseController {
} }
model.getMessages().clear(); model.getMessages().clear();
try { try {
comm.verify(contract, model); comm.verify(contract, messageHolder);
// 移除中间消息
if (!model.getMessages().isEmpty()) {
model.getMessages().removeIf(msg -> msg.getLevel().intValue() <= Level.INFO.intValue());
}
} catch (Exception e) { } catch (Exception e) {
logger.error(model.getCode().get(), e); logger.error(model.getCode().get(), e);
model.error(e.getMessage()); messageHolder.error(e.getMessage());
} }
if (model.getMessages().isEmpty()) { // 移除中间消息
if (!messageHolder.messages.isEmpty()) {
model.getMessages().setAll(messageHolder.messages.stream().filter(msg -> msg.getLevel().intValue() > Level.INFO.intValue()).limit(50).toList());
}
if (messageHolder.messages.isEmpty()) {
Platform.runLater(() -> { Platform.runLater(() -> {
viewTableDataSet.remove(model); viewTableDataSet.remove(model);
}); });
@@ -380,6 +386,38 @@ public class ContractVerifyWindowController extends BaseController {
ContractWindowController.show(contract, viewTable.getScene().getWindow()); ContractWindowController.show(contract, viewTable.getScene().getWindow());
} }
public void onShowVerifyStatusAction(ActionEvent event) {
// 在新新窗口中显示 状态消息 Model# messages
Model selectedItem = viewTable.getSelectionModel().getSelectedItem();
if (selectedItem == null) {
return;
}
ListView<MessageExt> listView = new ListView<>();
listView.setItems(selectedItem.messages);
listView.setCellFactory(v -> new ListCell<>() {
@Override
protected void updateItem(MessageExt item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
setGraphic(null);
} else {
setText(item.getMessage());
setGraphic(new Label(item.getPrefix()));
}
}
});
PopOver popOver = new PopOver(listView);
popOver.setArrowLocation(PopOver.ArrowLocation.TOP_LEFT);
MenuItem menuItem = (MenuItem) event.getSource();
Node node = viewTable.lookup(".table-row-cell:selected");
popOver.show(node);
}
public void onExportVerifyResultAsFileAction(ActionEvent e) { public void onExportVerifyResultAsFileAction(ActionEvent e) {
FileChooser chooser = new FileChooser(); FileChooser chooser = new FileChooser();
chooser.setTitle("导出核验结果"); chooser.setTitle("导出核验结果");

View File

@@ -2,6 +2,7 @@ package com.ecep.contract.controller.contract;
import java.io.File; import java.io.File;
import javafx.scene.control.*;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -12,14 +13,8 @@ import com.ecep.contract.DesktopUtils;
import com.ecep.contract.controller.AbstEntityController; import com.ecep.contract.controller.AbstEntityController;
import com.ecep.contract.controller.company.CompanyWindowController; import com.ecep.contract.controller.company.CompanyWindowController;
import com.ecep.contract.controller.tab.ContractTabSkinBase; 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.ContractTabSkinItemsV2;
import com.ecep.contract.controller.tab.ContractTabSkinPayPlan;
import com.ecep.contract.controller.tab.ContractTabSkinPurchaseOrders;
import com.ecep.contract.controller.tab.ContractTabSkinSaleOrders;
import com.ecep.contract.controller.tab.ContractTabSkinSubContract;
import com.ecep.contract.controller.tab.ContractTabSkinVendorBid;
import com.ecep.contract.service.CompanyService; import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.ContractService; import com.ecep.contract.service.ContractService;
import com.ecep.contract.task.ContractRepairTask; import com.ecep.contract.task.ContractRepairTask;
@@ -32,13 +27,6 @@ import com.ecep.contract.vo.ContractVo;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.scene.control.Button;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
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.BorderPane;
import javafx.stage.Window; import javafx.stage.Window;
import javafx.stage.WindowEvent; import javafx.stage.WindowEvent;
@@ -50,6 +38,8 @@ import javafx.stage.WindowEvent;
public class ContractWindowController public class ContractWindowController
extends AbstEntityController<ContractVo, ContractViewModel> { extends AbstEntityController<ContractVo, ContractViewModel> {
public static void show(ContractVo contract, Window owner) { public static void show(ContractVo contract, Window owner) {
show(ContractViewModel.from(contract), owner); show(ContractViewModel.from(contract), owner);
} }
@@ -75,6 +65,7 @@ public class ContractWindowController
public Button openRelativeCompanyVendorBtn; public Button openRelativeCompanyVendorBtn;
public TextField nameField; public TextField nameField;
public CheckBox contractNameLockedCk;
public TextField guidField; public TextField guidField;
public TextField codeField; public TextField codeField;
public TextField parentCodeField; public TextField parentCodeField;
@@ -154,7 +145,13 @@ public class ContractWindowController
payPlanTab.setText("收款计划"); payPlanTab.setText("收款计划");
tabs.add(saleOrderTab); tabs.add(saleOrderTab);
registerTabSkin(saleOrderTab, tab -> new ContractTabSkinSaleOrders(this, tab)); registerTabSkin(saleOrderTab, tab -> new ContractTabSkinSaleOrders(this, tab));
tabs.add(new Tab("")); Tab invoiceTab = new Tab("");
tabs.add(invoiceTab);
registerTabSkin(invoiceTab, tab -> new ContractTabSkinInvoices(this, tab));
tabs.add(new Tab("出库单"));
tabs.add(new Tab("发货单"));
tabs.add(new Tab("签收单"));
} else if (payWay == ContractPayWay.PAY) { } else if (payWay == ContractPayWay.PAY) {
registerTabSkin(extendVendorInfo, t -> new ContractTabSkinExtendVendorInfo(this)); registerTabSkin(extendVendorInfo, t -> new ContractTabSkinExtendVendorInfo(this));
tabs.remove(contractTab); tabs.remove(contractTab);
@@ -164,9 +161,9 @@ public class ContractWindowController
tabs.add(purchaseOrderTab); tabs.add(purchaseOrderTab);
registerTabSkin(purchaseOrderTab, tab -> new ContractTabSkinPurchaseOrders(this, tab)); registerTabSkin(purchaseOrderTab, tab -> new ContractTabSkinPurchaseOrders(this, tab));
tabs.add(new Tab("发货")); tabs.add(new Tab("入库"));
tabs.add(new Tab("签收单"));
tabs.add(new Tab("付款单")); tabs.add(new Tab("付款单"));
payPlanTab.setText("付款计划"); payPlanTab.setText("付款计划");
} }

View File

@@ -43,6 +43,15 @@ public class SalesOrderWindowController extends AbstEntityController<SalesOrderV
public TextField makeDateField; public TextField makeDateField;
public TextField makerField; public TextField makerField;
public TextArea descriptionField; public TextArea descriptionField;
public TextField refIdField;
public TextField taxRateField;
public TextField customerField;
public TextField customerAddressField;
public TextField modifierField;
public TextField modifierDateField;
public TextField closerField;
public TextField closerDateField;
public TextField contractField;
public static void show(SalesOrderViewModel viewModel, Window window) { public static void show(SalesOrderViewModel viewModel, Window window) {

View File

@@ -1,11 +1,26 @@
package com.ecep.contract.controller.customer; package com.ecep.contract.controller.customer;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.time.format.DateTimeFormatter;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer;
import com.ecep.contract.controller.AbstEntityController;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.service.CompanyCustomerFileTypeService;
import com.ecep.contract.service.ViewModelService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vo.CustomerFileTypeLocalVo;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.*;
import javafx.scene.input.MouseButton;
import javafx.scene.paint.Color;
import javafx.scene.text.FontWeight;
import javafx.util.converter.LocalDateStringConverter;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
@@ -15,6 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import com.ecep.contract.controller.BaseController; import com.ecep.contract.controller.BaseController;
@@ -29,11 +45,9 @@ import com.ecep.contract.vm.CustomerFileViewModel;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.geometry.Bounds; import javafx.geometry.Bounds;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.CheckBox; import javafx.scene.control.CheckBox;
import javafx.scene.control.DatePicker; import javafx.scene.control.DatePicker;
import javafx.scene.control.Label; import javafx.scene.control.Label;
@@ -50,29 +64,30 @@ import javafx.stage.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.stage.Window; import javafx.stage.Window;
import javafx.stage.WindowEvent; import javafx.stage.WindowEvent;
@Lazy @Lazy
@Scope("prototype") @Scope("prototype")
@Component @Component
public class CompanyCustomerEvaluationFormFileWindowController extends BaseController { @FxmlPath("/ui/company/customer/customer_evaluation_form.fxml")
private static final Logger logger = LoggerFactory.getLogger(CompanyCustomerEvaluationFormFileWindowController.class); public class CompanyCustomerEvaluationFormFileWindowController
extends AbstEntityController<CompanyCustomerEvaluationFormFileVo, CompanyCustomerEvaluationFormFileViewModel> {
private static final Logger logger = LoggerFactory
.getLogger(CompanyCustomerEvaluationFormFileWindowController.class);
public static void show(CustomerFileViewModel item, Window window) {
show(CompanyCustomerEvaluationFormFileWindowController.class, window, controller -> {
controller.fileViewModel = item;
});
}
public static void show(CompanyCustomerEvaluationFormFileVo saved, Window window) { public static void show(CompanyCustomerEvaluationFormFileVo saved, Window window) {
show(CompanyCustomerEvaluationFormFileViewModel.from(saved), window); show(CompanyCustomerEvaluationFormFileViewModel.from(saved), window);
} }
public static void show(CompanyCustomerEvaluationFormFileViewModel viewModel, Window window) { public static void show(CompanyCustomerEvaluationFormFileViewModel viewModel, Window window) {
String key = viewModel.getClass().getName() + "-" + viewModel.getId().get(); show(CompanyCustomerEvaluationFormFileWindowController.class, viewModel, window);
if (toFront(key)) {
return;
}
FxmlUtils.newLoaderAsyncWithRunLater("/ui/company/customer/customer_evaluation_form.fxml", null, loader -> {
CompanyCustomerEvaluationFormFileWindowController controller = loader.getController();
controller.viewModel = viewModel;
controller.show(loader, window, Modality.NONE, key);
});
} }
public Label idField; public Label idField;
public TextField filePathField; public TextField filePathField;
public CheckBox validField; public CheckBox validField;
@@ -91,64 +106,66 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
public ScrollPane leftPane; public ScrollPane leftPane;
public Label totalCreditScoreLabel; public Label totalCreditScoreLabel;
private CompanyCustomerEvaluationFormFileViewModel viewModel;
private final SimpleStringProperty catalogProperty = new SimpleStringProperty("");
private final SimpleStringProperty levelProperty = new SimpleStringProperty("");
private final SimpleIntegerProperty score1Property = new SimpleIntegerProperty(-1);
private final SimpleIntegerProperty score2Property = new SimpleIntegerProperty(-1);
private final SimpleIntegerProperty score3Property = new SimpleIntegerProperty(-1);
private final SimpleIntegerProperty score4Property = new SimpleIntegerProperty(-1);
private final SimpleIntegerProperty score5Property = new SimpleIntegerProperty(-1);
private final SimpleIntegerProperty creditLevelProperty = new SimpleIntegerProperty(-1);
private final SimpleIntegerProperty totalCreditScoreProperty = new SimpleIntegerProperty(-1); private final SimpleIntegerProperty totalCreditScoreProperty = new SimpleIntegerProperty(-1);
private SimpleObjectProperty<Image> imageProperty = new SimpleObjectProperty<>(); private CustomerFileViewModel fileViewModel;
private CompletableFuture<CompanyCustomerEvaluationFormFileVo> loadedFuture; private SimpleBooleanProperty changed = new SimpleBooleanProperty(false);
@Lazy
@Autowired
private CompanyCustomerFileService companyCustomerFileService;
@Lazy @Lazy
@Autowired @Autowired
private CompanyCustomerEvaluationFormFileService evaluationFormFileService; private CompanyCustomerEvaluationFormFileService evaluationFormFileService;
@Override @Override
public void show(Stage stage) { public void show(Stage stage) {
super.show(stage); super.show(stage);
stage.setFullScreen(false); stage.setFullScreen(false);
// Rectangle2D screenBounds = Screen.getPrimary().getVisualBounds();
//
// stage.setX(screenBounds.getMinX());
// stage.setY(screenBounds.getMinY());
// stage.setWidth(screenBounds.getWidth());
// stage.setHeight(screenBounds.getHeight());
//
// stage.isMaximized();
stage.setMaximized(true); stage.setMaximized(true);
getTitle().set("客户评估表单");
} }
@Override @Override
public void onShown(WindowEvent windowEvent) { public void onShown(WindowEvent windowEvent) {
super.onShown(windowEvent); super.onShown(windowEvent);
if (logger.isDebugEnabled()) { getTitle().set("客户评估表单");
logger.debug("onShown"); }
}
initializePane(); @Override
public ViewModelService<CompanyCustomerEvaluationFormFileVo, CompanyCustomerEvaluationFormFileViewModel> getViewModelService() {
return evaluationFormFileService;
}
loadedFuture = CompletableFuture.supplyAsync(() -> { @Override
int id = viewModel.getId().get(); protected void initializeData() {
CustomerFileVo customerFile = companyCustomerFileService.findById(id); CompanyCustomerEvaluationFormFileViewModel viewModel = new CompanyCustomerEvaluationFormFileViewModel();
CompanyCustomerEvaluationFormFileVo formFile = evaluationFormFileService.findByCustomerFile(customerFile); setViewModel(viewModel);
Platform.runLater(() -> update(formFile)); runAsync(() -> {
return formFile; CompanyCustomerEvaluationFormFileVo item = getCachedBean(CompanyCustomerEvaluationFormFileService.class)
.findByCustomerFile(fileViewModel.getId().get());
viewModel.getId().set(item.getId());
updateViewModel(item);
super.initializeData();
Platform.runLater(this::initializePane);
}); });
} }
@Override
protected void updateViewModel(CompanyCustomerEvaluationFormFileVo entity) {
super.updateViewModel(entity);
changed.set(false);
}
@Override
public BooleanBinding createTabSkinChangedBindings() {
return viewModel.getChanged().or(fileViewModel.getChanged()).or(changed);
}
@Override
public void saveTabSkins() {
save();
changed.setValue(false);
}
BiConsumer<ToggleGroup, String> stringRadioGroupUpdater = (group, newValue) -> { BiConsumer<ToggleGroup, String> stringRadioGroupUpdater = (group, newValue) -> {
if (newValue != null) { if (newValue != null) {
for (Toggle toggle : group.getToggles()) { for (Toggle toggle : group.getToggles()) {
@@ -164,14 +181,15 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
return; return;
} }
} }
changed.set(true);
} }
Platform.runLater(() -> { Platform.runLater(() -> {
group.selectToggle(null); group.selectToggle(null);
// Toggle first = group.getToggles().getFirst(); // Toggle first = group.getToggles().getFirst();
// first.setSelected(true); // first.setSelected(true);
// first.setSelected(false); // first.setSelected(false);
// RadioButton btn = (RadioButton) first; // RadioButton btn = (RadioButton) first;
// btn.setText(newValue); // btn.setText(newValue);
}); });
}; };
@@ -182,9 +200,11 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
} }
String data = (String) toggle.getUserData(); String data = (String) toggle.getUserData();
property.set(data); property.set(data);
changed.set(true);
}; };
BiConsumer<ToggleGroup, Number> numberRadioGroupUpdater = (group, newValue) -> { BiConsumer<ToggleGroup, Number> numberRadioGroupUpdater = (group, newValue) -> {
System.out.println("group = " + group + ", newValue = " + newValue);
String value = String.valueOf(newValue); String value = String.valueOf(newValue);
for (Toggle toggle : group.getToggles()) { for (Toggle toggle : group.getToggles()) {
String data = (String) toggle.getUserData(); String data = (String) toggle.getUserData();
@@ -195,12 +215,13 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
} }
Platform.runLater(() -> { Platform.runLater(() -> {
group.selectToggle(null); group.selectToggle(null);
// Toggle first = group.getToggles().getFirst(); // Toggle first = group.getToggles().getFirst();
// first.setSelected(true); // first.setSelected(true);
// first.setSelected(false); // first.setSelected(false);
// RadioButton btn = (RadioButton) first; // RadioButton btn = (RadioButton) first;
// btn.setText(String.valueOf(newValue)); // btn.setText(String.valueOf(newValue));
}); });
changed.set(true);
}; };
BiConsumer<SimpleIntegerProperty, Toggle> numberPropertyUpdater = (property, toggle) -> { BiConsumer<SimpleIntegerProperty, Toggle> numberPropertyUpdater = (property, toggle) -> {
@@ -210,9 +231,9 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
} }
String data = (String) toggle.getUserData(); String data = (String) toggle.getUserData();
property.set(Integer.parseInt(data)); property.set(Integer.parseInt(data));
changed.set(true);
}; };
int totalScoreToLevel(int score) { int totalScoreToLevel(int score) {
if (score >= 200) { if (score >= 200) {
return 4; return 4;
@@ -227,63 +248,73 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
} }
boolean calcValid() { boolean calcValid() {
if (creditLevelProperty.get() <= 0) { if (viewModel.getCreditLevel().get() <= 0) {
return false; return false;
} }
if (!StringUtils.hasText(catalogProperty.get())) { if (!StringUtils.hasText(viewModel.getCatalog().get())) {
return false; return false;
} }
if (!StringUtils.hasText(levelProperty.get())) { if (!StringUtils.hasText(viewModel.getLevel().get())) {
return false; return false;
} }
if (score1Property.get() <= 0) {
if (viewModel.getScore1().get() <= 0) {
return false; return false;
} }
if (score2Property.get() <= 0) { if (viewModel.getScore2().get() <= 0) {
return false; return false;
} }
if (score3Property.get() <= 0) { if (viewModel.getScore3().get() <= 0) {
return false; return false;
} }
if (score4Property.get() <= 0) { if (viewModel.getScore4().get() <= 0) {
return false; return false;
} }
if (score5Property.get() <= 0) { if (viewModel.getScore5().get() <= 0) {
return false; return false;
} }
if (creditLevelProperty.get() <= 0) { if (viewModel.getCreditLevel().get() <= 0) {
return false; return false;
} }
return true; return true;
} }
private void initializePane() { private void initializePane() {
setStatus("");
idField.textProperty().bind(viewModel.getId().asString()); idField.textProperty().bind(viewModel.getId().asString());
// filePathField.textProperty().bind(viewModel.getFilePath()); filePathField.textProperty().bind(fileViewModel.getFilePath());
// editFilePathField.textProperty().bind(viewModel.getEditFilePath()); editFilePathField.textProperty().bind(fileViewModel.getEditFilePath());
// signDateField.valueProperty().bindBidirectional(viewModel.getSignDate()); String pattern = "yyyy-MM-dd";
// validField.selectedProperty().bindBidirectional(viewModel.getValid()); DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
signDateField.setConverter(new LocalDateStringConverter(formatter, null));
initializeRadioGroup(catalog, catalogProperty); signDateField.valueProperty().bindBidirectional(fileViewModel.getSignDate());
initializeRadioGroup(level, levelProperty); signDateField.valueProperty().addListener((observable, oldValue, newValue) -> {
changed.set(true);
initializeRadioGroup(score1, score1Property);
initializeRadioGroup(score2, score2Property);
initializeRadioGroup(score3, score3Property);
initializeRadioGroup(score4, score4Property);
initializeRadioGroup(score5, score5Property);
creditLevelProperty.addListener((observable, oldValue, newValue) -> {
numberRadioGroupUpdater.accept(creditLevel, newValue);
}); });
SimpleIntegerProperty[] scores = new SimpleIntegerProperty[]{score1Property, score2Property, score3Property, score4Property, score5Property}; initializeRadioGroup(catalog, viewModel.getCatalog());
initializeRadioGroup(level, viewModel.getLevel());
initializeRadioGroup(score1, viewModel.getScore1());
initializeRadioGroup(score2, viewModel.getScore2());
initializeRadioGroup(score3, viewModel.getScore3());
initializeRadioGroup(score4, viewModel.getScore4());
initializeRadioGroup(score5, viewModel.getScore5());
// 信用等级
viewModel.getCreditLevel().addListener((observable, oldValue, newValue) -> {
numberRadioGroupUpdater.accept(creditLevel, newValue);
});
numberRadioGroupUpdater.accept(creditLevel, viewModel.getCreditLevel().get());
SimpleIntegerProperty[] scores = new SimpleIntegerProperty[]{viewModel.getScore1(), viewModel.getScore2(),
viewModel.getScore3(), viewModel.getScore4(), viewModel.getScore5()};
totalCreditScoreProperty.bind(Bindings.createIntegerBinding(() -> { totalCreditScoreProperty.bind(Bindings.createIntegerBinding(() -> {
int total = 0; int total = 0;
for (SimpleIntegerProperty score : scores) { for (SimpleIntegerProperty score : scores) {
total += score.get(); total += score.get();
} }
creditLevelProperty.set(totalScoreToLevel(total)); viewModel.getCreditLevel().set(totalScoreToLevel(total));
return total; return total;
}, scores)); }, scores));
@@ -291,70 +322,205 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
return "合计总分:" + score; return "合计总分:" + score;
})); }));
Bindings.createBooleanBinding(() -> { Bindings.createBooleanBinding(this::calcValid, viewModel.getCatalog(), viewModel.getLevel(),
boolean valid = calcValid(); viewModel.getScore1(), viewModel.getScore2(), viewModel.getScore3(), viewModel.getScore4(),
// viewModel.getValid().set(valid); viewModel.getScore5(), viewModel.getCreditLevel())
return valid; .addListener((observable, oldValue, newValue) -> {
}, catalogProperty, levelProperty, score1Property, score2Property, score3Property, score4Property, score5Property, creditLevelProperty).addListener(((observable, oldValue, newValue) -> { fileViewModel.getValid().set(newValue);
logger.info("valid:{}", newValue); changed.set(true);
})); });
validField.selectedProperty().bindBidirectional(fileViewModel.getValid());
validField.setSelected(fileViewModel.getValid().getValue());
fileViewModel.getFilePath().addListener((observable, oldValue, newValue) -> {
imageView.imageProperty().bind(viewModel.getFilePath().map(path -> { File file = new File(newValue);
if (FileUtils.withExtensions(path, FileUtils.PDF)) { loadFile(file);
File pdfFile = new File(path); });
try (PDDocument pdDocument = Loader.loadPDF(pdfFile)) { if (StringUtils.hasText(fileViewModel.getFilePath().get())) {
PDFRenderer pdfRenderer = new PDFRenderer(pdDocument); loadFile(new File(fileViewModel.getFilePath().get()));
BufferedImage bufferedImage = pdfRenderer.renderImageWithDPI(0, 300); }
// 获取 BufferedImage 的宽度和高度
int width = bufferedImage.getWidth();
int height = bufferedImage.getHeight();
WritableImage writableImage = new WritableImage(width, height);
PixelWriter pixelWriter = writableImage.getPixelWriter();
// 将 BufferedImage 的像素数据复制到 WritableImage
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int argb = bufferedImage.getRGB(x, y);
pixelWriter.setArgb(x, y, argb);
}
}
return writableImage;
} catch (Exception e) {
setStatus(e.getMessage());
}
return null;
} else {
File file = new File(path);
Image image = new Image(file.toURI().toString());
return image;
}
}));
leftPane.widthProperty().addListener((observable, oldValue, newValue) -> { leftPane.widthProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> { Platform.runLater(() -> {
imageView.setFitWidth(leftPane.getWidth()); imageView.setFitWidth(leftPane.getWidth());
imageView.setFitHeight(leftPane.getHeight()); imageView.setFitHeight(-1);
}); });
}); });
imageView.setFitWidth(leftPane.getWidth()); imageView.setFitWidth(leftPane.getWidth());
imageView.setFitHeight(leftPane.getHeight()); imageView.setFitHeight(-1);
imageView.setOnScroll(event -> { imageView.setOnScroll(event -> {
System.out.println("event = " + event);
System.out.println("event.getDeltaY() = " + event.getDeltaY());
Bounds bounds = imageView.getBoundsInLocal(); Bounds bounds = imageView.getBoundsInLocal();
// Bounds latestBounds = (Bounds) imageView.getProperties().get("latestBounds");
// if (latestBounds != null) {
// double latestBoundsWidth = latestBounds.getWidth();
// }
// if (bounds.getWidth() < leftPane.getWidth()) {
imageView.setFitWidth(bounds.getWidth() + event.getDeltaY()); imageView.setFitWidth(bounds.getWidth() + event.getDeltaY());
// } else { imageView.setFitHeight(-1);
imageView.setFitHeight(bounds.getHeight() + event.getDeltaY()); event.consume();
// } });
imageView.setOnMouseClicked(event -> {
System.out.println("imageView.getFitWidth() = " + imageView.getFitWidth());
System.out.println("imageView.getFitHeight() = " + imageView.getFitHeight());
System.out.println("leftPane.getWidth() = " + leftPane.getWidth());
System.out.println("leftPane.getViewportBounds().getWidth() = " + leftPane.getViewportBounds().getWidth());
if (event.getClickCount() == 2 && event.getButton() == MouseButton.PRIMARY) {
Image image = imageView.getImage();
if (image != null) {
System.out.println("image.getWidth() = " + image.getWidth());
if (image.getWidth() > imageView.getFitWidth()) {
imageView.setFitWidth(image.getWidth());
} else {
imageView.setFitWidth(leftPane.getWidth());
}
imageView.setFitHeight(-1);
}
}
});
}
private void loadFile(File file) {
setStatus("文件" + file.getAbsolutePath() + " 加载中...");
if (FileUtils.withExtensions(file.getName(), FileUtils.PDF)) {
loadPdf(file);
return;
}
Image image = new Image(file.toURI().toString(), true);
imageView.setImage(image);
}
private void loadPdf(File pdfFile) {
// 绘制文字, 等待加载
// 创建画布并绘制备用占位文字
javafx.scene.canvas.Canvas canvas = new javafx.scene.canvas.Canvas(leftPane.getWidth(), leftPane.getHeight());
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(javafx.scene.paint.Color.RED);
var h1 = javafx.scene.text.Font.font("Microsoft YaHei", FontWeight.BOLD, 24);
var h2 = javafx.scene.text.Font.font("Microsoft YaHei", FontWeight.NORMAL, 18);
gc.setFont(h1);
gc.fillText(fileViewModel.getType().get().name(), 50, 100);
Runnable updateImage = () -> {
WritableImage writableImage = new WritableImage((int) canvas.getWidth(), (int) canvas.getHeight());
// 将画布内容转为图像
Platform.runLater(() -> {
canvas.snapshot(null, writableImage);
imageView.setImage(writableImage);
});
};
runAsync(() -> {
CustomerFileTypeLocalVo localVo = getCachedBean(CompanyCustomerFileTypeService.class).findByLocaleAndType(getLocale(), fileViewModel.getType().get());
gc.setFill(Color.WHITE);
// 覆盖 fileViewModel.getType() 文字
gc.fillRect(0, 55, canvas.getWidth(), 55);
// 绘制 文件类型
gc.setFill(javafx.scene.paint.Color.RED);
gc.setFont(h1);
gc.fillText(localVo.getValue(), 50, 100);
updateImage.run();
});
gc.setStroke(javafx.scene.paint.Color.BLACK);
gc.setFont(h2);
gc.strokeText("正在加载文件..." + pdfFile.getName(), 50, 150);
updateImage.run();
runAsync(() -> {
//FileSystemUtils.
long fileSize = pdfFile.length();
byte[] bytes = new byte[0];
try (java.io.FileInputStream fis = new java.io.FileInputStream(pdfFile);
java.io.BufferedInputStream bis = new java.io.BufferedInputStream(fis)) {
bytes = new byte[(int) fileSize];
int totalBytesRead = 0;
int bytesRead;
byte[] buffer = new byte[8192]; // 8KB buffer
while ((bytesRead = bis.read(buffer)) != -1) {
System.arraycopy(buffer, 0, bytes, totalBytesRead, bytesRead);
totalBytesRead += bytesRead;
// 更新进度
double progress = (double) totalBytesRead / fileSize * 100;
final String status = String.format("正在加载文件... %s (%.1f%%)",
pdfFile.getName(), progress);
gc.setFill(Color.WHITE);
gc.fillRect(0, 200, canvas.getWidth(), 80);
gc.setFill(Color.BLACK);
gc.setFont(h2);
gc.fillText(status, 50, 250);
// 绘制进度条背景
gc.setFill(Color.LIGHTGRAY);
gc.fillRect(50, 270, 400, 20);
// 绘制进度条
gc.setFill(Color.GREEN);
gc.fillRect(50, 270, 400 * (totalBytesRead / (double) fileSize), 20);
// 绘制进度条边框
gc.setStroke(Color.BLACK);
gc.setLineWidth(1);
gc.strokeRect(50, 270, 400, 20);
updateImage.run();
}
gc.setFill(Color.BLACK);
gc.setFont(h2);
gc.fillText("Loading file: " + pdfFile.getName() + ", size: " + bytes.length + " bytes", 50, 320);
updateImage.run();
} catch (Exception e) {
throw new RuntimeException(e);
}
try (PDDocument pdDocument = Loader.loadPDF(bytes)) {
gc.setFill(Color.BLACK);
gc.setFont(h2);
gc.fillText("PDF has " + pdDocument.getNumberOfPages() + " pages", 50, 380);
updateImage.run();
PDFRenderer pdfRenderer = new PDFRenderer(pdDocument);
BufferedImage bufferedImage = pdfRenderer.renderImageWithDPI(0, 300);
// 获取 BufferedImage 的宽度和高度
int width = bufferedImage.getWidth();
int height = bufferedImage.getHeight();
canvas.resize(width, height);
GraphicsContext graphic = canvas.getGraphicsContext2D();
WritableImage writableImage1 = new WritableImage(width, height);
PixelWriter pixelWriter = writableImage1.getPixelWriter();
// 将 BufferedImage 的像素数据复制到 WritableImage
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int argb = bufferedImage.getRGB(x, y);
pixelWriter.setArgb(x, y, argb);
}
}
setStatus();
Platform.runLater(() -> {
imageView.setImage(writableImage1);
});
} catch (Exception e) {
setStatus(e.getMessage());
}
}); });
} }
@@ -365,6 +531,7 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
toggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> { toggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
stringPropertyUpdater.accept(property, newValue); stringPropertyUpdater.accept(property, newValue);
}); });
stringRadioGroupUpdater.accept(toggleGroup, property.getValue());
} }
private void initializeRadioGroup(ToggleGroup toggleGroup, SimpleIntegerProperty property) { private void initializeRadioGroup(ToggleGroup toggleGroup, SimpleIntegerProperty property) {
@@ -375,21 +542,8 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
toggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> { toggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
numberPropertyUpdater.accept(property, newValue); numberPropertyUpdater.accept(property, newValue);
}); });
numberRadioGroupUpdater.accept(toggleGroup, property.getValue());
} }
private void update(CompanyCustomerEvaluationFormFileVo formFile) {
viewModel.update(formFile);
// formFile.getScoreTemplateVersion();
catalogProperty.set(formFile.getCatalog());
levelProperty.set(formFile.getLevel());
score1Property.set(formFile.getScore1());
score2Property.set(formFile.getScore2());
score3Property.set(formFile.getScore3());
score4Property.set(formFile.getScore4());
score5Property.set(formFile.getScore5());
creditLevelProperty.set(formFile.getCreditLevel());
}
} }

View File

@@ -161,9 +161,7 @@ public class CustomerTabSkinFile
// 文件不是 Excel 文件时打开编辑UI // 文件不是 Excel 文件时打开编辑UI
if (!FileUtils.withExtensions(item.getFilePath().get(), FileUtils.XLS, if (!FileUtils.withExtensions(item.getFilePath().get(), FileUtils.XLS,
FileUtils.XLSX)) { FileUtils.XLSX)) {
CompanyCustomerEvaluationFormFileVo evaluationFormFile = getEvaluationFormFileService() CompanyCustomerEvaluationFormFileWindowController.show(item,
.findByCustomerFile(item.getId().get());
CompanyCustomerEvaluationFormFileWindowController.show(evaluationFormFile,
controller.root.getScene().getWindow()); controller.root.getScene().getWindow());
return; return;
} }
@@ -211,10 +209,7 @@ public class CustomerTabSkinFile
model.update(saved); model.update(saved);
dataSet.add(model); dataSet.add(model);
CompanyCustomerEvaluationFormFileVo evaluationFormFile = getCachedBean( CompanyCustomerEvaluationFormFileWindowController.show(model,
CompanyCustomerEvaluationFormFileService.class).findByCustomerFile(saved);
CompanyCustomerEvaluationFormFileWindowController.show(evaluationFormFile,
getTableView().getScene().getWindow()); getTableView().getScene().getWindow());
}); });
return; return;

View File

@@ -14,30 +14,16 @@ import lombok.Setter;
public abstract class AbstEmployeeBasedTabSkin public abstract class AbstEmployeeBasedTabSkin
extends AbstEntityBasedTabSkin<EmployeeWindowController, EmployeeVo, EmployeeViewModel> extends AbstEntityBasedTabSkin<EmployeeWindowController, EmployeeVo, EmployeeViewModel>
implements TabSkin { implements TabSkin {
@Setter
private PermissionService permissionService;
@Setter
private EmployeeRoleService employeeRoleService;
public AbstEmployeeBasedTabSkin(EmployeeWindowController controller) { public AbstEmployeeBasedTabSkin(EmployeeWindowController controller) {
super(controller); super(controller);
} }
protected EmployeeService getEmployeeService() {
return controller.employeeService;
}
protected EmployeeRoleService getEmployeeRoleService() { protected EmployeeRoleService getEmployeeRoleService() {
if (employeeRoleService == null) { return getCachedBean(EmployeeRoleService.class);
employeeRoleService = getBean(EmployeeRoleService.class);
}
return employeeRoleService;
} }
protected PermissionService getPermissionService() { protected PermissionService getPermissionService() {
if (permissionService == null) { return getCachedBean(PermissionService.class);
permissionService = getBean(PermissionService.class);
}
return permissionService;
} }
} }

View File

@@ -47,7 +47,7 @@ public class EmployeeManagerSkin
controller.accountColumn.setCellValueFactory(param -> param.getValue().getAccount()); controller.accountColumn.setCellValueFactory(param -> param.getValue().getAccount());
controller.departmentColumn.setCellValueFactory(param -> param.getValue().getDepartment()); controller.departmentColumn.setCellValueFactory(param -> param.getValue().getDepartment());
controller.departmentColumn.setCellFactory(param -> new DepartmentTableCell<>(getDepartmentService())); controller.departmentColumn.setCellFactory(DepartmentTableCell.forTableColumn(getDepartmentService()));
controller.emailColumn.setCellValueFactory(param -> param.getValue().getEmail()); controller.emailColumn.setCellValueFactory(param -> param.getValue().getEmail());
controller.createdColumn.setCellValueFactory(param -> param.getValue().getCreated()); controller.createdColumn.setCellValueFactory(param -> param.getValue().getCreated());

View File

@@ -2,7 +2,9 @@ package com.ecep.contract.controller.employee;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import com.ecep.contract.util.ParamUtils;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import com.ecep.contract.controller.tab.TabSkin; import com.ecep.contract.controller.tab.TabSkin;
@@ -48,7 +50,7 @@ public class EmployeeTabSkinRole
private void initializeListView() { private void initializeListView() {
// 非系统内置账户 // 非系统内置账户
HashMap<String, Object> params = new HashMap<>(); Map<String, Object> params = ParamUtils.builder().build();
List<EmployeeRoleVo> roles = getEmployeeRoleService().findAll(params, Pageable.ofSize(500)).getContent(); List<EmployeeRoleVo> roles = getEmployeeRoleService().findAll(params, Pageable.ofSize(500)).getContent();
controller.rolesField.getSourceItems().setAll(roles); controller.rolesField.getSourceItems().setAll(roles);

View File

@@ -5,10 +5,12 @@ import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.ecep.contract.controller.AbstEntityController; import com.ecep.contract.controller.AbstEntityController;
import com.ecep.contract.controller.BaseController;
import com.ecep.contract.service.InventoryService; import com.ecep.contract.service.InventoryService;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vm.InventoryViewModel; import com.ecep.contract.vm.InventoryViewModel;
import com.ecep.contract.vo.InventoryVo; import com.ecep.contract.vo.InventoryVo;
import javafx.stage.Window;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.fxml.FXML; import javafx.fxml.FXML;
@@ -26,6 +28,19 @@ import javafx.stage.WindowEvent;
@Component @Component
@FxmlPath("/ui/inventory/inventory.fxml") @FxmlPath("/ui/inventory/inventory.fxml")
public class InventoryWindowController extends AbstEntityController<InventoryVo, InventoryViewModel> { public class InventoryWindowController extends AbstEntityController<InventoryVo, InventoryViewModel> {
/**
* 显示界面
*/
public static void show(InventoryViewModel viewModel, Window window) {
BaseController.show(InventoryWindowController.class, viewModel, window);
}
/**
* 显示界面
*/
public static void show(InventoryVo inventory, Window owner) {
show(InventoryViewModel.from(inventory), owner);
}
@FXML @FXML
public BorderPane root; public BorderPane root;
@FXML @FXML

View File

@@ -0,0 +1,24 @@
package com.ecep.contract.controller.permission;
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
import com.ecep.contract.service.FunctionService;
import com.ecep.contract.service.PermissionService;
import com.ecep.contract.vm.FunctionViewModel;
import com.ecep.contract.vo.FunctionVo;
public abstract class AbstEmployeeFunctionBasedTabSkin
extends AbstEntityBasedTabSkin<EmployeeFunctionWindowController, FunctionVo, FunctionViewModel> {
public AbstEmployeeFunctionBasedTabSkin(EmployeeFunctionWindowController controller) {
super(controller);
}
FunctionService getFunctionService() {
return getCachedBean(FunctionService.class);
}
public PermissionService getPermissionService() {
return controller.permissionService;
}
}

View File

@@ -0,0 +1,27 @@
package com.ecep.contract.controller.permission;
import com.ecep.contract.controller.tab.TabSkin;
import javafx.scene.control.Tab;
public class EmployeeFunctionTabSkinBase extends AbstEmployeeFunctionBasedTabSkin implements TabSkin {
public EmployeeFunctionTabSkinBase(EmployeeFunctionWindowController controller) {
super(controller);
}
@Override
public Tab getTab() {
return controller.baseInfoTab;
}
@Override
public void initializeTab() {
controller.nameField.textProperty().bindBidirectional(viewModel.getName());
controller.keyField.textProperty().bindBidirectional(viewModel.getKey());
controller.controllerField.textProperty().bindBidirectional(viewModel.getController());
controller.iconField.textProperty().bindBidirectional(viewModel.getIcon());
controller.descriptionField.textProperty().bindBidirectional(viewModel.getDescription());
controller.activeField.selectedProperty().bindBidirectional(viewModel.getActive());
}
}

View File

@@ -0,0 +1,83 @@
package com.ecep.contract.controller.permission;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.AbstEntityController;
import com.ecep.contract.service.FunctionService;
import com.ecep.contract.service.PermissionService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vm.FunctionViewModel;
import com.ecep.contract.vo.FunctionVo;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
@Lazy
@Scope("prototype")
@Component
@FxmlPath("/ui/employee/function.fxml")
public class EmployeeFunctionWindowController extends AbstEntityController<FunctionVo, FunctionViewModel> {
private static final Logger logger = LoggerFactory.getLogger(EmployeeFunctionWindowController.class);
public static void show(FunctionViewModel viewModel, Window window) {
show(EmployeeFunctionWindowController.class, viewModel, window);
}
public BorderPane root;
public TabPane tabPane;
/*
* 基本信息标签页
*/
public Tab baseInfoTab;
public TextField nameField;
public TextField keyField;
public TextField controllerField;
public TextField iconField;
public TextField descriptionField;
@FXML
public CheckBox activeField;
public Label versionLabel;
/*
* 权限标签页
*/
public Tab permissionTab;
@Autowired
PermissionService permissionService;
@Autowired
FunctionService functionService;
@Override
public void onShown(WindowEvent windowEvent) {
super.onShown(windowEvent);
getTitle().bind(viewModel.getName().map(name -> "[" + viewModel.getId().get() + "] " + name + " 功能详情"));
}
@Override
protected void registerTabSkins() {
registerTabSkin(baseInfoTab, tab -> new EmployeeFunctionTabSkinBase(this));
}
@Override
public FunctionService getViewModelService() {
return functionService;
}
}

View File

@@ -61,8 +61,6 @@ public class EmployeeFunctionsManagerWindowController
public void onCreateNewAction(ActionEvent event) { public void onCreateNewAction(ActionEvent event) {
} }
public void onReBuildFilesAction(ActionEvent event) {
}
@Override @Override
public FunctionService getViewModelService() { public FunctionService getViewModelService() {

View File

@@ -54,9 +54,6 @@ public class EmployeeRoleManagerWindowController
public void onCreateNewAction(ActionEvent event) { public void onCreateNewAction(ActionEvent event) {
} }
public void onReBuildFilesAction(ActionEvent event) {
}
@Override @Override
public EmployeeRoleService getViewModelService() { public EmployeeRoleService getViewModelService() {
return employeeRoleService; return employeeRoleService;

View File

@@ -50,7 +50,7 @@ public class EmployeeRoleTabSkinFunctions extends AbstEmployeeRoleBasedTabSkin {
} }
private void loadSelectedRoles() { private void loadSelectedRoles() {
List<FunctionVo> selectedRoles = getRoleService().getFunctionsByRoleId(viewModel.getId().get()); List<FunctionVo> selectedRoles = getRoleService().getFunctionsByRole(getEntity());
if (selectedRoles != null) { if (selectedRoles != null) {
functionsField.getTargetItems().setAll(selectedRoles); functionsField.getTargetItems().setAll(selectedRoles);
} }
@@ -100,8 +100,7 @@ public class EmployeeRoleTabSkinFunctions extends AbstEmployeeRoleBasedTabSkin {
private void saveRoles(ActionEvent event) { private void saveRoles(ActionEvent event) {
EmployeeRoleVo entity = getEntity(); EmployeeRoleVo entity = getEntity();
entity.setFunctions(functionsField.getTargetItems()); getRoleService().saveRoleFunctions(entity, functionsField.getTargetItems());
save(entity);
loadSelectedRoles(); loadSelectedRoles();
} }
} }

View File

@@ -40,15 +40,8 @@ import lombok.Setter;
* 基础信息 * 基础信息
*/ */
public class ProjectTabSkinBase extends AbstProjectBasedTabSkin implements TabSkin { public class ProjectTabSkinBase extends AbstProjectBasedTabSkin implements TabSkin {
@Setter
EmployeeStringConverter employeeStringConverter;
@Setter @Setter
LocalDateStringConverter localDateStringConverter; LocalDateStringConverter localDateStringConverter;
@Setter
EmployeeService employeeService;
@Setter
private DeliverySignMethodService deliverySignMethodService;
public ProjectTabSkinBase(ProjectWindowController controller) { public ProjectTabSkinBase(ProjectWindowController controller) {
super(controller); super(controller);
@@ -59,10 +52,6 @@ public class ProjectTabSkinBase extends AbstProjectBasedTabSkin implements TabSk
return controller.baseInfoTab; return controller.baseInfoTab;
} }
private EmployeeService getEmployeeService() {
return getBean(EmployeeService.class);
}
private ProjectTypeService getProjectTypeService() { private ProjectTypeService getProjectTypeService() {
return getBean(ProjectTypeService.class); return getBean(ProjectTypeService.class);
} }
@@ -103,7 +92,6 @@ public class ProjectTabSkinBase extends AbstProjectBasedTabSkin implements TabSk
controller.standardPayWayField.selectedProperty().bindBidirectional(viewModel.getStandardPayWay()); controller.standardPayWayField.selectedProperty().bindBidirectional(viewModel.getStandardPayWay());
Bindings.bindBidirectional(controller.amountField.textProperty(), viewModel.getAmount(), Bindings.bindBidirectional(controller.amountField.textProperty(), viewModel.getAmount(),
new NumberStringConverter()); new NumberStringConverter());
ComboBoxUtils.initialComboBox(controller.saleTypeField, viewModel.getSaleType(), getSaleTypeService(), true); ComboBoxUtils.initialComboBox(controller.saleTypeField, viewModel.getSaleType(), getSaleTypeService(), true);
ComboBoxUtils.initialComboBox(controller.projectTypeField, viewModel.getProjectType(), getProjectTypeService(), ComboBoxUtils.initialComboBox(controller.projectTypeField, viewModel.getProjectType(), getProjectTypeService(),
true); true);
@@ -119,6 +107,9 @@ public class ProjectTabSkinBase extends AbstProjectBasedTabSkin implements TabSk
controller.saleTypeField.valueProperty().addListener((observable, oldValue, newValue) -> { controller.saleTypeField.valueProperty().addListener((observable, oldValue, newValue) -> {
Predicate<DeliverySignMethodVo> predicate = p -> { Predicate<DeliverySignMethodVo> predicate = p -> {
if (newValue == null) {
return true;
}
return p == null || Objects.equals(p.getSaleTypeId(), newValue.getId()); return p == null || Objects.equals(p.getSaleTypeId(), newValue.getId());
}; };

View File

@@ -1,14 +1,5 @@
package com.ecep.contract.controller.project; package com.ecep.contract.controller.project;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ecep.contract.ContractPayWay; import com.ecep.contract.ContractPayWay;
import com.ecep.contract.controller.tab.TabSkin; import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.table.EditableEntityTableTabSkin; import com.ecep.contract.controller.table.EditableEntityTableTabSkin;
@@ -23,12 +14,18 @@ import com.ecep.contract.vo.ContractPayPlanVo;
import com.ecep.contract.vo.ContractVo; import com.ecep.contract.vo.ContractVo;
import com.ecep.contract.vo.ProjectFundPlanVo; import com.ecep.contract.vo.ProjectFundPlanVo;
import com.ecep.contract.vo.ProjectVo; import com.ecep.contract.vo.ProjectVo;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
import javafx.scene.control.TableColumn; import javafx.scene.control.TableColumn;
import lombok.Setter; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/** /**
* 项目资金计划 * 项目资金计划
@@ -51,19 +48,13 @@ public class ProjectTabSkinFundPlan
public TableColumn<ProjectFundPlanViewModel, String> payWayColumn; public TableColumn<ProjectFundPlanViewModel, String> payWayColumn;
public Button updatePlanBtn; public Button updatePlanBtn;
@Setter
private ProjectFundPlanService projectFundPlanService;
public ProjectTabSkinFundPlan(ProjectWindowController controller) { public ProjectTabSkinFundPlan(ProjectWindowController controller) {
super(controller); super(controller);
} }
@Override @Override
protected ProjectFundPlanService getViewModelService() { protected ProjectFundPlanService getViewModelService() {
if (projectFundPlanService == null) { return getProjectFundPlanService();
projectFundPlanService = getBean(ProjectFundPlanService.class);
}
return projectFundPlanService;
} }
public ContractPayPlanService getContractPayPlanService() { public ContractPayPlanService getContractPayPlanService() {
@@ -249,10 +240,7 @@ public class ProjectTabSkinFundPlan
} }
private ProjectFundPlanService getProjectFundPlanService() { private ProjectFundPlanService getProjectFundPlanService() {
if (projectFundPlanService == null) { return getCachedBean(ProjectFundPlanService.class);
projectFundPlanService = getBean(ProjectFundPlanService.class);
}
return projectFundPlanService;
} }
@Override @Override

View File

@@ -149,6 +149,7 @@ public class ProjectWindowController extends AbstEntityController<ProjectVo, Pro
registerTabSkin(costTab, tab -> new ProjectTabSkinCost(this)); registerTabSkin(costTab, tab -> new ProjectTabSkinCost(this));
registerTabSkin(quotationApprovalTab, tab -> new ProjectTabSkinQuotation(this)); registerTabSkin(quotationApprovalTab, tab -> new ProjectTabSkinQuotation(this));
registerTabSkin(bidTab, tab -> new ProjectTabSkinBid(this)); registerTabSkin(bidTab, tab -> new ProjectTabSkinBid(this));
// 资金计划
registerTabSkin(fundPlanTab, tab -> new ProjectTabSkinFundPlan(this)); registerTabSkin(fundPlanTab, tab -> new ProjectTabSkinFundPlan(this));
// registerTabSkin(costItemTab, this::createCostItemTabSkin); // registerTabSkin(costItemTab, this::createCostItemTabSkin);
registerTabSkin(satisfactionTab, tab -> new ProjectTabSkinCustomerSatisfactionSurvey(this)); registerTabSkin(satisfactionTab, tab -> new ProjectTabSkinCustomerSatisfactionSurvey(this));
@@ -162,7 +163,6 @@ public class ProjectWindowController extends AbstEntityController<ProjectVo, Pro
private ProjectTabSkinBase createBaseTabSkin(Tab tab) { private ProjectTabSkinBase createBaseTabSkin(Tab tab) {
ProjectTabSkinBase skin = new ProjectTabSkinBase(this); ProjectTabSkinBase skin = new ProjectTabSkinBase(this);
skin.setLocalDateStringConverter(localDateStringConverter); skin.setLocalDateStringConverter(localDateStringConverter);
skin.setEmployeeService(employeeService);
return skin; return skin;
} }

View File

@@ -88,8 +88,23 @@ public class ProjectCostWindowController
@Override @Override
protected void registerTabSkins() { protected void registerTabSkins() {
registerTabSkin(baseInfoTab, tab -> new ProjectCostTabSkinBase(this)); registerTabSkin(baseInfoTab, tab -> new ProjectCostTabSkinBase(this));
registerTabSkin(itemTab, tab -> new ProjectCostTabSkinItems(this)); registerTabSkin(itemTab, tab -> new ProjectCostTabSkinItems(this));
}
@Override
protected void updateViewModel(ProjectCostVo entity) {
super.updateViewModel(entity);
var tabPane = baseInfoTab.getTabPane();
if (entity.getVersion() > 0) {
if (!tabPane.getTabs().contains(approvalTab)) {
tabPane.getTabs().add(approvalTab);
}
} else {
tabPane.getTabs().remove(approvalTab);
}
} }
@Override @Override

View File

@@ -1,9 +1,5 @@
package com.ecep.contract.controller.project.quotation; package com.ecep.contract.controller.project.quotation;
import java.io.File;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.CompletableFuture;
import com.ecep.contract.DesktopUtils; import com.ecep.contract.DesktopUtils;
import com.ecep.contract.MyDateTimeUtils; import com.ecep.contract.MyDateTimeUtils;
import com.ecep.contract.controller.customer.CompanyCustomerEvaluationFormFileWindowController; import com.ecep.contract.controller.customer.CompanyCustomerEvaluationFormFileWindowController;
@@ -11,21 +7,13 @@ import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
import com.ecep.contract.controller.tab.TabSkin; import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.converter.CompanyStringConverter; import com.ecep.contract.converter.CompanyStringConverter;
import com.ecep.contract.converter.EmployeeStringConverter; import com.ecep.contract.converter.EmployeeStringConverter;
import com.ecep.contract.model.Employee; import com.ecep.contract.service.*;
import com.ecep.contract.service.CompanyCustomerEvaluationFormFileService;
import com.ecep.contract.service.CompanyCustomerFileService;
import com.ecep.contract.service.CustomerService;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.ProjectQuotationService;
import com.ecep.contract.service.ProjectService;
import com.ecep.contract.util.ProxyUtils; import com.ecep.contract.util.ProxyUtils;
import com.ecep.contract.util.UITools; import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.ProjectQuotationViewModel; import com.ecep.contract.vm.ProjectQuotationViewModel;
import com.ecep.contract.vo.CompanyCustomerEvaluationFormFileVo; import com.ecep.contract.vo.CompanyCustomerEvaluationFormFileVo;
import com.ecep.contract.vo.CompanyVo;
import com.ecep.contract.vo.ProjectQuotationVo; import com.ecep.contract.vo.ProjectQuotationVo;
import com.ecep.contract.vo.ProjectVo; import com.ecep.contract.vo.ProjectVo;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
@@ -37,36 +25,22 @@ import javafx.util.converter.LocalDateTimeStringConverter;
import javafx.util.converter.NumberStringConverter; import javafx.util.converter.NumberStringConverter;
import lombok.Setter; import lombok.Setter;
import java.io.File;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.CompletableFuture;
/**
* 项目报价单
*/
public class ProjectQuotationTabSkinBase public class ProjectQuotationTabSkinBase
extends AbstEntityBasedTabSkin<ProjectQuotationWindowController, ProjectQuotationVo, ProjectQuotationViewModel> extends AbstEntityBasedTabSkin<ProjectQuotationWindowController, ProjectQuotationVo, ProjectQuotationViewModel>
implements TabSkin { implements TabSkin {
@Setter @Setter
private LocalDateTimeStringConverter localDateTimeStringConverter; private LocalDateTimeStringConverter localDateTimeStringConverter;
@Setter
private ProjectQuotationService projectQuotationService;
@Setter
private LocalDateStringConverter localDateStringConverter;
@Setter
private EmployeeStringConverter employeeStringConverter;
@Setter
private CompanyStringConverter companyStringConverter;
@Setter
private CompanyService companyService;
@Setter
private CustomerService customerService;
@Setter
private CompanyCustomerFileService customerFileService;
@Setter
private CompanyCustomerEvaluationFormFileService evaluationFormFileService;
@Setter
private ProjectService projectService;
private ProjectService getProjectService() { private ProjectService getProjectService() {
if (projectService == null) { return getCachedBean(ProjectService.class);
projectService = getBean(ProjectService.class);
}
return projectService;
} }
public ProjectQuotationTabSkinBase(ProjectQuotationWindowController controller) { public ProjectQuotationTabSkinBase(ProjectQuotationWindowController controller) {
@@ -133,7 +107,10 @@ public class ProjectQuotationTabSkinBase
new NumberStringConverter(getLocale())); new NumberStringConverter(getLocale()));
UITools.autoCompletion(controller.evaluationFileField, viewModel.getEvaluationFile(), UITools.autoCompletion(controller.evaluationFileField, viewModel.getEvaluationFile(),
getEvaluationFormFileService()); getEvaluationFormFileService(), getEvaluationFormFileService().getStringConverter(), searchText -> {
var project = getProjectService().findById(getEntity().getProject());
return getEvaluationFormFileService().searchByCompany(project.getCustomerId(), searchText);
});
controller.authorizationFileField.textProperty().bind(viewModel.getAuthorizationFile().map(File::getName)); controller.authorizationFileField.textProperty().bind(viewModel.getAuthorizationFile().map(File::getName));
@@ -220,7 +197,7 @@ public class ProjectQuotationTabSkinBase
} }
private void evaluationFileAutoCompletion(TextField textField, private void evaluationFileAutoCompletion(TextField textField,
SimpleObjectProperty<CompanyCustomerEvaluationFormFileVo> property) { SimpleObjectProperty<CompanyCustomerEvaluationFormFileVo> property) {
// 直接使用Vo类不再需要手动转换 // 直接使用Vo类不再需要手动转换
} }

View File

@@ -7,9 +7,11 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import com.ecep.contract.controller.BaseController; import com.ecep.contract.controller.BaseController;
import com.ecep.contract.service.EmployeeService;
import com.ecep.contract.util.BeanContext;
import com.ecep.contract.util.UITools; import com.ecep.contract.util.UITools;
public abstract class AbstGenericTabSkin<C extends BaseController> implements TabSkin { public abstract class AbstGenericTabSkin<C extends BaseController> implements TabSkin, BeanContext {
private static final Logger logger = LoggerFactory.getLogger(AbstGenericTabSkin.class); private static final Logger logger = LoggerFactory.getLogger(AbstGenericTabSkin.class);
/** /**
* 控制器 * 控制器
@@ -49,4 +51,7 @@ public abstract class AbstGenericTabSkin<C extends BaseController> implements Ta
UITools.showExceptionAndWait(message, ex); UITools.showExceptionAndWait(message, ex);
} }
public EmployeeService getEmployeeService() {
return getCachedBean(EmployeeService.class);
}
} }

View File

@@ -62,17 +62,13 @@ public class CompanyTabSkinBankAccount
bankAccountSearchBtn.setOnAction(this::onTableRefreshAction); bankAccountSearchBtn.setOnAction(this::onTableRefreshAction);
bankAccountTable_idColumn.setCellValueFactory(param -> param.getValue().getId()); bankAccountTable_idColumn.setCellValueFactory(param -> param.getValue().getId());
bankAccountTable_bankColumn.setCellValueFactory(param -> param.getValue().getBankId()); bankAccountTable_bankColumn.setCellValueFactory(param -> param.getValue().getBankId());
bankAccountTable_bankColumn.setCellFactory(param -> new BankTableCell<>(getBankService())); bankAccountTable_bankColumn.setCellFactory(BankTableCell.forTableColumn(getBankService()));
bankAccountTable_openingBankColumn.setCellValueFactory(param -> param.getValue().getOpeningBank()); bankAccountTable_openingBankColumn.setCellValueFactory(param -> param.getValue().getOpeningBank());
bankAccountTable_accountColumn.setCellValueFactory(param -> param.getValue().getAccount()); bankAccountTable_accountColumn.setCellValueFactory(param -> param.getValue().getAccount());
bankAccountTable_menu_refresh.setOnAction(this::onTableRefreshAction);
bankAccountTable_menu_add.setOnAction(this::onTableAddAction);
bankAccountTable_menu_del.setOnAction(this::onTableDeleteAction);
super.initializeTab(); super.initializeTab();
} }

View File

@@ -164,18 +164,18 @@ public class CompanyTabSkinContract
contractTable_startDateColumn.setCellValueFactory(param -> param.getValue().getStartDate()); contractTable_startDateColumn.setCellValueFactory(param -> param.getValue().getStartDate());
contractTable_endDateColumn.setCellValueFactory(param -> param.getValue().getEndDate()); contractTable_endDateColumn.setCellValueFactory(param -> param.getValue().getEndDate());
contractTable_setupPersonColumn.setCellValueFactory(param -> param.getValue().getSetupPerson()); contractTable_setupPersonColumn.setCellValueFactory(param -> param.getValue().getSetupPerson());
contractTable_setupPersonColumn.setCellFactory(param -> new EmployeeTableCell<>(getEmployeeService())); contractTable_setupPersonColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
contractTable_setupDateColumn.setCellValueFactory(param -> param.getValue().getSetupDate()); contractTable_setupDateColumn.setCellValueFactory(param -> param.getValue().getSetupDate());
// contractTable_setupDateColumn.setSortable(true); // contractTable_setupDateColumn.setSortable(true);
// contractTable_setupDateColumn.setSortType(TableColumn.SortType.DESCENDING); // contractTable_setupDateColumn.setSortType(TableColumn.SortType.DESCENDING);
contractTable_inurePersonColumn.setCellValueFactory(param -> param.getValue().getInurePerson()); contractTable_inurePersonColumn.setCellValueFactory(param -> param.getValue().getInurePerson());
contractTable_inurePersonColumn.setCellFactory(param -> new EmployeeTableCell<>(getEmployeeService())); contractTable_inurePersonColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
contractTable_inureDateColumn.setCellValueFactory(param -> param.getValue().getInureDate()); contractTable_inureDateColumn.setCellValueFactory(param -> param.getValue().getInureDate());
contractTable_varyPersonColumn.setCellValueFactory(param -> param.getValue().getVaryPerson()); contractTable_varyPersonColumn.setCellValueFactory(param -> param.getValue().getVaryPerson());
contractTable_varyPersonColumn.setCellFactory(param -> new EmployeeTableCell<>(getEmployeeService())); contractTable_varyPersonColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
contractTable_varyDateColumn.setCellValueFactory(param -> param.getValue().getVaryDate()); contractTable_varyDateColumn.setCellValueFactory(param -> param.getValue().getVaryDate());
contractTable_createdColumn.setCellValueFactory(param -> param.getValue().getCreated()); contractTable_createdColumn.setCellValueFactory(param -> param.getValue().getCreated());

View File

@@ -6,8 +6,12 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
import java.util.logging.Level;
import com.ecep.contract.task.CompanyMergeClientTasker;
import com.ecep.contract.util.ParamUtils; import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vo.CompanyOldNameVo; import com.ecep.contract.vo.CompanyOldNameVo;
import com.ecep.contract.vo.CompanyVo; import com.ecep.contract.vo.CompanyVo;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@@ -138,53 +142,20 @@ public class CompanyTabSkinOldName
} }
private void onTableMergeAction(ActionEvent event) { private void onTableMergeAction(ActionEvent event) {
CompanyVo updater = getParent(); // 收集所有曾用名
HashSet<String> nameSet = new HashSet<>(); List<String> nameList = dataSet.stream()
nameSet.add(updater.getName()); .map(viewModel -> viewModel.getName().get())
.filter(Objects::nonNull)
.toList();
List<CompanyOldNameViewModel> removes = new ArrayList<>(); // 创建独立的WebSocket客户端任务器
for (CompanyOldNameViewModel viewModel : dataSet) { CompanyMergeClientTasker task = new CompanyMergeClientTasker();
if (!nameSet.add(viewModel.getName().get())) { task.setCompany(getParent());
// fixed 曾用名中有重复时,删除重复的 task.setNameList(nameList);
deleteRow(viewModel); UITools.showTaskDialogAndWait("合并曾用名", task, null);
removes.add(viewModel);
}
}
if (!removes.isEmpty()) {
Platform.runLater(() -> {
dataSet.removeAll(removes);
});
setStatus("移除重复的曾用名" + removes.size());
}
int size = nameSet.size(); loadTableDataSet();
int count = 1;
int merge = 0;
for (String name : nameSet) {
controller.setRightStatus(count + "/" + size);
if (StringUtils.hasText(name)) {
List<CompanyVo> list = getParentService().findAllByName(name);
for (CompanyVo company : list) {
// fixed 曾用名中有可能有 updater 的名字,会导致自己删除自己
if (Objects.equals(company.getId(), updater.getId())) {
continue;
}
try {
getCompanyService().merge(company, updater);
setStatus("并户 " + company.getName() + "[" + company.getId() + "] 到当前公司");
merge++;
} catch (Exception e) {
throw new RuntimeException("合并 " + company.getName() + " -> " + updater.getName() + " 失败", e);
}
}
}
count++;
}
if (merge == 0) {
setStatus("没有需要并户的公司");
}
controller.setRightStatus("");
} }
@Override @Override
@@ -201,7 +172,7 @@ public class CompanyTabSkinOldName
return; return;
} }
String path = viewModel.getPath().get(); String path = viewModel.getPath().get();
if (StringUtils.hasText(path)) { if (org.springframework.util.StringUtils.hasText(path)) {
if (item.startsWith(path)) { if (item.startsWith(path)) {
item = "~" + item.substring(path.length()); item = "~" + item.substring(path.length());
} }
@@ -216,4 +187,4 @@ public class CompanyTabSkinOldName
} }
return companyOldNameService; return companyOldNameService;
} }
} }

View File

@@ -97,8 +97,10 @@ public class CompanyTabSkinOther
// Yu // // Yu //
public TitledPane yuCloudPane; public TitledPane yuCloudPane;
public TextField cloudYuIdField; public TextField cloudYuIdField;
public TextField cloudYuCloudIdField;
public TextField cloudYuLatestField; public TextField cloudYuLatestField;
public TextField cloudYuVendorUpdateDateField;
public TextField cloudYuCustomerUpdateDateField;
public CheckBox cloudYuActiveField;
public Label cloudYuVersionLabel; public Label cloudYuVersionLabel;
public Button yuCloudPaneSaveButton; public Button yuCloudPaneSaveButton;
@@ -417,8 +419,10 @@ public class CompanyTabSkinOther
} }
cloudYuIdField.textProperty().bind(yuCloudInfoViewModel.getId().asString()); cloudYuIdField.textProperty().bind(yuCloudInfoViewModel.getId().asString());
cloudYuCloudIdField.textProperty().bindBidirectional(yuCloudInfoViewModel.getCloudId());
cloudYuLatestField.textProperty().bind(yuCloudInfoViewModel.getLatestUpdate().map(MyDateTimeUtils::format)); cloudYuLatestField.textProperty().bind(yuCloudInfoViewModel.getLatestUpdate().map(MyDateTimeUtils::format));
cloudYuVendorUpdateDateField.textProperty().bind(yuCloudInfoViewModel.getVendorUpdateDate().map(MyDateTimeUtils::format));
cloudYuCustomerUpdateDateField.textProperty().bind(yuCloudInfoViewModel.getCustomerUpdateDate().map(MyDateTimeUtils::format));
cloudYuActiveField.selectedProperty().bindBidirectional(yuCloudInfoViewModel.getActive());
cloudYuVersionLabel.textProperty().bind(yuCloudInfoViewModel.getVersion().asString("Ver:%s")); cloudYuVersionLabel.textProperty().bind(yuCloudInfoViewModel.getVersion().asString("Ver:%s"));
Button button = yuCloudPaneSaveButton; Button button = yuCloudPaneSaveButton;
@@ -436,6 +440,34 @@ public class CompanyTabSkinOther
button.setDisable(false); button.setDisable(false);
}); });
}); });
DelayOnceExecutor saveExecutor = new DelayOnceExecutor(() -> {
save(yuCloudInfoViewModel);
cloudYuActiveField.setBorder(null);
}, 2, TimeUnit.SECONDS).exception(e -> logger.error(e.getMessage(), e));
cloudYuActiveField.setOnMouseClicked(event -> {
cloudYuActiveField.setBorder(Border.stroke(Color.RED));
saveExecutor.tick();
});
}
public void save(CloudYuInfoViewModel viewModel) {
int infoId = viewModel.getId().get();
YongYouU8Service service = getYongYouU8Service();
CloudYuVo cloudYu = service.findById(infoId);
if (cloudYu == null) {
throw new RuntimeException("CloudTyc not found");
}
if (viewModel.copyTo(cloudYu)) {
CloudYuVo saved = service.save(cloudYu);
if (Platform.isFxApplicationThread()) {
viewModel.update(saved);
} else {
Platform.runLater(() -> {
viewModel.update(saved);
});
}
}
} }
private void onCloudYuUpdateButtonClicked(ActionEvent event) { private void onCloudYuUpdateButtonClicked(ActionEvent event) {
@@ -516,9 +548,11 @@ public class CompanyTabSkinOther
CompanyExtendInfoViewModel viewModel = extendInfoViewModel; CompanyExtendInfoViewModel viewModel = extendInfoViewModel;
CompanyExtendInfoService service = getExtendInfoService(); CompanyExtendInfoService service = getExtendInfoService();
CompanyExtendInfoVo extendInfo = service.findByCompany(company); CompanyExtendInfoVo extendInfo = service.findByCompany(company);
Platform.runLater(() -> { if (extendInfo != null) {
viewModel.update(extendInfo); Platform.runLater(() -> {
}); viewModel.update(extendInfo);
});
}
} }
CloudRkService getCloudRkService() { CloudRkService getCloudRkService() {

View File

@@ -1,23 +1,65 @@
package com.ecep.contract.controller.tab; package com.ecep.contract.controller.tab;
import com.ecep.contract.MessageHolder; import com.ecep.contract.MessageHolder;
import com.ecep.contract.SpringApp;
import com.ecep.contract.task.Tasker; import com.ecep.contract.task.Tasker;
import com.ecep.contract.WebSocketClientService;
import com.ecep.contract.WebSocketClientTasker;
import com.ecep.contract.vo.ContractVo; import com.ecep.contract.vo.ContractVo;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import lombok.Getter;
import lombok.Setter; import lombok.Setter;
public class ContractFilesRebuildTasker extends Tasker<Object> { import java.util.Locale;
@Slf4j
public class ContractFilesRebuildTasker extends Tasker<Object> implements WebSocketClientTasker {
@Setter @Setter
private ContractVo contract; private ContractVo contract;
@Getter
@Setter
private boolean repaired = false;
@Override @Override
public Object execute(MessageHolder holder) { public String getTaskName() {
return null; return "ContractFilesRebuildTasker";
} }
public boolean isRepaired() { @Override
// TODO Auto-generated method stub public void updateProgress(long workDone, long max) {
throw new UnsupportedOperationException("Unimplemented method 'isRepaired'"); super.updateProgress(workDone, max);
}
@Override
public Object execute(MessageHolder holder) {
log.info("开始执行合同文件重建任务合同ID: {}", contract != null ? contract.getId() : "unknown");
if (contract == null) {
String errorMsg = "合同信息为空,无法执行文件重建任务";
holder.error(errorMsg);
throw new RuntimeException(errorMsg);
}
try {
holder.info("开始重建合同文件,合同编号: " + contract.getCode());
updateProgress(0, 100);
// 使用WebSocket调用远程任务
Object result = callRemoteTask(holder, Locale.getDefault(), contract.getId());
updateProgress(100, 100);
holder.info("合同文件重建任务已提交到服务器");
return result;
} catch (Exception e) {
log.error("合同文件重建任务执行失败", e);
holder.error("任务执行失败: " + e.getMessage());
throw new RuntimeException("合同文件重建任务执行失败", e);
}
} }
} }

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,84 @@
package com.ecep.contract.controller.tab;
import com.ecep.contract.controller.contract.ContractItemWindowController;
import com.ecep.contract.service.InventoryService;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.ContractItemViewModel;
import com.ecep.contract.vo.ContractItemVo;
import javafx.scene.control.Tab;
import javafx.util.converter.LocalDateTimeStringConverter;
/**
* 合同项目基础信息标签页
*/
public class ContractItemTabSkinBase
extends AbstEntityBasedTabSkin<ContractItemWindowController, ContractItemVo, ContractItemViewModel> {
public ContractItemTabSkinBase(ContractItemWindowController controller) {
super(controller);
}
@Override
public Tab getTab() {
return controller.baseInfoTab;
}
@Override
public void initializeTab() {
// 数据绑定
controller.idLabel.textProperty().bind(viewModel.getId().asString());
controller.codeField.textProperty().bindBidirectional(viewModel.getCode());
controller.titleField.textProperty().bindBidirectional(viewModel.getTitle());
controller.specificationField.textProperty().bindBidirectional(viewModel.getSpecification());
controller.unitField.textProperty().bindBidirectional(viewModel.getUnit());
// 财务信息绑定
controller.exclusiveTaxPriceField.textProperty().bindBidirectional(viewModel.getExclusiveTaxPrice().asObject(),
new javafx.util.converter.DoubleStringConverter());
controller.taxRateField.textProperty().bindBidirectional(viewModel.getTaxRate().asObject(),
new javafx.util.converter.DoubleStringConverter());
controller.taxPriceField.textProperty().bindBidirectional(viewModel.getTaxPrice().asObject(),
new javafx.util.converter.DoubleStringConverter());
controller.quantityField.textProperty().bindBidirectional(viewModel.getQuantity().asObject(),
new javafx.util.converter.DoubleStringConverter());
controller.taxAmountField.textProperty().bindBidirectional(viewModel.getTaxAmount().asObject(),
new javafx.util.converter.DoubleStringConverter());
controller.exclusiveTaxAmountField.textProperty().bindBidirectional(
viewModel.getExclusiveTaxAmount().asObject(),
new javafx.util.converter.DoubleStringConverter());
// 日期绑定
controller.startDateField.valueProperty().bindBidirectional(viewModel.getStartDate());
controller.endDateField.valueProperty().bindBidirectional(viewModel.getEndDate());
// 其他信息绑定
controller.refIdField.textProperty().bindBidirectional(viewModel.getRefId().asObject(),
new javafx.util.converter.IntegerStringConverter());
controller.remarkField.textProperty().bindBidirectional(viewModel.getRemark());
// 设置只读字段
LocalDateTimeStringConverter localDateTimeStringConverter = controller.getCurrentEmployee()
.getLocalDateTimeStringConverter();
controller.createDateLabel.textProperty().bindBidirectional(viewModel.getCreateDate(),
localDateTimeStringConverter);
controller.updateDateLabel.textProperty().bindBidirectional(viewModel.getUpdateDate(),
localDateTimeStringConverter);
UITools.autoCompletion(controller.updaterLabel, viewModel.getCreator(), controller.getEmployeeService());
UITools.autoCompletion(controller.updaterLabel, viewModel.getUpdater(), controller.getEmployeeService());
// 设置存货显示信息
UITools.autoCompletion(controller.inventoryField, viewModel.getInventory(),
getCachedBean(InventoryService.class));
}
@Override
protected ContractItemVo save(ContractItemVo entity) {
// 保存前自动计算金额
viewModel.calculateAmounts();
return super.save(entity);
}
}

View File

@@ -85,7 +85,9 @@ public class ContractTabSkinBase extends AbstContractBasedTabSkin {
controller.payWayField.textProperty().bind(viewModel.getPayWay().map(ContractPayWay::getText)); controller.payWayField.textProperty().bind(viewModel.getPayWay().map(ContractPayWay::getText));
controller.nameField.textProperty().bind(viewModel.getName()); controller.nameField.textProperty().bindBidirectional(viewModel.getName());
controller.nameField.editableProperty().bind(viewModel.getNameLocked());
controller.contractNameLockedCk.selectedProperty().bindBidirectional(viewModel.getNameLocked());
controller.codeField.textProperty().bind(viewModel.getCode()); controller.codeField.textProperty().bind(viewModel.getCode());
controller.parentCodeField.textProperty().bindBidirectional(viewModel.getParentCode()); controller.parentCodeField.textProperty().bindBidirectional(viewModel.getParentCode());
@@ -351,17 +353,20 @@ public class ContractTabSkinBase extends AbstContractBasedTabSkin {
} }
if (initialDirectory == null) { if (initialDirectory == null) {
if (entity.getPayWay() == ContractPayWay.RECEIVE) { if (entity.getPayWay() == ContractPayWay.RECEIVE) {
// 根据项目设置初始目录 Integer projectId = entity.getProject();
ProjectVo project = getProjectService().findById(entity.getProject()); if (projectId != null) {
if (project != null) { // 根据项目设置初始目录
// 根据项目销售方式设置初始目录 ProjectVo project = getProjectService().findById(projectId);
ProjectSaleTypeVo saleType = getSaleTypeService().findById(project.getSaleTypeId()); if (project != null) {
if (saleType != null) { // 根据项目销售方式设置初始目录
File dir = new File(saleType.getPath()); ProjectSaleTypeVo saleType = getSaleTypeService().findById(project.getSaleTypeId());
if (saleType.isStoreByYear()) { if (saleType != null) {
dir = new File(dir, "20" + project.getCodeYear()); File dir = new File(saleType.getPath());
if (saleType.isStoreByYear()) {
dir = new File(dir, "20" + project.getCodeYear());
}
initialDirectory = dir;
} }
initialDirectory = dir;
} }
} }
} else if (entity.getPayWay() == ContractPayWay.PAY) { } else if (entity.getPayWay() == ContractPayWay.PAY) {

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

@@ -3,7 +3,10 @@ package com.ecep.contract.controller.tab;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.HashMap;
import javax.naming.Binding;
import org.springframework.data.domain.Pageable;
import com.ecep.contract.ContractPayWay; import com.ecep.contract.ContractPayWay;
import com.ecep.contract.SpringApp; import com.ecep.contract.SpringApp;
@@ -20,22 +23,25 @@ import com.ecep.contract.service.InventoryService;
import com.ecep.contract.service.PurchaseOrderItemService; import com.ecep.contract.service.PurchaseOrderItemService;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.ParamUtils; import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.vm.ContractItemComposeViewModel;
import com.ecep.contract.vm.ContractItemViewModel; import com.ecep.contract.vm.ContractItemViewModel;
import com.ecep.contract.vm.InventoryViewModel; import com.ecep.contract.vm.InventoryViewModel;
import com.ecep.contract.vo.ContractItemVo; import com.ecep.contract.vo.ContractItemVo;
import com.ecep.contract.vo.InventoryVo; import com.ecep.contract.vo.InventoryVo;
import com.ecep.contract.vo.PurchaseOrderItemVo; import com.ecep.contract.vo.PurchaseOrderItemVo;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.scene.control.*; import javafx.beans.binding.Bindings;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.cell.TextFieldTableCell; import javafx.scene.control.cell.TextFieldTableCell;
import javafx.util.Callback; import javafx.util.Callback;
import javafx.util.StringConverter; import javafx.util.StringConverter;
import javafx.util.converter.CurrencyStringConverter; import javafx.util.converter.CurrencyStringConverter;
import javafx.util.converter.NumberStringConverter; import javafx.util.converter.NumberStringConverter;
import lombok.Setter; import lombok.Setter;
import org.springframework.data.domain.Pageable;
@FxmlPath("/ui/contract/contract-tab-item-v2.fxml") @FxmlPath("/ui/contract/contract-tab-item-v2.fxml")
public class ContractTabSkinItemsV2 public class ContractTabSkinItemsV2
@@ -193,10 +199,15 @@ public class ContractTabSkinItemsV2
} }
showInOwner(InventoryWindowController.class, InventoryViewModel.from(inventory)); showInOwner(InventoryWindowController.class, InventoryViewModel.from(inventory));
}); });
showInventory.visibleProperty().bind(
Bindings.select(getTableView().getSelectionModel().selectedItemProperty(), "inventory", "value").isNotNull());
contextMenu.getItems().addAll(showInventory);
} }
public static class QuantityTableCell extends AsyncUpdateTableCell<ContractItemViewModel, Number, PurchaseOrderItemVo> { public static class QuantityTableCell
public static Callback<TableColumn<ContractItemViewModel, Number>, TableCell<ContractItemViewModel, Number>> forTableColumn(NumberStringConverter stringConverter) { extends AsyncUpdateTableCell<ContractItemViewModel, Number, PurchaseOrderItemVo> {
public static Callback<TableColumn<ContractItemViewModel, Number>, TableCell<ContractItemViewModel, Number>> forTableColumn(
NumberStringConverter stringConverter) {
; ;
return param -> new QuantityTableCell(stringConverter); return param -> new QuantityTableCell(stringConverter);
} }
@@ -212,7 +223,10 @@ public class ContractTabSkinItemsV2
@Override @Override
protected void asyncLoadAndUpdate() { protected void asyncLoadAndUpdate() {
double sum = getService().findAll(ParamUtils.equal("contractItem", this.getTableRow().getItem().getId().get()), Pageable.unpaged()).stream().mapToDouble(PurchaseOrderItemVo::getQuantity).sum(); double sum = getService()
.findAll(ParamUtils.equal("contractItem", this.getTableRow().getItem().getId().get()),
Pageable.unpaged())
.stream().mapToDouble(PurchaseOrderItemVo::getQuantity).sum();
Platform.runLater(() -> setText(sum + "/" + getItem())); Platform.runLater(() -> setText(sum + "/" + getItem()));
} }
} }

View File

@@ -1,13 +1,9 @@
package com.ecep.contract.controller.tab; package com.ecep.contract.controller.tab;
import java.time.LocalDateTime;
import com.ecep.contract.MyDateTimeUtils; import com.ecep.contract.MyDateTimeUtils;
import com.ecep.contract.SpringApp;
import com.ecep.contract.controller.contract.sale_order.SalesOrderWindowController; import com.ecep.contract.controller.contract.sale_order.SalesOrderWindowController;
import com.ecep.contract.converter.EmployeeStringConverter;
import com.ecep.contract.service.CompanyService; import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.ContractFileService; import com.ecep.contract.service.ContractService;
import com.ecep.contract.util.UITools; import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.SalesOrderViewModel; import com.ecep.contract.vm.SalesOrderViewModel;
import com.ecep.contract.vo.SalesOrderVo; import com.ecep.contract.vo.SalesOrderVo;
@@ -15,18 +11,12 @@ import com.ecep.contract.vo.SalesOrderVo;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import lombok.Setter; import javafx.util.converter.NumberStringConverter;
public class SalesOrderTabSkinBase public class SalesOrderTabSkinBase
extends AbstEntityBasedTabSkin<SalesOrderWindowController, SalesOrderVo, SalesOrderViewModel> extends AbstEntityBasedTabSkin<SalesOrderWindowController, SalesOrderVo, SalesOrderViewModel>
implements TabSkin { implements TabSkin {
@Setter
private ContractFileService companyContactService;
@Setter
private CompanyService companyService;
private EmployeeStringConverter employeeStringConverter;
public SalesOrderTabSkinBase(SalesOrderWindowController controller) { public SalesOrderTabSkinBase(SalesOrderWindowController controller) {
super(controller); super(controller);
} }
@@ -39,13 +29,26 @@ public class SalesOrderTabSkinBase
@Override @Override
public void initializeTab() { public void initializeTab() {
controller.codeField.textProperty().bind(viewModel.getCode()); controller.codeField.textProperty().bind(viewModel.getCode());
controller.refIdField.textProperty().bindBidirectional(viewModel.getRefId(), new NumberStringConverter(getLocale()));
controller.taxRateField.textProperty().bindBidirectional(viewModel.getTaxRate(), new NumberStringConverter(getLocale()));
initializeEmployeeField(controller.employeeField, viewModel.getEmployee()); initializeEmployeeField(controller.employeeField, viewModel.getEmployee());
initializeEmployeeField(controller.makerField, viewModel.getMaker()); initializeEmployeeField(controller.makerField, viewModel.getMaker());
initializeEmployeeField(controller.verifierField, viewModel.getVerifier()); initializeEmployeeField(controller.verifierField, viewModel.getVerifier());
initializeEmployeeField(controller.modifierField, viewModel.getModifier());
initializeEmployeeField(controller.closerField, viewModel.getCloser());
initializeCompanyField(controller.customerField, viewModel.getCustomer());
controller.customerAddressField.textProperty().bindBidirectional(viewModel.getCustomerAddress());
initializeContractField(controller.contractField, viewModel.getContract());
controller.makeDateField.textProperty().bind(viewModel.getMakerDate().map(MyDateTimeUtils::format)); controller.makeDateField.textProperty().bind(viewModel.getMakerDate().map(MyDateTimeUtils::format));
controller.verifierDateField.textProperty().bind(viewModel.getVerifierDate().map(MyDateTimeUtils::format)); controller.verifierDateField.textProperty().bind(viewModel.getVerifierDate().map(MyDateTimeUtils::format));
controller.modifierDateField.textProperty().bind(viewModel.getModifierDate().map(MyDateTimeUtils::format));
controller.closerDateField.textProperty().bind(viewModel.getCloserDate().map(MyDateTimeUtils::format));
controller.descriptionField.textProperty().bindBidirectional(viewModel.getDescription()); controller.descriptionField.textProperty().bindBidirectional(viewModel.getDescription());
@@ -53,29 +56,23 @@ public class SalesOrderTabSkinBase
controller.saveBtn.setOnAction(this::onSaveAction); controller.saveBtn.setOnAction(this::onSaveAction);
} }
protected String localDateTimeFormatter(SimpleObjectProperty<LocalDateTime> property) {
LocalDateTime dateTime = property.get();
if (dateTime == null) {
return "";
}
return MyDateTimeUtils.format(dateTime);
}
private void initializeEmployeeField(TextField textField, SimpleObjectProperty<Integer> property) { private void initializeEmployeeField(TextField textField, SimpleObjectProperty<Integer> property) {
UITools.autoCompletion(textField, property, controller.getEmployeeService()); UITools.autoCompletion(textField, property, controller.getEmployeeService());
} }
public CompanyService getCompanyService() { private void initializeCompanyField(TextField textField, SimpleObjectProperty<Integer> property) {
if (companyService == null) { UITools.autoCompletion(textField, property, getCompanyService());
companyService = SpringApp.getBean(CompanyService.class); }
}
return companyService; private void initializeContractField(TextField textField, SimpleObjectProperty<Integer> property) {
UITools.autoCompletion(textField, property, getContractService());
} }
public EmployeeStringConverter getEmployeeStringConverter() { public CompanyService getCompanyService() {
if (employeeStringConverter == null) { return getCachedBean(CompanyService.class);
employeeStringConverter = SpringApp.getBean(EmployeeStringConverter.class); }
}
return employeeStringConverter; public ContractService getContractService() {
return getCachedBean(ContractService.class);
} }
} }

View File

@@ -1,20 +1,39 @@
package com.ecep.contract.controller.table.cell; package com.ecep.contract.controller.table.cell;
import com.ecep.contract.SpringApp;
import com.ecep.contract.service.BankService; import com.ecep.contract.service.BankService;
import com.ecep.contract.vo.BankVo; import com.ecep.contract.vo.BankVo;
import javafx.util.Callback;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import static com.ecep.contract.SpringApp.getBean;
/**
* 银行单元格
*/
@NoArgsConstructor @NoArgsConstructor
public class BankTableCell<T> extends AsyncUpdateTableCell<T, Integer, BankVo> { public class BankTableCell<T> extends AsyncUpdateTableCell<T, Integer, BankVo> {
/**
* 创建单元格工厂
*
* @param bankService 银行服务
* @return 单元格工厂
*/
public static <V> Callback<javafx.scene.control.TableColumn<V, Integer>, javafx.scene.control.TableCell<V, Integer>> forTableColumn(
BankService bankService) {
return param -> new BankTableCell<V>(bankService);
}
public BankTableCell(BankService service) { public BankTableCell(BankService service) {
setService(service); setService(service);
} }
@Override @Override
protected BankService getServiceBean() { protected BankService getServiceBean() {
return SpringApp.getBean(BankService.class); return getBean(BankService.class);
} }
@Override
public String format(BankVo entity) {
return getService().getStringConverter().toString(entity);
}
} }

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

@@ -4,10 +4,15 @@ import com.ecep.contract.SpringApp;
import com.ecep.contract.service.DepartmentService; import com.ecep.contract.service.DepartmentService;
import com.ecep.contract.vo.DepartmentVo; import com.ecep.contract.vo.DepartmentVo;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.util.Callback;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@NoArgsConstructor @NoArgsConstructor
public class DepartmentTableCell<T> extends AsyncUpdateTableCell<T, Integer, DepartmentVo> { public class DepartmentTableCell<T> extends AsyncUpdateTableCell<T, Integer, DepartmentVo> {
public DepartmentTableCell(DepartmentService service) { public DepartmentTableCell(DepartmentService service) {
setService(service); setService(service);
} }
@@ -17,4 +22,20 @@ public class DepartmentTableCell<T> extends AsyncUpdateTableCell<T, Integer, Dep
return SpringApp.getBean(DepartmentService.class); return SpringApp.getBean(DepartmentService.class);
} }
/**
* 为表格列创建 DepartmentTableCell 的工厂方法
*
* @param service 部门服务
* @param <T> 表格行数据类型
* @return 表格列的回调函数
*/
public static <T> Callback<TableColumn<T, Integer>, TableCell<T, Integer>> forTableColumn(DepartmentService service) {
return column -> new DepartmentTableCell<>(service);
}
@Override
public String format(DepartmentVo entity) {
return getService().getStringConverter().toString(entity);
}
} }

View File

@@ -42,7 +42,6 @@ public class VendorTypeTableCell<T> extends AsyncUpdateTableCell<T, VendorType,
protected VendorTypeLocalVo initialize() { protected VendorTypeLocalVo initialize() {
VendorType item = getItem(); VendorType item = getItem();
VendorTypeLocalVo localVo = getServiceBean().findByType(item); VendorTypeLocalVo localVo = getServiceBean().findByType(item);
System.out.println("item = " + item + ", localVo = " + getServiceBean().getStringConverter().toString(localVo));
return localVo; return localVo;
} }

View File

@@ -213,6 +213,7 @@ public class VendorApprovedListTabSkinVendors
task.setEveryYearMinContracts(qualifiedVendorEveryYearMinContractsSpinner.getValue()); task.setEveryYearMinContracts(qualifiedVendorEveryYearMinContractsSpinner.getValue());
UITools.showTaskDialogAndWait("导入供方", task, null); UITools.showTaskDialogAndWait("导入供方", task, null);
loadTableDataSet();
} }
public void onVendorTableRefreshAction(ActionEvent event) { public void onVendorTableRefreshAction(ActionEvent event) {

View File

@@ -19,6 +19,7 @@ import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.ecep.contract.constant.CompanyVendorConstant;
import com.ecep.contract.service.*; import com.ecep.contract.service.*;
import org.apache.poi.ss.usermodel.BorderStyle; import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Cell;
@@ -119,7 +120,8 @@ public class VendorApprovedListVendorExportTask extends Tasker<Object> {
} }
private File getVendorApprovedListTemplate() { private File getVendorApprovedListTemplate() {
return getVendorService().getVendorApprovedListTemplate(); String path = getConfService().getString(CompanyVendorConstant.KEY_APPROVED_LIST_TEMPLATE);
return new File(path);
} }
@Override @Override

View File

@@ -95,6 +95,7 @@ public class VendorApprovedListVendorImportTask extends Tasker<Object> {
} }
service = getBean(VendorApprovedService.class); service = getBean(VendorApprovedService.class);
// 检索供方
VendorService vendorService = getBean(VendorService.class); VendorService vendorService = getBean(VendorService.class);
Page<VendorVo> page = vendorService.findAll( Page<VendorVo> page = vendorService.findAll(
ParamUtils.builder() ParamUtils.builder()
@@ -136,7 +137,17 @@ public class VendorApprovedListVendorImportTask extends Tasker<Object> {
// 明确 company 实例 // 明确 company 实例
CompanyVo company = initializedVendorCompany(vendor); CompanyVo company = initializedVendorCompany(vendor);
if (company == null) { if (company == null) {
// 无效 // 无效, 删除异常数据
holder.error("供方(#" + vendor.getId() + ")无对应的公司消息");
List<VendorApprovedItemVo> items = getItemService().findAllByListAndVendor(approvedList, vendor);
if (items != null && !items.isEmpty()) {
// 删除
MessageHolder subHolder = holder.sub(" - ");
items.forEach(item -> {
getItemService().delete(item);
subHolder.info("删除 #" + item.getId() + ", " + item.getVendorName());
});
}
return; return;
} }
@@ -144,15 +155,15 @@ public class VendorApprovedListVendorImportTask extends Tasker<Object> {
VendorType vendorType = vendor.getType(); VendorType vendorType = vendor.getType();
if (vendorType == null) { if (vendorType == null) {
subHolder.error("供方分类为空"); subHolder.debug("供方分类未设置");
return;
} }
// 确认供方的developDate 是否在供方名录的发布日期之后 // 确认供方的developDate 是否在供方名录的发布日期之后
LocalDate developDate = vendor.getDevelopDate(); LocalDate developDate = vendor.getDevelopDate();
if (developDate == null) { if (developDate == null) {
subHolder.error("供方的开发日期为空"); subHolder.error("开发日期未设置");
} else if (developDate.isAfter(approvedList.getPublishDate())) { } else if (developDate.isAfter(approvedList.getPublishDate())) {
subHolder.info("开发日期在供方名录发布之后, 跳过...");
return; return;
} }
@@ -160,108 +171,13 @@ public class VendorApprovedListVendorImportTask extends Tasker<Object> {
List<VendorApprovedItemVo> items = getItemService().findAllByListAndVendor(approvedList, vendor); List<VendorApprovedItemVo> items = getItemService().findAllByListAndVendor(approvedList, vendor);
if (items == null || items.isEmpty()) { if (items == null || items.isEmpty()) {
// 供方不在供方名录中时,新建一个 // 供方不在供方名录中时,新建一个
VendorApprovedItemVo item = new VendorApprovedItemVo(); syncWhenNoItem(vendor, company, subHolder);
item.setListId(approvedList.getId());
item.setVendorId(vendor.getId());
// 当前供应商分类是不合格供应商时
if (vendorType == VendorType.UNQUALIFIED) {
// 检索查看供方的评价表, 看与发布日期期间是否有评价表
if (!checkAsQualifiedVendorByEvaluationFormFiles(vendor, item, subHolder)) {
// 不合同供方,跳过
if (logUnqualifiedVendor) {
subHolder.info("供方是不合格供方, 不纳入名录");
}
// 上一年度的供方目录中是否有此供应商
return;
}
item.setDescription("");
}
// 当前供应商分类是合格供方时
else if (vendorType == VendorType.TYPICALLY) {
// 协议经销商,认定为合格供方
if (vendor.isProtocolProvider()) {
item.setType(VendorType.QUALIFIED);
item.setDescription("协议经销商");
}
// 非协议经销商
else {
// 查看供方的合同看近3年期间是否有合同
List<ContractVo> contracts = findAllVendorContracts(vendor, approvedList.getPublishDate());
if (contracts.isEmpty()) {
// 没有合同,应该归入不合格供应商
// 保持一般供应商分类,后期流程处理是否变更分类
item.setType(VendorType.TYPICALLY);
if (logTypicallyVendorNoThreeYearContract) {
subHolder.warn("供方近" + vendorContractMinusYear + "年没有合作, 应该转为不合格供应商");
}
item.setDescription(STR_MEET_UNQUALIFIED);
} else {
// 检查近3年期间是否都有合同
if (checkAllYearHasContract(contracts, approvedList.getPublishDate())) {
// 每年都有合同,合同数是否符合合格供方标准
if (checkAllYearMinHasContract(contracts, approvedList.getPublishDate())) {
// 保持一般供应商分类,后期流程处理是否变更分类
item.setType(VendorType.TYPICALLY);
subHolder.info("供方近" + vendorContractMinusYear + "年每年都有合作, 符合合格供方标准");
item.setDescription(STR_MEET_QUALIFIED);
} else {
item.setType(VendorType.TYPICALLY);
subHolder.warn("供方近" + vendorContractMinusYear + "年每年都有合作, 但是合同数不足, 应转为一般供应商");
item.setDescription(STR_MEET_TYPICALLY);
}
} else {
item.setType(VendorType.TYPICALLY);
item.setDescription("");
}
}
}
}
// 当前供应商分类是合格供方时
else if (vendorType == VendorType.QUALIFIED) {
// 查看供方的合同看近3年期间是否有合同
List<ContractVo> contracts = findAllVendorContracts(vendor, approvedList.getPublishDate());
item.setType(vendorType);
if (contracts.isEmpty()) {
if (logTypicallyVendorNoThreeYearContract) {
subHolder.warn("供方近" + vendorContractMinusYear + "年没有合作, 应该转为不合格供应商");
}
item.setDescription(STR_MEET_UNQUALIFIED);
} else {
// 检查近3年期间是否都有合同
if (checkAllYearHasContract(contracts, approvedList.getPublishDate())) {
// 每年都有合同,合同数是否符合合格供方标准
if (checkAllYearMinHasContract(contracts, approvedList.getPublishDate())) {
item.setDescription("");
} else {
subHolder.warn("供方近" + vendorContractMinusYear + "年每年都有合作, 但是合同数不足, 应转为一般供应商");
item.setDescription(STR_MEET_TYPICALLY);
}
} else {
subHolder.warn("供方近" + vendorContractMinusYear + "年非每年都有合作");
item.setDescription("");
}
}
}
// 未知分类时
else {
item.setDescription("未知供方分类");
}
// 匹配的历史名称
updateVendorNameWithOldName(vendor, item);
getItemService().save(item);
return; return;
} }
if (items.size() == 1) { VendorApprovedItemVo first = items.getFirst();
VendorApprovedItemVo first = items.getFirst(); syncItem(vendor, company, first, subHolder);
if (!StringUtils.hasText(first.getVendorName())) {
updateVendorNameWithOldName(vendor, first);
}
updateItem(vendor, first, subHolder);
return;
}
for (int i = 1; i < items.size(); i++) { for (int i = 1; i < items.size(); i++) {
VendorApprovedItemVo item = items.get(i); VendorApprovedItemVo item = items.get(i);
@@ -270,11 +186,123 @@ public class VendorApprovedListVendorImportTask extends Tasker<Object> {
} }
} }
private void updateVendorNameWithOldName(VendorVo vendor, VendorApprovedItemVo item) { /**
CompanyVo company = initializedVendorCompany(vendor); * 当没有匹配的供方名录项时
if (company == null) { *
* @param vendor
* @param company
* @param subHolder
*/
private void syncWhenNoItem(VendorVo vendor, CompanyVo company, MessageHolder subHolder) {
VendorType vendorType = vendor.getType();
if (vendorType == null) {
subHolder.debug("供方分类未设置");
}
VendorApprovedItemVo item = new VendorApprovedItemVo();
item.setListId(approvedList.getId());
item.setVendorId(vendor.getId());
// 当前供应商分类是不合格供应商时
if (vendorType == VendorType.UNQUALIFIED) {
// 检索查看供方的评价表, 看与发布日期期间是否有评价表
if (!checkAsQualifiedVendorByEvaluationFormFiles(vendor, item, subHolder)) {
// 不合同供方,跳过
if (logUnqualifiedVendor) {
subHolder.info("供方是不合格供方, 不纳入名录");
}
// 上一年度的供方目录中是否有此供应商
return;
}
item.setDescription("");
}
// 当前供应商分类是合格供方时
else if (vendorType == VendorType.TYPICALLY) {
// 协议经销商,认定为合格供方
if (vendor.isProtocolProvider()) {
item.setType(VendorType.QUALIFIED);
item.setDescription("协议经销商");
}
// 非协议经销商
else {
// 查看供方的合同看近3年期间是否有合同
List<ContractVo> contracts = findAllVendorContracts(vendor, approvedList.getPublishDate());
if (contracts.isEmpty()) {
// 没有合同,应该归入不合格供应商
// 保持一般供应商分类,后期流程处理是否变更分类
item.setType(VendorType.TYPICALLY);
if (logTypicallyVendorNoThreeYearContract) {
subHolder.warn("供方近" + vendorContractMinusYear + "年没有合作, 应该转为不合格供应商");
}
item.setDescription(STR_MEET_UNQUALIFIED + "(缺合同1)");
} else {
// 检查近3年期间是否都有合同
if (checkAllYearHasContract(contracts, approvedList.getPublishDate())) {
// 每年都有合同,合同数是否符合合格供方标准
if (checkAllYearMinHasContract(contracts, approvedList.getPublishDate())) {
// 保持一般供应商分类,后期流程处理是否变更分类
item.setType(VendorType.TYPICALLY);
subHolder.info("供方近" + vendorContractMinusYear + "年每年都有合作, 符合合格供方标准");
item.setDescription(STR_MEET_QUALIFIED);
} else {
item.setType(VendorType.TYPICALLY);
subHolder.warn("供方近" + vendorContractMinusYear + "年每年都有合作, 但是合同数不足, 应转为一般供应商");
item.setDescription(STR_MEET_TYPICALLY);
}
} else {
item.setType(VendorType.TYPICALLY);
item.setDescription("");
}
}
}
}
// 当前供应商分类是合格供方时
else if (vendorType == VendorType.QUALIFIED) {
// 查看供方的合同看近3年期间是否有合同
List<ContractVo> contracts = findAllVendorContracts(vendor, approvedList.getPublishDate());
item.setType(vendorType);
if (contracts.isEmpty()) {
if (logTypicallyVendorNoThreeYearContract) {
subHolder.warn("供方近" + vendorContractMinusYear + "年没有合作, 应该转为不合格供应商");
}
item.setDescription(STR_MEET_UNQUALIFIED + "(缺合同2)");
} else {
// 检查近3年期间是否都有合同
if (checkAllYearHasContract(contracts, approvedList.getPublishDate())) {
// 每年都有合同,合同数是否符合合格供方标准
if (checkAllYearMinHasContract(contracts, approvedList.getPublishDate())) {
item.setDescription("");
} else {
subHolder.warn("供方近" + vendorContractMinusYear + "年每年都有合作, 但是合同数不足, 应转为一般供应商");
item.setDescription(STR_MEET_TYPICALLY);
}
} else {
subHolder.warn("供方近" + vendorContractMinusYear + "年非每年都有合作");
item.setDescription("");
}
}
}
// 未知分类时
else {
item.setDescription("未知供方分类");
}
// 匹配的历史名称
updateVendorNameWithOldName(company, item);
getItemService().save(item);
}
/**
* 匹配历史名称,当前供方名称为空时
* @param company
* @param item
*/
private void updateVendorNameWithOldName(CompanyVo company, VendorApprovedItemVo item) {
if (StringUtils.hasText(item.getVendorName())) {
// 已经有供方名称时,不更新
return; return;
} }
CompanyOldNameVo companyOldName = getCompanyOldNameService().findMatchByDate(company, CompanyOldNameVo companyOldName = getCompanyOldNameService().findMatchByDate(company,
approvedList.getPublishDate()); approvedList.getPublishDate());
if (companyOldName != null) { if (companyOldName != null) {
@@ -288,11 +316,12 @@ public class VendorApprovedListVendorImportTask extends Tasker<Object> {
@Setter @Setter
private boolean logUnqualifiedVendorRemove = true; private boolean logUnqualifiedVendorRemove = true;
private void updateItem( private void syncItem(
VendorVo vendor, VendorApprovedItemVo item, MessageHolder holder) { VendorVo vendor, CompanyVo company, VendorApprovedItemVo item, MessageHolder holder) {
VendorType t1 = item.getType(); VendorType t1 = item.getType();
VendorType vendorType = vendor.getType(); VendorType vendorType = vendor.getType();
VendorApprovedItemService itemService = getItemService(); VendorApprovedItemService itemService = getItemService();
updateVendorNameWithOldName(company, item);
if (t1 != vendorType) { if (t1 != vendorType) {
holder.warn("注意分类不一致, " + t1 + ", " + vendorType + "."); holder.warn("注意分类不一致, " + t1 + ", " + vendorType + ".");
} }

View File

@@ -2,6 +2,7 @@ package com.ecep.contract.controller.vendor.approved_list;
import java.io.File; import java.io.File;
import com.ecep.contract.task.VendorApprovedListMakePathTask;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -104,15 +105,13 @@ public class VendorApprovedListWindowController
} }
public void onApprovedListCreatePathAction(ActionEvent event) { public void onApprovedListCreatePathAction(ActionEvent event) {
VendorApprovedListMakePathTask task = new VendorApprovedListMakePathTask();
task.setApprovedList(getEntity());
UITools.showTaskDialogAndWait("创建目录", task, null);
int id = viewModel.getId().get(); int id = viewModel.getId().get();
VendorApprovedVo list = service.findById(id); VendorApprovedVo list = service.findById(id);
viewModel.update(list);
if (service.makePathAbsent(list)) {
VendorApprovedVo saved = service.save(list);
viewModel.update(saved);
} else {
setStatus("目录存在或创建失败");
}
} }
public void onApprovedListChangePathAction(ActionEvent event) { public void onApprovedListChangePathAction(ActionEvent event) {
@@ -123,8 +122,4 @@ public class VendorApprovedListWindowController
task.setApprovedList(getEntity()); task.setApprovedList(getEntity());
UITools.showTaskDialogAndWait("导出供方", task, null); UITools.showTaskDialogAndWait("导出供方", task, null);
} }
private void save(ActionEvent event) {
saveTabSkins();
}
} }

View File

@@ -0,0 +1,71 @@
package com.ecep.contract.converter;
import java.io.File;
import org.springframework.util.StringUtils;
import com.ecep.contract.SpringApp;
import com.ecep.contract.service.CompanyCustomerEvaluationFormFileService;
import com.ecep.contract.service.CompanyCustomerFileService;
import com.ecep.contract.vo.CompanyCustomerEvaluationFormFileVo;
import com.ecep.contract.vo.CustomerFileVo;
import javafx.util.StringConverter;
/**
* CompanyCustomerEvaluationFormFileVo的StringConverter实现用于JavaFX控件中的显示和转换
*/
public class CompanyCustomerEvaluationFormFileStringConverter
extends StringConverter<CompanyCustomerEvaluationFormFileVo> {
private final CompanyCustomerEvaluationFormFileService service;
private CompanyCustomerFileService customerFileService;
/**
* 构造函数
*
* @param service CompanyCustomerEvaluationFormFileService实例
*/
public CompanyCustomerEvaluationFormFileStringConverter(CompanyCustomerEvaluationFormFileService service) {
this.service = service;
}
private CompanyCustomerFileService getCustomerFileService() {
if (customerFileService == null) {
customerFileService = SpringApp.getBean(CompanyCustomerFileService.class);
}
return customerFileService;
}
/**
* 将CompanyCustomerEvaluationFormFileVo对象转换为字符串
*
* @param formFile CompanyCustomerEvaluationFormFileVo对象
* @return 转换后的字符串
*/
@Override
public String toString(CompanyCustomerEvaluationFormFileVo formFile) {
if (formFile == null || formFile.getCustomerFile() == null) {
return "";
}
CustomerFileVo customerFile = getCustomerFileService().findById(formFile.getCustomerFile());
if (customerFile == null || !StringUtils.hasText(customerFile.getFilePath())) {
return "#" + formFile.getCustomerFile();
}
File file = new File(customerFile.getFilePath());
return file.getName();
}
/**
* 将字符串转换为CompanyCustomerEvaluationFormFileVo对象
*
* @param string 字符串
* @return 转换后的CompanyCustomerEvaluationFormFileVo对象
*/
@Override
public CompanyCustomerEvaluationFormFileVo fromString(String string) {
// 由于文件名称可能不唯一这里返回null
// 实际使用时应该通过ID或其他唯一标识来查找
return null;
}
}

View File

@@ -0,0 +1,47 @@
package com.ecep.contract.converter;
import com.ecep.contract.service.DeliverySignMethodService;
import com.ecep.contract.vo.DeliverySignMethodVo;
import javafx.util.StringConverter;
/**
* DeliverySignMethodVo的StringConverter实现用于JavaFX控件中的显示和转换
*/
public class DeliverySignMethodStringConverter extends StringConverter<DeliverySignMethodVo> {
private final DeliverySignMethodService service;
/**
* 构造函数
*
* @param service DeliverySignMethodService实例
*/
public DeliverySignMethodStringConverter(DeliverySignMethodService service) {
this.service = service;
}
/**
* 将DeliverySignMethodVo对象转换为字符串
*
* @param method DeliverySignMethodVo对象
* @return 转换后的字符串
*/
@Override
public String toString(DeliverySignMethodVo method) {
return method == null ? "" : method.getCode() + " " + method.getName();
}
/**
* 将字符串转换为DeliverySignMethodVo对象
*
* @param string 字符串
* @return 转换后的DeliverySignMethodVo对象
*/
@Override
public DeliverySignMethodVo fromString(String string) {
if (string == null || string.isEmpty()) {
return null;
}
// 查找所有方法,然后通过名称匹配
return service.findByName(string);
}
}

View File

@@ -1,30 +1,37 @@
package com.ecep.contract.converter; package com.ecep.contract.converter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import com.ecep.contract.service.DepartmentService; import com.ecep.contract.service.DepartmentService;
import com.ecep.contract.vo.DepartmentVo; import com.ecep.contract.vo.DepartmentVo;
import javafx.util.StringConverter;
import jakarta.annotation.PostConstruct; /**
* 部门字符串转换器
@Lazy */
@Component public class DepartmentStringConverter extends StringConverter<DepartmentVo> {
public class DepartmentStringConverter extends EntityStringConverter<DepartmentVo> {
@Lazy
@Autowired
private DepartmentService service; private DepartmentService service;
public DepartmentStringConverter() { public DepartmentStringConverter() {
} }
@PostConstruct public DepartmentStringConverter(DepartmentService service) {
private void init() { this.service = service;
setInitialized(department -> service.findById(department.getId()));
setSuggestion(service::search);
} }
@Override
public String toString(DepartmentVo department) {
if (department == null) {
return "-";
}
return department.getCode() + " " + department.getName();
}
@Override
public DepartmentVo fromString(String string) {
if (service == null || string == null || string.trim().isEmpty()) {
return null;
}
return service.findByCode(string.trim());
}
} }

View File

@@ -1,29 +1,25 @@
package com.ecep.contract.converter; package com.ecep.contract.converter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import com.ecep.contract.service.ProductTypeService; import com.ecep.contract.service.ProductTypeService;
import com.ecep.contract.vo.ProductTypeVo; import com.ecep.contract.vo.ProductTypeVo;
import jakarta.annotation.PostConstruct; import javafx.util.StringConverter;
@Lazy public class ProductTypeStringConverter extends StringConverter<ProductTypeVo> {
@Component private final ProductTypeService service;
public class ProductTypeStringConverter extends EntityStringConverter<ProductTypeVo> {
@Lazy
@Autowired
private ProductTypeService service;
public ProductTypeStringConverter() {
public ProductTypeStringConverter(ProductTypeService service) {
this.service = service;
} }
@PostConstruct @Override
private void init() { public String toString(ProductTypeVo type) {
setInitialized(type -> service.findById(type.getId())); return type == null ? "" : type.getCode() + " " + type.getName();
setSuggestion(service::search); }
@Override
public ProductTypeVo fromString(String string) {
return service.findByName(string);
} }

View File

@@ -1,5 +1,6 @@
package com.ecep.contract.converter; package com.ecep.contract.converter;
import javafx.util.StringConverter;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -9,21 +10,21 @@ import com.ecep.contract.vo.ProductUsageVo;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
@Lazy public class ProductUsageStringConverter extends StringConverter<ProductUsageVo> {
@Component
public class ProductUsageStringConverter extends EntityStringConverter<ProductUsageVo> {
@Lazy
@Autowired
private ProductUsageService service; private ProductUsageService service;
public ProductUsageStringConverter() { public ProductUsageStringConverter(ProductUsageService service) {
this.service = service;
} }
@PostConstruct @Override
private void init() { public String toString(ProductUsageVo usage) {
setInitialized(usage -> service.findById(usage.getId())); return usage == null ? "" : usage.getCode() + " " + usage.getName();
setSuggestion(service::search); }
@Override
public ProductUsageVo fromString(String string) {
return service.findByName(string);
} }

View File

@@ -1,30 +1,31 @@
package com.ecep.contract.converter; package com.ecep.contract.converter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import com.ecep.contract.service.ProjectIndustryService; import com.ecep.contract.service.ProjectIndustryService;
import com.ecep.contract.vo.ProjectIndustryVo; import com.ecep.contract.vo.ProjectIndustryVo;
import jakarta.annotation.PostConstruct; import javafx.util.StringConverter;
@Lazy public class ProjectIndustryStringConverter extends StringConverter<ProjectIndustryVo> {
@Component
public class ProjectIndustryStringConverter extends EntityStringConverter<ProjectIndustryVo> {
@Lazy
@Autowired
private ProjectIndustryService service; private ProjectIndustryService service;
public ProjectIndustryStringConverter() { public ProjectIndustryStringConverter(ProjectIndustryService service) {
this.service = service;
} }
@PostConstruct @Override
private void init() { public String toString(ProjectIndustryVo object) {
setInitialized(industry -> service.findById(industry.getId())); if (object == null) {
setSuggestion(service::search); return "";
}
return object.getCode() + " " + object.getName();
} }
@Override
public ProjectIndustryVo fromString(String string) {
if (string == null || string.isEmpty()) {
return null;
}
return service.findByName(string);
}
} }

View File

@@ -0,0 +1,24 @@
package com.ecep.contract.converter;
import com.ecep.contract.service.ProjectSaleTypeService;
import com.ecep.contract.vo.ProjectSaleTypeVo;
import javafx.util.StringConverter;
public class ProjectSaleTypeStringConverter extends StringConverter<ProjectSaleTypeVo> {
private final ProjectSaleTypeService service;
public ProjectSaleTypeStringConverter(ProjectSaleTypeService service) {
this.service = service;
}
@Override
public String toString(ProjectSaleTypeVo type) {
return type == null ? "" : type.getCode() + " " + type.getName();
}
@Override
public ProjectSaleTypeVo fromString(String string) {
return service.findByName(string);
}
}

View File

@@ -0,0 +1,46 @@
package com.ecep.contract.converter;
import com.ecep.contract.service.ProjectTypeService;
import com.ecep.contract.vo.ProjectTypeVo;
import javafx.util.StringConverter;
/**
* ProjectTypeVo的StringConverter实现用于JavaFX控件中的显示和转换
*/
public class ProjectTypeStringConverter extends StringConverter<ProjectTypeVo> {
private final ProjectTypeService service;
/**
* 构造函数
*
* @param service ProjectTypeService实例
*/
public ProjectTypeStringConverter(ProjectTypeService service) {
this.service = service;
}
/**
* 将ProjectTypeVo对象转换为字符串
*
* @param type ProjectTypeVo对象
* @return 转换后的字符串
*/
@Override
public String toString(ProjectTypeVo type) {
return type == null ? "" : type.getCode() + " " + type.getName();
}
/**
* 将字符串转换为ProjectTypeVo对象
*
* @param string 字符串
* @return 转换后的ProjectTypeVo对象
*/
@Override
public ProjectTypeVo fromString(String string) {
if (string == null || string.isEmpty()) {
return null;
}
return service.findByName(string);
}
}

View File

@@ -13,6 +13,9 @@ public class VendorCatalogStringConverter extends StringConverter<VendorCatalogV
@Override @Override
public String toString(VendorCatalogVo object) { public String toString(VendorCatalogVo object) {
if (object == null) {
return "-";
}
return object.getName(); return object.getName();
} }

View File

@@ -1,30 +1,26 @@
package com.ecep.contract.converter; package com.ecep.contract.converter;
import org.springframework.beans.factory.annotation.Autowired; import javafx.util.StringConverter;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import com.ecep.contract.service.VendorGroupService; import com.ecep.contract.service.VendorGroupService;
import com.ecep.contract.vo.VendorGroupVo; import com.ecep.contract.vo.VendorGroupVo;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
@Lazy public class VendorGroupStringConverter extends StringConverter<VendorGroupVo> {
@Component
public class VendorGroupStringConverter extends EntityStringConverter<VendorGroupVo> {
@Lazy
@Autowired
private VendorGroupService service; private VendorGroupService service;
public VendorGroupStringConverter() { public VendorGroupStringConverter(VendorGroupService service) {
this.service = service;
} }
@PostConstruct @Override
private void init() { public String toString(VendorGroupVo object) {
setInitialized(group -> service.findById(group.getId())); return object == null ? "" : object.getCode() + " " + object.getName();
setSuggestion(service::search);
} }
@Override
public VendorGroupVo fromString(String string) {
return service.findByCode(string);
}
} }

View File

@@ -1,9 +1,11 @@
package com.ecep.contract.service; package com.ecep.contract.service;
import java.util.List;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.controlsfx.control.TaskProgressView; import org.controlsfx.control.TaskProgressView;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -21,6 +23,8 @@ import lombok.Data;
@Service @Service
public class CloudRkService extends QueryService<CloudRkVo, CloudRkViewModel> { public class CloudRkService extends QueryService<CloudRkVo, CloudRkViewModel> {
@Data @Data
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public static class EntInfo { public static class EntInfo {
@@ -69,6 +73,10 @@ public class CloudRkService extends QueryService<CloudRkVo, CloudRkViewModel> {
.findFirst().orElse(null); .findFirst().orElse(null);
} }
public Page<CloudRkVo> findAllByCompany(CompanyVo company) {
return findAll(ParamUtils.builder().equals("company", company.getId()).build(), Pageable.unpaged());
}
public boolean checkBlackListUpdateElapse(CompanyVo company, CloudRkVo cloudRk) { public boolean checkBlackListUpdateElapse(CompanyVo company, CloudRkVo cloudRk) {
// TODO Auto-generated method stub // TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'checkBlackListUpdateElapse'"); throw new UnsupportedOperationException("Unimplemented method 'checkBlackListUpdateElapse'");
@@ -78,4 +86,12 @@ public class CloudRkService extends QueryService<CloudRkVo, CloudRkViewModel> {
// TODO Auto-generated method stub // TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'updateBlackList'"); throw new UnsupportedOperationException("Unimplemented method 'updateBlackList'");
} }
public void mergeTo(CompanyVo from, CompanyVo to) {
List<CloudRkVo> list = findAllByCompany(from).getContent();
for (CloudRkVo item : list) {
item.setCompanyId(to.getId());
save(item);
}
}
} }

View File

@@ -1,7 +1,9 @@
package com.ecep.contract.service; package com.ecep.contract.service;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@@ -77,4 +79,16 @@ public class CloudTycService extends QueryService<CloudTycVo, CloudTycInfoViewMo
.findFirst().orElse(null); .findFirst().orElse(null);
} }
public Page<CloudTycVo> findAllByCompany(CompanyVo company) {
return findAll(ParamUtils.builder().equals("company", company.getId()).build(), Pageable.unpaged());
}
public void mergeTo(CompanyVo from, CompanyVo to) {
List<CloudTycVo> list = findAllByCompany(from).getContent();
for (CloudTycVo item : list) {
item.setCompanyId(to.getId());
save(item);
}
}
} }

View File

@@ -1,5 +1,6 @@
package com.ecep.contract.service; package com.ecep.contract.service;
import java.time.LocalDate;
import java.util.List; import java.util.List;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
@@ -32,4 +33,21 @@ public class CompanyContactService extends QueryService<CompanyContactVo, Compan
return page.getContent().getFirst(); return page.getContent().getFirst();
} }
public List<CompanyContactVo> findAllByCompany(CompanyVo company, LocalDate beginDate, LocalDate endDate) {
return findAll(ParamUtils.builder()
.equals("company", company.getId())
.between("setupDate", beginDate, endDate)
.build(), Pageable.unpaged()).getContent();
}
public void mergeTo(CompanyVo from, CompanyVo to) {
List<CompanyContactVo> contacts = findAllByCompany(from, LocalDate.MIN, LocalDate.MAX);
if (contacts == null || contacts.isEmpty()) {
return;
}
for (CompanyContactVo contact : contacts) {
contact.setCompanyId(to.getId());
save(contact);
}
}
} }

View File

@@ -17,4 +17,12 @@ public class CompanyCustomerEntityService extends QueryService<CompanyCustomerEn
.equals("customer", customer.getId()).build(), Pageable.unpaged()) .equals("customer", customer.getId()).build(), Pageable.unpaged())
.getContent(); .getContent();
} }
public void mergeTo(CustomerVo from, CustomerVo to) {
List<CompanyCustomerEntityVo> fromEntities = findAllByCustomer(from);
for (CompanyCustomerEntityVo fromEntity : fromEntities) {
fromEntity.setCustomerId(to.getId());
save(fromEntity);
}
}
} }

View File

@@ -1,5 +1,7 @@
package com.ecep.contract.service; package com.ecep.contract.service;
import com.ecep.contract.converter.CompanyCustomerEvaluationFormFileStringConverter;
import javafx.util.StringConverter;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -14,6 +16,8 @@ import java.util.List;
public class CompanyCustomerEvaluationFormFileService public class CompanyCustomerEvaluationFormFileService
extends QueryService<CompanyCustomerEvaluationFormFileVo, CompanyCustomerEvaluationFormFileViewModel> { extends QueryService<CompanyCustomerEvaluationFormFileVo, CompanyCustomerEvaluationFormFileViewModel> {
private final StringConverter<CompanyCustomerEvaluationFormFileVo> stringConverter = new CompanyCustomerEvaluationFormFileStringConverter(this);
/** /**
* 根据ID查找客户评估表文件 * 根据ID查找客户评估表文件
*/ */
@@ -25,18 +29,17 @@ public class CompanyCustomerEvaluationFormFileService
* 根据客户文件查找评估表文件 * 根据客户文件查找评估表文件
*/ */
public CompanyCustomerEvaluationFormFileVo findByCustomerFile(CustomerFileVo customerFile) { public CompanyCustomerEvaluationFormFileVo findByCustomerFile(CustomerFileVo customerFile) {
return findByCustomerFile(customerFile.getId()); return findOneByProperty("customerFile", customerFile.getId());
} }
public CompanyCustomerEvaluationFormFileVo findByCustomerFile(Integer customerFileId) { public CompanyCustomerEvaluationFormFileVo findByCustomerFile(Integer customerFileId) {
List<CompanyCustomerEvaluationFormFileVo> page = findAll(ParamUtils.builder() return findOneByProperty("customerFile", customerFileId);
.equals("customerFile", customerFileId) }
.build(), Pageable.ofSize(1))
.getContent(); public List<CompanyCustomerEvaluationFormFileVo> searchByCompany(Integer companyId, String searchText) {
if (page.isEmpty()) { ParamUtils.Builder params = getSpecification(searchText);
return null; params.equals("company", companyId);
} return findAll(params.build(), Pageable.ofSize(10)).getContent();
return page.getFirst();
} }
/** /**
@@ -46,4 +49,10 @@ public class CompanyCustomerEvaluationFormFileService
return super.save(formFile); return super.save(formFile);
} }
@Override
public StringConverter<CompanyCustomerEvaluationFormFileVo> getStringConverter() {
return stringConverter;
}
} }

View File

@@ -121,4 +121,12 @@ public class CompanyCustomerFileService extends QueryService<CustomerFileVo, Cus
public CustomerFileVo save(CustomerFileVo entity) { public CustomerFileVo save(CustomerFileVo entity) {
return super.save(entity); return super.save(entity);
} }
public void mergeTo(CustomerVo from, CustomerVo to) {
List<CustomerFileVo> fromFiles = findAllByCustomer(from);
for (CustomerFileVo fromFile : fromFiles) {
fromFile.setCustomer(to.getId());
save(fromFile);
}
}
} }

View File

@@ -25,7 +25,7 @@ import javafx.util.StringConverter;
@CacheConfig(cacheNames = "company-customer-file-type") @CacheConfig(cacheNames = "company-customer-file-type")
public class CompanyCustomerFileTypeService public class CompanyCustomerFileTypeService
extends QueryService<CustomerFileTypeLocalVo, CompanyCustomerFileTypeLocalViewModel> { extends QueryService<CustomerFileTypeLocalVo, CompanyCustomerFileTypeLocalViewModel> {
private final StringConverter<CustomerFileTypeLocalVo> stringConverter = new CustomerFileTypeStringConverter(this); private final CustomerFileTypeStringConverter stringConverter = new CustomerFileTypeStringConverter(this);
@Cacheable(key = "#p0") @Cacheable(key = "#p0")
@Override @Override
@@ -39,13 +39,13 @@ public class CompanyCustomerFileTypeService
return super.findAll(); return super.findAll();
} }
@Caching(put = { @CachePut(key = "#p0.id"), @CachePut(key = "'all'") }) @Caching(put = {@CachePut(key = "#p0.id"), @CachePut(key = "'all'")})
@Override @Override
public CustomerFileTypeLocalVo save(CustomerFileTypeLocalVo entity) { public CustomerFileTypeLocalVo save(CustomerFileTypeLocalVo entity) {
return super.save(entity); return super.save(entity);
} }
@Caching(put = { @CachePut(key = "#p0.id"), @CachePut(key = "'all'") }) @Caching(put = {@CachePut(key = "#p0.id"), @CachePut(key = "'all'")})
@Override @Override
public void delete(CustomerFileTypeLocalVo entity) { public void delete(CustomerFileTypeLocalVo entity) {
super.delete(entity); super.delete(entity);
@@ -53,8 +53,7 @@ public class CompanyCustomerFileTypeService
@Cacheable @Cacheable
public Map<CustomerFileType, CustomerFileTypeLocalVo> findAll(Locale locale) { public Map<CustomerFileType, CustomerFileTypeLocalVo> findAll(Locale locale) {
Map<String, Object> params = new HashMap<>(); Map<String, Object> params = ParamUtils.builder().equals("lang", locale.toLanguageTag()).build();
params.put("lang", locale.toLanguageTag());
return findAll(params, Pageable.unpaged()).stream() return findAll(params, Pageable.unpaged()).stream()
.collect(Collectors.toMap(CustomerFileTypeLocalVo::getType, Function.identity())); .collect(Collectors.toMap(CustomerFileTypeLocalVo::getType, Function.identity()));
} }
@@ -66,7 +65,7 @@ public class CompanyCustomerFileTypeService
/** /**
* 根据语言标签和参数查找单个 CustomerFileTypeLocalVo 对象 * 根据语言标签和参数查找单个 CustomerFileTypeLocalVo 对象
* *
* @param locale 语言区域 * @param locale 语言区域
* @param key 参数键 * @param key 参数键
* @param value 参数值 * @param value 参数值

View File

@@ -1,5 +1,6 @@
package com.ecep.contract.service; package com.ecep.contract.service;
import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.vm.CompanyInvoiceInfoViewModel; import com.ecep.contract.vm.CompanyInvoiceInfoViewModel;
import com.ecep.contract.vo.CompanyInvoiceInfoVo; import com.ecep.contract.vo.CompanyInvoiceInfoVo;
import com.ecep.contract.vo.CompanyVo; import com.ecep.contract.vo.CompanyVo;
@@ -15,9 +16,8 @@ import java.util.Map;
public class CompanyInvoiceInfoService extends QueryService<CompanyInvoiceInfoVo, CompanyInvoiceInfoViewModel> { public class CompanyInvoiceInfoService extends QueryService<CompanyInvoiceInfoVo, CompanyInvoiceInfoViewModel> {
public List<CompanyInvoiceInfoVo> searchByCompany(CompanyVo company, String searchText) { public List<CompanyInvoiceInfoVo> searchByCompany(CompanyVo company, String searchText) {
Map<String, Object> params = new HashMap<>(); Map<String, Object> params = ParamUtils.builder().equals("company", company.getId())
params.put("company", company); .search(searchText).build();
params.put("searchText", searchText);
return findAll(params, Pageable.unpaged()).getContent(); return findAll(params, Pageable.unpaged()).getContent();
} }
} }

View File

@@ -4,6 +4,7 @@ import java.io.File;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.List;
import com.ecep.contract.util.MyStringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
@@ -70,24 +71,29 @@ public class CompanyOldNameService extends QueryService<CompanyOldNameVo, Compan
} }
public CompanyOldNameVo findMatchByDate(CompanyVo company, LocalDate localDate) { public CompanyOldNameVo findMatchByDate(CompanyVo company, LocalDate localDate) {
Page<CompanyOldNameVo> page = findAll(ParamUtils.builder() findAll(ParamUtils.builder()
.equals("ambiguity", false)
.equals("company", company.getId()) .equals("company", company.getId())
.and(b -> b.isNotNull("beginDate").greaterThan("beginDate", localDate)) .equals("ambiguity", true)
.and(b -> b.isNotNull("endDate").lessThan("endDate", localDate)) .isNotNull("beginDate")
.build(), Pageable.ofSize(1)); .build(), Pageable.unpaged()).getContent();
if (page.isEmpty()) { return null;
return null;
}
return page.getContent().getFirst();
} }
public List<CompanyOldNameVo> findAllByCompanyAndName(CompanyVo company, String oldName) { public List<CompanyOldNameVo> findAllByCompanyAndName(CompanyVo company, String oldName) {
return findAll(ParamUtils.builder().equals("company", company.getId()).equals("oldName", oldName).build(), return findAll(ParamUtils.builder().equals("company", company.getId()).equals("name", oldName).build(),
Pageable.unpaged()).getContent(); Pageable.unpaged()).getContent();
} }
public List<CompanyOldNameVo> findAllByCompany(IdentityEntity company) { public List<CompanyOldNameVo> findAllByCompany(IdentityEntity company) {
return findAll(ParamUtils.equal("company", company.getId()), Pageable.unpaged()).getContent(); return findAll(ParamUtils.equal("company", company.getId()), Pageable.unpaged()).getContent();
} }
public void mergeTo(CompanyVo from, CompanyVo to) {
List<CompanyOldNameVo> fromOldNames = findAllByCompany(from);
for (CompanyOldNameVo fromOldName : fromOldNames) {
fromOldName.setMemo(MyStringUtils.appendIfAbsent(fromOldName.getMemo(), "转自 " + from.getId()));
fromOldName.setCompanyId(to.getId());
save(fromOldName);
}
}
} }

View File

@@ -6,6 +6,8 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import com.ecep.contract.SpringApp;
import com.ecep.contract.util.ParamUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
@@ -57,14 +59,26 @@ public class CompanyService extends QueryService<CompanyVo, CompanyViewModel> {
} }
public List<CompanyVo> findAllByName(String name) { public List<CompanyVo> findAllByName(String name) {
Map<String, Object> params = new HashMap<>(); Map<String, Object> params = ParamUtils.builder().equals("name", name).build();
params.put("name", name);
return findAll(params, Pageable.unpaged()).getContent(); return findAll(params, Pageable.unpaged()).getContent();
} }
public void merge(CompanyVo company, CompanyVo updater) { public void merge(CompanyVo from, CompanyVo to) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'merge'"); SpringApp.getBean(CloudRkService.class).mergeTo(from, to);
SpringApp.getBean(CloudTycService.class).mergeTo(from, to);
SpringApp.getBean(YongYouU8Service.class).mergeTo(from, to);
SpringApp.getBean(CompanyOldNameService.class).mergeTo(from, to);
SpringApp.getBean(CompanyContactService.class).mergeTo(from, to);
// 供应商和客户
SpringApp.getBean(VendorService.class).mergeTo(from, to);
SpringApp.getBean(CustomerService.class).mergeTo(from, to);
SpringApp.getBean(ContractService.class).mergeTo(from, to);
SpringApp.getBean(CompanyContactService.class).mergeTo(from, to);
delete(from);
} }
public CompanyVo createNewCompany(String newCompanyName) { public CompanyVo createNewCompany(String newCompanyName) {

View File

@@ -0,0 +1,217 @@
package com.ecep.contract.service;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
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.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.vm.ContractBalanceViewModel;
import com.ecep.contract.vo.ContractBalanceVo;
/**
* 合同余额服务客户端实现
* 继承QueryService提供基于WebSocket的异步通信和缓存机制
*/
@Service
@CacheConfig(cacheNames = "contract-balance")
public class ContractBalanceService extends QueryService<ContractBalanceVo, ContractBalanceViewModel> {
@Autowired
private ContractService contractService;
/**
* 根据ID查询合同余额信息
* 使用缓存机制提高查询性能
*
* @param id 余额记录ID
* @return 合同余额视图对象如果不存在则返回null
*/
@Cacheable(key = "#p0")
public ContractBalanceVo findById(Integer id) {
return super.findById(id);
}
/**
* 保存合同余额信息
* 支持新建和更新操作,包含缓存失效机制
*
* @param contractBalance 合同余额视图对象
* @return 保存后的合同余额视图对象
*/
@Caching(evict = {
@CacheEvict(key = "#p0.id"),
@CacheEvict(key = "'contract-'+#p0.contractId")
})
public ContractBalanceVo save(ContractBalanceVo contractBalance) {
return super.save(contractBalance);
}
/**
* 删除合同余额信息
* 包含相关缓存的失效处理
*
* @param contractBalance 合同余额视图对象
*/
@Caching(evict = {
@CacheEvict(key = "#p0.id"),
@CacheEvict(key = "'contract-'+#p0.contractId")
})
public void delete(ContractBalanceVo contractBalance) {
super.delete(contractBalance);
}
/**
* 根据合同ID查询所有余额记录
*
* @param contractId 合同ID
* @return 合同余额列表
*/
public List<ContractBalanceVo> findAllByContractId(Integer contractId) {
return findAll(ParamUtils.builder()
.equals("contractId", contractId)
.build(), Pageable.unpaged()).getContent();
}
/**
* 根据业务员ID查询余额记录
*
* @param employeeId 业务员ID
* @return 合同余额列表
*/
public List<ContractBalanceVo> findAllByEmployeeId(Integer employeeId) {
return findAll(ParamUtils.builder()
.equals("employeeId", employeeId)
.build(), Pageable.unpaged()).getContent();
}
/**
* 根据引用ID查询余额记录
*
* @param refId 引用ID
* @return 合同余额视图对象如果不存在则返回null
*/
public ContractBalanceVo findByRefId(String refId) {
return findAll(ParamUtils.builder()
.equals("refId", refId)
.build(), Pageable.ofSize(1)).stream()
.findFirst()
.orElse(null);
}
/**
* 根据GUID查询余额记录
*
* @param guid GUID
* @return 合同余额视图对象如果不存在则返回null
*/
public ContractBalanceVo findByGuid(String guid) {
return findAll(ParamUtils.builder()
.equals("guid", guid)
.build(), Pageable.ofSize(1)).stream()
.findFirst()
.orElse(null);
}
/**
* 根据合同ID分页查询余额记录
*
* @param contractId 合同ID
* @param pageable 分页参数
* @return 分页结果
*/
public Page<ContractBalanceVo> findByContractId(Integer contractId, Pageable pageable) {
return findAll(ParamUtils.builder()
.equals("contractId", contractId)
.build(), pageable);
}
/**
* 根据业务员ID和日期范围查询余额记录
*
* @param employeeId 业务员ID
* @param beginDate 开始日期
* @param endDate 结束日期
* @return 合同余额列表
*/
public List<ContractBalanceVo> findAllByEmployeeAndDateRange(Integer employeeId, LocalDate beginDate,
LocalDate endDate) {
return findAll(ParamUtils.builder()
.equals("employeeId", employeeId)
.between("setupDate", beginDate, endDate)
.build(), Pageable.unpaged()).getContent();
}
/**
* 根据凭证ID查询余额记录
*
* @param pzId 凭证ID
* @return 合同余额列表
*/
public List<ContractBalanceVo> findAllByPzId(String pzId) {
return findAll(ParamUtils.builder()
.equals("pzId", pzId)
.build(), Pageable.unpaged()).getContent();
}
/**
* 根据JSD类型查询余额记录
*
* @param jsdType JSD类型
* @return 合同余额列表
*/
public List<ContractBalanceVo> findAllByJsdType(String jsdType) {
return findAll(ParamUtils.builder()
.equals("jsdType", jsdType)
.build(), Pageable.unpaged()).getContent();
}
/**
* 检查指定的合同是否存在余额记录
*
* @param contractId 合同ID
* @return 如果存在余额记录返回true否则返回false
*/
public boolean hasBalanceByContractId(Integer contractId) {
Page<ContractBalanceVo> page = findByContractId(contractId, Pageable.ofSize(1));
return !page.isEmpty();
}
/**
* 统计指定合同的总余额数量
*
* @param contractId 合同ID
* @return 余额记录总数
*/
public long countByContractId(Integer contractId) {
return findByContractId(contractId, Pageable.unpaged()).getTotalElements();
}
/**
* 根据查询参数进行复杂查询
*
* @param params 查询参数
* @param pageable 分页参数
* @return 分页结果
*/
public Page<ContractBalanceVo> findAll(Map<String, Object> params, Pageable pageable) {
return super.findAll(params, pageable);
}
/**
* 获取所有余额记录
*
* @return 合同余额列表
*/
public List<ContractBalanceVo> findAll() {
return super.findAll();
}
}

View File

@@ -13,6 +13,12 @@ import com.ecep.contract.vo.ContractVo;
@Service @Service
public class ContractFileService extends QueryService<ContractFileVo, ContractFileViewModel> { public class ContractFileService extends QueryService<ContractFileVo, ContractFileViewModel> {
@Override
public ContractFileVo findById(Integer id) {
return super.findById(id);
}
public List<ContractFileVo> findAllByContract(ContractVo contract) { public List<ContractFileVo> findAllByContract(ContractVo contract) {
return findAll(ParamUtils.equal("contract", contract.getId()), Pageable.unpaged()).getContent(); return findAll(ParamUtils.equal("contract", contract.getId()), Pageable.unpaged()).getContent();
} }

View File

@@ -2,6 +2,7 @@ package com.ecep.contract.service;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -9,6 +10,7 @@ import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching; import org.springframework.cache.annotation.Caching;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -37,13 +39,13 @@ public class ContractFileTypeService extends QueryService<ContractFileTypeLocalV
return super.findAll(); return super.findAll();
} }
@Caching(put = { @CachePut(key = "#p0.id"), @CachePut(key = "'all'") }) @Caching(put = {@CachePut(key = "#p0.id"), @CachePut(key = "'all'")})
@Override @Override
public ContractFileTypeLocalVo save(ContractFileTypeLocalVo entity) { public ContractFileTypeLocalVo save(ContractFileTypeLocalVo entity) {
return super.save(entity); return super.save(entity);
} }
@Caching(put = { @CachePut(key = "#p0.id"), @CachePut(key = "'all'") }) @Caching(put = {@CachePut(key = "#p0.id"), @CachePut(key = "'all'")})
@Override @Override
public void delete(ContractFileTypeLocalVo entity) { public void delete(ContractFileTypeLocalVo entity) {
super.delete(entity); super.delete(entity);
@@ -55,6 +57,10 @@ public class ContractFileTypeService extends QueryService<ContractFileTypeLocalV
.collect(Collectors.toMap(ContractFileTypeLocalVo::getType, Function.identity())); .collect(Collectors.toMap(ContractFileTypeLocalVo::getType, Function.identity()));
} }
public CompletableFuture<Page<ContractFileTypeLocalVo>> asyncFindAll(Locale locale) {
return asyncFindAll(ParamUtils.builder().equals("lang", locale.toLanguageTag()).build(), Pageable.unpaged());
}
@Cacheable @Cacheable
public ContractFileTypeLocalVo findByType(Locale locale, ContractFileType type) { public ContractFileTypeLocalVo findByType(Locale locale, ContractFileType type) {
return findAll(ParamUtils.builder().equals("lang", locale.toLanguageTag()).equals("type", type).build(), Pageable.ofSize(1)).stream().findFirst().orElse(null); return findAll(ParamUtils.builder().equals("lang", locale.toLanguageTag()).equals("type", type).build(), Pageable.ofSize(1)).stream().findFirst().orElse(null);
@@ -67,7 +73,7 @@ public class ContractFileTypeService extends QueryService<ContractFileTypeLocalV
/** /**
* 根据语言标签和参数查找单个 ContractFileTypeLocalVo 对象 * 根据语言标签和参数查找单个 ContractFileTypeLocalVo 对象
* *
* @param locale 语言区域 * @param locale 语言区域
* @param key 参数键 * @param key 参数键
* @param value 参数值 * @param value 参数值
@@ -82,7 +88,7 @@ public class ContractFileTypeService extends QueryService<ContractFileTypeLocalV
public ContractFileTypeLocalVo findByLocaleAndValue(Locale locale, String string) { public ContractFileTypeLocalVo findByLocaleAndValue(Locale locale, String string) {
return findOneByLang(locale, "value", string); return findOneByLang(locale, "value", string);
} }
public ContractFileTypeLocalVo findByLocaleAndType(Locale locale, ContractFileType type) { public ContractFileTypeLocalVo findByLocaleAndType(Locale locale, ContractFileType type) {
return findOneByLang(locale, "type", type); return findOneByLang(locale, "type", type);
} }

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

@@ -5,6 +5,9 @@ import java.time.LocalDate;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import com.ecep.contract.SpringApp;
import com.ecep.contract.util.MyStringUtils;
import com.ecep.contract.vo.*;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
@@ -20,10 +23,6 @@ import com.ecep.contract.constant.ContractConstant;
import com.ecep.contract.util.ContractUtils; import com.ecep.contract.util.ContractUtils;
import com.ecep.contract.util.ParamUtils; import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.vm.ContractViewModel; import com.ecep.contract.vm.ContractViewModel;
import com.ecep.contract.vo.ContractFileVo;
import com.ecep.contract.vo.ContractVo;
import com.ecep.contract.vo.ProjectVo;
import com.ecep.contract.vo.VendorVo;
@Service @Service
@CacheConfig(cacheNames = "contract") @CacheConfig(cacheNames = "contract")
@@ -158,10 +157,24 @@ public class ContractService extends QueryService<ContractVo, ContractViewModel>
.build(), Pageable.unpaged()).getContent(); .build(), Pageable.unpaged()).getContent();
} }
public List<ContractVo> findAllByCompanyCustomer(CustomerVo customer, LocalDate beginDate, LocalDate endDate) {
return findAll(ParamUtils.builder()
.equals("company", customer.getCompanyId())
.between("setupDate", beginDate, endDate)
.build(), Pageable.unpaged()).getContent();
}
public List<ContractVo> findAllByCompany(CompanyVo company, LocalDate beginDate, LocalDate endDate) {
return findAll(ParamUtils.builder()
.equals("company", company.getId())
.between("setupDate", beginDate, endDate)
.build(), Pageable.unpaged()).getContent();
}
public List<ContractVo> findAllSalesByProject(ProjectVo project) { public List<ContractVo> findAllSalesByProject(ProjectVo project) {
return findAll(ParamUtils.builder() return findAll(ParamUtils.builder()
.equals("parentCode", "") .equals("parentCode", "")
.equals("project", project.getId()).build(), .equals("project", project.getId()).build(),
Pageable.unpaged()).getContent(); Pageable.unpaged()).getContent();
} }
@@ -183,4 +196,18 @@ public class ContractService extends QueryService<ContractVo, ContractViewModel>
// TODO Auto-generated method stub // TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'syncContractFile'"); throw new UnsupportedOperationException("Unimplemented method 'syncContractFile'");
} }
public void mergeTo(CompanyVo from, CompanyVo to) {
List<ContractVo> contracts = findAllByCompany(from, LocalDate.MIN, LocalDate.MAX);
if (contracts == null || contracts.isEmpty()) {
return;
}
for (ContractVo contract : contracts) {
contract.setDescription(MyStringUtils.appendIfAbsent(contract.getDescription(), "转自 " + from.getId()));
contract.setCompanyId(to.getId());
save(contract);
}
}
} }

View File

@@ -12,8 +12,6 @@ import org.springframework.stereotype.Service;
import com.ecep.contract.vm.ContractTypeViewModel; import com.ecep.contract.vm.ContractTypeViewModel;
import com.ecep.contract.vo.ContractTypeVo; import com.ecep.contract.vo.ContractTypeVo;
import javafx.util.StringConverter;
@Service @Service
@CacheConfig(cacheNames = "contract-type") @CacheConfig(cacheNames = "contract-type")
public class ContractTypeService extends QueryService<ContractTypeVo, ContractTypeViewModel> { public class ContractTypeService extends QueryService<ContractTypeVo, ContractTypeViewModel> {

View File

@@ -30,11 +30,7 @@ public class CustomerService extends QueryService<CustomerVo, CompanyCustomerVie
} }
public CustomerVo findByCompany(CompanyVo company) { public CustomerVo findByCompany(CompanyVo company) {
Page<CustomerVo> page = findAll(ParamUtils.equal("company", company.getId()), Pageable.ofSize(1)); return findOneByProperty("company", company.getId());
if (page.isEmpty()) {
return null;
}
return page.getContent().getFirst();
} }
/** /**
@@ -140,4 +136,43 @@ public class CustomerService extends QueryService<CustomerVo, CompanyCustomerVie
// 替换文件名中的非法字符 // 替换文件名中的非法字符
return fileName.replaceAll("[/\\:*?\"<>|]", "_"); return fileName.replaceAll("[/\\:*?\"<>|]", "_");
} }
public void mergeTo(CompanyVo from, CompanyVo to) {
CustomerVo fromCustomer = findByCompany(from);
if (fromCustomer == null) {
return;
}
CustomerVo toCustomer = findByCompany(to);
if (toCustomer == null) {
// 直接修改关联
fromCustomer.setCompanyId(to.getId());
save(fromCustomer);
return;
}
mergeTo(fromCustomer, toCustomer);
}
/**
* 合并客户
* 1. 删除源客户对象
* 2. 删除源客户对象关联的文件
* 3. 修改目标客户对象的关联公司
* 4. 修改目标客户对象的关联文件
*
* @param from
* @param to
*/
public void mergeTo(CustomerVo from, CustomerVo to) {
// file
CompanyCustomerFileService companyCustomerFileService = SpringApp.getBean(CompanyCustomerFileService.class);
companyCustomerFileService.mergeTo(from, to);
// entity
CompanyCustomerEntityService companyCustomerEntityService = SpringApp.getBean(CompanyCustomerEntityService.class);
companyCustomerEntityService.mergeTo(from, to);
// 删除源客户对象
delete(from);
}
} }

View File

@@ -1,5 +1,7 @@
package com.ecep.contract.service; package com.ecep.contract.service;
import java.util.List;
import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
@@ -7,16 +9,18 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.ecep.contract.converter.DeliverySignMethodStringConverter;
import com.ecep.contract.util.ParamUtils; import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.vm.DeliverySignMethodViewModel; import com.ecep.contract.vm.DeliverySignMethodViewModel;
import com.ecep.contract.vo.DeliverySignMethodVo; import com.ecep.contract.vo.DeliverySignMethodVo;
import com.ecep.contract.vo.ProjectSaleTypeVo; import com.ecep.contract.vo.ProjectSaleTypeVo;
import java.util.List; import javafx.util.StringConverter;
@Service @Service
@CacheConfig(cacheNames = "delivery-sign-method") @CacheConfig(cacheNames = "delivery-sign-method")
public class DeliverySignMethodService extends QueryService<DeliverySignMethodVo, DeliverySignMethodViewModel> { public class DeliverySignMethodService extends QueryService<DeliverySignMethodVo, DeliverySignMethodViewModel> {
private final StringConverter<DeliverySignMethodVo> stringConverter = new DeliverySignMethodStringConverter(this);
@Cacheable(key = "#id") @Cacheable(key = "#id")
@Override @Override
@@ -24,6 +28,14 @@ public class DeliverySignMethodService extends QueryService<DeliverySignMethodVo
return super.findById(id); return super.findById(id);
} }
public DeliverySignMethodVo findByCode(String code) {
return findOneByProperty("code", code);
}
public DeliverySignMethodVo findByName(String name) {
return findOneByProperty("name", name);
}
/** /**
* 根据销售类型和编码查询发货方式 * 根据销售类型和编码查询发货方式
* *
@@ -32,15 +44,16 @@ public class DeliverySignMethodService extends QueryService<DeliverySignMethodVo
* @return * @return
*/ */
public DeliverySignMethodVo findBySaleTypeAndCode(ProjectSaleTypeVo saleType, String code) { public DeliverySignMethodVo findBySaleTypeAndCode(ProjectSaleTypeVo saleType, String code) {
Page<DeliverySignMethodVo> page = findAll(ParamUtils.builder() var builder = ParamUtils.builder();
.equals("saleType", saleType.getId()).build(), Pageable.unpaged()); builder.equals("saleType", saleType.getId());
builder.equals("code", code);
Page<DeliverySignMethodVo> page = findAll(builder.build(), Pageable.unpaged());
if (page.isEmpty()) { if (page.isEmpty()) {
return null; return null;
} }
return page.stream().filter(v -> v.getCode().equals(code)).findFirst().orElse(null); return page.stream().findFirst().orElse(null);
} }
@Cacheable(key = "'all'") @Cacheable(key = "'all'")
@Override @Override
public List<DeliverySignMethodVo> findAll() { public List<DeliverySignMethodVo> findAll() {
@@ -58,4 +71,9 @@ public class DeliverySignMethodService extends QueryService<DeliverySignMethodVo
public void delete(DeliverySignMethodVo entity) { public void delete(DeliverySignMethodVo entity) {
super.delete(entity); super.delete(entity);
} }
@Override
public StringConverter<DeliverySignMethodVo> getStringConverter() {
return stringConverter;
}
} }

View File

@@ -1,11 +1,81 @@
package com.ecep.contract.service; package com.ecep.contract.service;
import java.util.List;
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 org.springframework.stereotype.Service;
import com.ecep.contract.converter.DepartmentStringConverter;
import com.ecep.contract.vm.DepartmentViewModel; import com.ecep.contract.vm.DepartmentViewModel;
import com.ecep.contract.vo.DepartmentVo; import com.ecep.contract.vo.DepartmentVo;
import javafx.util.StringConverter;
@Service @Service
@CacheConfig(cacheNames = "department")
public class DepartmentService extends QueryService<DepartmentVo, DepartmentViewModel> { public class DepartmentService extends QueryService<DepartmentVo, DepartmentViewModel> {
private final DepartmentStringConverter stringConverter = new DepartmentStringConverter(this);
@Cacheable(key = "#p0")
@Override
public DepartmentVo findById(Integer id) {
return super.findById(id);
}
public DepartmentVo findByName(String name) {
try {
return async("findByName", name, String.class).handle((response, ex) -> {
DepartmentVo newEntity = createNewEntity();
return updateValue(newEntity, response);
}).get();
} catch (Exception e) {
throw new RuntimeException("查询实体失败" + name, e);
}
}
public DepartmentVo findByCode(String code) {
try {
return async("findByCode", code, String.class).handle((response, ex) -> {
if (ex != null) {
throw new RuntimeException("远程方法+findByCode+调用失败", ex);
}
DepartmentVo newEntity = createNewEntity();
return updateValue(newEntity, response);
}).get();
} catch (Exception e) {
throw new RuntimeException("查询实体失败" + code, e);
}
}
@Cacheable(key = "'departments'")
@Override
public List<DepartmentVo> findAll() {
return super.findAll();
}
@Caching(evict = {
@CacheEvict(key = "#p0.id"), @CacheEvict(key = "'departments'"),
})
@Override
public void delete(DepartmentVo entity) {
super.delete(entity);
}
@Caching(evict = {
@CacheEvict(key = "#p0.id"), @CacheEvict(key = "'departments'"),
})
@Override
public DepartmentVo save(DepartmentVo entity) {
return super.save(entity);
}
@Override
public StringConverter<DepartmentVo> getStringConverter() {
return stringConverter;
}
} }

View File

@@ -1,7 +1,11 @@
package com.ecep.contract.service; package com.ecep.contract.service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import com.ecep.contract.vo.DepartmentVo;
import javafx.collections.ObservableList;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.ecep.contract.vm.EmployeeRoleViewModel; import com.ecep.contract.vm.EmployeeRoleViewModel;
@@ -11,11 +15,38 @@ import com.ecep.contract.vo.FunctionVo;
@Service @Service
public class EmployeeRoleService extends QueryService<EmployeeRoleVo, EmployeeRoleViewModel> { public class EmployeeRoleService extends QueryService<EmployeeRoleVo, EmployeeRoleViewModel> {
public List<FunctionVo> getFunctionsByRoleId(int roleId) { public List<FunctionVo> getFunctionsByRole(EmployeeRoleVo role) {
// TODO Auto-generated method stub try {
throw new UnsupportedOperationException("Unimplemented method 'getFunctionsByRoleId'"); return async("getFunctionsByRoleId", role.getId(), Integer.class).handle((response, ex) -> {
if (ex != null) {
throw new RuntimeException("远程方法+getFunctionsByRoleId+调用失败", ex);
}
List<FunctionVo> list = new ArrayList<>();
try {
objectMapper.readerForUpdating(list)
.forType(objectMapper.getTypeFactory().constructCollectionType(List.class, FunctionVo.class))
.readValue(response);
} catch (IOException e) {
throw new RuntimeException(e);
}
return list;
}).get();
} catch (Exception e) {
throw new RuntimeException("查询实体失败, Function#" + role.getId(), e);
}
} }
public void saveRoleFunctions(EmployeeRoleVo role, List<FunctionVo> functions) {
try {
async("saveRoleFunctions", new Object[]{role.getId(), functions.stream().mapToInt(FunctionVo::getId).toArray()}, new Object[]{Integer.class, Integer[].class}).handle((response, ex) -> {
if (ex != null) {
throw new RuntimeException("远程方法+saveRoleFunctions+调用失败", ex);
}
return null;
}).get();
} catch (Exception e) {
throw new RuntimeException("保存角色的功能失败, 角色#" + role.getId(), e);
}
}
} }

Some files were not shown because too many files have changed in this diff Show More