127 Commits
0.0.45 ... main

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
5b3ab3ed00 refactor(util): 重构BeanContext接口及相关实现
将ContextUtils重命名为BeanContext,并统一客户端和服务端的实现
添加DefaultBeanContext作为默认实现
优化Inventory同步任务逻辑,支持WebSocket远程调用
更新tasker_mapper.json添加新的任务映射
移除未使用的syncInventory方法
2025-10-12 22:39:32 +08:00
59d78619da refactor(CloudRkService): 优化导入语句顺序并移除未使用的导入
移除未使用的Bank类导入,并按照标准顺序重新组织导入语句,提高代码可读性和维护性
2025-10-12 12:52:47 +08:00
86e18632aa feat(采购订单): 添加合同条目关联及税率绑定功能
新增采购订单条目与合同条目的关联字段,实现税率和税率锁定的UI绑定
优化采购订单同步逻辑,支持从U8系统获取更多字段信息
调整界面文本显示,修复部分字段绑定问题
2025-10-12 12:49:18 +08:00
ddd9dad945 refactor: 优化采购订单相关功能及代码结构
重构采购订单模块,包括以下改进:
1. 移除PurchaseOrderItemVo中冗余字段
2. 在ContractCtx中添加调试日志
3. 修改InventoryTabSkinContracts和PurchaseOrderTabSkinBillVoucher中的equals比较逻辑
4. 调整PurchaseOrderVo的税率字段类型并添加锁定标志
5. 修正FXML文件中的控制器路径
6. 优化InventoryStringConverter的toString方法格式
7. 在PurchaseBillVoucherItemService中添加凭证查询条件
8. 改进ContractRepairTasker的进度更新逻辑
9. 修复AbstContractRepairTasker中的子合同同步问题
10. 优化PurchaseOrderTabSkinItems的表格列显示
11. 添加InventoryCatalogStringConverter及相关缓存支持
12. 完善PurchaseBillVoucherService的查询逻辑
13. 增强ContractService的库存查询功能
14. 改进PurchaseOrderItemService的合同项查询逻辑
15. 为InventoryService添加缓存支持
16. 优化ContractTabSkinPurchaseOrders的员工列显示
17. 改进ContractTabSkinItemsV2的数量显示逻辑
18. 重构PurchaseOrderViewModel的数据绑定逻辑
2025-10-12 00:52:01 +08:00
333feb0d5a feat: 添加按ID查询的仓库方法并优化合同同步逻辑
添加了多个仓库的按ID查询方法,如findByCompanyId和findAllByContractId
优化了合同同步任务,使用ContractVo替代Contract减少数据库访问
重构了部分服务方法,支持通过ID直接操作,提升性能
修复了ProjectCostService中字段映射错误的问题
2025-10-11 17:56:02 +08:00
553feac0a4 UP 2025-10-11 09:21:14 +08:00
bda92193d4 feat: 添加ContextUtils接口和VendorReBuildFilesTasker类 2025-10-11 09:21:03 +08:00
0dcc9236a8 feat: 实现节假日服务功能并更新项目版本
refactor(CloudRkManagerWindowController): 简化任务初始化逻辑
feat(HolidayService): 添加节假日调整和检查功能
feat(HolidayTable): 实现Voable接口并添加toVo方法
feat(HolidayTableRepository): 添加节假日查询方法
docs: 添加服务器端Service类规则文档
build: 更新项目版本至0.0.101-SNAPSHOT
2025-10-10 10:21:51 +08:00
e49952a63c docs(entity_rules): 更新实体规则文档中的注解和接口描述
- 合并特定领域接口的描述为单一条目
- 明确指定@Table注解的schema默认值为"supplier_ms"
2025-10-09 18:32:50 +08:00
35e8fba805 docs(实体规则): 更新实体类目录结构和接口规范
- 将业务领域实体类按领域划分到不同目录
- 明确基础实体类和特定领域接口的存放位置
- 补充业务领域目录结构示例
2025-10-09 18:32:16 +08:00
c4eec0a9dd refactor(model): 重构模型类包结构并优化序列化处理
重构模型类包结构,将模型类按功能模块划分到不同的子包中。优化序列化处理,为VO类添加serialVersionUID并实现Serializable接口。移除部分冗余的serialVersionUID字段,简化模型类代码。同时修复UITools中空值处理的问题,并更新pom版本至0.0.100-SNAPSHOT。

- 将模型类按功能模块划分到ds子包中
- 为VO类添加序列化支持
- 移除冗余的serialVersionUID字段
- 修复UITools空值处理问题
- 更新项目版本号
2025-10-09 18:27:48 +08:00
51b8c16798 feat: 添加多个实体类实现基础接口和Voable接口
添加公司、项目、合同、供应商、客户等相关实体类,实现IdentityEntity、BasedEntity、CompanyBasedEntity等基础接口和Voable接口
包含公司文件、合同文件、供应商文件、客户文件等实体类
实现equals、hashCode和toVo方法
添加相关字段和JPA注解
2025-10-09 18:26:04 +08:00
49413ad473 refactor(service): 统一Service缓存为VO对象并优化关联实体处理
重构Service类实现,将QueryService泛型参数调整为VO类型,确保缓存VO对象而非实体。优化关联实体处理逻辑,减少重复代码。修改findById方法返回VO对象,新增getById方法获取实体。更新相关调用点以适配新接口。

调整WebSocket处理、控制器及Service实现,确保数据类型一致性。完善文档记录重构过程及发现的问题。为后续优化提供基础架构支持。
2025-09-29 19:31:51 +08:00
64471b46f8 docs: 更新server模块service缓存调整为Vo对象的文档标题
将所有相关文档标题从"IEntityService接口泛型修改任务"统一修改为"Server模块Service缓存调整为Vo对象",保持文档命名一致性
2025-09-28 19:19:01 +08:00
1f354853b0 refactor(service): 调整接口泛型参数并优化缓存策略
docs(task): 更新接口泛型修改相关文档

- 重构QueryService接口,移除未使用的Specification相关方法
- InventoryCatalogService实现IEntityService和QueryService接口
- 新增delete方法实现并调整缓存配置
- 删除过时的任务文档并重新组织文档结构
- 更新ALIGNMENT、CONSENSUS等文档,添加WebSocket服务兼容性说明
2025-09-28 19:11:53 +08:00
b03b5385a5 refactor(service): 修改IEntityService泛型为VO类型并优化缓存策略
重构所有注解@CacheConfig的Service类,将IEntityService泛型从实体类改为VO类
实现实体与VO之间的转换逻辑,使用VO替代实体进行缓存以避免序列化问题
更新相关依赖组件和测试用例,确保功能完整性和系统兼容性
优化Redis缓存配置,清理旧缓存数据并验证新缓存策略有效性
2025-09-28 18:19:00 +08:00
df6188db40 feat: 新增WebSocket任务管理器及相关任务实现
实现WebSocketServerTaskManager用于管理WebSocket任务,并添加多个任务类:
- CompanyContext和CloudRkContext接口定义
- WebSocketServerTasker接口及多个具体任务实现类
- ContractVerifyTasker合同验证任务
- ContractRepairTasker合同修复任务
- CompanyCustomerRebuildFilesTasker客户文件重建任务
- CompanyVerifyTasker企业验证任务
- CustomerFileMoveTasker客户文件移动任务
- CompanyCompositeUpdateTasker企业综合更新任务
- ProjectCostImportItemsFromContractsTasker项目成本导入任务
- 其他相关辅助任务类

这些任务类通过WebSocket与前端交互,实现各种业务功能
2025-09-28 18:18:32 +08:00
510952d72e feat: 添加企业文件管理功能及相关任务类
refactor: 重构企业文件验证和移动逻辑

fix: 修复企业合规验证逻辑及路径处理问题

docs: 添加VerifyContext工具类及相关文档

style: 优化代码格式及注释
2025-09-26 19:40:34 +08:00
a4db8a1644 docs: 更新VoableService接口实现分析文档
将详细的服务实现列表转换为表格形式,简化文档结构并提高可读性
2025-09-26 12:50:01 +08:00
42a8f9ab30 refactor(service): 实现VoableService接口以统一VO与实体映射逻辑
refactor(model): 重构实体类与VO类的字段映射关系
style: 调整代码格式与注释
fix: 修复部分字段映射错误
2025-09-26 12:31:08 +08:00
045a1e9eed docs: 添加Service层合规性分析报告文档
添加Contract-Manager项目Service层合规性分析报告,评估各Service实现是否符合规范要求,并列出不符合项和改进建议
2025-09-26 12:16:17 +08:00
97a2586c21 更新策略 2025-09-25 19:31:55 +08:00
45f7b611c5 feat: 实现VoableService接口并重构相关服务
refactor: 优化WebSocket通信和任务处理逻辑

fix: 修复客户和供应商路径选择功能

docs: 更新任务通信规则文档

build: 更新项目版本至0.0.86-SNAPSHOT

style: 清理无用导入和日志输出

test: 添加CustomerFileMoveTasker测试类

chore: 更新tasker_mapper.json注册信息
2025-09-25 18:57:17 +08:00
bf90117116 docs: 添加客户端模块Service层规则文档
添加客户端模块Service层的架构设计、缓存管理、数据操作模式等详细规则文档,总结项目中的最佳实践和规范要求
2025-09-25 17:56:36 +08:00
928d10c681 Merge branch 'main' of http://10.84.210.110/songqq/contract-manager 2025-09-25 11:02:48 +08:00
fa6920806d refactor(客户文件): 重构客户文件相关任务处理逻辑
- 移除CompanyCustomerFileService中未实现的searchEvaluationFile方法
- 删除已不再使用的tasker_mapper.json配置文件
- 重构CompanyCustomerEvaluationFormUpdateTask,移除无用日志记录
- 更新客户端与服务端Tasker通信文档中的文件路径格式
- 重构CustomerTabSkinFile中的文件重建和日期计算逻辑,改为直接调用服务方法
2025-09-25 11:02:45 +08:00
c21269975d 更新 README.md 2025-09-25 10:08:17 +08:00
ad42a49858 docs(task): 更新任务通信规则文档并添加任务注册描述
添加任务注册信息的描述字段到tasker_mapper.json
完善WebSocket通信机制文档,补充核心组件说明
修正属性同步机制中的空指针问题
优化代码格式和注释
2025-09-25 09:56:27 +08:00
2057c3ca67 feat: 实现客户端与服务器端Tasker通信机制及文件管理功能
refactor: 重构Tasker基类与服务获取逻辑
fix: 修复文件路径显示问题及任务注册加载机制
docs: 添加客户端与服务器端Tasker通信规则文档
style: 优化代码格式与日志输出
build: 添加tasker_mapper.json配置文件
chore: 清理无用代码与文件
2025-09-25 00:14:34 +08:00
dc764e6ed8 feat(表格单元格): 添加文件路径表格单元格基础类 2025-09-25 00:14:13 +08:00
09b0da498b feat(service): 实现国际化支持并优化Service层
重构文件类型相关Service以支持国际化查询
添加findOneByLang辅助方法统一查询逻辑
实现StringConverter支持UI控件显示
优化缓存配置和查询性能
新增UnitStringConverter和CustomerCatalogStringConverter
完善文档和测试用例
2025-09-24 16:20:49 +08:00
45eed8281f docs 2025-09-24 00:11:21 +08:00
7b023fd07b docs: 添加数据库结构文档和任务说明文档
添加多个数据库表结构SQL文件,包括供应商评分、企业报告等JSON数据模板
添加任务说明文档,包括枚举类型本地化实现方案、VO创建指南和异步任务监控方案
删除无用的CustomerFileTypeLocalRepository.java文件
2025-09-24 00:11:05 +08:00
9561ad99e6 refactor(vendor): 重构供应商相关类命名和包结构
将CompanyVendor前缀的类重命名为Vendor前缀,优化包结构
调整供应商相关控制器、服务、仓库类的命名和位置
修复TableViewUtils中的类型安全警告
更新FXML文件中的控制器引用路径
2025-09-23 23:37:44 +08:00
9c3306eea3 feat: 添加供应商管理相关功能及数据库表结构
新增供应商名录管理功能,包括合格供应商名录、供应商文件、供应商关联实体等模块。主要变更包括:

1. 添加COMPANY_VENDOR_ENTITY表的CREATOR_ID、MODIFIER_ID和MODIFY_DATE字段
2. 实现供应商同步任务类(VendorClassSyncTask等)
3. 新增供应商相关VO类(VendorApprovedVo、VendorFileVo等)
4. 添加供应商相关Repository接口(VendorRepository、VendorFileRepository等)
5. 实现供应商相关服务类(VendorApprovedService、VendorFileService等)
6. 添加供应商管理界面控制器及皮肤类
7. 新增供应商文件类型枚举和本地化配置
2025-09-23 23:37:04 +08:00
71d3ecab52 CompanyVendor 改为 Vendor 2025-09-23 22:50:30 +08:00
5919636c04 feat(供应商管理): 新增供应商实体类及VO转换方法
添加CompanyVendor实体类,包含供应商基本信息、关联关系和VO转换功能
2025-09-23 22:50:16 +08:00
57fbae90c5 feat: 实现文件类型枚举的本地化支持
新增文件类型枚举的本地化功能,包括供应商、项目、公司、合同和客户文件类型。添加了相关的SQL表结构、Repository、Service、ViewModel和StringConverter实现。同时更新了文档说明如何创建和使用枚举类型的本地化功能。

修改了客户文件类型的相关代码,统一使用CustomerFileTypeLocalVo替代原有的CompanyCustomerFileTypeLocal,优化了代码结构和一致性。添加了文件类型枚举的缓存支持,提高了性能。

更新了create_enum.md文档,详细说明了文件类型枚举本地化的实现方式和相关组件清单。
2025-09-23 22:34:59 +08:00
4b8c1d4038 refactor(文件类型): 重构文件类型相关仓库和服务
- 将CompanyVendorFileTypeLocalRepository重命名为VendorFileTypeLocalRepository
- 新增CustomerFileTypeLocalRepository
- 更新VendorFileTypeService使用新的仓库名称
- 新增CustomerFileTypeLocal实体类
- 更新文档结构
2025-09-23 18:42:46 +08:00
543311c676 refactor(controller): 重构枚举类型相关代码,优化类型转换和显示逻辑
重构枚举类型的处理逻辑,统一使用Vo对象替代Model对象
- 修改ComboBoxUtils初始化逻辑,支持更多类型转换
- 实现VendorCatalogStringConverter用于类型转换
- 更新ContractFileTypeListCell和ContractFileTypeTableCell显示逻辑
- 调整VendorTabSkinFile和CompanyVendorTabSkinBase使用新的类型转换方式
- 更新相关服务类接口,添加类型转换方法
- 修改FXML文件添加类型选择控件
2025-09-23 18:24:00 +08:00
73cbb4e19e feat: 添加合同文件类型相关类和文档
添加了VendorCatalogStringConverter转换器类、ContractFileTypeListCell列表单元格类和ContractFileTypeTableCell表格单元格类,并创建了相关文档文件
2025-09-23 18:21:26 +08:00
515b255567 feat: 添加供应商类型本地化支持及优化表格单元格显示
refactor: 重构供应商类型相关服务及控制器
fix: 修复供应商类型表格单元格初始化问题
style: 优化代码格式及导入顺序
2025-09-23 14:12:09 +08:00
386b6d01b4 feat: 添加项目文件和客户文件类型服务类
添加 ProjectFileTypeService 和 CompanyCustomerFileTypeService 服务类,用于处理文件和客户类型相关逻辑
2025-09-23 14:11:49 +08:00
39dbce013f feat(converter): 实现通用枚举转换器和供应商类型转换器
添加EnumEntityStringConverter作为通用枚举转换基类
实现VendorTypeStringConverter用于供应商类型本地化转换
在VendorTypeService中添加findByLocaleAndValue方法支持转换器
优化ComboBoxUtils的绑定逻辑使其支持可选属性
新增VendorCatalogService提供供应商目录CRUD功能
2025-09-22 23:54:50 +08:00
b84e011857 feat: 添加枚举和供应商类型转换器及供应商目录服务
添加三个新类:
1. EnumEntityStringConverter - 枚举实体字符串转换器
2. VendorTypeStringConverter - 供应商类型字符串转换器
3. VendorCatalogService - 供应商目录服务
2025-09-22 23:54:23 +08:00
866e08224a refactor(vo): 重构VO类及相关模型,添加Voable接口实现
feat(constant): 添加WebSocket错误码常量
docs(model): 为模型类添加注释
fix(service): 修复ProductUsageService缓存键问题
refactor(converter): 重构字符串转换器,移除EntityStringConverter依赖
feat(tab): 添加ComboBoxUtils工具类,优化下拉框初始化
style: 移除无用导入和字段
2025-09-22 23:11:21 +08:00
8aac509e51 feat(vendor): 添加VendorTypeService基础类结构 2025-09-22 23:10:45 +08:00
35a15f4702 refactor(contract): 重构客户文件类型相关代码,统一命名和继承结构
- 将 CompanyCustomerFileType 重命名为 CustomerFileType
- 统一相关 VO 和 model 的继承结构,使用 BaseEnumEntity
- 更新所有引用点,保持代码一致性
- 优化表格单元格显示逻辑,使用专用单元格工厂
2025-09-22 17:25:24 +08:00
3c3003fdf3 feat(contract): 添加供应商和客户文件类型相关类
添加VendorFileTypeLocalVo、CustomerFileType枚举和CustomerFileTypeLocalVo类,用于处理文件类型分类和本地化展示
2025-09-22 17:25:03 +08:00
35b33d401b feat: 添加VendorGroupRequireFileTypeVo及相关服务功能
refactor: 重构多个服务类和方法,优化代码结构
fix: 修复PermissionVo中code字段更名为key的问题
docs: 更新create_vo.md文档,添加新创建的VO记录
perf: 优化WebSocketClientService中的session关闭逻辑
style: 清理无用导入和注释,统一代码格式
2025-09-21 23:08:34 +08:00
039d753bab refactor(vo): 重构VO对象结构,统一字段命名和接口实现
重构所有VO对象,统一字段命名规范,移除冗余字段,优化接口实现
新增Voable接口用于VO对象转换
调整BaseViewModel和ProjectBasedViewModel接口定义
更新相关服务和控制器以适应VO对象变更
2025-09-21 17:47:52 +08:00
07c3f39a95 feat(vo): 新增并更新多个VO类实现ContractBasedVo接口
新增CloudTycVo、CloudYuVo、ExtendVendorInfoVo等VO类
更新SalesOrderVo、PurchaseOrderVo等实现ContractBasedVo接口
统一布尔类型字段为boolean并设置默认值false
2025-09-18 09:19:45 +08:00
f113cd8c48 feat(contract): 添加ContractBasedVo接口作为合同相关VO的基类 2025-09-18 09:19:29 +08:00
2752828094 refactor(client): 重构银行和公司相关代码
- 更新了多个类中的导入语句,替换了模型类为对应的VO类
- 优化了部分方法的参数和返回类型,使用VO类替代模型类
- 重构了自动补全功能,使用统一的泛型方法
- 添加了缓存注解,提高了数据访问性能
- 优化了部分代码结构,提高了可维护性
2025-09-18 09:08:57 +08:00
d0645c33f1 feat(common): 新增多个 VO 类
- 新增了多个与公司、合同、项目、库存等相关的 VO 类
- 这些类用于数据传输和接口定义,提高了系统的可维护性和扩展性
- 使用了 Lombok 注解,简化了类的编写
2025-09-18 08:45:08 +08:00
588779d611 feat(contract): 新增合同管理相关 VO 类
- 新增 CompanyContractVo 类,用于公司合同信息的传输
- 新增 CompanyCustomerEntityVo 类,用于公司客户实体信息的传输
- 新增 CompanyCustomerEvaluationFormFileVo 类,用于公司客户评估表文件信息的传输
2025-09-18 02:03:21 +08:00
9ffaac39cb feat(common): 新增 BankVo、CompanyBlackReasonVo 和 CompanyContactVo 类
- 新增 BankVo 类,用于表示银行信息
- 新增 CompanyBlackReasonVo 类,用于表示公司黑名单原因
- 新增 CompanyContactVo 类,用于表示公司联系人信息
2025-09-18 02:03:01 +08:00
9ef90f98c1 feat(contract): 添加公司银行账户价值对象
新增 CompanyBankAccountVo 类,用于表示公司银行账户相关信息。该类包含以下属性:
- id: 主键
- companyId:公司 ID,关联 CompanyVo- bankId: 银行 ID,关联 BankVo
- account: 银行账号
- openingBank: 开户行
- description: 描述信息
- active: 账户状态

此添加为合同模块提供了新的数据传输对象,有助于管理和展示公司银行账户信息。
2025-09-18 02:01:35 +08:00
c0e4916474 refactor: 重构供应商文件类型枚举及相关服务
feat: 为多个服务添加缓存支持
fix: 修复WebSocket任务管理和远程调用异常处理
refactor: 重命名CompanyVendorFileType为VendorFileType
refactor: 优化项目成本导入任务实现
fix: 修复ContractTabSkinBase中的空指针问题
refactor: 统一WebSocket客户端任务调用接口
2025-09-17 22:28:17 +08:00
7560250036 feat(contract): 添加供应商文件类型相关类及服务实现
添加供应商文件类型枚举、模型、视图模型及服务类
实现供应商文件类型的缓存查询、保存和删除功能
2025-09-17 22:27:05 +08:00
c42ff7501d refactor: 重构WebSocket服务及相关实体类
重构WebSocket服务名称从WebSocketService改为WebSocketClientService,并实现Serializable接口
添加WebSocket常量定义和消息处理实现
优化实体类equals和hashCode方法
修复控制器路径和日志配置
添加查询服务和任务接口方法
2025-09-17 11:45:50 +08:00
30deb0a280 feat: 实现WebSocket通信框架及任务管理功能
新增WebSocket客户端和服务端通信框架,包括会话管理、心跳检测和自动重连机制
添加任务管理器用于处理WebSocket任务创建和执行
实现消息回调处理和错误处理机制
重构销售类型服务并添加缓存支持
移除旧的销售类型服务实现
2025-09-17 11:44:39 +08:00
ada539bebf refactor(controller): 重构控制器类名和路径,优化代码结构
feat(service): 新增QueryService接口实现,支持通用查询功能

docs(util): 完善ProxyUtils工具类的注释说明

fix(model): 修复CustomerCatalog实现IdentityEntity接口

style: 优化代码格式和导入顺序

perf(util): 提升FileUtils工具类功能,新增文件处理方法

chore: 更新README.md文件,补充UI资源路径说明

build: 更新pom.xml文件中的mainClass配置

test: 调整测试类命名和路径

ci: 更新CI配置文件中的类引用

refactor(controller): 重构表格单元格异步更新逻辑

docs(constant): 新增常量定义和注释

style: 统一代码风格和命名规范

refactor(service): 重构服务类继承关系

perf(controller): 优化表格数据加载性能

fix(service): 修复文件类型服务缓存问题

docs(model): 完善视图模型类注释

refactor(util): 重构文件工具类方法

style: 清理无用导入和代码

chore: 更新.gitignore文件

build: 调整项目依赖配置

refactor(controller): 重构控制器基类

perf(service): 优化查询服务性能

fix(controller): 修复表格数据加载异常

docs: 更新代码注释和文档

style: 统一代码缩进和格式
2025-09-13 01:02:43 +08:00
5edb44f619 feat: 添加新模块的基础类和主应用类
添加了多个基础服务类和视图模型类,包括SysConfViewModel、CompanyConstant等
实现了客户端和服务端的主应用类,包含启动配置和默认连接参数
2025-09-12 21:29:59 +08:00
98e48c520f feat(proxy): 实现代理对象初始化检查与懒加载机制
添加ProxyUtils工具类用于检查代理对象初始化状态
实现代理对象创建和标记初始化功能
添加ProxyObjectDeserializerModifier处理反序列化时的代理对象创建
修改WebSocketService错误消息字段从errorMsg改为message
实现ContractItemService.findAllByInventory方法
优化ContractService查询条件处理并添加缓存支持
重构InventoryTabSkinHistoryPrice的service获取方式
2025-09-12 16:00:45 +08:00
422994efcd refactor: 重构服务依赖注入和上下文管理
移除硬编码的服务注入,改为使用缓存机制动态获取Bean
优化上下文类结构,统一服务获取方式
添加PageContent类支持分页数据封装
实现异步数据加载功能
2025-09-12 12:20:15 +08:00
fc263288e4 feat: 添加合约文件类型服务及错误处理改进
refactor: 重构合约文件类型相关代码,优化错误处理逻辑

fix: 修复WebSocket会话未绑定用户时的错误处理

style: 调整代码格式,提高可读性

docs: 更新部分代码注释

test: 添加合约文件类型服务的测试用例

chore: 移除无用代码,清理项目结构
2025-09-12 00:12:51 +08:00
a1b87de7c0 feat: 添加日志配置和Logback依赖
refactor: 重构实体类equals和hashCode方法

fix: 修复WebSocketService消息发送逻辑

style: 格式化代码和优化导入

docs: 更新JacksonConfig日期序列化格式

test: 添加CompanyFilePathTableCell测试类

chore: 清理无用代码和注释
2025-09-11 19:44:28 +08:00
375de610ef refactor(client): 重构服务类继承关系并统一使用QueryService
重构所有服务类,使其继承自QueryService接口,统一数据查询逻辑。同时为服务类添加@Service注解,确保Spring容器管理。更新相关FXML文件的控制器路径,从manager.ds调整为controller目录结构。调整pom.xml版本号至0.0.84-SNAPSHOT。新增MessageNotitfication和SimpleMessage消息类,提供基础消息结构支持。
2025-09-11 00:06:22 +08:00
23e1f98ae5 feat: 实现基于JSON的登录API和安全认证
refactor: 重构登录逻辑和会话管理

fix: 修复会话ID类型和WebSocket连接问题

build: 更新项目版本号和添加Servlet API依赖

style: 清理无用导入和注释代码
2025-09-08 17:46:48 +08:00
3b90db0450 refactor: 移除Hibernate依赖并重构代理对象初始化检查逻辑
feat(controller): 新增多个任务类用于合同和客户相关操作
feat(service): 新增ProxyUtils工具类替代Hibernate.isInitialized检查
refactor(controller): 重构多个控制器和皮肤类,使用ProxyUtils替代Hibernate
refactor(service): 重构服务类,移除Hibernate依赖并优化方法实现
fix(controller): 修复表格单元格初始化逻辑,确保代理对象正确加载
chore: 更新项目版本号至0.0.58-SNAPSHOT
docs: 添加MyProperties类用于管理下载路径配置
2025-09-06 17:22:12 +08:00
effd7b103c feat: 新增多个服务类及工具类,重构部分代码结构
重构服务类结构,将分散的服务统一整合到service包下
新增ProjectConstant常量类及多个实体服务类
添加SecurityUtils安全工具类和BeanCacher工具类
优化部分UI控件和转换器的实现
2025-09-06 13:43:52 +08:00
0e444508ff feat: 重构员工控制器并优化JSON序列化配置
refactor(EmployeeController): 重命名EmployyeController为EmployeeController并优化代码结构
feat(EmployeeController): 添加@JsonIgnoreProperties注解解决循环引用问题
feat(JacksonConfig): 新增Jackson配置类处理Hibernate代理和循环引用
fix(EmployeeService): 修复缓存注解格式问题
feat(Employee): 添加@JsonIgnoreProperties注解忽略可能导致循环引用的字段
feat(EmployeeRole): 添加@JsonIgnore注解忽略关联字段
fix(application.properties): 调整Redis缓存配置和错误处理设置
refactor(IndexController): 移除错误处理方法
feat(GlobalExceptionHandler): 新增全局异常处理类
refactor(SecurityConfig): 优化安全配置并启用方法级安全注解
refactor(AbstractCtx): 优化日期时间处理方法
build: 更新项目版本至0.0.53-SNAPSHOT
2025-09-04 16:06:47 +08:00
acb63116d5 docs: 优化README.md的格式和可读性
调整项目介绍中的模块说明格式,使用更清晰的列表展示方式
2025-09-03 23:11:54 +08:00
72f8e2e209 Merge branch 'main' of http://10.84.210.110/songqq/contract-manager 2025-09-03 23:09:48 +08:00
b927f9d572 build: 添加FixPackageNames相关编译文件
添加FixPackageNames.class及其内部类FixPackageNames$1.class的编译输出文件
2025-09-03 23:09:40 +08:00
a2f5e4864b 拆分模块 2025-09-03 20:56:44 +08:00
08cc2c29a5 拆分模块 2025-09-03 20:48:39 +08:00
7d684a5006 删除 null 2025-08-31 23:10:08 +08:00
8139a45f06 更新 docs/异步任务监控实现方案.md 2025-08-31 23:09:54 +08:00
cca51c6fcc feat(project): 新增项目资金计划功能 #2
添加项目资金计划模块,包括以下内容:
- 新增项目资金计划实体类及相关数据库表结构
- 实现资金计划Repository和服务层
- 添加资金计划Tab页及FXML界面
- 实现从合同付款计划同步资金计划功能
- 添加资金计划表格展示及更新操作
2025-08-28 19:54:20 +08:00
1514cb0f9f refactor(CloudTycService): 优化配置获取方式并移除多余空行
将SysConfRepository替换为SysConfService以获取配置参数,提升代码可维护性
清理类中多余的空行,保持代码整洁
2025-08-28 16:07:03 +08:00
cf73769ef2 refactor(配置管理): 重构配置绑定与保存逻辑
将配置绑定逻辑抽象到StringConfig类中,统一处理配置的保存与控件绑定
提取配置键常量到对应服务类中,提高代码可维护性
使用LocalDateTime替代Instant处理时间字段
简化SysConfRepository继承结构,使用自定义基础仓库
优化SysConfWindowController的配置管理逻辑,减少重复代码
2025-08-28 15:55:24 +08:00
c793c0925e refactor(配置管理): 重构配置绑定组件结构并添加布尔类型支持
将原有配置绑定组件重构为泛型抽象基类,统一处理不同类型配置的绑定逻辑。新增BooleanConfig支持布尔类型配置,并优化了LocalDateTimeConfig的转换器处理。

重构后的AbstractConfigBounder提供通用属性绑定和变更处理,子类只需实现特定类型的双向绑定逻辑。同时调整了相关UI界面,添加了布尔配置开关控件。
2025-08-28 15:09:54 +08:00
c69d3f1af2 refactor(ui): 重构BaseController及相关组件,优化代码结构和性能
重构BaseController,移除冗余属性,引入bean缓存机制
新增LocalDateTimeConfig类处理日期时间配置
优化AbstEntityManagerSkin的分页和表格数据处理逻辑
调整ContractTabSkinBase的依赖注入方式,使用缓存bean
统一日期、数字和货币的格式化处理,使用CurrentEmployee的配置
2025-08-27 19:36:30 +08:00
f810532824 fix: 将 AsyncUpdateTableCell 的日志级别从 debug 调整为 warn
修改日志级别以减少不必要的调试信息输出,仅保留警告及以上级别的日志
2025-08-27 14:44:45 +08:00
fb28bac53a refactor(ui): 优化表格数据加载和异步单元格更新逻辑
修复表格高度调整时频繁触发数据加载的问题,增加调整阈值判断
改进异步单元格更新逻辑,增加取消机制和状态检查
重构常量命名,使用更清晰的KEY_SYNC_BY前缀
添加日志记录和注释说明
2025-08-27 14:43:55 +08:00
9ff84ebe8a refactor(u8配置): 重构配置管理相关代码
将配置管理相关代码重构为ConfigBounder接口及其实现类,提取公共逻辑到AbstractConfigBounder基类
修复公司创建日期配置的显示问题,使用更直观的中文描述
将合同同步相关配置常量集中到ContractCtx中管理
2025-08-25 19:40:07 +08:00
6711657663 feat(u8): 添加用友U8配置窗口和数据迁移功能
新增用友U8配置窗口,支持日期和文本配置项的编辑与保存。实现从CloudInfo到CloudYu的数据迁移任务,优化任务执行方式。重构多个同步任务类继承Tasker基类,统一任务处理逻辑。扩展YongYouU8Service功能,添加配置相关接口。调整UI布局和菜单项,增加配置入口。

refactor: 重命名CompanyTableCell为EmployeeRoleTableCell

style: 清理无用导入和格式化代码

fix: 修复ContractTypeSyncTask中分类和方向字段设置错误
2025-08-25 18:57:17 +08:00
17e326b35c refactor(ui): 重命名并移动EvaluationFileTableCell为CompanyTableCell
将EvaluationFileTableCell从project.controller包移动到table.cell包,并重命名为CompanyTableCell。同时修改其实现以使用CompanyService而非CompanyCustomerFileService,使其更符合新的业务需求。
2025-08-25 18:56:55 +08:00
danyz
fa25130c9f Merge branch 'main' of http://10.84.210.110/songqq/contract-manager into main 2025-08-25 00:41:45 +08:00
danyz
b0b67b5d7f feat(任务监控): 添加Executor信息监控面板并重构界面
重构任务监控界面布局,将演示任务功能移至监控窗口工具栏
新增Executor信息监控面板,显示线程池详细状态信息
移除主界面中的演示任务按钮,更新项目版本号
2025-08-25 00:41:34 +08:00
1602 changed files with 86683 additions and 34035 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 ###
.vscode/
/config.properties
/config.properties
node_modules
node_modules

View File

@@ -0,0 +1,223 @@
# 客户端 Controller 类规则
## 1. 目录结构
客户端控制器位于`client/src/main/java/com/ecep/contract/controller/`目录下,按业务模块组织:
- **根目录**:包含基础控制器、抽象控制器和主窗口控制器
- **业务子包**:按业务领域组织,如`company/``project/``contract/``vendor/`
- **tab/子包**包含所有Tab皮肤控制器和相关接口、抽象类
- **table/子包**:包含表格相关控制器和单元格实现
## 2. 命名规范
- **基础控制器**`BaseController.java`
- **抽象控制器**:以`Abst`开头,如`AbstEntityController.java``AbstManagerWindowController.java`
- **窗口控制器**:以`WindowController`结尾,如`HomeWindowController.java`
- **管理窗口控制器**:以`ManagerWindowController`结尾,如`CompanyManagerWindowController.java`
- **Tab皮肤控制器**:以`TabSkin`结尾,如`CompanyTabSkinBase.java`
- **管理器皮肤**:以`ManagerSkin`结尾,如`CompanyManagerSkin.java`
- **组件控制器**:如`OkHttpLoginController.java`
## 3. 继承关系
控制器类遵循以下继承层次结构:
- `BaseController`:所有控制器的基础类,提供窗口显示、异常处理等通用功能
- `AbstEntityBasedController`:基于实体的控制器抽象类
- `AbstManagerWindowController`:管理窗口控制器的抽象类,包含搜索、分页等功能
- `AbstEntityController<T extends IdentityEntity, TV extends IdentityViewModel<T>>`:实体详情窗口控制器的抽象类
- 具体业务窗口控制器,如`CompanyWindowController``ProjectWindowController`
Tab相关控制器继承关系
- `Skin`:皮肤接口基础
- `TabSkin`Tab皮肤接口
- `AbstGenericTabSkin<C extends BaseController>`通用Tab皮肤抽象类
- `AbstEntityBasedTabSkin<C extends AbstEntityController<T, V>, T extends IdentityEntity, V extends IdentityViewModel<T>>`基于实体的Tab皮肤抽象类
- 具体业务Tab皮肤控制器`CompanyTabSkinBase.java``ContractTabSkinFiles.java`
## 4. 注解使用
客户端控制器类应使用以下注解:
- `@Component`声明为Spring组件使其可被Spring容器管理
- `@Scope("prototype")`:设置为原型作用域,确保每次请求创建新实例
- `@Lazy`:延迟加载,提高应用启动性能
- `@FxmlPath("/ui/[业务模块]/[文件名].fxml")`指定对应的FXML文件路径
- `@Autowired`:自动注入依赖的服务层组件
## 5. FXML文件规范
- **文件位置**FXML文件通常位于`/client/src/main/resources/ui/`目录下,按业务模块组织子目录
- **命名规范**:使用小写字母和下划线,如`home.fxml``company/company.fxml`
- **关联方式**:通过`@FxmlPath`注解指定控制器对应的FXML文件路径
- **组件ID**FXML文件中组件的id应与控制器中对应的字段名保持一致驼峰命名法
## 6. 控制器方法规范
### 6.1 初始化方法
- `initialize()`控制器初始化方法用于设置UI组件初始状态、绑定事件监听器等
- 此方法会在FXML加载完成后自动调用
### 6.2 窗口生命周期方法
- `onShown(WindowEvent windowEvent)`:窗口显示时触发的方法
- 用于执行窗口显示后的初始化操作,如数据加载、组件状态更新等
- 通常需要调用`super.onShown(windowEvent)`确保父类逻辑执行
### 6.3 窗口显示方法
- `public static void show()`:静态方法,用于便捷地显示控制器对应的窗口
- 通常有多个重载版本支持不同参数如viewModel、owner窗口等
- 内部调用`BaseController.show()`方法实现窗口显示逻辑
### 6.4 Tab相关方法
Tab皮肤控制器包含以下核心方法
- `install()`安装Tab皮肤加载FXML并初始化UI组件
- `initializeUIComponents()`初始化UI组件设置数据绑定和事件监听
- `getTab()`获取对应的Tab对象
- `save()`保存Tab中的数据变更
- `dispose()`:释放资源,清理监听器等
## 7. 字段规范
- **UI组件字段**与FXML文件中的组件id对应使用`public`访问修饰符
- **服务依赖字段**:使用`@Autowired`注解注入,通常使用`private`访问修饰符
- **ViewModel字段**:通常作为控制器的核心数据模型,提供`getter/setter`方法
- **命名规范**:使用驼峰命名法,如`nameField``tabPane``saveBtn`
## 8. 数据绑定与更新
- **ViewModel绑定**控制器通常通过ViewModel与实体数据进行绑定
- **异步数据加载**:使用`CompletableFuture`进行异步数据加载避免阻塞UI线程
- **UI更新**在JavaFX应用线程中更新UI组件可使用`Platform.runLater()`确保线程安全
## 9. 事件处理
- **按钮点击事件**:通过`setOnAction()`方法绑定事件处理器
- **表单字段变更**可通过JavaFX的属性绑定机制监听字段变更
- **Tab切换事件**:实现`onTabShown()`方法处理Tab显示事件
## 10. 异常处理
- 使用`handleException()`方法统一处理异常
- 异常信息通常会显示在状态栏并记录日志
- 异步操作中的异常应通过`exceptionally()``handle()`方法捕获处理
## 11. 最佳实践
- **职责分离**控制器应专注于UI交互和数据展示业务逻辑应委托给服务层
- **避免重复代码**:利用抽象类和接口提取通用逻辑
- **资源管理**:及时释放资源,避免内存泄漏
- **线程安全**确保在JavaFX应用线程中更新UI
- **代码可读性**:添加适当的注释,遵循命名规范
## 12. 示例代码结构
```java
@Lazy
@Scope("prototype")
@Component
@FxmlPath("/ui/[模块名]/[文件名].fxml")
public class [业务]WindowController extends AbstEntityController<[Vo类型], [ViewModel类型]> {
private static final Logger logger = LoggerFactory.getLogger([业务]WindowController.class);
@Autowired
private [业务]Service [业务]Service;
// UI组件字段
public BorderPane root;
public TabPane tabPane;
public TextField nameField;
// ...其他UI组件
public static void show([Vo类型] entity, Window window) {
show([ViewModel类型].from(entity), window);
}
public static void show([ViewModel类型] viewModel, Window window) {
BaseController.show([业务]WindowController.class, viewModel, window);
}
@Override
public void initialize() {
// 初始化UI组件和事件监听
}
@Override
public void onShown(WindowEvent windowEvent) {
super.onShown(windowEvent);
// 窗口显示后的逻辑
}
}
```
## 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

@@ -0,0 +1,239 @@
# 客户端转换器类规则
## 1. 目录结构
- 所有类型转换器类必须位于 `com.ecep.contract.converter` 包下
- 按照实体类型组织,无需进一步分包
## 2. 命名规范
- 类名格式:`[EntityName]StringConverter`
- 例如:`ContractStringConverter``CompanyStringConverter``VendorStringConverter`
## 3. 继承关系
### 3.1 基础转换器类型
- 直接继承 `javafx.util.StringConverter<T>` 接口
- 适用于简单的转换场景
- 例如:
```java
public class ContractStringConverter extends StringConverter<ContractVo> {
// 实现代码
}
```
### 3.2 增强转换器类型
- 继承自定义的 `EntityStringConverter<T>` 抽象类
- 适用于需要更多功能(如延迟初始化、搜索建议等)的场景
- 大多数业务实体转换器应使用此类型
- 例如:
```java
public class CompanyStringConverter extends EntityStringConverter<CompanyVo> {
// 实现代码
}
```
### 3.3 枚举转换器类型
- 继承 `EnumEntityStringConverter<E extends Enum<?>, T extends BaseEnumEntity<E>>`
- 专门用于处理枚举类型实体
- 例如:
```java
public class EnumEntityStringConverter<E extends Enum<?>, T extends BaseEnumEntity<E>> extends StringConverter<T> {
// 实现代码
}
```
## 4. 核心接口实现
### 4.1 StringConverter<T> 接口
- 所有转换器必须实现 `toString(T object)` 方法
- 所有转换器必须实现 `fromString(String string)` 方法
## 5. Spring框架集成
### 5.1 组件注解
- 增强转换器类型通常使用 `@Component` 注解标记为Spring组件
- 结合 `@Lazy` 注解实现延迟加载
- 例如:
```java
@Lazy
@Component
public class CompanyStringConverter extends EntityStringConverter<CompanyVo> {
// 实现代码
}
```
### 5.2 依赖注入
- 使用 `@Autowired` 注解注入对应的Service实例
- 通常也需要为注入的Service添加 `@Lazy` 注解
- 例如:
```java
@Lazy
@Autowired
private CompanyService service;
```
## 6. 初始化机制
### 6.1 构造函数
- 基础转换器通常使用构造函数接收Service实例
- 例如:
```java
public ContractStringConverter(ContractService service) {
this.service = service;
}
```
### 6.2 初始化方法
- 增强转换器使用 `@PostConstruct` 注解标记初始化方法
- 在初始化方法中配置转换器的各种功能
- 例如:
```java
@PostConstruct
private void init() {
setInitialized(project -> service.findById(project.getId()));
setSuggestion(service::search);
setFromString(service::findByName);
}
```
## 7. EntityStringConverter 特性配置
继承自 EntityStringConverter 的增强转换器可以配置以下特性:
### 7.1 对象初始化
- 使用 `setInitialized(Function<T, T> initialized)` 配置对象延迟初始化逻辑
- 通常调用Service的findById方法
- 例如:`setInitialized(project -> service.findById(project.getId()))`
### 7.2 搜索建议
- 使用 `setSuggestion(Function<String, List<T>> suggestion)` 配置自动补全数据提供方法
- 通常调用Service的search方法
- 例如:`setSuggestion(service::search)`
### 7.3 字符串转换
- 使用 `setFromString(Function<String, T> fromString)` 配置从字符串到对象的转换逻辑
- 通常调用Service的findByName方法
- 例如:`setFromString(service::findByName)`
### 7.4 格式化器
- 使用 `setFormater(Function<T, String> formater)` 配置自定义格式化逻辑
- 用于自定义对象的字符串表示
- 例如:`setFormater(contract -> contract.getCode() + " " + contract.getName())`
## 8. 核心方法实现规范
### 8.1 toString(T object) 方法
- 将对象转换为可读的字符串表示
- 必须处理null情况
- 通常返回对象的名称、编码或其他关键标识
- 对于基础转换器:
```java
@Override
public String toString(ContractVo cc) {
return cc.getCode() + " " + cc.getName();
}
```
- 对于枚举转换器:
```java
@Override
public String toString(T object) {
return object == null ? "" : object.getValue();
}
```
### 8.2 fromString(String string) 方法
- 从字符串还原对象实例
- 通常调用Service的findByName或findByCode方法
- 例如:
```java
@Override
public ContractVo fromString(String string) {
return service.findByCode(string);
}
```
- 对于某些场景如枚举转换器可能返回null
## 9. 与Service类的关联
### 9.1 Service中的转换器创建
- 在Service类中通常会创建并持有对应的StringConverter实例
- 实现 `getStringConverter()` 方法返回转换器实例
- 例如:
```java
@Service
@CacheConfig(cacheNames = "contract-cache")
public class ContractService extends QueryService<ContractVo, ContractViewModel> {
private final ContractStringConverter stringConverter = new ContractStringConverter(this);
@Override
public StringConverter<ContractVo> getStringConverter() {
return stringConverter;
}
}
```
### 9.2 Service方法支持
- Service需提供 `findByName``findById``search` 等方法供转换器使用
- 这些方法通常需要添加缓存注解以提高性能
## 10. 使用场景
StringConverter主要用于以下场景
- ComboBox、ChoiceBox等选择组件的数据绑定和显示
- 表单数据的绑定和转换
- 支持用户输入的自动补全功能
- 在表格、列表等UI组件中显示对象的可读表示
## 11. 最佳实践
- 对于大多数业务实体,优先使用继承 `EntityStringConverter<T>` 的增强转换器
- 使用Spring的依赖注入和组件管理功能
- 正确处理null值和空字符串
- 为频繁调用的Service方法添加缓存支持
- 实现合理的toString逻辑提供有意义的对象表示
## 12. 示例代码结构
### 12.1 基础转换器示例
```java
package com.ecep.contract.converter;
import com.ecep.contract.service.ContractService;
import com.ecep.contract.vo.ContractVo;
import javafx.util.StringConverter;
public class ContractStringConverter extends StringConverter<ContractVo> {
private final ContractService service;
public ContractStringConverter(ContractService service) {
this.service = service;
}
@Override
public String toString(ContractVo cc) {
return cc == null ? "" : cc.getCode() + " " + cc.getName();
}
@Override
public ContractVo fromString(String string) {
return string == null || string.isEmpty() ? null : service.findByCode(string);
}
}
```
### 12.2 增强转换器示例
```java
package com.ecep.contract.converter;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.vo.CompanyVo;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
@Lazy
@Component
public class CompanyStringConverter extends EntityStringConverter<CompanyVo> {
@Lazy
@Autowired
private CompanyService service;
@PostConstruct
private void init() {
setInitialized(company -> service.findById(company.getId()));
setSuggestion(service::search);
setFromString(service::findByName);
}
}
```

View File

@@ -0,0 +1,508 @@
# Client Service 实现编写指南
## 📋 概述
本指南总结 Client 模块 Service 层的实现经验,用于指导后续 Service 的编写。本指南基于 Contract-Manager 项目中已实现的 Service 模式整理。
---
## 🏗️ 基础架构
### 1. 继承层次结构
```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
@CacheConfig(cacheNames = "business")
public class XxxService extends QueryService<XxxVo, XxxViewModel> {
// 业务特定实现
}
```
### 2. 核心特性
- **泛型支持**`QueryService` 使用泛型处理不同类型的 Vo 和 ViewModel
- **WebSocket 通信**:通过 `WebSocketClientService` 与 Server 端通信
- **异步处理**:使用 `CompletableFuture` 实现异步操作
- **缓存机制**:集成 Spring Cache 支持多级缓存
- **错误处理**:统一的异常处理和日志记录
---
## 📝 Service 编写规范
### 1. 类声明和注解
```java
@Service // Spring 组件注解
@CacheConfig(cacheNames = "xxx") // 缓存配置xxx为业务域名称
public class XxxService extends QueryService<XxxVo, XxxViewModel> {
// Service 实现
}
```
### 2. 缓存策略
#### 缓存注解使用
```java
@Cacheable(key = "#p0") // 按ID缓存
@Cacheable(key = "'code-'+#p0") // 按代码缓存
@Cacheable(key = "'name-'+#p0") // 按名称缓存
@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
// 异步调用示例
public XxxVo 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);
}
}
```
### 2. 复杂对象处理
```java
public List<XxxDetailVo> findDetailsByXxxId(Integer xxxId) {
try {
return async("findDetailsByXxxId", List.of(xxxId), List.of(Integer.class))
.handle((response, ex) -> {
if (ex != null) {
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);
}
}
```
---
## 💼 业务逻辑模式
### 1. 文件系统集成
#### 路径管理
```java
@Autowired
private SysConfService confService;
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,74 @@
# 实体类规则
## 1. 目录结构
- **基础实体类和接口**:放置在 `common/src/main/java/com/ecep/contract/model/` 目录下
- **业务领域实体类**:放置在 `server/src/main/java/com/ecep/contract/ds/{业务领域}/model/` 目录下,按业务领域组织
- 例如:公司相关实体在 `ds/company/model/` 目录下
- 合同相关实体在 `ds/contract/model/` 目录下
- 客户相关实体在 `ds/customer/model/` 目录下
- 项目相关实体在 `ds/project/model/` 目录下
- 供应商相关实体在 `ds/vendor/model/` 目录下
## 2. 命名规范
- 类名:使用驼峰命名法,首字母大写,如 `Contract.java``Company.java`
- 表名:通常与类名对应,使用大写字母和下划线,如 `CONTRACT``COMPANY`
- 字段名:使用小写字母和驼峰命名法,对应数据库中使用大写字母和下划线的列名
## 3. 继承与接口实现
- 实体类通常实现以下接口:
- `IdentityEntity`提供主键ID的getter/setter及equals方法实现
- `BasedEntity`提供toPrettyString()方法
- `NamedEntity`提供name属性的getter/setter适用于有名称的实体
- `Voable<T>`提供toVo()方法用于将实体转换为对应的VO对象
- **特定领域接口**:放置在对应的业务领域目录下,如 `CompanyBasedEntity``ds/company/model/` 目录下定义与某一业务领域相关的通用方法如getter/setter
## 4. 注解规范
- **JPA注解**
- `@Entity`:标记为实体类
- `@Table(name = "表名", schema = "supplier_ms")`指定对应的数据库表schema通常为"supplier_ms"
- `@Id`:标记主键字段
- `@GeneratedValue(strategy = GenerationType.IDENTITY)`:指定主键生成策略
- `@Column(name = "列名", nullable = false, length = 长度)`:指定字段对应的数据库列属性
- `@ManyToOne(fetch = FetchType.LAZY)`指定多对一关系通常使用LAZY加载
- `@JoinColumn(name = "外键列名")`:指定外键列
- `@Enumerated(EnumType.ORDINAL)`:指定枚举类型的存储方式
- `@Lob`:指定大对象类型
- `@Version`:指定乐观锁版本字段
- **Lombok注解**
- `@Getter`生成getter方法
- `@Setter`生成setter方法
- `@ToString`生成toString方法复杂关联对象可使用`@ToString.Exclude`排除
## 5. 字段规范
- 主键字段:
- 类型:`Integer`
- 命名:`id`
- 注解:`@Id``@GeneratedValue(strategy = GenerationType.IDENTITY)`
- 其他字段:
- 基本类型使用Java包装类型`Integer``Boolean`)而不是原始类型
- 时间类型:使用`java.time.LocalDate``java.time.LocalDateTime`
- 布尔类型:使用`boolean``Boolean`,对应数据库中的`BIT``BOOLEAN`类型
- 关联关系:使用`@ManyToOne``@OneToMany`等注解定义
## 6. 方法规范
- `equals`方法:通常使用`HibernateProxyUtils`工具类处理代理对象的比较
- `hashCode`方法:通常使用`HibernateProxyUtils.hashCode(this)`实现
- `toVo`方法:实现`Voable`接口将实体转换为对应的VO对象
- `toString`方法通常使用Lombok的`@ToString`注解生成
## 7. 注释规范
- 类注释使用JavaDoc格式描述实体类的用途
- 字段注释使用JavaDoc格式描述字段的含义、数据来源、取值范围等
- 对于特殊字段,可包含详细的说明表格或示例
## 8. 序列化规范
- **重要**:实体类**不包含**`Serializable`接口和`serialVersionUID`字段
- 序列化相关功能由对应的VO类实现
## 9. 最佳实践
- 实体类应保持简洁,只包含数据和基本的数据访问方法
- 业务逻辑应在服务层实现
- 复杂的查询逻辑应在Repository层或通过Specification实现
- 避免在实体类中使用复杂的计算逻辑
- 对于多对多关系,建议使用中间表和两个一对多关系实现
- 关联关系应使用懒加载LAZY以提高性能

View File

@@ -1,13 +1,284 @@
Java 21
Spring Boot 3.3.7
Spring Data JPA 3.3.7
JavaFX 21
ControlsFX 11.1.2
MySQL 8.0.33
Lombok 1.18.32
POI 5.2.5
PDFBox 3.0.1
# 项目规则
## 技术栈规范
### server 模块
- Java 21
- Spring Boot 3.3.7
- Spring Data JPA 3.3.7
- MySQL 8.0.33
- Lombok 1.18.32
- POI 5.2.5
- PDFBox 3.0.1
- Redis
### client 模块
- Java 21
- JavaFX 21
- ControlsFX 11.1.2
- Lombok 1.18.32
- caffeine 3.1.8
- .fxml 界面UI, 放置于 /client/src/main/resources/ui/ 目录下
- websocket 与 server 模块通信
- StringConverter 创建规则与实现逻辑 `docs/task/string_converter_implementation_guide.md`
- TableCell 规则与逻辑 `docs/task/table_cell_implementation_guide.md`
- Service 规则与逻辑 `docs/task/serservice_layer_rules.md`
- 客户端 Tasker 至 服务器端 Tasker 通信规则与逻辑 `docs/task/client_server_tasker_communication_rules.md`
### common 模块
- Java 21
- Lombok 1.18.32
## 文件命名规范
- Java类名使用驼峰命名法首字母大写`ContractService.java`
- 接口名使用驼峰命名法首字母大写以I开头`IContractService.java`
- 控制器类名以Controller结尾`ContractController.java`
- 服务类名以Service结尾`ContractService.java`
- 实体类名:使用驼峰命名法,首字母大写,如 `Contract.java`
- FXML文件使用小写字母和下划线`contract_view.fxml`
- SQL文件表名使用大写和下划线`CONTRACT_TYPE_LOCAL.sql`
## 目录结构规范
### 项目整体结构
- `client/`: 客户端模块基于JavaFX实现的桌面应用
- `server/`: 服务端模块基于Spring Boot实现的后端服务
- `common/`: 公共模块,包含客户端和服务端共享的代码
- `docs/`: 项目文档和数据库脚本目录
- `.trae/`: Trae IDE相关配置和规则
- `.mvn/`: Maven包装器配置
- 根目录下包含Maven构建文件和配置文件
### server 模块目录结构
- `src/main/java/com/ecep/contract/`: 主包路径
- `api/`: API接口定义
- `cloud/`: 云服务集成相关代码
- `config/`: Spring Boot配置类
- `controller/`: Web控制器
- `ds/`: 数据访问层,按业务领域组织
- `company/`: 公司相关业务
- `contract/`: 合同相关业务
- `customer/`: 客户相关业务
- `project/`: 项目相关业务
- `vendor/`: 供应商相关业务
- 每个业务领域包含:`model/`(实体类)、`repository/`(数据访问接口)、`service/`(业务逻辑, 详细规范见 `.trae\rules\server_service_rules.md`)、`tasker/`(任务处理器, 详细规范见 `.trae\rules\server_tasker_rules.md`)、`controller/`(控制器, 详细规范见 `.trae\rules\server_controller_rules.md`)
- `handler/`: WebSocket处理器
- `service/`: 服务层,包含一些通用服务和任务处理器
- `ui/`: UI相关组件
- `util/`: 工具类
- 核心服务接口和基类文件直接位于contract包下
- `src/main/resources/`: 资源文件目录
- `src/test/`: 测试代码目录
### client 模块目录结构
- `src/main/java/com/ecep/contract/`: 主包路径
- `controller/`: JavaFX控制器按业务领域组织, 详细规范见 .trae\rules\client_controller_rules.md
- 包含各种业务窗口控制器和Tab皮肤控制器
- `converter/`: 类型转换器,详细规范见 .trae\rules\client_converter_rules.md
- `serializer/`: 序列化相关类
- `service/`: 客户端服务层,与服务端交互, 详细规范见 .trae\rules\client_service_rules.md
- `task/`: 客户端任务类, 详细规范见 .trae\rules\client_task_rules.md
- `util/`: 工具类
- `vm/`: 视图模型
- `src/main/resources/ui/`: FXML界面文件目录
- `src/test/`: 测试代码目录
### common 模块目录结构
- `src/main/java/ecep/contract/`: 包含客户端和服务端共享的代码
- `constant/`: 常量类,按业务领域组织
- `model/`: 实体类,按业务领域组织, 不包含Serializable接口和serialVersionUID字段 详细规范见 .trae\rules\entity_rules.md
- `vo/`: 视图对象,按业务领域组织, 包含Serializable接口和serialVersionUID字段, 详细规范见 .trae\rules\vo_rules.md
- `util/`: 工具类
### 文档和资源目录
- `docs/db/`: 数据库脚本文件
- `docs/task/`: 任务相关文档和规范
- `docs/model/`: 数据模型文档
- 其他项目文档和资源
## 数据库规范
- 表名使用大写字母和下划线,如 `COMPANY_VENDOR_FILE_TYPE_LOCAL`
- 字段名使用大写字母和下划线,如 `CREATE_DATE`
- 主键命名为 `ID`
- 外键命名格式为 `[关联表名]_ID`
- 唯一约束命名格式为 `UK_[表名缩写]_[字段名]`
## 代码规范
- 使用Lombok注解简化代码`@Data``@Slf4j`
- 类和方法应有适当的JavaDoc注释
- 变量命名应清晰表达其含义
- 避免魔法数字,使用常量替代
- 异常处理使用统一的异常处理机制
## 忽略文件
ignore:
- .idea
- target
- *.iml
- .gitignore
- .gitattributes
- out/
- *.log
- build/
- .DS_Store
- *.class
# 6A工作流执行规则
## 阶段1: Align (对齐阶段)
### 目标: 模糊需求 → 精确规范
### 执行步骤
1. **项目上下文分析**
- 分析现有项目结构、技术栈、架构模式、依赖关系
- 分析现有代码模式、现有文档和约定
- 理解业务域和数据模型
2. **需求理解确认**
- 创建 `docs/任务名/ALIGNMENT_[任务名].md`
- 包含项目和任务特性规范
- 包含原始需求、边界确认(明确任务范围)、需求理解(对现有项目的理解)、疑问澄清(存在歧义的地方)
3. **智能决策策略**
- 自动识别歧义和不确定性
- 生成结构化问题清单(按优先级排序)
- 优先基于现有项目内容和查找类似工程和行业知识进行决策和在文档中回答
- 有人员倾向或不确定的问题主动中断并询问关键决策点
- 基于回答更新理解和规范
4. **中断并询问关键决策点**
- 主动中断询问,迭代执行智能决策策略
5. **最终共识**
- 生成 `docs/任务名/CONSENSUS_[任务名].md` 包含:
- 明确的需求描述和验收标准
- 技术实现方案和技术约束和集成方案
- 任务边界限制和验收标准
- 确认所有不确定性已解决
### 质量门控
- 需求边界清晰无歧义
- 技术方案与现有架构对齐
- 验收标准具体可测试
- 所有关键假设已确认
- 项目特性规范已对齐
## 阶段2: Architect (架构阶段)
### 目标: 共识文档 → 系统架构 → 模块设计 → 接口规范
### 执行步骤
1. **系统分层设计**
- 基于CONSENSUS、ALIGNMENT文档设计架构
- 生成 `docs/任务名/DESIGN_[任务名].md` 包含:
- 整体架构图(mermaid绘制)
- 分层设计和核心组件
- 模块依赖关系图
- 接口契约定义
- 数据流向图
- 异常处理策略
2. **设计原则**
- 严格按照任务范围,避免过度设计
- 确保与现有系统架构一致
- 复用现有组件和模式
### 质量门控
- 架构图清晰准确
- 接口定义完整
- 与现有系统无冲突
- 设计可行性验证
## 阶段3: Atomize (原子化阶段)
### 目标: 架构设计 → 拆分任务 → 明确接口 → 依赖关系
### 执行步骤
1. **子任务拆分**
- 基于DESIGN文档生成 `docs/任务名/TASK_[任务名].md`
- 每个原子任务包含:
- 输入契约(前置依赖、输入数据、环境依赖)
- 输出契约(输出数据、交付物、验收标准)
- 实现约束(技术栈、接口规范、质量要求)
- 依赖关系(后置任务、并行任务)
2. **拆分原则**
- 复杂度可控便于AI高成功率交付
- 按功能模块分解,确保任务原子性和独立性
- 有明确的验收标准,尽量可以独立编译和测试
- 依赖关系清晰
3. **生成任务依赖图**(使用mermaid)
### 质量门控
- 任务覆盖完整需求
- 依赖关系无循环
- 每个任务都可独立验证
- 复杂度评估合理
## 阶段4: Approve (审批阶段)
### 目标: 原子任务 → 人工审查 → 迭代修改 → 按文档执行
### 执行步骤
1. **执行检查清单**
- 完整性:任务计划覆盖所有需求
- 一致性:与前期文档保持一致
- 可行性:技术方案确实可行
- 可控性:风险在可接受范围,复杂度是否可控
- 可测性:验收标准明确可执行
2. **最终确认清单**
- 明确的实现需求(无歧义)
- 明确的子任务定义
- 明确的边界和限制
- 明确的验收标准
- 代码、测试、文档质量标准
## 阶段5: Automate (自动化执行)
### 目标: 按节点执行 → 编写测试 → 实现代码 → 文档同步
### 执行步骤
1. **逐步实施子任务**
- 创建 `docs/任务名/ACCEPTANCE_[任务名].md` 记录完成情况
2. **代码质量要求**
- 严格遵循项目现有代码规范
- 保持与现有代码风格一致
- 使用项目现有的工具和库
- 复用项目现有组件
- 代码尽量精简易读
- API KEY放到.env文件中并且不要提交git
3. **异常处理**
- 遇到不确定问题立刻中断执行
- 在TASK文档中记录问题详细信息和位置
- 寻求人工澄清后继续
4. **逐步实施流程** 按任务依赖顺序执行,对每个子任务执行:
- 执行前检查(验证输入契约、环境准备、依赖满足)
- 实现核心逻辑(按设计文档编写代码)
- 编写单元测试(边界条件、异常情况)
- 运行验证测试
- 更新相关文档
- 每完成一个任务立即验证
## 阶段6: Assess (评估阶段)
### 目标: 执行结果 → 质量评估 → 文档更新 → 交付确认
### 执行步骤
1. **验证执行结果**
- 更新 `docs/任务名/ACCEPTANCE_[任务名].md`
- 整体验收检查:
- 所有需求已实现
- 验收标准全部满足
- 项目编译通过
- 所有测试通过
- 功能完整性验证
- 实现与设计文档一致
2. **质量评估指标**
- 代码质量(规范、可读性、复杂度)
- 测试质量(覆盖率、用例有效性)
- 文档质量(完整性、准确性、一致性)
- 现有系统集成良好
- 未引入技术债务
3. **最终交付物**
- 生成 `docs/任务名/FINAL_[任务名].md`(项目总结报告)
- 生成 `docs/任务名/TODO_[任务名].md`(精简明确哪些待办的事宜和哪些缺少的配置等,我方便直接寻找支持)
4. **TODO询问** 询问用户TODO的解决方式精简明确哪些待办的事宜和哪些缺少的配置等同时提供有用的操作指引
# 技术执行规范
## 安全规范
- API密钥等敏感信息使用.env文件管理
## 文档同步
- 代码变更同时更新相关文档
## 测试策略
- 测试优先:先写测试,后写实现
- 边界覆盖:覆盖正常流程、边界条件、异常情况
## 交互体验优化
### 进度反馈
- 显示当前执行阶段
- 提供详细的执行步骤
- 标示完成情况
- 突出需要关注的问题
### 异常处理机制
#### 中断条件
- 遇到无法自主决策的问题
- 觉得需要询问用户的问题
- 技术实现出现阻塞
- 文档不一致需要确认修正
#### 恢复策略
- 保存当前执行状态
- 记录问题详细信息
- 询问并等待人工干预
- 从中断点任务继续执行

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

@@ -0,0 +1,435 @@
# 服务器端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
└── ...
```
## 核心基类和接口体系
### 主要基类
- **EntityService<M, Vo, ID>**: 通用实体服务基类提供CRUD操作的标准实现
- **CompanyBasicService**: 专门处理公司相关业务的基础服务类,支持公司关联查询
### 核心接口
- **IEntityService<T>**: 实体基本操作接口
- **QueryService<Vo>**: 查询服务接口
- **VoableService<M, Vo>**: 实体与视图对象转换服务接口
## 注解使用规范
### 类级别注解
```java
@Lazy // 延迟加载,避免循环依赖
@Service // Spring服务组件
@CacheConfig(cacheNames = "业务缓存名称") // 缓存配置
public class CompanyService extends EntityService<Company, CompanyVo, Integer>
implements IEntityService<Company>, QueryService<CompanyVo>, VoableService<Company, CompanyVo> {
// 实现代码
}
```
### 方法级别注解
```java
// 查询方法缓存 - 使用参数作为缓存键
@Cacheable(key = "#p0") // ID查询
public CompanyVo findById(Integer id) {
return repository.findById(id).map(Company::toVo).orElse(null);
}
@Cacheable(key = "'name-'+#p0") // 名称查询,带前缀
public CompanyVo findByName(String name) {
return repository.findFirstByName(name).map(Company::toVo).orElse(null);
}
// 修改方法缓存清理 - 清理所有相关缓存
@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);
}
```
## 依赖注入规范
### Repository注入
```java
@Lazy
@Autowired
private CompanyRepository repository;
```
### Service间依赖注入
```java
@Lazy
@Autowired
private ContractService contractService;
@Lazy
@Autowired
private VendorService vendorService;
@Lazy
@Autowired
private CompanyContactService companyContactService;
```
### 外部服务依赖注入
```java
@Lazy
@Autowired
private CloudRkService cloudRkService;
@Lazy
@Autowired
private CloudTycService cloudTycService;
@Autowired(required = false) // 可选依赖
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
@Lazy
@Service
@CacheConfig(cacheNames = "contract")
public class ContractService extends EntityService<Contract, ContractVo, Integer>
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;
}
}
```
### 2. 继承CompanyBasicService的Service
```java
@Lazy
@Service
@CacheConfig(cacheNames = "company-customer")
public class CustomerService extends CompanyBasicService
implements IEntityService<CompanyCustomer>, QueryService<CustomerVo>,
VoableService<CompanyCustomer, CustomerVo> {
// 提供公司关联查询
public CompanyCustomer findByCompany(Company company) {
return repository.findByCompany(company).orElse(null);
}
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;
}
}
```
## 文件管理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块中关闭资源

58
.trae/rules/vo_rules.md Normal file
View File

@@ -0,0 +1,58 @@
# VO类规则
## 1. 目录结构
- VO类统一放置在 `common/src/main/java/com/ecep/contract/vo/` 目录下
- 按业务领域组织,不使用子目录
## 2. 命名规范
- 类名使用驼峰命名法首字母大写以Vo结尾`ContractVo.java``CompanyVo.java`
## 3. 继承与接口实现
- **必须实现** `java.io.Serializable` 接口,用于对象序列化
- **通常实现** `IdentityEntity` 接口提供主键ID的getter/setter方法
- **可能实现** `NamedEntity` 接口提供name属性的getter/setter方法适用于有名称的VO
- **可能实现**特定领域接口,如:
- `CompanyBasedVo`提供companyId属性的getter/setter方法
- `ProjectBasedVo`提供project属性的getter/setter方法
- `ContractBasedVo`提供contractId属性的getter/setter方法
- 其他类似的基于特定实体的接口
## 4. 注解规范
- 通常使用Lombok的 `@Data` 注解自动生成getter、setter、equals、hashCode和toString方法
## 5. 序列化规范
- **必须包含** `private static final long serialVersionUID = 1L;` 字段,用于版本控制
- 所有字段类型必须是可序列化的
## 6. 字段规范
- 字段类型:
- 整数类型:可使用 `Integer` 包装类型或 `int` 原始类型
- 布尔类型:可使用 `Boolean` 包装类型或 `boolean` 原始类型
- 时间类型:使用 `java.time.LocalDate`(日期)或 `java.time.LocalDateTime`(日期时间)
- 字符串类型:使用 `String`
- 浮点数类型:使用 `double``Double`
- 关联关系:
- 通常只包含关联实体的ID字段`companyId``project``contractId`
- 不包含实体对象的直接引用
- 默认值:
- 布尔类型字段通常有默认值(如 `false`
- 其他类型根据业务需求设置默认值
## 7. 注释规范
- 类注释使用JavaDoc格式描述VO类的用途`/** 项目报价视图对象 */`
- 字段注释使用JavaDoc格式描述字段的含义、用途或业务规则
- 关键字段(如外键、状态字段)应提供清晰的注释说明
## 8. 最佳实践
- VO类应保持简洁只包含数据和基本的数据访问方法
- 避免在VO类中包含复杂的业务逻辑
- 字段命名应与对应的实体类保持一致或有明确的映射关系
- 确保所有字段都是可序列化的
- 对于有默认值的字段,应在声明时初始化
## 9. 与实体类的区别
- **序列化**VO类必须实现 `Serializable` 接口并包含 `serialVersionUID` 字段,而实体类不包含
- **用途**VO类主要用于数据传输和展示实体类主要用于持久化
- **关联关系**VO类通常只包含关联实体的ID实体类包含实体对象的引用
- **注解**VO类主要使用Lombok注解实体类使用JPA注解和Lombok注解
- **方法**VO类通常只有getter/setter方法实体类可能包含更多业务相关方法

231
README.md
View File

@@ -1,33 +1,198 @@
## 项目改进与优化分析报告
### 1. 同步任务与调度优化
- 调度策略优化 :各同步任务(如 ContractSyncTask 、 VendorSyncTask )使用不同的调度间隔和策略,建议统一调度配置,避免重复执行和资源浪费
- 增量同步实现 多数同步任务缺乏断点续传机制建议添加基于最后同步ID或时间戳的增量同步功能
- 任务依赖管理 :部分任务存在隐式依赖关系(如 OldVersionSyncVendorTask 中的多步骤执行),建议引入任务依赖管理机制
### 2. 线程池与异步处理
- 线程池配置 :当前项目未明确配置线程池参数(核心线程数、最大线程数等),建议在 SpringApp 中添加显式线程池配置
- 线程池隔离 不同类型的任务IO密集型、CPU密集型应使用不同线程池避免资源竞争
- 异步任务监控 :添加异步任务执行状态监控和超时处理机制,避免任务长时间阻塞
### 3. 缓存机制优化
- 缓存策略细化 :当前缓存配置较为简单( CaffeineCacheManager ),建议为不同类型数据配置差异化缓存策略(过期时间、最大容量等)
- 缓存一致性 :修复 CloudRkService 中缓存更新问题(注释中提到的"这个可以无法更新缓存"
- 缓存预热 :添加关键数据缓存预热机制,提高系统启动后响应速度
### 4. 依赖注入与懒加载
- 依赖注入规范化 :减少 SpringApp.getBean() 手动获取bean的方式推广构造函数注入或字段注入
- 懒加载优化 :充分利用 @Lazy 注解和 BootstrapMode.LAZY 配置,优化应用启动性能
- 服务解耦 :继续推进服务拆分工作(如之前的 ContractService 拆分为专用服务),减少服务间耦合
### 5. 异常处理与重试机制
- 异常处理统一 :规范异常处理策略,确保所有异常都被适当记录和处理
- 重试机制添加 :为网络请求和数据库操作添加重试机制(如使用 Spring Retry
- 熔断降级 对外部系统调用如用友U8接口添加熔断降级机制提高系统稳定性
### 6. 数据库操作优化
- 批量操作引入 对大量数据的CRUD操作建议使用批量处理API提高性能
- 查询优化 :添加适当索引,优化复杂查询,避免全表扫描
- 连接池配置 :优化 HikariDataSource 配置参数(如连接池大小、超时时间等)
### 7. 代码质量与维护性
- 重复代码消除 :提取同步任务中的共同逻辑(如进度更新、异常处理)为抽象基类
- 注释完善 :补充关键类和方法的文档注释,特别是复杂业务逻辑和优化点
- 技术债务清理 解决代码中的TODO项如 CloudRkService 中的缓存更新问题)
### 8. 配置管理优化
- 配置集中管理 :将分散在代码中的配置项(如同步间隔、批处理大小)集中到配置文件
- 动态配置支持 :添加动态配置更新机制,避免重启应用
以上优化点已按优先级和影响范围排序,建议逐步实施。实施过程中应注意性能测试和兼容性验证,确保优化不会引入新问题。
# Contract-Manager 项目报告
## 项目概述
Contract-Manager是一个企业级合同管理系统提供完整的合同生命周期管理包括合同的创建、审批、执行、归档等功能同时集成了供应商管理、客户管理、项目管理等相关业务模块。
## 项目架构
### 模块划分
项目采用模块化设计,分为三个主要模块:
- **common**:公共模块,包含实体类、枚举、异常、工具类等共享组件
- **server**服务端模块基于Spring Boot开发提供HTTP服务和WebSocket通信
- **client**客户端模块基于JavaFX开发提供图形用户界面
### 技术栈
#### 服务端技术栈
- Java 21
- Spring Boot 3.3.7
- Spring Data JPA 3.3.7
- MySQL 8.0.33
- Lombok 1.18.32
- POI 5.2.5 (Office文档处理)
- PDFBox 3.0.1 (PDF文档处理)
- Redis (缓存)
- WebSocket (实时通信)
#### 客户端技术栈
- Java 21
- JavaFX 21
- ControlsFX 11.1.2 (UI控件增强)
- Lombok 1.18.32
- Caffeine 3.1.8 (本地缓存)
- WebSocket (与服务端通信)
## 目录结构
```
Contract-Manager/
├── common/ # 公共模块
│ ├── src/main/java/com/ecep/contract/
│ │ ├── model/ # 实体类
│ │ ├── vo/ # 值对象
│ │ ├── util/ # 工具类
│ │ ├── constant/ # 常量定义
│ │ └── msg/ # 消息相关
├── server/ # 服务端模块
│ ├── src/main/java/com/ecep/contract/
│ │ ├── api/ # API接口
│ │ ├── controller/ # 控制器
│ │ ├── service/ # 服务层
│ │ ├── ds/ # 数据访问层
│ │ ├── config/ # 配置类
│ │ ├── util/ # 工具类
│ │ ├── handler/ # WebSocket处理器
│ │ ├── cloud/ # 云端服务集成
│ │ └── ui/ # 服务端UI组件
├── client/ # 客户端模块
│ ├── src/main/java/com/ecep/contract/
│ │ ├── controller/ # 控制器
│ │ ├── service/ # 服务层
│ │ ├── vm/ # 视图模型
│ │ ├── util/ # 工具类
│ │ ├── task/ # 任务处理
│ │ ├── converter/ # 类型转换器
│ │ └── serializer/ # 序列化器
│ └── src/main/resources/ui/ # FXML界面文件
└── docs/ # 文档目录
└── db/ # 数据库相关文件
└── task/ # 任务
```
## 核心功能模块
### 1. 公司管理
- 公司基本信息管理
- 公司联系方式管理
- 公司银行账户管理
- 公司文件管理
- 公司黑名单管理
### 2. 供应商管理
- 供应商信息管理
- 供应商资质审核
- 供应商分类管理
- 供应商文件管理
- 供应商评估
### 3. 客户管理
- 客户信息管理
- 客户分类管理
- 客户满意度调查
- 客户文件管理
### 4. 合同管理
- 合同创建与编辑
- 合同审批流程
- 合同执行跟踪
- 合同付款计划
- 合同文件管理
- 合同分类与归档
### 5. 项目管理
- 项目信息管理
- 项目成本管理
- 项目报价管理
- 项目文件管理
- 项目资金计划
### 6. 库存管理
- 库存物品管理
- 库存价格历史
- 库存分类管理
### 7. 员工与权限管理
- 员工信息管理
- 角色权限管理
- 登录历史记录
### 8. 云端服务集成
- 天眼查企业信息集成
- 用友U8系统集成
- 其他云端服务集成
## 数据模型设计
系统采用JPA注解式实体类设计主要实体类包括
- **基础实体类**BasedEntity, IdentityEntity, NamedEntity等
- **公司相关**Company, CompanyContact, CompanyBankAccount等
- **供应商相关**Vendor, VendorApproved, VendorFile等
- **客户相关**CompanyCustomer, CustomerSatisfactionSurvey等
- **合同相关**Contract, ContractItem, ContractPayPlan等
- **项目相关**Project, ProjectCost, ProjectFundPlan等
- **枚举实体类**各种类型的本地枚举实体如VendorTypeLocal, ContractFileTypeLocal等
## 通信机制
系统采用WebSocket进行客户端与服务端的实时通信主要组件包括
- **服务端**WebSocketController, WebSocketServerHandler
- **客户端**WebSocketClientService, WebSocketClientSession
## 任务处理机制
系统实现了异步任务处理机制,支持长时间运行的任务监控和管理:
- **TaskMonitorCenter**:任务监控中心
- **MonitoredTask**:可监控的任务基类
- **TaskHistory**:任务历史记录
## 扩展性设计
系统采用良好的分层架构和接口设计,具有较强的扩展性:
1. **服务接口标准化**通过IEntityService等接口统一服务访问方式
2. **模块化设计**:各功能模块相对独立,便于扩展和维护
3. **配置管理**:支持动态配置和属性绑定
4. **国际化支持**通过messages.properties实现多语言支持
## 安全机制
系统实现了基本的安全机制:
- **用户认证与授权**:基于角色的权限控制
- **登录历史记录**:记录用户登录信息
- **异常处理**:全局异常处理机制
## 部署说明
### 服务端部署
1. 配置application.properties中的数据库连接和Redis连接信息
2. 打包为jar文件`mvn clean package`
3. 运行jar文件`java -jar server.jar`
### 客户端部署
1. 确保安装Java 21运行环境
2. 配置连接到服务端的WebSocket地址
3. 打包为可执行jar或使用jpackage创建安装包
## Tasker实现规范指南
有关Tasker框架的详细实现规范请参阅 [Tasker实现规范指南](docs/task/tasker_implementation_guide.md)。
## 开发环境要求
- JDK 21
- Maven 3.6+
- MySQL 8.0+
- Redis 6.0+
- IntelliJ IDEA或Eclipse推荐使用IDEA
## 后续优化方向
1. 完善单元测试和集成测试
2. 优化数据库查询性能
3. 增强系统安全机制
4. 改进用户界面体验
5. 扩展更多云端服务集成

143
client/pom.xml Normal file
View File

@@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.ecep.contract</groupId>
<artifactId>Contract-Manager</artifactId>
<version>0.0.135-SNAPSHOT</version>
</parent>
<groupId>com.ecep.contract</groupId>
<artifactId>client</artifactId>
<version>0.0.135-SNAPSHOT</version>
<properties>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.ecep.contract</groupId>
<artifactId>common</artifactId>
<version>0.0.135-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- JavaFX -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-web</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.controlsfx</groupId>
<artifactId>controlsfx</artifactId>
<version>11.2.0</version>
</dependency>
<!-- Caffeine Cache -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>
<!-- Spring Context Support for CaffeineCacheManager -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>3.3.7</version>
</dependency>
<!-- OkHttp -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<!-- Logback 日志实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.14</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<configuration>
<mainClass>com.ecep.contract.ClientV2</mainClass>
<launcher>app</launcher>
<jlinkZipName>app-jlink</jlinkZipName>
<jlinkImageName>app-jlink-image</jlinkImageName>
<noManPages>true</noManPages>
<stripDebug>true</stripDebug>
<compress>2</compress>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>versions-maven-plugin</artifactId>
<version>2.16.2</version>
<executions>
<execution>
<inherited>true</inherited>
<id>increment-version</id>
<phase>install</phase>
<goals>
<goal>set</goal>
</goals>
<configuration>
<generateBackupPoms>false</generateBackupPoms>
<processAllModules>true</processAllModules>
<!-- <newVersion>${project.version}</newVersion> -->
<nextSnapshot>true</nextSnapshot>
<nextSnapshotIndexToIncrement>3</nextSnapshotIndexToIncrement>
<!-- <removeSnapshot>true</removeSnapshot>-->
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.2</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/relativePath/client/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,17 @@
package com.ecep.contract;
import javafx.application.Application;
import java.io.File;
/**
* Created by Administrator on 2017/4/16.
*/
public class ClientV2 {
public static void main(String[] args) {
System.out.println("当前目录 = " + new File(".").getAbsolutePath());
Application.launch(Desktop.class, args);
}
public static final String DEFAULT_HOST = "10.84.209.154";
}

View File

@@ -1,27 +1,4 @@
package com.ecep.contract.manager;
import com.ecep.contract.manager.ds.other.controller.LoginWidowController;
import com.ecep.contract.manager.ui.BaseController;
import com.ecep.contract.manager.ui.MessageHolder;
import com.ecep.contract.manager.ui.task.TaskMonitorCenter;
import com.ecep.contract.manager.util.UITools;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
package com.ecep.contract;
import java.io.File;
import java.io.FileInputStream;
@@ -29,8 +6,42 @@ import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.*;
import java.util.logging.Level;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import com.ecep.contract.controller.BaseController;
import com.ecep.contract.controller.HomeWindowController;
import com.ecep.contract.controller.OkHttpLoginController;
import com.ecep.contract.task.TaskMonitorCenter;
import com.ecep.contract.util.HibernateProxyUtils;
import com.ecep.contract.util.TextMessageHolder;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.CurrentEmployee;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import lombok.Getter;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
/**
* JavaFx 应用程序
@@ -58,9 +69,11 @@ public class Desktop extends Application {
private ScheduledExecutorService scheduledExecutorService = null;
private final TaskMonitorCenter taskMonitorCenter = new TaskMonitorCenter();
private final SimpleIntegerProperty sessionId = new SimpleIntegerProperty(0);
private final SimpleStringProperty sessionId = new SimpleStringProperty("");
@Getter
private final CurrentEmployee activeEmployee = new CurrentEmployee();
@Getter
private OkHttpClient httpClient;
public void setActiveEmployeeId(int activeEmployeeId) {
activeEmployee.getId().set(activeEmployeeId);
@@ -70,11 +83,11 @@ public class Desktop extends Application {
return activeEmployee.getId().get();
}
public int getSessionId() {
public String getSessionId() {
return sessionId.get();
}
public void setSessionId(int sessionId) {
public void setSessionId(String sessionId) {
this.sessionId.set(sessionId);
}
@@ -122,8 +135,12 @@ public class Desktop extends Application {
}
}
public CompletableFuture<Void> runAsync(Runnable runnable) {
return CompletableFuture.runAsync(runnable, getExecutorService());
}
private void startSpringApp(Stage primaryStage, Parent root, FXMLLoader loader) {
System.out.println("Desktop.startSpringApp");
logger.debug("startSpringApp");
// 更新窗口标题
Node titleNode = root.lookup("#title");
if (titleNode != null) {
@@ -138,34 +155,26 @@ public class Desktop extends Application {
ScrollPane logPane = (ScrollPane) root.lookup("#logPane");
logBox.getChildren().clear();
MessageHolder holder = (level, message) -> {
Text text = new Text(message);
if (Level.WARNING == level) { // warning
text.setFill(Color.YELLOW);
} else if (Level.SEVERE == level) {// error
text.setFill(Color.RED);
} else if (Level.FINE == level) { // debug
text.setFill(Color.GRAY);
} else {
text.setFill(Color.WHITE);
TextMessageHolder holder = new TextMessageHolder() {
@Override
public void addTextMessage(Text text) {
Platform.runLater(() -> {
logBox.getChildren().add(text);
logPane.layout();
logPane.setVvalue(1.0);
});
}
Platform.runLater(() -> {
logBox.getChildren().add(text);
logPane.layout();
logPane.setVvalue(1.0);
});
};
holder.info("启动中,请稍后...");
CompletableFuture.runAsync(() -> {
runAsync(() -> {
try {
//
holder.info("读取配置文件...");
Properties properties = new Properties();
File configFile = new File("config.properties");
if (configFile.exists()) {
holder.debug("读取配置文件 " + configFile.getName() + "...");
holder.info("读取配置文件 " + configFile.getAbsolutePath() + "...");
try (FileInputStream input = new FileInputStream(configFile)) {
properties.load(input);
holder.info("配置文件读取成功.");
@@ -174,9 +183,12 @@ public class Desktop extends Application {
logger.error(e.getMessage(), e);
return;
}
} else {
logger.warn("配置文件{}不存在", configFile.getAbsolutePath());
}
HibernateProxyUtils.useProxy(false);
CompletableFuture.runAsync(() -> {
runAsync(() -> {
SpringApp.launch(properties, holder);
ConfigurableListableBeanFactory beanFactory = SpringApp.context.getBeanFactory();
@@ -185,16 +197,37 @@ public class Desktop extends Application {
});
try {
LoginWidowController controller = new LoginWidowController();
initHttpClient();
OkHttpLoginController controller = new OkHttpLoginController();
controller.setHttpClient(this.httpClient);
controller.setHolder(holder);
controller.setPrimaryStage(primaryStage);
controller.setProperties(properties);
MyProperties myProperties = new MyProperties();
myProperties.loadFromProperties(properties);
controller.setProperties(myProperties);
while (true) {
controller.tryLogin();
break;
}
if (logger.isDebugEnabled()) {
logger.debug("login in");
try {
controller.tryLogin().get();
holder.info("登录成功");
try {
while (!SpringApp.isRunning()) {
System.out.println("等待启动");
Thread.sleep(1000);
}
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
// 必须要等待启动成功后才能关闭主场景否则进程结束程序退出
HomeWindowController.show().thenRun(() -> Platform.runLater(primaryStage::close));
break;
} catch (Exception ex) {
holder.error(ex.getMessage());
Thread.sleep(3000);
}
}
} catch (Exception e) {
holder.error("登录失败:" + e.getMessage());
@@ -206,6 +239,27 @@ public class Desktop extends Application {
}
});
System.out.println("Desktop.startSpringApp.");
}
private void initHttpClient() {
this.httpClient = new OkHttpClient().newBuilder().cookieJar(new CookieJar() {
private final List<Cookie> cookies = new java.util.ArrayList<>();
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
// 保存服务器返回的Cookie如JSESSIONID
this.cookies.addAll(cookies);
System.out.println("保存Cookie: " + cookies);
}
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
// 请求时自动携带Cookie
return cookies;
}
}).build();
}
@Override
@@ -253,5 +307,4 @@ public class Desktop extends Application {
}
scheduledExecutorService.close();
}
}
}

View File

@@ -1,4 +1,4 @@
package com.ecep.contract.manager.util;
package com.ecep.contract;
import java.io.File;
import java.io.IOException;
@@ -6,8 +6,6 @@ import java.util.function.Consumer;
import org.springframework.util.StringUtils;
import com.ecep.contract.manager.Desktop;
public class DesktopUtils {
/**

View File

@@ -0,0 +1,236 @@
package com.ecep.contract;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import lombok.Getter;
import lombok.Setter;
/**
* 应用程序配置类,用于管理系统配置信息
*/
@Component
public class MyProperties implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(MyProperties.class);
private static final String FILE_NAME = "config.properties";
@Getter
@Setter
private String downloadsPath;
@Getter
@Setter
private String serverHost;
@Getter
@Setter
private String serverPort;
@Getter
@Setter
private String userName;
@Getter
@Setter
private String password;
@Getter
@Setter
private boolean rememberPassword;
@Override
public void afterPropertiesSet() throws Exception {
// 初始化时加载配置文件
try {
loadFromFile();
} catch (Exception e) {
logger.warn("初始化配置文件失败: {}", e.getMessage());
}
}
/**
* 从文件加载配置
*/
public void loadFromFile() {
File configFile = new File(FILE_NAME);
if (!configFile.exists()) {
logger.debug("配置文件不存在: {}", configFile.getPath());
return;
}
try (FileInputStream input = new FileInputStream(configFile)) {
Properties properties = new Properties();
properties.load(input);
loadFromProperties(properties);
logger.debug("成功从配置文件加载配置: {}", configFile.getPath());
} catch (Exception e) {
logger.error("加载配置文件失败: {}", configFile.getPath(), e);
}
}
/**
* 设置Properties对象并加载配置
* 用于从外部设置配置项
*
* @param properties 配置对象
*/
public void setProperties(Properties properties) {
this.loadFromProperties(properties);
}
/**
* 从Properties对象加载配置
* 用于从config.properties文件中读取配置项
*
* @param properties 配置对象
*/
public void loadFromProperties(Properties properties) {
if (properties == null) {
logger.warn("Properties对象为空无法加载配置");
return;
}
// 加载下载路径配置
String downloadsPath = properties.getProperty("my.downloadsPath");
if (StringUtils.hasText(downloadsPath)) {
this.setDownloadsPath(downloadsPath);
logger.debug("从配置文件加载下载路径: {}", downloadsPath);
}
// 加载服务器配置
String serverHost = properties.getProperty("server.host", "127.0.0.1");
if (StringUtils.hasText(serverHost)) {
this.setServerHost(serverHost);
logger.debug("从配置文件加载服务器地址: {}", serverHost);
}
String serverPort = properties.getProperty("server.port", "8080");
if (StringUtils.hasText(serverPort)) {
this.setServerPort(serverPort);
logger.debug("从配置文件加载服务器端口: {}", serverPort);
}
// 加载用户凭证配置
String userName = properties.getProperty("user.name");
if (StringUtils.hasText(userName)) {
this.setUserName(userName);
logger.debug("从配置文件加载用户名");
}
// 只有在记住密码的情况下才加载密码
String rememberPasswordStr = properties.getProperty("user.rememberPassword");
boolean rememberPassword = "true".equals(rememberPasswordStr);
this.setRememberPassword(rememberPassword);
if (rememberPassword) {
String password = properties.getProperty("user.password");
if (StringUtils.hasText(password)) {
this.setPassword(password);
logger.debug("从配置文件加载密码");
}
}
}
/**
* 将配置保存到Properties对象
* 用于将配置项保存到config.properties文件
*
* @param properties 配置对象
*/
public void saveToProperties(Properties properties) {
if (properties == null) {
logger.warn("Properties对象为空无法保存配置");
return;
}
// 保存下载路径配置
if (StringUtils.hasText(getDownloadsPath())) {
properties.setProperty("my.downloadsPath", getDownloadsPath());
logger.debug("保存下载路径到配置文件: {}", getDownloadsPath());
}
// 保存服务器配置
if (StringUtils.hasText(getServerHost())) {
properties.setProperty("server.host", getServerHost());
logger.debug("保存服务器地址到配置文件: {}", getServerHost());
}
if (StringUtils.hasText(getServerPort())) {
properties.setProperty("server.port", getServerPort());
logger.debug("保存服务器端口到配置文件: {}", getServerPort());
}
// 保存用户凭证配置
if (StringUtils.hasText(getUserName())) {
properties.setProperty("user.name", getUserName());
logger.debug("保存用户名为配置文件");
}
properties.setProperty("user.rememberPassword", String.valueOf(isRememberPassword()));
// 只有在记住密码的情况下才保存密码
if (isRememberPassword() && StringUtils.hasText(getPassword())) {
properties.setProperty("user.password", getPassword());
logger.debug("保存密码到配置文件");
} else if (properties.containsKey("user.password")) {
// 如果不记住密码,删除已存在的密码配置
properties.remove("user.password");
}
}
/**
* 尝试返回当前用户的下载文件夹
*/
public File getDownloadDirectory() {
String downloadsPath = getDownloadsPath();
if (StringUtils.hasText(downloadsPath)) {
// 确保目录存在
File dir = new File(downloadsPath);
if (!dir.exists()) {
boolean created = dir.mkdirs();
logger.debug("创建下载目录: {}, 结果: {}", downloadsPath, created);
}
return dir;
}
// 没有配置下载目录时,尝试使用默认设置
String home = System.getProperty("user.home");
Path path = Paths.get(home, "Downloads");
return path.toFile();
}
/**
* 保存配置到文件
*/
public void save() {
File configFile = new File(FILE_NAME);
try (FileOutputStream output = new FileOutputStream(configFile)) {
Properties properties = new Properties();
// 如果文件已存在,先读取现有配置,避免覆盖其他配置
if (configFile.exists()) {
try (FileInputStream input = new FileInputStream(configFile)) {
properties.load(input);
}
}
// 保存当前配置
saveToProperties(properties);
properties.store(output, "Contract Manager 应用程序配置");
logger.debug("成功保存配置到文件: {}", configFile.getPath());
} catch (Exception e) {
logger.error("保存配置到文件失败: {}", configFile.getPath(), e);
}
}
}

View File

@@ -1,4 +1,4 @@
package com.ecep.contract.manager;
package com.ecep.contract;
import java.time.Duration;
import java.time.Instant;
@@ -25,7 +25,6 @@ import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.EventListener;
@@ -36,11 +35,7 @@ import org.springframework.core.metrics.StartupStep;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import com.ecep.contract.manager.cloud.CloudRepositoriesConfig;
import com.ecep.contract.manager.ds.DsRepositoriesConfig;
import com.ecep.contract.manager.ui.MessageHolder;
import com.ecep.contract.manager.util.MyDateTimeUtils;
import com.ecep.contract.manager.util.UITools;
import com.ecep.contract.util.UITools;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
@@ -50,15 +45,20 @@ import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
@SpringBootApplication(exclude = { org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration.class })
@SpringBootApplication(exclude = {
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration.class,
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration.class,
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.class,
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration.class
})
@EnableScheduling
@EnableAsync
@EnableCaching
public class SpringApp {
private static final Logger logger = LoggerFactory.getLogger(SpringApp.class);
static SpringApplication application;
static ConfigurableApplicationContext context;
public static SpringApplication application;
public static ConfigurableApplicationContext context;
public static <T> T getBean(Class<T> requiredType) throws BeansException {
return context.getBean(requiredType);
@@ -92,23 +92,29 @@ public class SpringApp {
environment.getPropertySources().addLast(new PropertiesPropertySource("dynamicProperties", properties));
// app.getBeanFactory().registerSingleton("dataSource", dataSource());
logger.debug("app = {}", app);
if (app instanceof AnnotationConfigApplicationContext ctx) {
ctx.register(DsRepositoriesConfig.class);
ctx.register(CloudRepositoriesConfig.class);
}
});
startup.start("");
context = application.run();
logger.debug("SpringApp.launch application.run().");
Duration between = Duration.between(startup.getBufferedTimeline().getStartTime(), Instant.now());
// 初始化MyProperties从properties加载配置
try {
MyProperties myProperties = context.getBean(MyProperties.class);
myProperties.loadFromProperties(properties);
holder.info("MyProperties配置加载完成");
} catch (Exception e) {
logger.error("加载MyProperties配置失败", e);
holder.error("加载MyProperties配置失败: " + e.getMessage());
}
holder.info("应用程序环境加载完成... " + between);
});
CompletableFuture.runAsync(() -> {
// 在这里调用 startup 性能分析
analyzeStartupPerformance(startup);
});
}, Desktop.instance.getExecutorService());
}
/**
@@ -242,14 +248,18 @@ public class SpringApp {
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
// LocalDate
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE));
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(
DateTimeFormatter.ofPattern(MyDateTimeUtils.DEFAULT_DATETIME_FORMAT_PATTERN)));
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(
DateTimeFormatter.ofPattern(MyDateTimeUtils.DEFAULT_DATETIME_FORMAT_PATTERN)));
// LocalTime
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ISO_LOCAL_TIME));
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ISO_LOCAL_TIME));
// LocalDateTime
javaTimeModule.addSerializer(LocalDateTime.class,
new LocalDateTimeSerializer(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
javaTimeModule.addDeserializer(LocalDateTime.class,
new LocalDateTimeDeserializer(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}

View File

@@ -0,0 +1,375 @@
package com.ecep.contract;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ecep.contract.constant.WebSocketConstant;
import com.ecep.contract.controller.OkHttpLoginController;
import com.ecep.contract.msg.SimpleMessage;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import lombok.Getter;
import lombok.Setter;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import okio.ByteString;
/**
* WebSocket消息服务
* 提供向服务器端发送WebSocket消息的功能
*/
@Service
public class WebSocketClientService {
private static final Logger logger = LoggerFactory.getLogger(WebSocketClientService.class);
@Getter
@Setter
private WebSocket webSocket;
@Getter
@Autowired
private ObjectMapper objectMapper;
private static final int RECONNECT_DELAY_MS = 5000; // 重连延迟时间(毫秒)
private static final int HEARTBEAT_INTERVAL_MS = 30000; // 心跳间隔时间(毫秒)
@Getter
@Setter
private long readTimeout = 30000;
private boolean isActive = false; // 标记连接是否活跃
private ScheduledFuture<?> heartbeatTask; // 心跳任务
private ScheduledFuture<?> reconnectFuture; // 修改类型为CompletableFuture<Void>
private SimpleBooleanProperty online = new SimpleBooleanProperty(false);
private SimpleStringProperty message = new SimpleStringProperty("");
// 存储所有活跃的WebSocket会话
private final Map<String, WebSocketClientSession> sessions = Collections.synchronizedMap(new HashMap<>());
// 存储所有活跃的WebSocket会话
private final Map<String, CompletableFuture<JsonNode>> callbacks = Collections.synchronizedMap(new HashMap<>());
WebSocketListener listener = new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
Platform.runLater(() -> {
online.setValue(true);
message.setValue("已连接");
});
startHeartbeat(); // 启动心跳
}
@Override
public void onMessage(WebSocket webSocket, String text) {
// 处理收到的文本消息
logger.debug("收到WebSocket消息: {}", text);
// 这里可以根据需要处理从服务器接收的数据
if ("pong".equals(text)) {
// 收到pong响应说明连接正常
logger.debug("收到心跳响应");
return;
}
try {
JsonNode node = objectMapper.readTree(text);
if (node.has(WebSocketConstant.MESSAGE_ID_FIELD_NAME)) {
String messageId = node.get(WebSocketConstant.MESSAGE_ID_FIELD_NAME).asText();
CompletableFuture<JsonNode> future = callbacks.remove(messageId);
if (future != null) {
onCallbackMessage(future, node);
} else {
logger.error("未找到对应的回调future: {}", messageId);
}
return;
}
if (node.has(WebSocketConstant.SESSION_ID_FIELD_NAME)) {
String sessionId = node.get(WebSocketConstant.SESSION_ID_FIELD_NAME).asText();
WebSocketClientSession session = sessions.get(sessionId);
if (session != null) {
try {
session.onMessage(node);
} catch (Exception e) {
session.updateMessage(java.util.logging.Level.SEVERE, "会话异常: " + e.getMessage());
}
}
return;
}
if (node.has(WebSocketConstant.ERROR_CODE_FIELD_NAME)) {
int errorCode = node.get(WebSocketConstant.ERROR_CODE_FIELD_NAME).asInt();
String errorMsg = node.get(WebSocketConstant.MESSAGE_FIELD_NAME).asText();
logger.error("收到错误消息: 错误码={}, 错误信息={}", errorCode, errorMsg);
if (errorCode == WebSocketConstant.ERROR_CODE_UNAUTHORIZED) {
// 调用所有的 callbacks 和 session 失败并且移除
callbacks.keySet().stream().toList()
.forEach(key -> callbacks.remove(key).completeExceptionally(new Exception("未授权")));
sessions.values().stream().toList().forEach(session -> {
session.updateMessage(java.util.logging.Level.SEVERE, "未授权");
session.close();
});
isActive = false;
webSocket.close(1000, "");
WebSocketClientService.this.webSocket = null;
// 处理未授权错误,重新登录
OkHttpLoginController controller = new OkHttpLoginController();
controller.setHttpClient(Desktop.instance.getHttpClient());
controller.setProperties(SpringApp.getBean(MyProperties.class));
controller.tryLogin().get();
// 需要把窗口顶置
initWebSocket();
}
return;
}
} catch (Exception e) {
logger.error("处理WebSocket消息失败: {}", e.getMessage(), e);
}
}
@Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
// 处理收到的二进制消息
logger.debug("收到二进制WebSocket消息长度: " + bytes.size());
}
@Override
public void onClosing(WebSocket webSocket, int code, String reason) {
logger.debug("WebSocket连接正在关闭: 代码=" + code + ", 原因=" + reason);
stopHeartbeat(); // 停止心跳
}
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
logger.debug("WebSocket连接已关闭: 代码=" + code + ", 原因=" + reason);
stopHeartbeat(); // 停止心跳
// 处理重连逻辑
scheduleReconnect();
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
logger.error("WebSocket连接失败: " + t.getMessage() + ", response=" + response, t);
Platform.runLater(() -> {
online.setValue(false);
message.set("连接失败: " + t.getMessage());
});
stopHeartbeat(); // 停止心跳
// 处理重连逻辑
scheduleReconnect();
}
};
private void onCallbackMessage(CompletableFuture<JsonNode> future, JsonNode node) {
if (node.has(WebSocketConstant.SUCCESS_FIELD_NAME)) {
if (!node.get(WebSocketConstant.SUCCESS_FIELD_NAME).asBoolean()) {
future.completeExceptionally(
new RuntimeException(
"请求失败:来自服务器的消息=" + node.get(WebSocketConstant.MESSAGE_FIELD_NAME).asText()));
return;
}
}
// 使用具体类型后,这里不会再出现类型不匹配的错误
if (node.has("data")) {
future.complete(node.get("data"));
} else {
future.complete(node);
}
}
public void send(String string) {
if (webSocket != null && webSocket.send(string)) {
logger.debug("send message success:{}", string);
} else if (webSocket == null) {
logger.warn("Failed to send message: WebSocket is not initialized");
}
}
public void send(Object message) throws JsonProcessingException {
send(objectMapper.writeValueAsString(message));
}
/**
* WebSocketServerCallbackManage#onMessage 负责接收处理
*
* @param msg
* @return
*/
public CompletableFuture<JsonNode> send(SimpleMessage msg) {
CompletableFuture<JsonNode> future = new CompletableFuture<>();
try {
if (webSocket == null) {
throw new IllegalStateException("WebSocket is not initialized");
}
if (!online.get()) {
throw new IllegalStateException("WebSocket is not online");
}
String json = objectMapper.writeValueAsString(msg);
callbacks.put(msg.getMessageId(), future);
if (webSocket.send(json)) {
logger.debug("send json success:{}", json);
} else {
if (isActive) {
future.completeExceptionally(new RuntimeException("Failed to send WebSocket message"));
} else {
future.completeExceptionally(new RuntimeException("WebSocket is not active"));
}
}
} catch (Exception e) {
logger.error("Failed to send WebSocket message: {}", e.getMessage());
future.completeExceptionally(e);
}
return future;
}
public CompletableFuture<JsonNode> invoke(String service, String method, Object... params) {
SimpleMessage msg = new SimpleMessage();
msg.setService(service);
msg.setMethod(method);
msg.setArguments(params);
return send(msg).orTimeout(getReadTimeout(), TimeUnit.MILLISECONDS);
}
public void initWebSocket() {
isActive = true;
OkHttpClient httpClient = Desktop.instance.getHttpClient();
try {
// 构建WebSocket请求包含认证信息
var myProperties = SpringApp.getBean(MyProperties.class);
String webSocketUrl = "ws://" + myProperties.getServerHost() + ":" + myProperties.getServerPort() + "/ws";
Request request = new Request.Builder()
.url(webSocketUrl)
.build();
webSocket = httpClient.newWebSocket(request, listener);
} catch (Exception e) {
logger.error("建立WebSocket连接失败: " + e.getMessage());
Platform.runLater(() -> {
online.setValue(false);
message.set("连接失败: " + e.getMessage());
});
// 处理重连逻辑
scheduleReconnect();
}
}
/**
* 启动心跳任务定期发送ping消息保持连接
*/
private void startHeartbeat() {
// 先停止可能存在的心跳任务
stopHeartbeat();
ScheduledExecutorService executorService = Desktop.instance.getExecutorService();
heartbeatTask = executorService.scheduleAtFixedRate(this::heartbeat, RECONNECT_DELAY_MS, HEARTBEAT_INTERVAL_MS,
TimeUnit.MILLISECONDS);
}
void heartbeat() {
if (!isActive) {
return;
}
try {
if (webSocket != null) {
logger.debug("发送心跳 ping");
webSocket.send("ping");
}
} catch (Exception e) {
logger.error("发送心跳失败: {}", e.getMessage());
}
}
/**
* 停止心跳任务
*/
private void stopHeartbeat() {
if (heartbeatTask != null && !heartbeatTask.isCancelled()) {
heartbeatTask.cancel(true);
heartbeatTask = null;
}
}
/**
* 安排重连任务
*/
private void scheduleReconnect() {
if (!isActive) {
return; // 如果连接已被主动关闭,则不再重连
}
// 取消之前可能存在的重连任务
if (reconnectFuture != null && !reconnectFuture.isDone()) {
reconnectFuture.cancel(true);
}
// 创建新的重连任务s
logger.info("计划在 {} 毫秒后尝试重连WebSocket", RECONNECT_DELAY_MS);
reconnectFuture = Desktop.instance.getExecutorService().schedule(() -> {
if (isActive) {
logger.info("尝试重新连接WebSocket");
Platform.runLater(() -> {
online.setValue(false);
message.set("正在重新连接WebSocket...");
});
initWebSocket();
}
}, RECONNECT_DELAY_MS, TimeUnit.MILLISECONDS);
}
/**
* 关闭WebSocket连接
*/
public void closeWebSocket() {
isActive = false;
stopHeartbeat();
if (reconnectFuture != null && !reconnectFuture.isDone()) {
reconnectFuture.cancel(false);
reconnectFuture = null;
}
if (webSocket != null) {
webSocket.close(1000, "主动关闭连接");
webSocket = null;
}
}
public StringProperty getMessageProperty() {
return message;
}
public BooleanProperty getOnlineProperty() {
return online;
}
public void closeSession(WebSocketClientSession session) {
if (session != null) {
sessions.remove(session.getSessionId());
// session.close();
}
}
public WebSocketClientSession createSession() {
WebSocketClientSession session = new WebSocketClientSession(this);
sessions.put(session.getSessionId(), session);
return session;
}
}

View File

@@ -0,0 +1,131 @@
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.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
/**
*
*/
public class WebSocketClientSession {
private static final Logger logger = LoggerFactory.getLogger(WebSocketClientSession.class);
/**
* 会话ID由客户端创建服务器不保存会话只回传会话ID
*/
@Getter
private final String sessionId = UUID.randomUUID().toString();
@Getter
private boolean done = false;
private WebSocketClientTasker tasker;
private final WebSocketClientService webSocketService;
public WebSocketClientSession(WebSocketClientService webSocketService) {
this.webSocketService = webSocketService;
}
public void close() {
webSocketService.closeSession(this);
}
public void submitTask(WebSocketClientTasker tasker, Locale locale, Object... args) throws JsonProcessingException {
this.tasker = tasker;
send("createTask", tasker.getTaskName(), locale.toLanguageTag(), args);
}
public void send(String type, Object... args) throws JsonProcessingException {
Map<String, Object> arguments = Map.of(
WebSocketConstant.SESSION_ID_FIELD_NAME, getSessionId(),
"type", type,
WebSocketConstant.ARGUMENTS_FIELD_NAME, args);
webSocketService.send(arguments);
}
public void onMessage(JsonNode node) {
if (node.has("type")) {
String type = node.get("type").asText();
JsonNode args = node.get(WebSocketConstant.ARGUMENTS_FIELD_NAME);
if (type.equals("message")) {
handleAsMessage(args);
} else if (type.equals("title")) {
handleAsTitle(args);
} else if (type.equals("property")) {
handleAsProperty(args);
} else if (type.equals("progress")) {
handleAsProgress(args);
} else if (type.equals("start")) {
handleAsStart(args);
} else if (type.equals("done")) {
handleAsDone(args);
done = true;
close();
} else {
tasker.updateMessage(java.util.logging.Level.INFO, "未知的消息类型: " + node.toString());
}
} else {
tasker.updateMessage(java.util.logging.Level.INFO, "未知的消息: " + node.toString());
}
}
private void handleAsStart(JsonNode args) {
tasker.updateMessage(java.util.logging.Level.INFO, "任务开始");
}
private void handleAsDone(JsonNode args) {
tasker.updateMessage(java.util.logging.Level.INFO, "任务完成");
}
private void handleAsProgress(JsonNode args) {
long current = args.get(0).asLong();
long total = args.get(1).asLong();
tasker.updateProgress(current, total);
}
private void handleAsProperty(JsonNode args) {
String name = args.get(0).asText();
Object value = args.get(1);
try {
PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(tasker.getClass(), name);
if (descriptor == null) {
tasker.updateMessage(java.util.logging.Level.SEVERE, "属性 " + name + " 不存在");
return;
}
Object object = webSocketService.getObjectMapper().convertValue(value, descriptor.getPropertyType());
if (descriptor.getWriteMethod() == null) {
tasker.updateMessage(java.util.logging.Level.SEVERE, "属性 " + name + " 不可写");
} else {
descriptor.getWriteMethod().invoke(tasker, object);
}
} catch (Exception e) {
tasker.updateMessage(java.util.logging.Level.SEVERE, "属性设置失败: " + name + " = " + value);
logger.error("set {} = {}", name, value, e);
}
}
private void handleAsTitle(JsonNode args) {
String message = args.get(0).asText();
tasker.updateTitle(message);
}
private void handleAsMessage(JsonNode args) {
String level = args.get(0).asText();
String message = args.get(1).asText();
updateMessage(java.util.logging.Level.parse(level), "[R] " + message);
}
public void updateMessage(Level level, String message) {
tasker.updateMessage(level, message);
}
}

View File

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

View File

@@ -1,7 +1,10 @@
package com.ecep.contract.manager.ui;
package com.ecep.contract.controller;
import com.ecep.contract.manager.ds.other.model.IdentityEntity;
import com.ecep.contract.manager.ds.other.vo.IdentityViewModel;
import org.apache.poi.ss.formula.functions.T;
import com.ecep.contract.model.IdentityEntity;
import com.ecep.contract.service.ViewModelService;
import com.ecep.contract.vm.IdentityViewModel;
import javafx.scene.control.TableView;
import javafx.stage.WindowEvent;

View File

@@ -1,11 +1,21 @@
package com.ecep.contract.manager.ui;
package com.ecep.contract.controller;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
import com.ecep.contract.controller.tab.RefreshableSkin;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.model.IdentityEntity;
import com.ecep.contract.service.QueryService;
import com.ecep.contract.service.ViewModelService;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.BaseViewModel;
import com.ecep.contract.vm.IdentityViewModel;
import com.ecep.contract.manager.ds.other.model.IdentityEntity;
import com.ecep.contract.manager.ds.other.vo.BaseViewModel;
import com.ecep.contract.manager.ds.other.vo.IdentityViewModel;
import com.ecep.contract.manager.ui.tab.AbstEntityBasedTabSkin;
import com.ecep.contract.manager.ui.tab.TabSkin;
import com.ecep.contract.manager.util.UITools;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
@@ -18,12 +28,6 @@ import javafx.stage.WindowEvent;
import lombok.Getter;
import lombok.Setter;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
public abstract class AbstEntityController<T extends IdentityEntity, TV extends IdentityViewModel<T>>
extends BaseController {
@@ -41,38 +45,55 @@ public abstract class AbstEntityController<T extends IdentityEntity, TV extends
protected CompletableFuture<T> loadedFuture;
private final ObservableList<TabSkin> tabSkins = FXCollections.observableArrayList();
@Override
public void onShown(WindowEvent windowEvent) {
Class<?> aClass = getClass();
super.onShown(windowEvent);
loadedFuture = CompletableFuture.supplyAsync(() -> {
T entity = loadEntity();
if (entity == null) {
// fixed, bind change if new view model create
if (viewModel != null) {
viewModel.bindListener();
}
return null;
}
Platform.runLater(() -> {
setStatus();
viewModel.update(entity);
});
viewModel.bindListener();
return entity;
});
// 载数据
initializeData();
// 注册 skin
registerTabSkins();
// 初始化保存按钮
initializeSaveBtn();
// 安装 skin
installTabSkins();
}
protected void initializeSaveBtn() {
if (saveBtn != null) {
saveBtn.disableProperty().bind(createTabSkinChangedBindings().not());
saveBtn.setOnAction(event -> saveTabSkins());
}
installTabSkins();
}
protected T loadEntity() {
return getViewModelService().findById(viewModel.getId().get());
protected void initializeData() {
ViewModelService<T, TV> service = getViewModelService();
if (service instanceof QueryService<T, TV> queryService) {
setStatus("读取...");
loadedFuture = queryService.asyncFindById(viewModel.getId().get());
} else {
loadedFuture = CompletableFuture.supplyAsync(() -> {
return getViewModelService().findById(viewModel.getId().get());
});
}
loadedFuture.thenAccept(entity -> {
// fixed, bind change if new view model create
if (viewModel != null) {
updateViewModel(entity);
viewModel.bindListener();
}
setStatus();
});
loadedFuture.exceptionally(ex -> {
handleException("载入失败,#" + viewModel.getId().get(), ex);
return null;
});
}
protected void updateViewModel(T entity) {
BaseViewModel.updateInFxApplicationThread(entity, viewModel);
}
public T getEntity() {
@@ -94,6 +115,19 @@ public abstract class AbstEntityController<T extends IdentityEntity, TV extends
return saved;
}
public void save() {
T entity = getEntity();
if (entity == null) {
return;
}
if (viewModel.copyTo(entity)) {
save(entity);
}
}
/**
* 注册 skin
*/
protected void registerTabSkins() {
}
@@ -105,6 +139,9 @@ public abstract class AbstEntityController<T extends IdentityEntity, TV extends
return f;
}
/**
* 安装 skin
*/
protected void installTabSkins() {
for (TabSkin tabSkin : tabSkins) {
try {
@@ -190,35 +227,42 @@ public abstract class AbstEntityController<T extends IdentityEntity, TV extends
public CompletableFuture<Void> refresh() {
CompletableFuture<Void> future = new CompletableFuture<>();
T entity = loadEntity();
Platform.runLater(() -> {
ViewModelService<T, TV> service = getViewModelService();
if (service instanceof QueryService<T, TV> queryService) {
loadedFuture = queryService.asyncFindById(viewModel.getId().get());
loadedFuture.whenComplete((entity, ex) -> {
if (ex != null) {
future.completeExceptionally(ex);
return;
}
BaseViewModel.updateInFxApplicationThread(entity, viewModel);
});
} else {
T entity = service.findById(viewModel.getId().get());
setEntity(entity);
}
List<RefreshableSkin> list = tabSkins.stream()
.filter(v -> v instanceof RefreshableSkin)
.map(v -> ((RefreshableSkin) v)).toList();
List<RefreshableSkin> list = tabSkins.stream()
.filter(v -> v instanceof RefreshableSkin)
.map(v -> ((RefreshableSkin) v)).toList();
if (list.isEmpty()) {
future.complete(null);
return;
}
if (list.isEmpty()) {
future.complete(null);
return future;
}
CompletableFuture.allOf(list
.stream()
.map(RefreshableSkin::refresh)
.filter(Objects::nonNull)
.toArray(CompletableFuture<?>[]::new))
.whenComplete((v, ex) -> {
if (ex != null) {
future.completeExceptionally(ex);
} else {
future.complete(null);
}
});
});
return future;
return CompletableFuture.allOf(list
.stream()
.map(RefreshableSkin::refresh)
.filter(Objects::nonNull)
.toArray(CompletableFuture<?>[]::new))
.whenComplete((v, ex) -> {
if (ex != null) {
future.completeExceptionally(ex);
} else {
future.complete(null);
}
});
}
public abstract ViewModelService<T, TV> getViewModelService();

View File

@@ -1,27 +1,31 @@
package com.ecep.contract.manager.ui;
package com.ecep.contract.controller;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import com.ecep.contract.manager.ui.table.EditableEntityTableTabSkin;
import com.ecep.contract.manager.ui.table.TableTabSkin;
import com.ecep.contract.util.ParamUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import com.ecep.contract.manager.ds.other.model.IdentityEntity;
import com.ecep.contract.manager.ds.other.vo.IdentityViewModel;
import com.ecep.contract.manager.util.TableViewUtils;
import com.ecep.contract.manager.util.UITools;
import com.ecep.contract.controller.table.EditableEntityTableTabSkin;
import com.ecep.contract.controller.table.TableTabSkin;
import com.ecep.contract.model.IdentityEntity;
import com.ecep.contract.service.QueryService;
import com.ecep.contract.service.ViewModelService;
import com.ecep.contract.util.TableViewUtils;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.IdentityViewModel;
import javafx.application.Platform;
import javafx.beans.property.Property;
@@ -43,12 +47,15 @@ import javafx.scene.input.KeyEvent;
import javafx.util.converter.NumberStringConverter;
/**
* 实体管理器皮肤
* 提供了实体管理器的基本功能如查询新增删除修改分页等
*
* @param <T> Entity 的类型
* @param <TV> Entity 对应的ViewModel
* @param <Skin> Skin 的类型
* @param <SKIN> Skin 的类型
* @param <C>
*/
public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends IdentityViewModel<T>, Skin extends ManagerSkin, C extends AbstManagerWindowController<T, TV, Skin>>
public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends IdentityViewModel<T>, SKIN extends ManagerSkin, C extends AbstManagerWindowController<T, TV, SKIN>>
implements ManagerSkin, TableTabSkin<T, TV>, EditableEntityTableTabSkin<T, TV> {
private static final Logger logger = LoggerFactory.getLogger(AbstEntityManagerSkin.class);
/**
@@ -61,11 +68,20 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
protected PageRequest currentPageable = PageRequest.ofSize(25);
protected final SimpleIntegerProperty currentPageNumber = new SimpleIntegerProperty();
// 是否允许调整表格高度
private boolean allowResize = true;
// 记录延时任务信息
private ScheduledFuture<?> loadTableDataSetFuture;
public AbstEntityManagerSkin(C controller) {
this.controller = controller;
}
@Override
public <TT> TT getBean(Class<TT> requiredType) throws BeansException {
return controller.getCachedBean(requiredType);
}
@Override
public TableView<TV> getTableView() {
return controller.table;
@@ -199,6 +215,9 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
* 根据表格高度重新计算分页的页大小
*/
private void resizeTable(Object observable, Bounds old, Bounds newBounds) {
if (!allowResize) {
return;
}
double tableHeight = newBounds.getHeight();
if (tableHeight <= 0) {
return;
@@ -208,8 +227,13 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
if (lookup != null) {
double rowHeight = lookup.prefHeight(-1);
int rows = (int) Math.round(table.getHeight() / rowHeight) - 1;
int pageNumber = (int) Math.abs(currentPageable.getOffset() / rows);
// 只有当行数变化超过一定阈值时才重新加载数据
int currentRows = currentPageable.getPageSize();
if (Math.abs(rows - currentRows) <= 2) {
return; // 避免微小变化导致频繁刷新
}
int pageNumber = (int) Math.abs(currentPageable.getOffset() / rows);
if (currentPageable.getPageNumber() == pageNumber && currentPageable.getPageSize() == rows) {
return;
}
@@ -259,9 +283,17 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
}
}
protected <K> void acceptCellEditEvent(TableColumn.CellEditEvent<TV, K> event, Function<TV, Property<K>> function) {
/**
* 处理单元格编辑事件
*
* @param <K>
* @param event
* @param propGetter
*/
protected <K> void acceptCellEditEvent(TableColumn.CellEditEvent<TV, K> event,
Function<TV, Property<K>> propGetter) {
TV row = event.getRowValue();
Property<K> property = function.apply(row);
Property<K> property = propGetter.apply(row);
property.setValue(event.getNewValue());
try {
saveRowData(row);
@@ -317,21 +349,47 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
row.saveInFxApplicationThread(service);
}
/**
* 加载行数据
*
* @param row
* @return
*/
public T loadRowData(TV row) {
return getViewModelService().findById(row.getId().get());
if (row.getId() == null) {
return null;
}
T entity = getViewModelService().findById(row.getId().get());
return entity;
}
/**
* 删除行数据
*
* @param entity
*/
public void deleteRowData(T entity) {
if (entity == null) {
return;
}
ViewModelService<T, TV> service = getViewModelService();
getViewModelService().delete(entity);
}
/**
* 保存行数据
*
* @param entity
* @return
*/
public T saveRowData(T entity) {
if (entity == null) {
return null;
}
ViewModelService<T, TV> service = getViewModelService();
return getViewModelService().save(entity);
}
// 记录延时任务信息
private ScheduledFuture<?> loadTableDataSetFuture;
@Override
public void loadTableDataSet() {
loadTableDataSet(false);
@@ -359,18 +417,18 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
private CompletableFuture<Void> _reloadTableData() {
CompletableFuture<Void> future = new CompletableFuture<>();
Platform.runLater(() -> {
allowResize = false; // 禁用调整
dataSet.clear();
runAsync(() -> {
controller.setStatus("载入中...");
// 异步加载数据
if (getViewModelService() instanceof QueryService<T, TV> queryService) {
asyncLoadTableData(queryService, future);
return;
}
// 同步加载方法
List<TV> models = loadTableData();
Platform.runLater(() -> {
try {
updateTableDataSet(models);
future.complete(null);
} catch (Exception e) {
future.completeExceptionally(e);
}
});
_updateModels(models, future);
}).exceptionally(ex -> {
future.completeExceptionally(ex);
return null;
@@ -379,20 +437,60 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
return future;
}
private void _updateModels(List<TV> models, CompletableFuture<Void> future) {
Platform.runLater(() -> {
try {
updateTableDataSet(models);
allowResize = true; // 恢复调整
future.complete(null);
} catch (Exception e) {
allowResize = true; // 恢复调整
future.completeExceptionally(e);
}
});
}
/**
* 更新表格数据
*
* @param models
*/
protected void updateTableDataSet(List<TV> models) {
long timeMillis = System.currentTimeMillis();
dataSet.setAll(models);
// 清除所有选择状态避免选择状态混乱
if (getTableView() != null && getTableView().getSelectionModel() != null) {
getTableView().getSelectionModel().clearSelection();
}
// 先清空再设置新数据避免数据叠加
dataSet.clear();
if (models != null) {
dataSet.addAll(models);
}
// 强制刷新表格布局
if (getTableView() != null) {
getTableView().requestLayout();
getTableView().refresh();
}
long timeCost = System.currentTimeMillis() - timeMillis;
if (logger.isDebugEnabled()) {
logger.debug("update table dataSet cost: {} ms", timeCost);
}
}
/**
* 加载表格数据
*
* @return
*/
protected List<TV> loadTableData() {
Specification<T> spec = getSpecification();
ParamUtils.Builder params = getSpecification();
ViewModelService<T, TV> service = getViewModelService();
long timeMillis = System.currentTimeMillis();
Page<T> page = service.findAll(spec, getPageable());
Page<T> page = service.findAll(params == null ? null : params.build(), getPageable());
long timeCost = System.currentTimeMillis() - timeMillis;
if (logger.isDebugEnabled()) {
logger.debug("load table data cost: {} ms", timeCost);
@@ -404,17 +502,45 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
return page.map(service::from).toList();
}
/**
* 异步加载表格数据
*
* @param queryService
* @param future
*/
private void asyncLoadTableData(QueryService<T, TV> queryService, CompletableFuture<Void> future) {
ParamUtils.Builder params = getSpecification();
queryService.asyncFindAll(params == null ? null : params.build(), getPageable()).whenComplete((result, ex) -> {
if (ex != null) {
future.completeExceptionally(ex);
return;
}
updateFooter(result);
List<TV> models = result.map(getViewModelService()::from).toList();
_updateModels(models, future);
});
}
/**
* 获取ViewModelService
*
* @return
*/
protected ViewModelService<T, TV> getViewModelService() {
ViewModelService<T, TV> service = controller.getViewModelService();
if (service == null) {
if (logger.isWarnEnabled()) {
logger.warn("ViewModelService is null");
}
throw new IllegalArgumentException("ViewModelService is null");
}
return service;
}
public Specification<T> getSpecification() {
/**
* 获取查询条件
*
* @return
*/
public ParamUtils.Builder getSpecification() {
TextField field = controller.searchKeyField;
if (field != null) {
return getViewModelService().getSpecification(field.getText());
@@ -422,8 +548,14 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
return null;
}
protected Specification<T> getSpecification(String searchText) {
return controller.getViewModelService().getSpecification(searchText);
/**
* 获取查询条件
*
* @param searchText
* @return
*/
protected ParamUtils.Builder getSpecification(String searchText) {
return getViewModelService().getSpecification(searchText);
}
/**
@@ -434,6 +566,11 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
protected void onTableRowDoubleClickedAction(TV item) {
}
/**
* 更新页脚
*
* @param page
*/
protected void updateFooter(Page<T> page) {
Platform.runLater(() -> {
controller.previousPageBtn.setDisable(!page.hasPrevious());
@@ -444,19 +581,44 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
});
}
/**
* 获取表格排序
*
* @return
*/
public List<Sort.Order> getTableOrders() {
return TableViewUtils.getOrders(getTableView());
}
/**
* 获取表格排序
*
* @return
*/
public Sort getSortByTable() {
if (getTableView() == null) {
return Sort.unsorted();
}
return Sort.by(getTableOrders());
}
/**
* 获取分页参数
*
* @return
*/
public Pageable getPageable() {
Sort sort = getSortByTable();
return currentPageable.withSort(sort);
}
/**
* 显示在当前窗口为父窗口的新窗口
*
* @param <Controller> 控制器类型
* @param clz 控制器类
* @param model 数据
*/
protected <Controller extends AbstEntityController<T, TV>> void showInOwner(Class<Controller> clz, TV model) {
BaseController.show(clz, model, getTableView().getScene().getWindow());
}

View File

@@ -1,7 +1,7 @@
package com.ecep.contract.manager.ui;
package com.ecep.contract.controller;
import com.ecep.contract.manager.ds.other.model.IdentityEntity;
import com.ecep.contract.manager.ds.other.vo.IdentityViewModel;
import com.ecep.contract.model.IdentityEntity;
import com.ecep.contract.vm.IdentityViewModel;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
@@ -41,6 +41,4 @@ public abstract class AbstManagerWindowController<T extends IdentityEntity, TV e
}
}

View File

@@ -1,14 +1,35 @@
package com.ecep.contract.manager.ui;
package com.ecep.contract.controller;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import com.ecep.contract.util.BeanContext;
import com.ecep.contract.util.DefaultBeanContext;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.FxmlUtils;
import com.ecep.contract.vo.EmployeeVo;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import com.ecep.contract.Desktop;
import com.ecep.contract.SpringApp;
import com.ecep.contract.model.Employee;
import com.ecep.contract.model.IdentityEntity;
import com.ecep.contract.service.EmployeeService;
import com.ecep.contract.service.SysConfService;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.CurrentEmployee;
import com.ecep.contract.vm.IdentityViewModel;
import com.ecep.contract.manager.Desktop;
import com.ecep.contract.manager.SpringApp;
import com.ecep.contract.manager.ds.other.model.Employee;
import com.ecep.contract.manager.ds.other.model.IdentityEntity;
import com.ecep.contract.manager.ds.other.service.EmployeeService;
import com.ecep.contract.manager.ds.other.service.SysConfService;
import com.ecep.contract.manager.ds.other.vo.IdentityViewModel;
import com.ecep.contract.manager.util.FxmlUtils;
import com.ecep.contract.manager.util.UITools;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
@@ -21,23 +42,8 @@ import javafx.stage.Stage;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
import lombok.Getter;
import lombok.Setter;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
public class BaseController {
public class BaseController implements BeanContext {
private static final Logger logger = LoggerFactory.getLogger(BaseController.class);
public static HashMap<String, Stage> stages = new HashMap<>();
@@ -45,7 +51,8 @@ public class BaseController {
return show(clz, owner, null);
}
public static <T extends BaseController> CompletableFuture<Void> show(Class<T> clz, Window owner, Consumer<T> consumer) {
public static <T extends BaseController> CompletableFuture<Void> show(Class<T> clz, Window owner,
Consumer<T> consumer) {
String key = clz.getName();
if (toFront(key)) {
return null;
@@ -63,10 +70,10 @@ public class BaseController {
controller.show(loader, owner, Modality.NONE, key);
});
}
@SuppressWarnings("unchecked")
public static <K extends IdentityEntity, M extends IdentityViewModel<K>, T extends BaseController>
void show(Class<T> clz, M viewModel, Window owner) {
public static <K extends IdentityEntity, M extends IdentityViewModel<K>, T extends BaseController> void show(
Class<T> clz, M viewModel, Window owner) {
String key = getKey(clz, viewModel);
if (toFront(key)) {
return;
@@ -76,7 +83,6 @@ public class BaseController {
throw new RuntimeException("@FxmlPath is required");
}
FxmlUtils.newLoaderAsyncWithRunLater(annotation.value(), null, loader -> {
T controller = loader.getController();
if (controller instanceof AbstEntityController<?, ?>) {
@@ -114,7 +120,6 @@ public class BaseController {
});
}
public static boolean toFront(String key) {
Stage stage = stages.get(key);
if (stage != null) {
@@ -161,6 +166,11 @@ public class BaseController {
return future;
}
public CompletableFuture<Void> runAsync(Runnable runnable) {
return CompletableFuture.runAsync(runnable, Desktop.instance.getExecutorService())
.exceptionally(this::handleException);
}
/**
* 窗口标题
*/
@@ -177,42 +187,44 @@ public class BaseController {
private String stageKey;
public Label leftStatusLabel;
public Label rightStatusLabel;
private EmployeeVo currentUser;
@Getter
@Setter
private Locale locale = Locale.getDefault();
private BeanContext beanContext = new DefaultBeanContext();
public <T> T getBean(Class<T> requiredType) throws BeansException {
return beanContext.getBean(requiredType);
}
@Setter
private SysConfService confService;
@Setter
private EmployeeService employeeService;
private Employee currentUser;
protected <T> T getBean(Class<T> requiredType) throws BeansException {
return SpringApp.getBean(requiredType);
public <T> T getCachedBean(Class<T> requiredType) throws BeansException {
return beanContext.getCachedBean(requiredType);
}
public SysConfService getConfService() {
if (confService == null) {
confService = getBean(SysConfService.class);
}
return confService;
return getCachedBean(SysConfService.class);
}
public EmployeeService getEmployeeService() {
if (employeeService == null) {
employeeService = getBean(EmployeeService.class);
}
return employeeService;
return getCachedBean(EmployeeService.class);
}
public Employee getCurrentUser() {
public EmployeeVo getCurrentUser() {
if (currentUser == null) {
currentUser = getEmployeeService().findById(Desktop.instance.getActiveEmployeeId());
}
return currentUser;
}
public CurrentEmployee getCurrentEmployee() {
return Desktop.instance.getActiveEmployee();
}
public Locale getLocale() {
CurrentEmployee currentEmployee = getCurrentEmployee();
if (currentEmployee == null) {
return Locale.getDefault();
}
return currentEmployee.localeProperty().get();
}
public String getMessage(String code, Object... args) {
return SpringApp.getMessage(code, args, getLocale());
}

View File

@@ -1,7 +1,10 @@
package com.ecep.contract.manager;
package com.ecep.contract.controller;
import org.springframework.context.ApplicationEvent;
import com.ecep.contract.vm.CurrentEmployee;
import lombok.Getter;
import org.springframework.context.ApplicationEvent;
@Getter
public class CurrentEmployeeInitialedEvent extends ApplicationEvent {

View File

@@ -1,11 +1,16 @@
package com.ecep.contract.manager.ds.other.controller;
package com.ecep.contract.controller;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import com.ecep.contract.controller.cloud.rk.CloudRkManagerWindowController;
import com.ecep.contract.controller.cloud.tyc.CloudTycManagerWindowController;
import com.ecep.contract.controller.cloud.u8.YongYouU8ManagerWindowController;
import org.controlsfx.control.TaskProgressView;
import org.controlsfx.glyphfont.Glyph;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
@@ -14,32 +19,26 @@ import org.springframework.context.annotation.Scope;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import com.ecep.contract.manager.CurrentEmployee;
import com.ecep.contract.manager.CurrentEmployeeInitialedEvent;
import com.ecep.contract.manager.Desktop;
import com.ecep.contract.manager.cloud.old.OldVersionService;
import com.ecep.contract.manager.cloud.rk.CloudRkManagerWindowController;
import com.ecep.contract.manager.cloud.rk.CloudRkService;
import com.ecep.contract.manager.cloud.tyc.CloudTycManagerWindowController;
import com.ecep.contract.manager.cloud.u8.ContractSyncTask;
import com.ecep.contract.manager.cloud.u8.YongYouU8ManagerWindowController;
import com.ecep.contract.manager.cloud.u8.YongYouU8Service;
import com.ecep.contract.manager.ds.company.controller.CompanyManagerWindowController;
import com.ecep.contract.manager.ds.contract.controller.ContractManagerWindowController;
import com.ecep.contract.manager.ds.customer.controller.CompanyCustomerManagerWindowController;
import com.ecep.contract.manager.ds.other.controller.bank.BankManagerWindowController;
import com.ecep.contract.manager.ds.other.controller.department.DepartmentManagerWindowController;
import com.ecep.contract.manager.ds.other.controller.employee.EmployeeManagerWindowController;
import com.ecep.contract.manager.ds.other.controller.inventory.InventoryManagerWindowController;
import com.ecep.contract.manager.ds.other.controller.permission.EmployeeFunctionsManagerWindowController;
import com.ecep.contract.manager.ds.other.controller.permission.EmployeeRoleManagerWindowController;
import com.ecep.contract.manager.ds.project.controller.ProjectManagerWindowController;
import com.ecep.contract.manager.ds.vendor.controller.CompanyVendorManagerWindowController;
import com.ecep.contract.manager.ui.BaseController;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.ui.task.TaskMonitorViewController;
import com.ecep.contract.manager.util.DesktopUtils;
import com.ecep.contract.manager.util.FxmlUtils;
import com.ecep.contract.Desktop;
import com.ecep.contract.DesktopUtils;
import com.ecep.contract.WebSocketClientService;
import com.ecep.contract.controller.bank.BankManagerWindowController;
import com.ecep.contract.controller.company.CompanyManagerWindowController;
import com.ecep.contract.controller.contract.ContractManagerWindowController;
import com.ecep.contract.controller.customer.CompanyCustomerManagerWindowController;
import com.ecep.contract.controller.department.DepartmentManagerWindowController;
import com.ecep.contract.controller.employee.EmployeeManagerWindowController;
import com.ecep.contract.controller.inventory.InventoryManagerWindowController;
import com.ecep.contract.controller.permission.EmployeeFunctionsManagerWindowController;
import com.ecep.contract.controller.permission.EmployeeRoleManagerWindowController;
import com.ecep.contract.controller.project.ProjectManagerWindowController;
import com.ecep.contract.controller.vendor.VendorManagerWindowController;
import com.ecep.contract.service.CloudRkService;
import com.ecep.contract.service.YongYouU8Service;
import com.ecep.contract.task.ContractSyncTask;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.FxmlUtils;
import com.ecep.contract.vm.CurrentEmployee;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
@@ -75,13 +74,15 @@ public class HomeWindowController extends BaseController {
public Button openCustomManagerWindow;
public TaskProgressView<Task<?>> taskProgressView;
public Label taskMonitorLabel;
public Label webSocketMonitorLabel;
public Glyph webSocketMonitorIcon;
public Label employeeStatusLabel;
public void initialize() {
openCompanyManagerWindow.setOnAction(event -> showInOwner(CompanyManagerWindowController.class));
openProjectManagerWindow.setOnAction(event -> showInOwner(ProjectManagerWindowController.class));
openContractManagerWindow.setOnAction(event -> showInOwner(ContractManagerWindowController.class));
openVendorManagerWindow.setOnAction(event -> showInOwner(CompanyVendorManagerWindowController.class));
openVendorManagerWindow.setOnAction(event -> showInOwner(VendorManagerWindowController.class));
openCustomManagerWindow.setOnAction(event -> showInOwner(CompanyCustomerManagerWindowController.class));
}
@@ -102,20 +103,26 @@ public class HomeWindowController extends BaseController {
employeeStatusLabel.textProperty().bind(Desktop.instance.getActiveEmployee().getName());
Desktop.instance.getTaskMonitorCenter().bindStatusLabel(taskMonitorLabel);
Desktop.instance.getActiveEmployee().initialize();
WebSocketClientService webSocketService = getBean(WebSocketClientService.class);
webSocketMonitorIcon.iconProperty()
.bind(webSocketService.getOnlineProperty().map(b -> b ? "CHAIN" : "CHAIN_BROKEN"));
webSocketMonitorLabel.textProperty().bind(webSocketService.getMessageProperty());
webSocketMonitorLabel.setOnMouseClicked(event -> {
webSocketService.send("webSocketUrl - " + LocalDateTime.now().toString());
});
webSocketService.initWebSocket();
}
@EventListener
public void onCurrentEmployeeInitialed(CurrentEmployeeInitialedEvent event) {
System.out.println("event = " + event);
CurrentEmployee currentEmployee = event.getEmployee();
if (currentEmployee.isSystemAdministrator()) {
if (logger.isInfoEnabled()) {
logger.info("You are administrator, try schedule sync tasks.");
}
Desktop.instance.getExecutorService().schedule(() -> {
try {
getBean(OldVersionService.class).scheduledTasks(taskProgressView);
} catch (BeansException ignored) {
}
try {
getBean(YongYouU8Service.class).scheduledTasks(taskProgressView);
} catch (BeansException ignored) {
@@ -140,14 +147,6 @@ public class HomeWindowController extends BaseController {
// scheduledExecutorService.shutdownNow();
}
@Override
public void onHidden(WindowEvent windowEvent) {
System.out.println("windowEvent = " + windowEvent);
super.onHidden(windowEvent);
// Platform.exit();
}
/**
* 打开 配置 窗口
*/
@@ -221,12 +220,11 @@ public class HomeWindowController extends BaseController {
showInOwner(TaskMonitorViewController.class);
}
/**
* 运行任务监控演示
*/
public void onRunTaskMonitorDemo(ActionEvent event) {
com.ecep.contract.manager.ui.task.DemoTask.runDemo();
// 自动打开任务监控窗口
showInOwner(TaskMonitorViewController.class);
@Override
public void onHidden(WindowEvent windowEvent) {
System.out.println("windowEvent = " + windowEvent);
WebSocketClientService webSocketService = getBean(WebSocketClientService.class);
webSocketService.closeWebSocket(); // 在窗口隐藏时关闭WebSocket连接
super.onHidden(windowEvent);
}
}

View File

@@ -1,4 +1,6 @@
package com.ecep.contract.manager.ui;
package com.ecep.contract.controller;
import com.ecep.contract.controller.tab.Skin;
public interface ManagerSkin extends Skin {

View File

@@ -0,0 +1,369 @@
package com.ecep.contract.controller;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
import javafx.beans.property.SimpleStringProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import com.ecep.contract.Desktop;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.MyProperties;
import com.ecep.contract.SpringApp;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import lombok.Setter;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.WebSocket;
public class OkHttpLoginController implements MessageHolder {
private static final Logger logger = LoggerFactory.getLogger(OkHttpLoginController.class);
public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
@Setter
private MessageHolder holder;
@Setter
private Stage primaryStage;
@Setter
private MyProperties properties;
@Setter
private OkHttpClient httpClient;
private SimpleStringProperty serverUrl = new SimpleStringProperty();
private String webSocketUrl;
public OkHttpLoginController() {
}
@Override
public void addMessage(Level level, String message) {
if (holder != null) {
holder.addMessage(level, message);
}
}
private void initServerUrls() {
String host = properties.getServerHost();
String port = properties.getServerPort();
serverUrl.set("http://" + host + ":" + port);
this.webSocketUrl = "ws://" + host + ":" + port + "/ws";
}
public CompletableFuture<Void> tryLogin() {
initServerUrls();
// 检查配置文件中是否保存用户名和密码
String userName = getUserName();
String password = getPassword();
if (StringUtils.hasText(userName) && StringUtils.hasText(password)) {
return login(userName, password);
} else {
CompletableFuture<Void> loginFuture = new CompletableFuture<>();
Platform.runLater(() -> {
showLoginDialog();
loginFuture.complete(null);
});
return loginFuture;
}
}
private String getUserName() {
return properties.getUserName();
}
private String getPassword() {
return properties.getPassword();
}
private void showLoginDialog() {
Stage stage = new Stage();
stage.initOwner(primaryStage);
stage.setTitle("系统登录");
// 创建 BorderPane 并设置边距
BorderPane borderPane = new BorderPane();
borderPane.setPadding(new Insets(20));
// 创建布局
GridPane grid = new GridPane();
grid.setHgap(10);
grid.setVgap(15);
Label hostLabel = new Label("服务器:");
TextField hostField = new TextField();
{
hostField.textProperty().bindBidirectional(serverUrl);
grid.add(hostLabel, 0, 0);
grid.add(hostField, 1, 0);
}
// 账户输入框
Label userLabel = new Label("用户名:");
TextField userField = new TextField();
{
String username = getUserName();
if (StringUtils.hasText(username)) {
userField.setText(username);
}
grid.add(userLabel, 0, 1);
grid.add(userField, 1, 1);
}
// 密码输入框
Label passwordLabel = new Label("密码:");
PasswordField passwordField = new PasswordField();
{
String password = getPassword();
if (StringUtils.hasText(password)) {
passwordField.setText(password);
}
grid.add(passwordLabel, 0, 2);
grid.add(passwordField, 1, 2);
}
// 记住密码复选框
CheckBox rememberCheckBox = new CheckBox("记住密码");
{
boolean remember = properties.isRememberPassword();
if (remember) {
rememberCheckBox.setSelected(true);
}
}
grid.add(rememberCheckBox, 1, 3);
// 错误消息提示
Label errorLabel = new Label();
errorLabel.setStyle("-fx-text-fill: red;");
errorLabel.setVisible(false);
grid.add(errorLabel, 0, 4);
GridPane.setColumnSpan(errorLabel, 2);
borderPane.setCenter(grid);
// 登录按钮
Button loginButton = new Button("登录");
loginButton.setDefaultButton(true);
loginButton.setPrefWidth(100);
borderPane.setBottom(loginButton);
BorderPane.setAlignment(loginButton, javafx.geometry.Pos.CENTER);
BorderPane.setMargin(loginButton, new Insets(20, 0, 0, 0));
// 登录按钮点击事件
loginButton.setOnAction(event -> {
String username = userField.getText();
String password = passwordField.getText();
boolean remember = rememberCheckBox.isSelected();
if (!StringUtils.hasText(username) || !StringUtils.hasText(password)) {
errorLabel.setText("用户名和密码不能为空");
errorLabel.setVisible(true);
return;
}
errorLabel.setVisible(false);
// 保存配置
if (remember) {
properties.setUserName(username);
properties.setPassword(password);
properties.setRememberPassword(true);
} else {
properties.setUserName(username);
properties.setPassword("");
properties.setRememberPassword(false);
}
properties.save();
// 执行登录
login(username, password).whenComplete((v, e) -> {
if (e != null) {
Platform.runLater(() -> {
errorLabel.setText(e.getMessage());
errorLabel.setVisible(true);
// showError("登录失败", e.getMessage());
});
return;
}
Platform.runLater(() -> {
stage.close();
});
});
});
// 创建场景并设置到窗口
Scene scene = new Scene(borderPane, 400, 280);
stage.setScene(scene);
stage.setResizable(false);
stage.showAndWait();
System.out.println("登录窗口关闭");
}
private CompletableFuture<Void> login(String username, String password) {
// 添加详细日志记录服务器URL和请求准备情况
info("正在连接服务器: " + serverUrl.get());
logger.debug("用户名: {}", username);
CompletableFuture<Void> future = new CompletableFuture<>();
try {
while (!SpringApp.isRunning()) {
holder.info("环境准备中,请稍后...");
Thread.sleep(1000);
}
ObjectMapper objectMapper = SpringApp.getBean(ObjectMapper.class);
ObjectNode objectNode = objectMapper.createObjectNode();
objectNode.put("username", username);
objectNode.put("password", password);
objectNode.put("type", "client");
// 将MacIP列表转换为Map<String, String>格式MAC地址->IP地址
List<MacIP> macIpList = getMacAndIP().join();
ObjectNode signNode = objectMapper.createObjectNode();
for (MacIP macIp : macIpList) {
signNode.put(macIp.mac, macIp.ip);
}
objectNode.set("sign", signNode);
// 构建JSON格式的登录请求
RequestBody body = RequestBody.create(objectNode.toString(), JSON);
// 构建并记录完整的请求URL
String loginUrl = serverUrl.get() + "/api/login";
logger.debug("构建登录请求URL: " + loginUrl);
Request request = new Request.Builder()
.url(loginUrl)
.post(body)
.build();
httpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
future.completeExceptionally(
new IOException("登录失败: 无法连接到服务器,请检查网络连接或服务器配置 - " + e.getMessage(), e));
}
@Override
public void onResponse(Call call, Response response) throws IOException {
try {
if (!response.isSuccessful()) {
future.completeExceptionally(
new IOException("登录失败: 服务器返回错误码 - " + response.toString()));
return;
}
ResponseBody body = response.body();
System.out.println("contentType = " + body.contentType());
JsonNode jsonNode = objectMapper.readTree(body.string());
boolean success = jsonNode.get("success").asBoolean(false);
if (!success) {
future.completeExceptionally(
new IOException("登录失败: 服务器返回错误 - " + jsonNode.get("error").asText()));
return;
}
System.out.println("登录成功: " + jsonNode.toString());
// 登录成功后调用新的API端点获取用户信息
Desktop.instance.setActiveEmployeeId(jsonNode.get("employeeId").asInt());
Desktop.instance.setSessionId(jsonNode.get("sessionId").asText());
future.complete(null);
} finally {
// 确保主响应体被关闭
response.close();
}
}
});
} catch (Exception e) {
future.completeExceptionally(new IOException("登录过程中发生错误: " + e.getMessage(), e));
}
return future;
}
CompletableFuture<List<MacIP>> getMacAndIP() {
return CompletableFuture.supplyAsync(() -> {
List<MacIP> list = new ArrayList<>();
try {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface anInterface = interfaces.nextElement();
if (anInterface.isLoopback() || !anInterface.isUp()) {
continue;
}
byte[] hardwareAddress = anInterface.getHardwareAddress();
if (hardwareAddress == null) {
continue;
}
Enumeration<InetAddress> inetAddresses = anInterface.getInetAddresses();
if (!inetAddresses.hasMoreElements()) {
continue;
}
// -分割16进制表示法
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hardwareAddress.length; i++) {
sb.append(
String.format("%02X%s", hardwareAddress[i], i < hardwareAddress.length - 1 ? "-" : ""));
}
String mac = sb.toString();
while (inetAddresses.hasMoreElements()) {
InetAddress inetAddress = inetAddresses.nextElement();
if (inetAddress.getHostAddress().contains(":")) {
continue; // 跳过IPv6地址
}
list.add(new MacIP(mac, inetAddress.getHostAddress()));
}
}
} catch (SocketException e) {
throw new RuntimeException(e);
}
return list;
});
}
static class MacIP {
String mac;
String ip;
public MacIP(String mac, String ip) {
this.mac = mac;
this.ip = ip;
}
}
}

View File

@@ -0,0 +1,133 @@
package com.ecep.contract.controller;
import java.io.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.ecep.contract.constant.CompanyCustomerConstant;
import com.ecep.contract.constant.CompanyVendorConstant;
import com.ecep.contract.constant.ContractConstant;
import com.ecep.contract.util.StringConfig;
import jakarta.annotation.PreDestroy;
import javafx.event.ActionEvent;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
@Lazy
@Scope("prototype")
@Component
public class SysConfWindowController extends BaseController {
private static final Logger logger = LoggerFactory.getLogger(SysConfWindowController.class);
public Label companyContractPathLabel;
public TextField u8DataBaseServerHostField;
public TextField u8DataBaseCatalogField;
public TextField u8DataBaseUserNameField;
public TextField u8DataBasePasswordField;
public Label vendorPathLabel;
public Label vendorEvaluationFormTemplateLabel;
public Label customerPathLabel;
public Label customerEvaluationFormTemplateLabel;
public Label customerSaleBookPathLabel;
StringConfig contractPathConfig = new StringConfig(ContractConstant.KEY_BASE_PATH);
StringConfig vendorPathConfig = new StringConfig(CompanyVendorConstant.KEY_BASE_PATH);
StringConfig vendorEvaluationFormTemplateConfig = new StringConfig(
CompanyVendorConstant.KEY_EVALUATION_FORM_TEMPLATE);
StringConfig customerPathConfig = new StringConfig(CompanyCustomerConstant.KEY_BASE_PATH);
StringConfig customerEvaluationFormTemplateConfig = new StringConfig(
CompanyCustomerConstant.KEY_EVALUATION_FORM_TEMPLATE);
StringConfig customerSaleBookPathConfig = new StringConfig(CompanyCustomerConstant.KEY_SALEBOOK_PATH);
public void initialize() {
contractPathConfig.setControl(companyContractPathLabel);
contractPathConfig.initialize();
vendorPathConfig.setControl(vendorPathLabel);
vendorPathConfig.initialize();
vendorEvaluationFormTemplateConfig.setControl(vendorEvaluationFormTemplateLabel);
vendorEvaluationFormTemplateConfig.initialize();
customerPathConfig.setControl(customerPathLabel);
customerPathConfig.initialize();
customerEvaluationFormTemplateConfig.setControl(customerEvaluationFormTemplateLabel);
customerEvaluationFormTemplateConfig.initialize();
customerSaleBookPathConfig.setControl(customerSaleBookPathLabel);
customerSaleBookPathConfig.initialize();
logger.debug("#initialize()");
}
private void directoryChoose(StringConfig config, String title, String key, ActionEvent event) {
DirectoryChooser chooser = new DirectoryChooser();
chooser.setTitle(title);
File value = new File(config.getProperty().getValue());
chooser.setInitialDirectory(value);
Node node = (Node) event.getSource();
File selected = chooser.showDialog(node.getScene().getWindow());
if (selected != null) {
config.getProperty().setValue(selected.getAbsolutePath());
config.save();
}
}
public void changeCompanyContractPath(ActionEvent actionEvent) {
directoryChoose(contractPathConfig, "请选择合同目录", ContractConstant.KEY_BASE_PATH, actionEvent);
}
public void changeVendorPath(ActionEvent actionEvent) {
directoryChoose(vendorPathConfig, "请选择供应商目录", CompanyVendorConstant.KEY_BASE_PATH, actionEvent);
}
public void changeCustomerPath(ActionEvent actionEvent) {
directoryChoose(customerPathConfig, "请选择客户目录", CompanyCustomerConstant.KEY_BASE_PATH, actionEvent);
}
public void changeCustomerSaleBookPath(ActionEvent actionEvent) {
directoryChoose(customerSaleBookPathConfig, "请选择销售台账目录", CompanyCustomerConstant.KEY_SALEBOOK_PATH, actionEvent);
}
// 模拟销毁方法
@PreDestroy
public void destroy() {
}
private void fileChoose(StringConfig config, String title, String key, ActionEvent event) {
FileChooser chooser = new FileChooser();
chooser.setTitle(title);
File value = new File(config.getProperty().getValue());
chooser.setInitialDirectory(value.getParentFile());
chooser.setInitialFileName(value.getName());
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(" 模板文件(*.xlsx)", "*.xlsx"));
Node node = (Node) event.getSource();
File selected = chooser.showOpenDialog(node.getScene().getWindow());
if (selected != null) {
config.getProperty().setValue(selected.getAbsolutePath());
config.save();
}
}
public void changeCustomerEvaluationFormTemplate(ActionEvent actionEvent) {
fileChoose(customerEvaluationFormTemplateConfig, "请选择客户资信评估表模板",
CompanyCustomerConstant.KEY_EVALUATION_FORM_TEMPLATE, actionEvent);
}
public void changeVendorEvaluationFormTemplate(ActionEvent actionEvent) {
fileChoose(vendorEvaluationFormTemplateConfig, "请选择供方调查评价表模板",
CompanyVendorConstant.KEY_EVALUATION_FORM_TEMPLATE,
actionEvent);
}
}

View File

@@ -1,18 +1,25 @@
package com.ecep.contract.manager.ui.task;
package com.ecep.contract.controller;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import com.ecep.contract.util.FxmlPath;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.ecep.contract.manager.Desktop;
import com.ecep.contract.manager.ui.BaseController;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.ui.Message;
import com.ecep.contract.manager.util.MyDateTimeUtils;
import com.ecep.contract.Desktop;
import com.ecep.contract.Message;
import com.ecep.contract.MyDateTimeUtils;
import com.ecep.contract.task.MonitoredTask;
import com.ecep.contract.task.TaskHistory;
import com.ecep.contract.task.TaskMonitorCenter;
import javafx.beans.property.SimpleStringProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
@@ -69,10 +76,47 @@ public class TaskMonitorViewController extends BaseController {
private Button cancelTaskButton;
@FXML
private Button clearHistoryButton;
@FXML
private Button refreshExecutorInfoButton;
@FXML
private TableView<ExecutorInfo> executorInfoTable;
@FXML
private TableColumn<ExecutorInfo, String> executorFieldColumn;
@FXML
private TableColumn<ExecutorInfo, String> executorValueColumn;
@FXML
private TableColumn<ExecutorInfo, String> executorDescriptionColumn;
public TaskMonitorViewController() {
}
/**
* 用于存储ExecutorService信息的内部类
*/
public static class ExecutorInfo {
private final String field;
private final String value;
private final String description;
public ExecutorInfo(String field, String value, String description) {
this.field = field;
this.value = value;
this.description = description;
}
public String getField() {
return field;
}
public String getValue() {
return value;
}
public String getDescription() {
return description;
}
}
@Override
public void onShown(WindowEvent windowEvent) {
super.onShown(windowEvent);
@@ -144,6 +188,94 @@ public class TaskMonitorViewController extends BaseController {
});
cancelTaskButton.disableProperty().bind(activeTasksTable.getSelectionModel().selectedItemProperty().isNull());
// 初始化Executor信息表格
initializeExecutorInfoTable();
}
/**
* 初始化Executor信息表格
*/
private void initializeExecutorInfoTable() {
executorFieldColumn.setCellValueFactory(param -> new SimpleStringProperty(param.getValue().getField()));
executorValueColumn.setCellValueFactory(param -> new SimpleStringProperty(param.getValue().getValue()));
executorDescriptionColumn
.setCellValueFactory(param -> new SimpleStringProperty(param.getValue().getDescription()));
// 初始加载Executor信息
refreshExecutorInfo();
// 绑定刷新按钮事件
refreshExecutorInfoButton.setOnAction(event -> refreshExecutorInfo());
}
/**
* 刷新Executor信息
*/
@FXML
private void refreshExecutorInfo() {
executorInfoTable.getItems().clear();
try {
ExecutorService executorService = Desktop.instance.getExecutorService();
if (executorService instanceof ThreadPoolExecutor threadPoolExecutor) {
executorInfoTable.getItems().add(new ExecutorInfo(
"PoolSize",
String.valueOf(threadPoolExecutor.getPoolSize()),
"PoolSize"));
executorInfoTable.getItems().add(new ExecutorInfo(
"活动线程数",
String.valueOf(threadPoolExecutor.getActiveCount()),
"当前正在执行任务的线程数"));
// 添加线程池信息
executorInfoTable.getItems().add(new ExecutorInfo(
"核心线程数",
String.valueOf(threadPoolExecutor.getCorePoolSize()),
"线程池维护的最小线程数"));
executorInfoTable.getItems().add(new ExecutorInfo(
"最大线程数",
String.valueOf(threadPoolExecutor.getMaximumPoolSize()),
"线程池允许的最大线程数"));
executorInfoTable.getItems().add(new ExecutorInfo(
"KeepAliveTime",
String.valueOf(threadPoolExecutor.getKeepAliveTime(TimeUnit.SECONDS)),
"the thread keep-alive time, which is the amount of time that threads may remain idle before being terminated. Threads that wait this amount of time without processing a task will be terminated if there are more than the core number of threads currently in the pool, or if this pool allows core thread timeout."));
executorInfoTable.getItems().add(new ExecutorInfo(
"任务队列大小",
String.valueOf(threadPoolExecutor.getQueue().size()),
"等待执行的任务数量"));
executorInfoTable.getItems().add(new ExecutorInfo(
"已完成任务数",
String.valueOf(threadPoolExecutor.getCompletedTaskCount()),
"已完成执行的任务总数"));
executorInfoTable.getItems().add(new ExecutorInfo(
"LargestPoolSize",
String.valueOf(threadPoolExecutor.getLargestPoolSize()),
"线程池当前状态"));
// 如果是ScheduledExecutorService添加额外信息
if (executorService instanceof ScheduledExecutorService) {
executorInfoTable.getItems().add(new ExecutorInfo(
"线程池类型",
"ScheduledExecutorService",
"可调度的线程池"));
} else {
executorInfoTable.getItems().add(new ExecutorInfo(
"线程池类型",
"ThreadPoolExecutor",
"普通线程池"));
}
} else {
executorInfoTable.getItems().add(new ExecutorInfo(
"错误",
"未知的ExecutorService类型",
"无法获取线程池详细信息"));
}
} catch (Exception e) {
executorInfoTable.getItems().add(new ExecutorInfo(
"异常",
e.getMessage(),
"获取ExecutorService信息时发生错误"));
}
}
/**
@@ -205,7 +337,7 @@ public class TaskMonitorViewController extends BaseController {
text.setFill(Color.ORANGE);
} else if (msg.getLevel() == Level.SEVERE) {
text.setFill(Color.RED);
}else if (msg.getLevel()== Level.CONFIG) {
} else if (msg.getLevel() == Level.CONFIG) {
text.setFill(Color.GRAY);
}
vBox.getChildren().add(text);
@@ -218,4 +350,11 @@ public class TaskMonitorViewController extends BaseController {
alert.getDialogPane().setContent(scrollPane);
alert.showAndWait();
}
/**
* 运行任务监控演示
*/
public void onRunTaskMonitorDemo(ActionEvent event) {
com.ecep.contract.task.DemoTask.runDemo();
}
}

View File

@@ -1,17 +1,18 @@
package com.ecep.contract.manager.ds.other.controller.bank;
package com.ecep.contract.controller.bank;
import com.ecep.contract.controller.AbstEntityManagerSkin;
import com.ecep.contract.controller.ManagerSkin;
import com.ecep.contract.controller.table.EditableEntityTableTabSkin;
import com.ecep.contract.vm.BankViewModel;
import com.ecep.contract.vo.BankVo;
import com.ecep.contract.manager.ds.other.model.Bank;
import com.ecep.contract.manager.ds.other.vo.BankViewModel;
import com.ecep.contract.manager.ui.AbstEntityManagerSkin;
import com.ecep.contract.manager.ui.table.EditableEntityTableTabSkin;
import com.ecep.contract.manager.ui.ManagerSkin;
import javafx.scene.control.TableColumn;
import javafx.scene.control.cell.TextFieldTableCell;
public class BankManagerSkin
extends AbstEntityManagerSkin<Bank, BankViewModel, BankManagerSkin, BankManagerWindowController>
implements ManagerSkin, EditableEntityTableTabSkin<Bank, BankViewModel> {
extends AbstEntityManagerSkin<BankVo, BankViewModel, BankManagerSkin, BankManagerWindowController>
implements ManagerSkin, EditableEntityTableTabSkin<BankVo, BankViewModel> {
public BankManagerSkin(BankManagerWindowController controller) {
super(controller);

View File

@@ -1,23 +1,26 @@
package com.ecep.contract.manager.ds.other.controller.bank;
package com.ecep.contract.controller.bank;
import com.ecep.contract.manager.ds.other.model.Bank;
import com.ecep.contract.manager.ds.other.service.BankService;
import com.ecep.contract.manager.ds.other.vo.BankViewModel;
import com.ecep.contract.manager.ui.AbstManagerWindowController;
import com.ecep.contract.manager.ui.FxmlPath;
import javafx.event.ActionEvent;
import javafx.scene.control.TableColumn;
import org.controlsfx.control.SearchableComboBox;
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.BankService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vm.BankViewModel;
import com.ecep.contract.vo.BankVo;
import javafx.event.ActionEvent;
import javafx.scene.control.TableColumn;
@Lazy
@Scope("prototype")
@Component
@FxmlPath("/ui/bank-manager.fxml")
public class BankManagerWindowController
extends AbstManagerWindowController<Bank, BankViewModel, BankManagerSkin> {
extends AbstManagerWindowController<BankVo, BankViewModel, BankManagerSkin> {
@Autowired
private BankService bankService;
@@ -32,7 +35,6 @@ public class BankManagerWindowController
}
public void onCreateNewAction(ActionEvent event) {
}
public void onReBuildFilesAction(ActionEvent event) {

View File

@@ -1,14 +1,15 @@
package com.ecep.contract.manager.cloud.rk;
package com.ecep.contract.controller.cloud.rk;
import org.hibernate.Hibernate;
import com.ecep.contract.manager.SpringApp;
import com.ecep.contract.manager.ui.table.cell.CompanyTableCell;
import com.ecep.contract.manager.ds.company.controller.CompanyWindowController;
import com.ecep.contract.manager.ds.company.model.Company;
import com.ecep.contract.manager.ds.company.service.CompanyService;
import com.ecep.contract.manager.ui.AbstEntityManagerSkin;
import com.ecep.contract.manager.ui.table.cell.LocalDateTimeTableCell;
import com.ecep.contract.SpringApp;
import com.ecep.contract.controller.AbstEntityManagerSkin;
import com.ecep.contract.controller.company.CompanyWindowController;
import com.ecep.contract.controller.table.cell.CompanyTableCell;
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
import com.ecep.contract.service.CloudRkService;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.vm.CloudRkViewModel;
import com.ecep.contract.vo.CloudRkVo;
import com.ecep.contract.vo.CompanyVo;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
@@ -19,7 +20,7 @@ import javafx.scene.control.cell.CheckBoxTableCell;
import lombok.Setter;
public class CloudRkManagerSkin
extends AbstEntityManagerSkin<CloudRk, CloudRkInfoViewModel, CloudRkManagerSkin, CloudRkManagerWindowController> {
extends AbstEntityManagerSkin<CloudRkVo, CloudRkViewModel, CloudRkManagerSkin, CloudRkManagerWindowController> {
@Setter
private CompanyService companyService;
@@ -47,13 +48,14 @@ public class CloudRkManagerSkin
controller.cloudIdColumn.setCellValueFactory(param -> param.getValue().getCloudId());
controller.latestUpdateColumn.setCellValueFactory(param -> param.getValue().getLatest());
controller.latestUpdateColumn.setCellValueFactory(param -> param.getValue().getLatestUpdate());
controller.latestUpdateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
controller.cloudLatestColumn.setCellValueFactory(param -> param.getValue().getCloudLatest());
controller.cloudLatestColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
controller.cloudBlackListUpdatedColumn.setCellValueFactory(param -> param.getValue().getCloudBlackListUpdated());
controller.cloudBlackListUpdatedColumn
.setCellValueFactory(param -> param.getValue().getCloudBlackListUpdated());
controller.cloudBlackListUpdatedColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
controller.cloudEntUpdateColumn.setCellValueFactory(param -> param.getValue().getCloudEntUpdate());
@@ -65,13 +67,12 @@ public class CloudRkManagerSkin
controller.descriptionColumn.setCellValueFactory(param -> param.getValue().getDescription());
}
private void onAutoUpdateColumnEditCommit(TableColumn.CellEditEvent<CloudRkInfoViewModel, Boolean> event) {
CloudRkInfoViewModel row = event.getRowValue();
private void onAutoUpdateColumnEditCommit(TableColumn.CellEditEvent<CloudRkViewModel, Boolean> event) {
CloudRkViewModel row = event.getRowValue();
row.getAutoUpdate().set(event.getNewValue());
saveRowData(row);
}
@Override
protected void createContextMenu(ContextMenu contextMenu) {
MenuItem item2 = new MenuItem("刷新");
@@ -89,23 +90,21 @@ public class CloudRkManagerSkin
* @param event event
*/
public void onTableClearDescriptionAction(ActionEvent event) {
ObservableList<CloudRkInfoViewModel> selectedItems = getTableView().getSelectionModel().getSelectedItems();
ObservableList<CloudRkViewModel> selectedItems = getTableView().getSelectionModel().getSelectedItems();
if (selectedItems.isEmpty()) {
return;
}
CloudRkService service = getCloudRkService();
for (CloudRkInfoViewModel selectedItem : selectedItems) {
for (CloudRkViewModel selectedItem : selectedItems) {
selectedItem.getDescription().set("");
selectedItem.saveInFxApplicationThread(service);
}
}
@Override
protected void onTableRowDoubleClickedAction(CloudRkInfoViewModel item) {
Company company = item.getCompany().get();
if (!Hibernate.isInitialized(item)) {
company = getCompanyService().findById(company.getId());
}
protected void onTableRowDoubleClickedAction(CloudRkViewModel item) {
Integer companyId = item.getCompany().get();
CompanyVo company = getCompanyService().findById(companyId);
CompanyWindowController.show(company, getTableView().getScene().getWindow());
}
}

View File

@@ -0,0 +1,80 @@
package com.ecep.contract.controller.cloud.rk;
import java.time.LocalDateTime;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer;
import com.ecep.contract.controller.AbstManagerWindowController;
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.Message;
import com.ecep.contract.SpringApp;
import com.ecep.contract.service.CloudRkService;
import com.ecep.contract.task.CloudRkSyncTask;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.CloudRkViewModel;
import com.ecep.contract.vo.CloudRkVo;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.scene.control.TableColumn;
import javafx.stage.WindowEvent;
@Lazy
@Scope("prototype")
@Component
@FxmlPath("/ui/cloud/rk_manager.fxml")
public class CloudRkManagerWindowController
extends AbstManagerWindowController<CloudRkVo, CloudRkViewModel, CloudRkManagerSkin> {
private static final Logger logger = LoggerFactory.getLogger(CloudRkManagerWindowController.class);
public static void show() {
show(CloudRkManagerWindowController.class, null);
}
@Autowired
private CloudRkService cloudRkService;
public TableColumn<CloudRkViewModel, Number> idColumn;
public TableColumn<CloudRkViewModel, LocalDateTime> latestUpdateColumn;
/**
* 集团相关方, Company
*/
public TableColumn<CloudRkViewModel, Integer> companyColumn;
public TableColumn<CloudRkViewModel, String> cloudIdColumn;
public TableColumn<CloudRkViewModel, LocalDateTime> cloudLatestColumn;
public TableColumn<CloudRkViewModel, LocalDateTime> cloudBlackListUpdatedColumn;
public TableColumn<CloudRkViewModel, LocalDateTime> cloudEntUpdateColumn;
public TableColumn<CloudRkViewModel, Boolean> autoUpdateColumn;
public TableColumn<CloudRkViewModel, String> descriptionColumn;
@Override
protected CloudRkManagerSkin createDefaultSkin() {
return new CloudRkManagerSkin(this);
}
@Override
public void onShown(WindowEvent windowEvent) {
super.onShown(windowEvent);
getTitle().set("数据源:集团相关方");
}
public void onDataRepairAction(ActionEvent event) {
}
public void onSyncAction(ActionEvent event) {
CloudRkSyncTask task = new CloudRkSyncTask();
UITools.showTaskDialogAndWait("同步数据", task, null);
}
@Override
public CloudRkService getViewModelService() {
return cloudRkService;
}
}

View File

@@ -1,21 +1,16 @@
package com.ecep.contract.manager.cloud.tyc;
package com.ecep.contract.controller.cloud.tyc;
import java.util.List;
import com.ecep.contract.SpringApp;
import com.ecep.contract.controller.AbstEntityManagerSkin;
import com.ecep.contract.controller.company.CompanyWindowController;
import com.ecep.contract.controller.table.cell.CompanyTableCell;
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
import com.ecep.contract.service.CloudTycService;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.vm.CloudTycInfoViewModel;
import com.ecep.contract.vo.CloudTycVo;
import com.ecep.contract.vo.CompanyVo;
import org.hibernate.Hibernate;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.StringUtils;
import com.ecep.contract.manager.SpringApp;
import com.ecep.contract.manager.ui.table.cell.CompanyTableCell;
import com.ecep.contract.manager.ds.company.controller.CompanyWindowController;
import com.ecep.contract.manager.ds.company.model.Company;
import com.ecep.contract.manager.ds.company.service.CompanyService;
import com.ecep.contract.manager.ui.AbstEntityManagerSkin;
import com.ecep.contract.manager.ui.table.cell.LocalDateTimeTableCell;
import jakarta.persistence.criteria.Path;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.scene.control.ContextMenu;
@@ -24,7 +19,7 @@ import lombok.Setter;
public class CloudTycManagerSkin
extends
AbstEntityManagerSkin<CloudTyc, CloudTycInfoViewModel, CloudTycManagerSkin, CloudTycManagerWindowController> {
AbstEntityManagerSkin<CloudTycVo, CloudTycInfoViewModel, CloudTycManagerSkin, CloudTycManagerWindowController> {
@Setter
private CloudTycService cloudTycService;
@@ -49,31 +44,6 @@ public class CloudTycManagerSkin
return companyService;
}
@Override
protected List<CloudTycInfoViewModel> loadTableData() {
String searchText = controller.searchKeyField.getText();
Specification<CloudTyc> spec = null;
if (StringUtils.hasText(searchText)) {
Specification<CloudTyc> companySpec = (root, query, builder) -> {
Path<Object> company = root.get("company");
return builder.or(
builder.like(company.get("name"), "%" + searchText + "%"),
builder.like(company.get("shortName"), "%" + searchText + "%"));
};
Specification<CloudTyc> cloudIdSpec = (root, query, builder) -> {
return builder.like(root.get("cloudId"), "%" + searchText + "%");
};
spec = Specification.anyOf(companySpec, cloudIdSpec);
}
Page<CloudTyc> page = getCloudTycService().findAll(spec, getPageable());
updateFooter(page);
return page.map(CloudTycInfoViewModel::from).toList();
}
@Override
public void initializeTable() {
controller.idColumn.setCellValueFactory(param -> param.getValue().getId());
@@ -82,7 +52,7 @@ public class CloudTycManagerSkin
controller.cloudIdColumn.setCellValueFactory(param -> param.getValue().getCloudId());
controller.latestUpdateColumn.setCellValueFactory(param -> param.getValue().getLatest());
controller.latestUpdateColumn.setCellValueFactory(param -> param.getValue().getLatestUpdate());
controller.latestUpdateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
controller.cloudLatestColumn.setCellValueFactory(param -> param.getValue().getCloudLatest());
@@ -114,21 +84,20 @@ public class CloudTycManagerSkin
return;
}
for (CloudTycInfoViewModel selectedItem : selectedItems) {
CloudTyc cloudTyc = getCloudTycService().findById(selectedItem.getId().get());
CloudTycVo cloudTyc = getCloudTycService().findById(selectedItem.getId().get());
// selectedItem.getDescription().set("");
if (selectedItem.copyTo(cloudTyc)) {
CloudTyc saved = getCloudTycService().save(cloudTyc);
CloudTycVo saved = getCloudTycService().save(cloudTyc);
selectedItem.update(saved);
}
}
}
//
@Override
protected void onTableRowDoubleClickedAction(CloudTycInfoViewModel item) {
Company company = item.getCompany().get();
if (!Hibernate.isInitialized(item)) {
company = getCompanyService().findById(company.getId());
}
Integer companyId = item.getCompany().get();
CompanyVo company = getCompanyService().findById(companyId);
CompanyWindowController.show(company, getTableView().getScene().getWindow());
}
}

View File

@@ -0,0 +1,76 @@
package com.ecep.contract.controller.cloud.tyc;
import java.time.LocalDateTime;
import com.ecep.contract.controller.AbstManagerWindowController;
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.service.CloudTycService;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vm.CloudTycInfoViewModel;
import com.ecep.contract.vo.CloudTycVo;
import javafx.fxml.FXML;
import javafx.scene.control.TableColumn;
import javafx.stage.Stage;
/**
* 天眼查信息管理窗口控制器
*/
@Lazy
@Scope("prototype")
@Component
@FxmlPath("/ui/cloud/tyc_manager.fxml")
public class CloudTycManagerWindowController
extends AbstManagerWindowController<CloudTycVo, CloudTycInfoViewModel, CloudTycManagerSkin> {
public static void show() {
show(CloudTycManagerWindowController.class, null);
}
@Autowired
private CloudTycService cloudTycService;
@Autowired
private CompanyService companyService;
@FXML
public TableColumn<CloudTycInfoViewModel, Number> idColumn;
@FXML
public TableColumn<CloudTycInfoViewModel, LocalDateTime> latestUpdateColumn;
/**
* 公司, Company
*/
@FXML
public TableColumn<CloudTycInfoViewModel, Integer> companyColumn;
@FXML
public TableColumn<CloudTycInfoViewModel, String> cloudIdColumn;
@FXML
public TableColumn<CloudTycInfoViewModel, LocalDateTime> cloudLatestColumn;
@FXML
public TableColumn<CloudTycInfoViewModel, Number> scoreColumn;
@FXML
public TableColumn<CloudTycInfoViewModel, String> descriptionColumn;
@Override
public CloudTycService getViewModelService() {
return cloudTycService;
}
@Override
protected CloudTycManagerSkin createDefaultSkin() {
return new CloudTycManagerSkin(this);
}
@Override
public void show(Stage stage) {
super.show(stage);
getTitle().set("数据源:天眼查");
}
}

View File

@@ -0,0 +1,70 @@
package com.ecep.contract.controller.cloud.u8;
import com.ecep.contract.controller.BaseController;
import org.controlsfx.control.ToggleSwitch;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.ecep.contract.constant.CloudYuConstant;
import com.ecep.contract.util.BooleanConfig;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.LocalDateConfig;
import com.ecep.contract.util.LocalDateTimeConfig;
import com.ecep.contract.util.StringConfig;
import javafx.fxml.FXML;
import javafx.scene.control.DatePicker;
import javafx.scene.control.TextField;
import javafx.stage.WindowEvent;
@Lazy
@Scope("prototype")
@Component
@FxmlPath("/ui/cloud/u8_config.fxml")
public class YongYouU8ConfigWindowController extends BaseController {
@FXML
private DatePicker auto_create_company_after;
@FXML
private TextField contract_latest_date;
@FXML
private TextField contract_latest_id;
@FXML
private TextField sync_elapse;
@FXML
private ToggleSwitch use_latest_id;
LocalDateConfig config1 = new LocalDateConfig(CloudYuConstant.KEY_AUTO_CREATE_COMPANY_AFTER);
LocalDateTimeConfig config2 = new LocalDateTimeConfig(CloudYuConstant.KEY_SYNC_BY_LATEST_DATE);
StringConfig config3 = new StringConfig(CloudYuConstant.KEY_SYNC_BY_LATEST_ID);
StringConfig config4 = new StringConfig(CloudYuConstant.KEY_SYNC_ELAPSE);
BooleanConfig config5 = new BooleanConfig(CloudYuConstant.KEY_SYNC_USE_LATEST_ID);
@Override
public void onShown(WindowEvent windowEvent) {
super.onShown(windowEvent);
getTitle().set("用友U8配置");
auto_create_company_after.setConverter(getCurrentEmployee().getLocalDateStringConverter());
config1.setControl(auto_create_company_after);
config1.initialize();
config2.setControl(contract_latest_date);
config2.setControlConverter(getCurrentEmployee().getLocalDateTimeStringConverter());
config2.initialize();
config3.setControl(contract_latest_id);
config3.initialize();
config4.setControl(sync_elapse);
config4.initialize();
config5.setControl(use_latest_id);
config5.initialize();
}
}

View File

@@ -0,0 +1,98 @@
package com.ecep.contract.controller.cloud.u8;
import com.ecep.contract.controller.AbstEntityManagerSkin;
import com.ecep.contract.controller.ManagerSkin;
import com.ecep.contract.controller.company.CompanyWindowController;
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.service.CompanyService;
import com.ecep.contract.service.YongYouU8Service;
import com.ecep.contract.vm.CloudYuInfoViewModel;
import com.ecep.contract.vo.CloudYuVo;
import com.ecep.contract.vo.CompanyVo;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.cell.CheckBoxTableCell;
public class YongYouU8ManagerSkin
extends
AbstEntityManagerSkin<CloudYuVo, CloudYuInfoViewModel, YongYouU8ManagerSkin, YongYouU8ManagerWindowController>
implements ManagerSkin {
public YongYouU8ManagerSkin(YongYouU8ManagerWindowController controller) {
super(controller);
}
YongYouU8Service getU8Service() {
return getBean(YongYouU8Service.class);
}
CompanyService getCompanyService() {
return getBean(CompanyService.class);
}
@Override
public void initializeTable() {
controller.idColumn.setCellValueFactory(param -> param.getValue().getId());
controller.companyColumn.setCellValueFactory(param -> param.getValue().getCompany());
controller.companyColumn.setCellFactory( CompanyTableCell.forTableColumn(getCompanyService()));
controller.latestUpdateColumn.setCellValueFactory(param -> param.getValue().getLatestUpdate());
controller.latestUpdateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
controller.cloudLatestColumn.setCellValueFactory(param -> param.getValue().getCloudLatest());
controller.cloudLatestColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
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
protected void createContextMenu(ContextMenu contextMenu) {
MenuItem item2 = new MenuItem("刷新");
item2.setOnAction(this::onTableRefreshAction);
MenuItem item3 = new MenuItem("清空备注");
item3.setOnAction(this::onTableClearDescriptionAction);
contextMenu.getItems().addAll(item2, item3);
}
/**
* 请空选择行的注释
*
* @param event event
*/
public void onTableClearDescriptionAction(ActionEvent event) {
ObservableList<CloudYuInfoViewModel> selectedItems = getTableView().getSelectionModel().getSelectedItems();
if (selectedItems.isEmpty()) {
return;
}
for (CloudYuInfoViewModel selectedItem : selectedItems) {
CloudYuVo yongYouU8Vo = getU8Service().findById(selectedItem.getId().get());
selectedItem.getExceptionMessage().set("");
if (selectedItem.copyTo(yongYouU8Vo)) {
CloudYuVo saved = getU8Service().save(yongYouU8Vo);
selectedItem.update(saved);
}
}
}
@Override
protected void onTableRowDoubleClickedAction(CloudYuInfoViewModel item) {
Integer companyId = item.getCompany().get();
CompanyVo company = getCompanyService().findById(companyId);
CompanyWindowController.show(company, getTableView().getScene().getWindow());
}
}

View File

@@ -0,0 +1,124 @@
package com.ecep.contract.controller.cloud.u8;
import java.time.LocalDateTime;
import com.ecep.contract.controller.AbstManagerWindowController;
import com.ecep.contract.controller.BaseController;
import com.ecep.contract.task.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.ecep.contract.Desktop;
import com.ecep.contract.service.YongYouU8Service;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.CloudYuInfoViewModel;
import com.ecep.contract.vo.CloudYuVo;
import javafx.event.ActionEvent;
import javafx.scene.control.TableColumn;
import javafx.stage.Stage;
@Lazy
@Scope("prototype")
@Component
@FxmlPath("/ui/cloud/u8_manager.fxml")
public class YongYouU8ManagerWindowController
extends AbstManagerWindowController<CloudYuVo, CloudYuInfoViewModel, YongYouU8ManagerSkin> {
private static final Logger logger = LoggerFactory.getLogger(YongYouU8ManagerWindowController.class);
public static void show() {
show(YongYouU8ManagerWindowController.class, null);
}
public TableColumn<CloudYuInfoViewModel, Number> idColumn;
public TableColumn<CloudYuInfoViewModel, LocalDateTime> latestUpdateColumn;
public TableColumn<CloudYuInfoViewModel, Integer> companyColumn;
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;
@Override
public YongYouU8Service getViewModelService() {
return getSkin().getU8Service();
}
@Override
protected YongYouU8ManagerSkin createDefaultSkin() {
return new YongYouU8ManagerSkin(this);
}
@Override
public void show(Stage stage) {
super.show(stage);
getTitle().set("用友U8");
}
/**
* 打开配置窗口
*/
public void onConfigAction(ActionEvent event) {
BaseController.show(YongYouU8ConfigWindowController.class, null);
}
public void onPersonSyncAction(ActionEvent event) {
EmployeesSyncTask task = new EmployeesSyncTask();
UITools.showTaskDialogAndWait("员工数据同步", task, null);
}
public void onContractSyncAction(ActionEvent event) {
ContractSyncTask task = new ContractSyncTask();
UITools.showTaskDialogAndWait("合同数据增量同步", task, null);
}
public void onContractAllSyncAction(ActionEvent event) {
ContractSyncAllTask task = new ContractSyncAllTask();
UITools.showTaskDialogAndWait("合同数据全量同步", task, null);
}
public void onVendorSyncAction(ActionEvent event) {
VendorSyncTask task = new VendorSyncTask();
UITools.showTaskDialogAndWait("供应商数据同步", task, null);
}
public void onCustomerSyncAction(ActionEvent event) {
CustomerSyncTask task = new CustomerSyncTask();
UITools.showTaskDialogAndWait("客户数据同步", task, null);
}
public void onContractGroupSyncAction(ActionEvent event) {
ContractGroupSyncTask task = new ContractGroupSyncTask();
UITools.showTaskDialogAndWait("合同组数据同步", task, null);
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
}
public void onContractTypeSyncAction(ActionEvent event) {
ContractTypeSyncTask task = new ContractTypeSyncTask();
UITools.showTaskDialogAndWait("合同类型数据同步", task, null);
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
}
public void onContractKindSyncAction(ActionEvent event) {
ContractKindSyncTask task = new ContractKindSyncTask();
UITools.showTaskDialogAndWait("合同类型数据同步", task, null);
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
}
public void onVendorClassSyncAction(ActionEvent event) {
VendorClassSyncTask task = new VendorClassSyncTask();
UITools.showTaskDialogAndWait("客户分类数据同步", task, null);
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
}
public void onCustomerClassSyncAction(ActionEvent event) {
CustomerClassSyncTask task = new CustomerClassSyncTask();
UITools.showTaskDialogAndWait("客户分类数据同步", task, null);
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
}
}

View File

@@ -0,0 +1,28 @@
package com.ecep.contract.controller.company;
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.vm.CompanyViewModel;
import com.ecep.contract.vo.CompanyVo;
import javafx.concurrent.Task;
public abstract class AbstCompanyBasedTabSkin
extends AbstEntityBasedTabSkin<CompanyWindowController, CompanyVo, CompanyViewModel>
implements TabSkin {
public AbstCompanyBasedTabSkin(CompanyWindowController controller) {
super(controller);
}
protected CompanyService getCompanyService() {
Task<CompanyService> task = new Task<>() {
@Override
protected CompanyService call() throws Exception {
return controller.getViewModelService();
}
};
return controller.getViewModelService();
}
}

View File

@@ -0,0 +1,45 @@
package com.ecep.contract.controller.company;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.table.AbstEntityTableTabSkin;
import com.ecep.contract.controller.table.TableOfTabSkin;
import com.ecep.contract.model.IdentityEntity;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.vm.CompanyBasedViewModel;
import com.ecep.contract.vm.CompanyViewModel;
import com.ecep.contract.vm.IdentityViewModel;
import com.ecep.contract.vo.CompanyVo;
public abstract class AbstCompanyTableTabSkin<T extends IdentityEntity, TV extends IdentityViewModel<T>>
extends AbstEntityTableTabSkin<CompanyWindowController, CompanyVo, CompanyViewModel, T, TV>
implements TabSkin, TableOfTabSkin<CompanyVo, T, TV> {
public AbstCompanyTableTabSkin(CompanyWindowController controller) {
super(controller);
viewModel = controller.getViewModel();
}
@Override
protected TV createNewViewModel() {
TV model = super.createNewViewModel();
if (model instanceof CompanyBasedViewModel m) {
m.getCompanyId().set(getEntity().getId());
}
return model;
}
protected CompanyService getCompanyService() {
return getParentService();
}
protected CompanyService getParentService() {
return controller.getViewModelService();
}
@Override
public ParamUtils.Builder getSpecification(CompanyVo parent) {
return ParamUtils.builder().equals("company", parent.getId());
}
}

View File

@@ -1,11 +1,11 @@
package com.ecep.contract.manager.ds.company.controller.contact;
package com.ecep.contract.controller.company;
import com.ecep.contract.manager.ds.company.model.CompanyContact;
import com.ecep.contract.manager.ds.company.repository.CompanyContactRepository;
import com.ecep.contract.manager.ui.BaseController;
import com.ecep.contract.manager.ds.company.vo.CompanyContactViewModel;
import com.ecep.contract.manager.util.FxmlUtils;
import com.ecep.contract.manager.util.UITools;
import com.ecep.contract.controller.BaseController;
import com.ecep.contract.service.CompanyContactService;
import com.ecep.contract.util.FxmlUtils;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.CompanyContactViewModel;
import com.ecep.contract.vo.CompanyContactVo;
import javafx.application.Platform;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
@@ -25,13 +25,14 @@ import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationTargetException;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@Lazy
@Scope("prototype")
@Component
public class CompanyContactWindowController extends BaseController {
private static final Logger logger = LoggerFactory.getLogger(CompanyContactWindowController.class);
/**
* 显示界面
*/
@@ -59,8 +60,9 @@ public class CompanyContactWindowController extends BaseController {
@Getter
@Setter
private CompanyContactViewModel viewModel;
@Autowired
private CompanyContactRepository companyContactRepository;
private CompanyContactService companyContactService;
public TextField nameField;
public TextField positionField;
@@ -73,12 +75,13 @@ public class CompanyContactWindowController extends BaseController {
public Label versionLabel;
public Button saveBtn;
private CompletableFuture<CompanyContact> companyContactLoadedFuture;
private CompletableFuture<CompanyContactVo> companyContactLoadedFuture;
@Override
public void show(Stage stage) {
super.show(stage);
getTitle().bind(viewModel.getName().map(v -> "[" + viewModel.getId().get() + "] " + viewModel.getName().getValue() + " 曾用名详情"));
getTitle().bind(viewModel.getName()
.map(v -> "[" + viewModel.getId().get() + "] " + viewModel.getName().getValue() + " 曾用名详情"));
}
@Override
@@ -91,24 +94,19 @@ public class CompanyContactWindowController extends BaseController {
initializeBaseTab();
companyContactLoadedFuture = CompletableFuture.supplyAsync(() -> {
Optional<CompanyContact> optional = companyContactRepository.findById(viewModel.getId().get());
if (optional.isPresent()) {
CompanyContact oldName = optional.get();
Platform.runLater(() -> {
viewModel.update(oldName);
viewModel.bindListener();
if (logger.isDebugEnabled()) {
logger.debug("bind ViewModel({}) Listener", viewModel.getName().get());
}
tabPane.getSelectionModel().getSelectedItem().getOnSelectionChanged().handle(null);
});
return oldName;
}
return null;
CompanyContactVo oldName = companyContactService.findById(viewModel.getId().get());
Platform.runLater(() -> {
viewModel.update(oldName);
viewModel.bindListener();
if (logger.isDebugEnabled()) {
logger.debug("bind ViewModel({}) Listener", viewModel.getName().get());
}
tabPane.getSelectionModel().getSelectedItem().getOnSelectionChanged().handle(null);
});
return oldName;
});
}
private void initializeBaseTab() {
baseInfoTab.setOnSelectionChanged(event -> {
if (logger.isDebugEnabled()) {
@@ -121,9 +119,9 @@ public class CompanyContactWindowController extends BaseController {
saveBtn.disableProperty().bind(viewModel.getChanged().not());
saveBtn.setOnAction(event -> {
try {
CompanyContact contact = companyContactLoadedFuture.join();
CompanyContactVo contact = companyContactLoadedFuture.join();
viewModel.copyTo(contact);
CompanyContact saved = companyContactRepository.save(contact);
CompanyContactVo saved = companyContactService.save(contact);
viewModel.update(saved);
companyContactLoadedFuture = CompletableFuture.completedFuture(saved);
} catch (Exception e) {
@@ -155,7 +153,7 @@ public class CompanyContactWindowController extends BaseController {
if (logger.isDebugEnabled()) {
logger.debug("onBaseTabShown company contact {}", contact.getName());
}
// viewModel.update(contact);
// viewModel.update(contact);
}).exceptionally(ex -> {
UITools.showExceptionAndWait(ex.getMessage(), ex);
return null;
@@ -176,4 +174,3 @@ public class CompanyContactWindowController extends BaseController {
super.onHidden(windowEvent);
}
}

View File

@@ -1,22 +1,22 @@
package com.ecep.contract.manager.ds.company.controller;
package com.ecep.contract.controller.company;
import java.util.List;
import java.util.Optional;
import com.ecep.contract.controller.AbstEntityManagerSkin;
import com.ecep.contract.service.CompanyOldNameService;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.vm.CompanyViewModel;
import com.ecep.contract.vo.CompanyVo;
import com.ecep.contract.manager.ds.company.model.Company;
import com.ecep.contract.manager.ds.company.service.CompanyOldNameService;
import com.ecep.contract.manager.ds.company.service.CompanyService;
import com.ecep.contract.manager.ds.company.vo.CompanyViewModel;
import com.ecep.contract.manager.ui.AbstEntityManagerSkin;
import com.ecep.contract.manager.ui.ManagerSkin;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.scene.control.Alert;
import javafx.scene.control.TextInputDialog;
import lombok.Setter;
import java.util.List;
import java.util.Optional;
public class CompanyManagerSkin
extends AbstEntityManagerSkin<Company, CompanyViewModel, CompanyManagerSkin, CompanyManagerWindowController> {
extends AbstEntityManagerSkin<CompanyVo, CompanyViewModel, CompanyManagerSkin, CompanyManagerWindowController> {
@Setter
private CompanyOldNameService companyOldNameService;
@@ -65,11 +65,11 @@ public class CompanyManagerSkin
if (optional.isPresent()) {
CompanyService companyService = getCompanyService();
String newCompanyName = optional.get();
List<Company> list = companyService.findAllByName(newCompanyName);
List<CompanyVo> list = companyService.findAllByName(newCompanyName);
if (list == null || list.isEmpty()) {
// 未登记过
Company company = companyService.createNewCompany(newCompanyName);
Company saved = companyService.save(company);
CompanyVo company = companyService.createNewCompany(newCompanyName);
CompanyVo saved = companyService.save(company);
CompanyWindowController.show(saved, getTableView().getScene().getWindow());
} else {
Alert alert = new Alert(Alert.AlertType.INFORMATION);

View File

@@ -1,4 +1,4 @@
package com.ecep.contract.manager.ds.company.controller;
package com.ecep.contract.controller.company;
import java.time.LocalDate;
@@ -7,13 +7,13 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.ecep.contract.manager.ds.company.model.Company;
import com.ecep.contract.manager.ds.company.service.CompanyService;
import com.ecep.contract.manager.ds.company.tasker.CompanyFilesRebuildTasker;
import com.ecep.contract.manager.ds.company.vo.CompanyViewModel;
import com.ecep.contract.manager.ui.AbstManagerWindowController;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.util.UITools;
import com.ecep.contract.controller.AbstManagerWindowController;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.task.CompanyFilesRebuildTasker;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vo.CompanyVo;
import com.ecep.contract.vm.CompanyViewModel;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
@@ -25,7 +25,7 @@ import javafx.stage.Stage;
@Component
@FxmlPath("/ui/company/company-manager.fxml")
public class CompanyManagerWindowController
extends AbstManagerWindowController<Company, CompanyViewModel, CompanyManagerSkin> {
extends AbstManagerWindowController<CompanyVo, CompanyViewModel, CompanyManagerSkin> {
// columns
@FXML

View File

@@ -1,11 +1,12 @@
package com.ecep.contract.manager.ds.company.controller;
package com.ecep.contract.controller.company;
import com.ecep.contract.manager.ui.BaseController;
import com.ecep.contract.manager.ui.FxmlPath;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.ecep.contract.controller.BaseController;
import com.ecep.contract.util.FxmlPath;
@Lazy
@Scope("prototype")
@Component

View File

@@ -1,32 +1,8 @@
package com.ecep.contract.manager.ds.company.controller;
package com.ecep.contract.controller.company;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import com.ecep.contract.manager.SpringApp;
import com.ecep.contract.manager.ds.company.model.Company;
import com.ecep.contract.manager.ds.company.service.CompanyFileService;
import com.ecep.contract.manager.ds.company.service.CompanyService;
import com.ecep.contract.manager.ds.company.tasker.CompanyCompositeUpdateTasker;
import com.ecep.contract.manager.ds.company.tasker.CompanyVerifyTasker;
import com.ecep.contract.manager.ds.company.vo.CompanyViewModel;
import com.ecep.contract.manager.ds.contract.service.ContractService;
import com.ecep.contract.manager.ds.customer.controller.CompanyCustomerWindowController;
import com.ecep.contract.manager.ds.customer.model.CompanyCustomer;
import com.ecep.contract.manager.ds.customer.service.CompanyCustomerService;
import com.ecep.contract.manager.ds.vendor.controller.CompanyVendorWindowController;
import com.ecep.contract.manager.ds.vendor.model.CompanyVendor;
import com.ecep.contract.manager.ds.vendor.service.CompanyVendorService;
import com.ecep.contract.manager.ui.tab.AbstEntityBasedTabSkin;
import com.ecep.contract.manager.ui.AbstEntityController;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.util.DesktopUtils;
import com.ecep.contract.manager.util.UITools;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.stage.Window;
import org.hibernate.Hibernate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -35,25 +11,59 @@ import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import com.ecep.contract.DesktopUtils;
import com.ecep.contract.controller.AbstEntityController;
import com.ecep.contract.controller.customer.CompanyCustomerWindowController;
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
import com.ecep.contract.controller.tab.CompanyTabSkinBankAccount;
import com.ecep.contract.controller.tab.CompanyTabSkinBase;
import com.ecep.contract.controller.tab.CompanyTabSkinBlackReason;
import com.ecep.contract.controller.tab.CompanyTabSkinContact;
import com.ecep.contract.controller.tab.CompanyTabSkinContract;
import com.ecep.contract.controller.tab.CompanyTabSkinFile;
import com.ecep.contract.controller.tab.CompanyTabSkinInvoice;
import com.ecep.contract.controller.tab.CompanyTabSkinOldName;
import com.ecep.contract.controller.tab.CompanyTabSkinOther;
import com.ecep.contract.controller.tab.CompanyTabSkinPurchaseBillVoucher;
import com.ecep.contract.controller.vendor.VendorWindowController;
import com.ecep.contract.service.CustomerService;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.VendorService;
import com.ecep.contract.task.CompanyCompositeUpdateTasker;
import com.ecep.contract.task.CompanyVerifyTasker;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.CompanyViewModel;
import com.ecep.contract.vo.CustomerVo;
import com.ecep.contract.vo.VendorVo;
import com.ecep.contract.vo.CompanyVo;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
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.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.stage.Window;
@Lazy
@Scope("prototype")
@Component
@FxmlPath("/ui/company/company.fxml")
public class CompanyWindowController
extends AbstEntityController<Company, CompanyViewModel> {
extends AbstEntityController<CompanyVo, CompanyViewModel> {
private static final Logger logger = LoggerFactory.getLogger(CompanyWindowController.class);
public static void show(Company company, Window window) {
CompanyViewModel viewModel = new CompanyViewModel();
if (!Hibernate.isInitialized(company)) {
company = SpringApp.getBean(CompanyService.class).findById(company.getId());
}
viewModel.update(company);
show(viewModel, window);
public static void show(CompanyVo company, Window window) {
show(CompanyViewModel.from(company), window);
}
/**
@@ -65,14 +75,6 @@ public class CompanyWindowController
@Autowired
private CompanyService companyService;
@Autowired
private CompanyFileService companyFileService;
@Autowired
private ContractService contractService;
@Autowired
private CompanyCustomerService companyCustomerService;
@Autowired
private CompanyVendorService companyVendorService;
public BorderPane root;
public TabPane tabPane;
@@ -117,12 +119,13 @@ public class CompanyWindowController
public Button companyPathChangeBtn;
public Button companyPathSameAsNameBtn;
// private final CompanyCustomerViewModel companyCustomerViewModel = new
// CompanyCustomerViewModel();
// private final CompanyVendorViewModel companyVendorViewModel = new
// CompanyVendorViewModel();
// private final CompanyCustomerViewModel companyCustomerViewModel = new CompanyCustomerViewModel();
// private final CompanyVendorViewModel companyVendorViewModel = new CompanyVendorViewModel();
private final SimpleObjectProperty<CompanyCustomer> companyCustomerProperty = new SimpleObjectProperty<>();
private final SimpleObjectProperty<CompanyVendor> companyVendorProperty = new SimpleObjectProperty<>();
private final SimpleObjectProperty<CustomerVo> companyCustomerProperty = new SimpleObjectProperty<>();
private final SimpleObjectProperty<VendorVo> companyVendorProperty = new SimpleObjectProperty<>();
public Pane customerTab_pane1;
public Button customerTab_openBtn;
@@ -134,7 +137,6 @@ public class CompanyWindowController
public Pane vendorTab_pane2;
public Button vendorTab_createBtn;
@Override
public void show(Stage stage) {
super.show(stage);
@@ -143,17 +145,6 @@ public class CompanyWindowController
getTitle().set("[" + viewModel.getId().get() + "] " + viewModel.getName().getValue() + " 公司详情");
}
@Override
protected Company loadEntity() {
return companyService.findById(viewModel.getId().get());
}
@Override
protected Company saveEntity(Company entity) {
return companyService.save(entity);
}
@Override
protected void registerTabSkins() {
registerTabSkin(baseInfoTab, tab1 -> new CompanyTabSkinBase(this));
@@ -161,18 +152,16 @@ public class CompanyWindowController
registerTabSkin(contactTab, tab1 -> new CompanyTabSkinContact(this));
registerTabSkin(blackReasonTab, tab1 -> new CompanyTabSkinBlackReason(this));
registerTabSkin(bankAccountTab, tab1 -> new CompanyTabSkinBankAccount(this));
registerTabSkin(contractTab, this::createContractTabSkin);
registerTabSkin(fileTab, this::createFileTabSkin);
registerTabSkin(contractTab, tab -> new CompanyTabSkinContract(this));
registerTabSkin(fileTab, tab -> new CompanyTabSkinFile(this));
registerTabSkin(invoiceTab, tab -> new CompanyTabSkinInvoice(this));
registerTabSkin(purchaseBillVoucherTab, tab -> new CompanyTabSkinPurchaseBillVoucher(this));
registerTabSkin(otherTab, tab -> new CompanyTabSkinOther(this));
initializeVendorTab();
initializeCustomerTab();
}
@Override
protected <K extends AbstEntityBasedTabSkin<?, ?, ?>> K registerTabSkin(Tab tab, Function<Tab, K> func) {
K skin = super.registerTabSkin(tab, func);
@@ -187,19 +176,6 @@ public class CompanyWindowController
return companyService;
}
private CompanyTabSkinContract createContractTabSkin(Tab tab) {
CompanyTabSkinContract skin = new CompanyTabSkinContract(this);
skin.setContractService(contractService);
return skin;
}
private CompanyTabSkinFile createFileTabSkin(Tab tab) {
CompanyTabSkinFile skin = new CompanyTabSkinFile(this);
skin.setCompanyFileService(companyFileService);
return skin;
}
private void initializeCustomerTab() {
customerTab.setOnSelectionChanged(event -> {
if (logger.isDebugEnabled()) {
@@ -210,7 +186,6 @@ public class CompanyWindowController
}
});
customerTab_pane1.visibleProperty().bind(customerTab_pane2.visibleProperty().not());
customerTab_pane2.visibleProperty().bind(companyCustomerProperty.isNull());
customerTab_createBtn.setOnAction(event -> {
@@ -227,7 +202,8 @@ public class CompanyWindowController
logger.debug("onCustomerTabShown");
}
getLoadedFuture().thenAcceptAsync(company -> {
companyCustomerProperty.set(companyCustomerService.findByCompany(company));
CustomerVo customerVo = getCachedBean(CustomerService.class).findByCompany(company);
companyCustomerProperty.set(customerVo);
}).exceptionally(ex -> {
UITools.showExceptionAndWait(ex.getMessage(), ex);
return null;
@@ -251,7 +227,7 @@ public class CompanyWindowController
});
vendorTab_openBtn.setOnAction(event -> {
CompanyVendorWindowController.show(companyVendorProperty.get(), root.getScene().getWindow());
VendorWindowController.show(companyVendorProperty.get(), root.getScene().getWindow());
});
}
@@ -263,7 +239,8 @@ public class CompanyWindowController
if (logger.isDebugEnabled()) {
logger.debug("onVendorTabShown company {}", company.getName());
}
companyVendorProperty.set(companyVendorService.findByCompany(company));
VendorService vendorService = getBean(VendorService.class);
companyVendorProperty.set(vendorService.findByCompany(company));
}).exceptionally(ex -> {
UITools.showExceptionAndWait(ex.getMessage(), ex);
return null;
@@ -277,29 +254,29 @@ public class CompanyWindowController
CompanyCompositeUpdateTasker task = new CompanyCompositeUpdateTasker();
task.setCompany(getEntity());
UITools.showTaskDialogAndWait("更新企业信息", task, null);
refresh();
}
/**
* 企业合规检查
*/
public void onCompanyVerifyAction(ActionEvent event) {
Company company = getEntity();
CompanyVo company = getEntity();
CompanyVerifyTasker task = new CompanyVerifyTasker();
task.setCompanyService(companyService);
task.setCompany(company);
UITools.showTaskDialogAndWait("企业合规性验证", task, null);
refresh();
}
public void onCompanyOpenInExplorerAction(ActionEvent event) {
Company company = getEntity();
CompanyVo company = getEntity();
String path = company.getPath();
CompletableFuture.runAsync(() -> {
if (!StringUtils.hasText(path)) {
ButtonType buttonType = UITools.showConfirmation("目录未设置", "是否创建目录").join();
if (buttonType == ButtonType.OK) {
if (companyService.makePathAbsent(company)) {
if (companyService.makePathAbsent(company, (level, message) -> setStatus(message))) {
save(company);
}
} else {

View File

@@ -0,0 +1,59 @@
package com.ecep.contract.controller.company.bank_account;
import com.ecep.contract.controller.company.CompanyWindowController;
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.service.BankService;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.CompanyBankAccountViewModel;
import com.ecep.contract.vo.CompanyBankAccountVo;
import com.ecep.contract.vo.CompanyVo;
import javafx.scene.control.Tab;
public class BankAccountBaseTabSkin
extends AbstEntityBasedTabSkin<BankAccountWindowController, CompanyBankAccountVo, CompanyBankAccountViewModel>
implements TabSkin {
public BankAccountBaseTabSkin(BankAccountWindowController controller) {
super(controller);
viewModel = controller.getViewModel();
}
@Override
public Tab getTab() {
return controller.baseInfoTab;
}
@Override
public void initializeTab() {
UITools.autoCompletion(controller.companyField, viewModel.getCompanyId(), getCompanyService());
UITools.autoCompletion(controller.bankField, viewModel.getBankId(), getBankService());
controller.openingBankField.textProperty().bindBidirectional(viewModel.getOpeningBank());
controller.bankAccountField.textProperty().bindBidirectional(viewModel.getAccount());
// controller.descriptionField.textProperty().bindBidirectional(viewModel.getDescription());
controller.createdField.textProperty().bind(viewModel.getCreated().asString());
controller.versionLabel.textProperty().bind(viewModel.getVersion().asString());
controller.relativeCompanyBtn.disableProperty().bind(viewModel.getCompanyId().isNull());
controller.relativeCompanyBtn.setOnAction(event -> {
Integer companyId = viewModel.getCompanyId().get();
if (companyId != null) {
CompanyVo company = getCompanyService().findById(companyId);
CompanyWindowController.show(company, controller.root.getScene().getWindow());
}
});
}
public BankService getBankService() {
return getCachedBean(BankService.class);
}
public CompanyService getCompanyService() {
return getCachedBean(CompanyService.class);
}
}

View File

@@ -1,24 +1,31 @@
package com.ecep.contract.manager.ds.company.controller.bank_account;
package com.ecep.contract.controller.company.bank_account;
import com.ecep.contract.manager.ds.company.model.CompanyBankAccount;
import com.ecep.contract.manager.ds.company.service.CompanyBankAccountService;
import com.ecep.contract.manager.ds.company.vo.CompanyBankAccountViewModel;
import com.ecep.contract.manager.ui.AbstEntityController;
import com.ecep.contract.manager.ui.FxmlPath;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
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.CompanyBankAccountService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vm.CompanyBankAccountViewModel;
import com.ecep.contract.vo.CompanyBankAccountVo;
import javafx.scene.control.Button;
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/company/bank-account.fxml")
public class BankAccountWindowController extends AbstEntityController<CompanyBankAccount, CompanyBankAccountViewModel> {
public class BankAccountWindowController extends AbstEntityController<CompanyBankAccountVo, CompanyBankAccountViewModel> {
public BorderPane root;
public TabPane tabPane;

View File

@@ -1,20 +1,21 @@
package com.ecep.contract.manager.ds.company.controller.old_name;
package com.ecep.contract.controller.company.old_name;
import java.time.format.DateTimeFormatter;
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.service.CompanyOldNameService;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.vo.CompanyOldNameVo;
import com.ecep.contract.vm.CompanyOldNameViewModel;
import com.ecep.contract.manager.ds.company.model.CompanyOldName;
import com.ecep.contract.manager.ds.company.service.CompanyOldNameService;
import com.ecep.contract.manager.ds.company.service.CompanyService;
import com.ecep.contract.manager.ds.company.vo.CompanyOldNameViewModel;
import com.ecep.contract.manager.ui.tab.AbstEntityBasedTabSkin;
import com.ecep.contract.manager.ui.tab.TabSkin;
import javafx.scene.control.Tab;
import javafx.util.converter.LocalDateStringConverter;
import lombok.Setter;
import java.time.format.DateTimeFormatter;
public class CompanyOldNameTabSkinBase
extends AbstEntityBasedTabSkin<CompanyOldNameWindowController, CompanyOldName, CompanyOldNameViewModel>
extends AbstEntityBasedTabSkin<CompanyOldNameWindowController, CompanyOldNameVo, CompanyOldNameViewModel>
implements TabSkin {
@Setter
private CompanyService companyService;

View File

@@ -0,0 +1,366 @@
package com.ecep.contract.controller.company.old_name;
import java.io.File;
import java.util.List;
import java.util.function.Consumer;
import org.springframework.util.StringUtils;
import com.ecep.contract.CompanyFileType;
import com.ecep.contract.DesktopUtils;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.table.AbstEntityTableTabSkin;
import com.ecep.contract.service.CompanyFileService;
import com.ecep.contract.service.CompanyOldNameService;
import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.util.ParamUtils.Builder;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.CompanyFileViewModel;
import com.ecep.contract.vm.CompanyOldNameViewModel;
import com.ecep.contract.vo.CompanyFileVo;
import com.ecep.contract.vo.CompanyOldNameVo;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.scene.control.Tab;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableView;
import lombok.Setter;
/**
*
*/
public class CompanyOldNameTabSkinFile
extends
AbstEntityTableTabSkin<CompanyOldNameWindowController, CompanyOldNameVo, CompanyOldNameViewModel, CompanyFileVo, CompanyFileViewModel>
implements TabSkin {
@Setter
private CompanyOldNameService companyOldNameService;
@Setter
private CompanyFileService companyFileService;
public CompanyOldNameTabSkinFile(CompanyOldNameWindowController controller) {
super(controller);
setDragAndDrop(true);
setDragAndDropFileHandler(this::moveFileToCompany);
}
@Override
public Tab getTab() {
return controller.fileTab;
}
@Override
public TableView<CompanyFileViewModel> getTableView() {
return controller.fileTable;
}
@Override
protected CompanyFileService getViewModelService() {
return companyFileService;
}
@Override
public Builder getSpecification(CompanyOldNameVo parent) {
return ParamUtils.builder().equals("company", parent.getCompanyId());
}
@Override
public void initializeTab() {
// controller.fileTable_file_move_btn.setOnAction(this::onTableMoveFileAction);
// controller.fileTable_file_retrieve_from_download_dir_btn.setOnAction(this::onTableRetrieveFromDownloadDirAction);
// controller.fileTable_file_reset_btn.setOnAction(this::onTableResetAction);
//
//
// controller.fileTable_idColumn.setCellValueFactory(param ->
// param.getValue().getId());
// ObservableMap<CompanyFileType, CompanyFileTypeLocal> observableMapByLocal =
// getBean(CompanyFileTypeLocalRepository.class).getObservableMapByLocal();
// controller.fileTable_typeColumn.setCellValueFactory(param ->
// Bindings.valueAt(observableMapByLocal,
// param.getValue().getType()).map(CompanyFileTypeLocal::getValue));
// controller.fileTable_filePathColumn.setCellValueFactory(param ->
// param.getValue().getFilePath());
// controller.fileTable_filePathColumn.setCellFactory(param -> new
// FileTableFilePathTableCell());
// controller.fileTable_applyDateColumn.setCellValueFactory(param ->
// param.getValue().getApplyDate());
// controller.fileTable_expiringDateColumn.setCellValueFactory(param ->
// param.getValue().getExpiringDate());
//
//
// controller.fileTable_menu_refresh.setOnAction(this::onTableRefreshAction);
// controller.fileTable_menu_del.setOnAction(this::onTableDeleteAction);
// controller.fileTable_menu_copy_as_matched_by_contract.setOnAction(this::onTableCopyAsMatchedByContractAction);
// controller.fileTable_menu_copy_as_matched_by_contract.setOnMenuValidation(this::onTableCopyAsMatchedMenuValidation);
super.initializeTab();
}
private void onTableResetAction(ActionEvent event) {
CompanyOldNameVo oldName = getParent();
// CompletableFuture.runAsync(() -> {
// if (getCompanyFileService().reBuildingFiles(oldName, this::setStatus)) {
// loadTableDataSet();
// }
// });
}
/**
* 从 下载目录 中查找相关的资质文件
*/
private void onTableRetrieveFromDownloadDirAction(ActionEvent event) {
// CompanyOldName oldName = getParent();
// MyProperties myProperties = getMyProperties();
// File dir = myProperties.getDownloadDirectory();
// if (!dir.exists()) {
// setStatus("下载目录 " + dir.getAbsolutePath() + " 不存在,请检查");
// return;
// }
//
// setStatus("开始检索 下载 文件夹:" + dir.getAbsolutePath() + "...");
// File[] files = dir.listFiles(File::isFile);
// if (files == null) {
// setStatus("检索 下载 文件夹失败");
// return;
// }
// if (files.length == 0) {
// setStatus("下载 文件夹没有文件");
// return;
// }
// setStatus("下载 文件夹中共有文件 " + files.length + " 个文件");
//
// if (getCompanyOldNameService().retrieveFromDownloadFiles(oldName, files,
// this::setStatus)) {
// // fixed if update
// viewModel.update(oldName);
// loadTableDataSet();
// }
}
/**
* 把文件从 老系统中移到 \\10.84.209.8\项目信息\相关方信息 目录中
*/
private void onTableMoveFileAction(ActionEvent event) {
// CompanyFileService companyFileService = getCompanyFileService();
// CompanyOldName oldName = getParent();
// List<CompanyFile> list = companyFileService.findByCompany(oldName);
// if (list.isEmpty()) {
// return;
// }
// if (getCompanyService().makePathAbsent(oldName)) {
// save(oldName);
// }
//
// String path = oldName.getPath();
// if (!StringUtils.hasText(path)) {
// setStatus("异常, 企业目录未设置");
// return;
// }
// File companyPath = new File(path);
// for (CompanyFile companyFile : list) {
// String filePath = companyFile.getFilePath();
// if (StringUtils.hasText(filePath)) {
// File file = new File(filePath);
// if (file.exists()) {
// if (file.getParentFile().equals(companyPath)) {
// continue;
// }
// File dest = new File(companyPath, file.getName());
// if (file.renameTo(dest)) {
// companyFile.setFilePath(dest.getAbsolutePath());
// companyFileService.save(companyFile);
// setStatus(file.getName() + " 移动到 " + companyPath.getName());
// }
// }
// }
// }
}
/**
*
*/
private void onTableCopyAsMatchedByContractAction(ActionEvent event) {
UITools.showDialogAndWait("复制资信评估报告", "按当前评估报告复制一个合同中最匹配的", list -> {
onTableCopyAsMatchedAction_(msg -> {
Platform.runLater(() -> {
list.add(msg);
});
});
});
}
private void onTableCopyAsMatchedAction_(Consumer<String> state) {
// CompanyOldName oldName = getParent();
//
// CompanyFileViewModel selectedItem =
// table.getSelectionModel().getSelectedItem();
// if (selectedItem == null) {
// state.accept("未选择行");
// return;
// }
// if (selectedItem.getApplyDate().get() == null) {
// state.accept("有效日期不能未空");
// return;
// }
//
// LocalDate nextCreditReportDate = null;
// try {
// nextCreditReportDate = companyFileService.getNextCreditReportDate(oldName,
// state);
// if (nextCreditReportDate == null) {
// state.accept("没有找到下一个咨询评估日期");
// return;
// }
// } catch (Exception e) {
// state.accept("获取下一个咨询评估日期失败:" + e.getMessage());
// return;
// }
//
// state.accept("下一个咨询评估日期:" + nextCreditReportDate);
//
// if (!nextCreditReportDate.isBefore(selectedItem.getApplyDate().get())) {
// state.accept("咨询评估日期晚于下一个咨询评估日期");
// return;
// }
//
//
// File src = new File(selectedItem.getFilePath().get());
// if (!src.exists()) {
// state.accept("当前选择行的文件不存在");
// return;
// }
//
// String srcDate = MyDateTimeUtils.format(selectedItem.getApplyDate().get());
// String destDate = MyDateTimeUtils.format(nextCreditReportDate);
// String srcFileName = src.getName();
// String destFileName;
//
// // 天眼查的报告
// if (CloudTycService.isTycReport(srcFileName)) {
// state.accept("天眼查的报告按标准格式命名");
// String name = oldName.getName() + "_" + CloudTycService.NAME;
// if (srcFileName.contains(CloudTycService.TYC_ENTERPRISE_BASIC_REPORT)) {
// name = name + "_" + CloudTycService.TYC_ENTERPRISE_BASIC_REPORT;
// } else if (srcFileName.contains(CloudTycService.TYC_ENTERPRISE_MAJOR_REPORT))
// {
// name = name + "_" + CloudTycService.TYC_ENTERPRISE_MAJOR_REPORT;
// } else if
// (srcFileName.contains(CloudTycService.TYC_ENTERPRISE_ANALYSIS_REPORT)) {
// name = name + "_" + CloudTycService.TYC_ENTERPRISE_ANALYSIS_REPORT;
// }
// destFileName = name + "_" + destDate + "_cp." +
// StringUtils.getFilenameExtension(srcFileName);
// } else {
// if (srcFileName.contains(srcDate)) {
// // 如果文件名中包含日期,则替换为新日期
// destFileName = srcFileName.replace(srcDate, destDate + "_cp");
// } else {
// // 如果文件名中不包含日期,则添加日期
// destFileName = oldName.getName() + "_" + destDate + "_cp." +
// StringUtils.getFilenameExtension(srcFileName);
// }
// }
//
// state.accept("新文件名:" + destFileName);
//
// File dest = new File(src.getParent(), destFileName);
// try {
// FileSystemUtils.copyRecursively(src, dest);
// state.accept("新文件复制成功");
// } catch (IOException e) {
// state.accept("新文件复制失败:" + e.getMessage());
// }
//
// CompanyFile companyFile = new CompanyFile();
// companyFile.setFilePath(dest.getAbsolutePath());
// companyFile.setApplyDate(nextCreditReportDate);
// companyFile.setExpiringDate(nextCreditReportDate.plusYears(1));
// companyFile.setType(CompanyFileType.CreditReport);
// companyFile.setCompany(oldName);
// companyFileService.save(companyFile);
//
// state.accept("新文件已记录");
//
// loadTableDataSet();
// state.accept("文件表已刷新");
}
/**
* 当fileTable选中的行是咨询评估时可用
*
* @param event event
*/
public void onTableCopyAsMatchedMenuValidation(Event event) {
// 当fileTable选中的行是咨询评估时可用
CompanyFileViewModel selectedItem = getSelectedItem();
if (selectedItem == null) {
event.consume();
return;
}
CompanyFileType type = selectedItem.getType().get();
if (type != CompanyFileType.CreditReport) {
event.consume();
return;
}
}
private void moveFileToCompany(List<File> files) {
String path = viewModel.getPath().get();
if (!StringUtils.hasText(path)) {
setStatus("未设置目录");
return;
}
File dir = new File(path);
if (!dir.exists()) {
setStatus("目录错误,不存在");
return;
}
}
@Override
protected void onTableRowDoubleClickedAction(CompanyFileViewModel item) {
String path = item.getFilePath().get();
if (StringUtils.hasText(path)) {
File file = new File(path);
if (!file.exists()) {
setStatus("文件不存在 " + file.getName());
return;
}
DesktopUtils.showInExplorer(file);
}
}
class FileTableFilePathTableCell extends TableCell<CompanyFileViewModel, String> {
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText("");
return;
}
String path = viewModel.getPath().get();
if (StringUtils.hasText(path)) {
if (item.startsWith(path)) {
item = "~" + item.substring(path.length());
}
}
setText(item);
}
}
private CompanyFileService getCompanyFileService() {
if (companyFileService == null) {
companyFileService = getBean(CompanyFileService.class);
}
return companyFileService;
}
public CompanyOldNameService getCompanyOldNameService() {
if (companyOldNameService == null) {
companyOldNameService = getBean(CompanyOldNameService.class);
}
return companyOldNameService;
}
}

View File

@@ -1,36 +1,44 @@
package com.ecep.contract.manager.ds.company.controller.old_name;
package com.ecep.contract.controller.company.old_name;
import java.io.File;
import java.util.concurrent.CompletableFuture;
import com.ecep.contract.manager.ds.company.model.CompanyOldName;
import com.ecep.contract.manager.ds.company.service.CompanyOldNameService;
import com.ecep.contract.manager.ds.company.service.CompanyService;
import com.ecep.contract.manager.ds.company.vo.CompanyFileViewModel;
import com.ecep.contract.manager.ds.company.vo.CompanyOldNameViewModel;
import com.ecep.contract.manager.ui.AbstEntityController;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.ui.ViewModelService;
import com.ecep.contract.manager.util.DesktopUtils;
import com.ecep.contract.manager.util.UITools;
import javafx.event.ActionEvent;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
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 org.springframework.util.StringUtils;
import java.io.File;
import java.util.concurrent.CompletableFuture;
import com.ecep.contract.DesktopUtils;
import com.ecep.contract.controller.AbstEntityController;
import com.ecep.contract.service.CompanyOldNameService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vo.CompanyOldNameVo;
import com.ecep.contract.vm.CompanyFileViewModel;
import com.ecep.contract.vm.CompanyOldNameViewModel;
import javafx.event.ActionEvent;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
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.TableView;
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/company/company_old_name.fxml")
public class CompanyOldNameWindowController extends AbstEntityController<CompanyOldName, CompanyOldNameViewModel> {
public class CompanyOldNameWindowController extends AbstEntityController<CompanyOldNameVo, CompanyOldNameViewModel> {
private static final Logger logger = LoggerFactory.getLogger(CompanyOldNameWindowController.class);
/**
@@ -45,12 +53,6 @@ public class CompanyOldNameWindowController extends AbstEntityController<Company
public TabPane tabPane;
public Tab fileTab;
@Autowired
private CompanyOldNameService companyOldNameService;
@Autowired
private CompanyService companyService;
public TextField nameField;
public CheckBox ambiguityField;
public DatePicker startDateField;
@@ -62,26 +64,14 @@ public class CompanyOldNameWindowController extends AbstEntityController<Company
public Button saveBtn;
public Button saveBtn2;
public TableView<CompanyFileViewModel> fileTable;
@Override
public void onShown(WindowEvent windowEvent) {
super.onShown(windowEvent);
getTitle().set("[" + viewModel.getId().get() + "] " + viewModel.getName().getValue() + " 曾用名详情");
}
@Override
protected CompanyOldName loadEntity() {
return companyOldNameService.findById(viewModel.getId().get());
}
@Override
protected CompanyOldName saveEntity(CompanyOldName entity) {
return companyOldNameService.save(entity);
}
@Override
protected void registerTabSkins() {
registerTabSkin(baseInfoTab, this::createBaseTabSkin);
@@ -90,32 +80,31 @@ public class CompanyOldNameWindowController extends AbstEntityController<Company
@Override
public CompanyOldNameService getViewModelService() {
return companyOldNameService;
return getCachedBean(CompanyOldNameService.class);
}
private CompanyOldNameTabSkinBase createBaseTabSkin(Tab tab) {
CompanyOldNameTabSkinBase skin = new CompanyOldNameTabSkinBase(this);
skin.setCompanyOldNameService(companyOldNameService);
skin.setCompanyOldNameService(getViewModelService());
return skin;
}
private CompanyOldNameTabSkinFile createFileTabSkin(Tab tab) {
CompanyOldNameTabSkinFile skin = new CompanyOldNameTabSkinFile(this);
skin.setCompanyOldNameService(companyOldNameService);
// skin.setCompanyFileService(companyFileService);
skin.setCompanyOldNameService(getViewModelService());
// skin.setCompanyFileService(companyFileService);
return skin;
}
public void onOldCompanyOpenInExplorerAction(ActionEvent event) {
CompanyOldName companyOldName = getEntity();
CompanyOldNameVo companyOldName = getEntity();
String path = companyOldName.getPath();
CompletableFuture.runAsync(() -> {
if (!StringUtils.hasText(path)) {
ButtonType buttonType = UITools.showConfirmation("目录未设置", "是否创建目录").join();
if (buttonType == ButtonType.OK) {
if (companyOldNameService.makePathAbsent(companyOldName)) {
if (getViewModelService().makePathAbsent(companyOldName)) {
save(companyOldName);
}
} else {
@@ -128,4 +117,3 @@ public class CompanyOldNameWindowController extends AbstEntityController<Company
});
}
}

View File

@@ -0,0 +1,22 @@
package com.ecep.contract.controller.contract;
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.service.ContractService;
import com.ecep.contract.vm.ContractViewModel;
import com.ecep.contract.vo.ContractVo;
public abstract class AbstContractBasedTabSkin
extends AbstEntityBasedTabSkin<ContractWindowController, ContractVo, ContractViewModel>
implements TabSkin {
public AbstContractBasedTabSkin(ContractWindowController controller) {
super(controller);
viewModel = controller.getViewModel();
}
public ContractService getContractService() {
return controller.getViewModelService();
}
}

View File

@@ -0,0 +1,32 @@
package com.ecep.contract.controller.contract;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.table.AbstEntityTableTabSkin;
import com.ecep.contract.controller.table.TableOfTabSkin;
import com.ecep.contract.model.IdentityEntity;
import com.ecep.contract.service.ContractService;
import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.vm.ContractViewModel;
import com.ecep.contract.vm.IdentityViewModel;
import com.ecep.contract.vo.ContractVo;
public abstract class AbstContractTableTabSkin<T extends IdentityEntity, TV extends IdentityViewModel<T>>
extends AbstEntityTableTabSkin<ContractWindowController, ContractVo, ContractViewModel, T, TV>
implements TabSkin, TableOfTabSkin<ContractVo, T, TV> {
public AbstContractTableTabSkin(ContractWindowController controller) {
super(controller);
}
public ContractService getContractService() {
return controller.getViewModelService();
}
@Override
public ParamUtils.Builder getSpecification(ContractVo parent) {
ParamUtils.Builder params = getSpecification();
params.equals("contract", parent.getId());
return params;
}
}

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,4 +1,4 @@
package com.ecep.contract.manager.ds.contract.controller;
package com.ecep.contract.controller.contract;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -8,19 +8,16 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.ecep.contract.manager.ds.company.model.Company;
import com.ecep.contract.manager.ds.contract.model.Contract;
import com.ecep.contract.manager.ds.contract.model.ContractGroup;
import com.ecep.contract.manager.ds.contract.model.ContractKind;
import com.ecep.contract.manager.ds.contract.model.ContractType;
import com.ecep.contract.manager.ds.contract.service.ContractService;
import com.ecep.contract.manager.ds.contract.tasker.ContractFilesRebuildAllTasker;
import com.ecep.contract.manager.ds.contract.tasker.ContractRepairAllTasker;
import com.ecep.contract.manager.ds.contract.vo.ContractViewModel;
import com.ecep.contract.manager.ds.other.model.Employee;
import com.ecep.contract.manager.ui.AbstManagerWindowController;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.util.UITools;
import com.ecep.contract.controller.AbstManagerWindowController;
import com.ecep.contract.controller.tab.ContractManagerSkin;
import com.ecep.contract.service.ContractService;
import com.ecep.contract.task.ContractFilesRebuildAllTasker;
import com.ecep.contract.task.ContractRepairAllTasker;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.ContractViewModel;
import com.ecep.contract.vo.ContractGroupVo;
import com.ecep.contract.vo.ContractVo;
import javafx.event.ActionEvent;
import javafx.scene.control.CheckBox;
@@ -33,27 +30,27 @@ import javafx.stage.Stage;
@Component
@FxmlPath("/ui/contract/contract-manager.fxml")
public class ContractManagerWindowController
extends AbstManagerWindowController<Contract, ContractViewModel, ContractManagerSkin> {
extends AbstManagerWindowController<ContractVo, ContractViewModel, ContractManagerSkin> {
public ComboBox<ContractGroup> groupSelector;
public ComboBox<ContractGroupVo> groupSelector;
public CheckBox composeViewBtn;
// columns
public TableColumn<ContractViewModel, Number> idColumn;
public TableColumn<ContractViewModel, String> nameColumn;
public TableColumn<ContractViewModel, String> codeColumn;
public TableColumn<ContractViewModel, ContractGroup> groupColumn;
public TableColumn<ContractViewModel, ContractType> typeColumn;
public TableColumn<ContractViewModel, ContractKind> kindColumn;
public TableColumn<ContractViewModel, Integer> groupColumn;
public TableColumn<ContractViewModel, Integer> typeColumn;
public TableColumn<ContractViewModel, Integer> kindColumn;
public TableColumn<ContractViewModel, String> parentCodeColumn;
public TableColumn<ContractViewModel, LocalDate> setupDateColumn;
public TableColumn<ContractViewModel, LocalDate> orderDateColumn;
public TableColumn<ContractViewModel, LocalDate> startDateColumn;
public TableColumn<ContractViewModel, Employee> employeeColumn;
public TableColumn<ContractViewModel, Integer> employeeColumn;
public TableColumn<ContractViewModel, LocalDateTime> createdColumn;
public TableColumn<ContractViewModel, Number> amountColumn;
public TableColumn<ContractViewModel, Company> companyColumn;
public TableColumn<ContractViewModel, Integer> companyColumn;
@Autowired
private ContractService contractService;

View File

@@ -1,20 +1,16 @@
package com.ecep.contract.manager.ds.contract.controller;
package com.ecep.contract.controller.contract;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.hibernate.Hibernate;
import com.ecep.contract.manager.SpringApp;
import com.ecep.contract.manager.ds.contract.model.Contract;
import com.ecep.contract.manager.ds.contract.model.ExtendVendorInfo;
import com.ecep.contract.manager.ds.contract.service.ExtendVendorInfoService;
import com.ecep.contract.manager.ds.contract.vo.ExtendVendorInfoViewModel;
import com.ecep.contract.manager.ds.vendor.model.VendorGroup;
import com.ecep.contract.manager.ds.vendor.service.VendorGroupService;
import com.ecep.contract.manager.ui.ComboBoxUtils;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.util.UITools;
import com.ecep.contract.util.ComboBoxUtils;
import com.ecep.contract.service.ExtendVendorInfoService;
import com.ecep.contract.service.VendorGroupService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.ExtendVendorInfoViewModel;
import com.ecep.contract.vo.ContractVo;
import com.ecep.contract.vo.ExtendVendorInfoVo;
import com.ecep.contract.vo.VendorGroupVo;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
@@ -26,19 +22,15 @@ import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TextField;
import javafx.util.converter.NumberStringConverter;
import lombok.Setter;
@FxmlPath("/ui/contract/contract-tab-ext-vendor-info.fxml")
public class ContractTabSkinExtendVendorInfo
extends AbstContractBasedTabSkin {
@Setter
private ExtendVendorInfoService extendVendorInfoService;
@Setter
private VendorGroupService vendorGroupService;
public class ContractTabSkinExtendVendorInfo extends AbstContractBasedTabSkin {
/**
* VendorGroup
*/
@FXML
public ComboBox<VendorGroup> vendorGroupField;
public ComboBox<VendorGroupVo> vendorGroupField;
@FXML
public Label vendorGroupLabel;
@FXML
@@ -48,7 +40,7 @@ public class ContractTabSkinExtendVendorInfo
@FXML
public CheckBox prePurchaseField;
CompletableFuture<ExtendVendorInfo> loadedFuture;
CompletableFuture<ExtendVendorInfoVo> loadedFuture;
private ExtendVendorInfoViewModel viewModel = new ExtendVendorInfoViewModel();
public ContractTabSkinExtendVendorInfo(ContractWindowController controller) {
@@ -76,13 +68,13 @@ public class ContractTabSkinExtendVendorInfo
if (loadedFuture == null) {
loadedFuture = CompletableFuture.supplyAsync(() -> {
initializeTab();
Contract contract = getEntity();
ContractVo contract = getEntity();
return loadExtendVendorInfo(contract);
});
}
}
void updateViewModel(ExtendVendorInfo info) {
void updateViewModel(ExtendVendorInfoVo info) {
if (Platform.isFxApplicationThread()) {
viewModel.update(info);
} else {
@@ -90,13 +82,14 @@ public class ContractTabSkinExtendVendorInfo
}
}
private ExtendVendorInfo loadExtendVendorInfo(Contract contract) {
private ExtendVendorInfoVo loadExtendVendorInfo(ContractVo contract) {
ExtendVendorInfoService service = getExtendVendorInfoService();
try {
ExtendVendorInfo info = service.findByContract(contract);
ExtendVendorInfoVo info = service.findByContract(contract);
if (info == null) {
info = service.newInstanceByContract(contract);
info = service.save(info);
info = new ExtendVendorInfoVo();
info.setContractId(contract.getId());
// 注意这里可能需要调整取决于service接口的实现
}
updateViewModel(info);
viewModel.bindListener();
@@ -109,17 +102,11 @@ public class ContractTabSkinExtendVendorInfo
@Override
public void initializeTab() {
List<VendorGroup> groups = getVendorGroupService().findAll();
ComboBoxUtils.initialComboBox(vendorGroupField, groups, true);
vendorGroupField.valueProperty().bindBidirectional(viewModel.getGroup());
ComboBoxUtils.initialComboBox(vendorGroupField, viewModel.getGroup(), getVendorGroupService(), true);
vendorGroupLabel.textProperty().bind(vendorGroupField.valueProperty().map(v -> {
if (v == null) {
return "-";
}
if (!Hibernate.isInitialized(v)) {
v = getVendorGroupService().findById(v.getId());
viewModel.getGroup().set(v);
}
return v.getDescription();
}));
@@ -127,7 +114,11 @@ public class ContractTabSkinExtendVendorInfo
new NumberStringConverter());
assignedProviderField.selectedProperty().bindBidirectional(viewModel.getAssignedProvider());
assignedProviderField.disableProperty().bind(Bindings.createBooleanBinding(() -> {
VendorGroup group = viewModel.getGroup().get();
Integer groupId = viewModel.getGroup().get();
if (groupId == null) {
return false;
}
VendorGroupVo group = getVendorGroupService().findById(groupId);
if (group == null) {
return false;
}
@@ -140,9 +131,10 @@ public class ContractTabSkinExtendVendorInfo
@Override
public void save() {
if (loadedFuture != null) {
ExtendVendorInfo vendorInfo = loadedFuture.join();
ExtendVendorInfoVo vendorInfo = loadedFuture.join();
if (viewModel.copyTo(vendorInfo)) {
ExtendVendorInfo saved = getExtendVendorInfoService().save(vendorInfo);
// 注意这里需要根据实际service接口实现调整可能需要调用不同的方法
ExtendVendorInfoVo saved = getExtendVendorInfoService().save(vendorInfo);
updateViewModel(saved);
loadedFuture = CompletableFuture.completedFuture(saved);
}
@@ -150,16 +142,10 @@ public class ContractTabSkinExtendVendorInfo
}
public ExtendVendorInfoService getExtendVendorInfoService() {
if (extendVendorInfoService == null) {
extendVendorInfoService = SpringApp.getBean(ExtendVendorInfoService.class);
}
return extendVendorInfoService;
return getCachedBean(ExtendVendorInfoService.class);
}
public VendorGroupService getVendorGroupService() {
if (vendorGroupService == null) {
vendorGroupService = SpringApp.getBean(VendorGroupService.class);
}
return vendorGroupService;
return getCachedBean(VendorGroupService.class);
}
}

View File

@@ -1,35 +1,26 @@
package com.ecep.contract.manager.ds.contract.controller;
package com.ecep.contract.controller.contract;
import com.ecep.contract.manager.SpringApp;
import com.ecep.contract.manager.cloud.u8.ctx.ContractCtx;
import com.ecep.contract.manager.ds.company.CompanyFileUtils;
import com.ecep.contract.manager.ds.contract.ContractFileType;
import com.ecep.contract.manager.ds.contract.ContractPayWay;
import com.ecep.contract.manager.ds.contract.model.Contract;
import com.ecep.contract.manager.ds.contract.model.ContractFile;
import com.ecep.contract.manager.ds.contract.model.ContractFileTypeLocal;
import com.ecep.contract.manager.ds.contract.model.ContractType;
import com.ecep.contract.manager.ds.contract.service.ContractFileService;
import com.ecep.contract.manager.ds.contract.tasker.ContractFilesRebuildTasker;
import com.ecep.contract.manager.ds.contract.tasker.CustomerContractCostFormUpdateTask;
import com.ecep.contract.manager.ds.contract.vo.ContractFileViewModel;
import com.ecep.contract.manager.ui.table.EditableEntityTableTabSkin;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.ui.tab.TabSkin;
import com.ecep.contract.manager.ui.util.LocalDateFieldTableCell;
import com.ecep.contract.manager.util.DesktopUtils;
import com.ecep.contract.manager.util.MyDateTimeUtils;
import com.ecep.contract.manager.util.UITools;
import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.collections.MapChangeListener;
import com.ecep.contract.*;
import com.ecep.contract.constant.ContractConstant;
import com.ecep.contract.controller.tab.ContractFilesRebuildTasker;
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.cell.ContractFileTypeTableCell;
import com.ecep.contract.controller.table.cell.LocalDateFieldTableCell;
import com.ecep.contract.service.ContractFileService;
import com.ecep.contract.service.ContractFileTypeService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.ContractFileViewModel;
import com.ecep.contract.vo.ContractFileTypeLocalVo;
import com.ecep.contract.vo.ContractFileVo;
import com.ecep.contract.vo.ContractVo;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.event.ActionEvent;
import javafx.scene.control.*;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.stage.WindowEvent;
import javafx.util.StringConverter;
import lombok.Setter;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.io.IOUtils;
@@ -60,8 +51,8 @@ import java.util.Objects;
*/
@FxmlPath("/ui/contract/contract-tab-file.fxml")
public class ContractTabSkinFiles
extends AbstContractTableTabSkin<ContractFile, ContractFileViewModel>
implements TabSkin, EditableEntityTableTabSkin<ContractFile, ContractFileViewModel> {
extends AbstContractTableTabSkin<ContractFileVo, ContractFileViewModel>
implements TabSkin, EditableEntityTableTabSkin<ContractFileVo, ContractFileViewModel> {
public Button fileTableReBuildBtn;
public MenuItem fileTable_menu_refresh;
@@ -71,15 +62,13 @@ public class ContractTabSkinFiles
public Menu fileTable_menu_change_type;
public Menu fileTable_menu_change_type_and_name;
public TableColumn<ContractFileViewModel, Number> fileTable_idColumn;
public TableColumn<ContractFileViewModel, ContractFileTypeLocal> fileTable_typeColumn;
public TableColumn<ContractFileViewModel, ContractFileType> fileTable_typeColumn;
public TableColumn<ContractFileViewModel, String> fileTable_filePathColumn;
public TableColumn<ContractFileViewModel, LocalDate> fileTable_applyDateColumn;
public TableColumn<ContractFileViewModel, String> fileTable_descriptionColumn;
@Setter
private ContractFileService contractFileService;
private final ObservableMap<ContractFileType, ContractFileTypeLocal> fileTypeLocalMap = FXCollections.observableHashMap();
public ContractTabSkinFiles(ContractWindowController controller) {
super(controller);
@@ -104,26 +93,10 @@ public class ContractTabSkinFiles
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
public void initializeTab() {
TableView<ContractFileViewModel> table = getTableView();
ContractPayWay payWay = viewModel.getPayWay().get();
ContractType contractType = viewModel.getType().get();
boolean isCustomer = payWay == ContractPayWay.RECEIVE;
boolean isVendor = payWay == ContractPayWay.PAY;
@@ -139,47 +112,13 @@ public class ContractTabSkinFiles
fileTable_idColumn.setEditable(false);
fileTable_idColumn.setReorderable(false);
fileTable_typeColumn.setCellValueFactory(param -> Bindings.valueAt(fileTypeLocalMap, param.getValue().getType()));
fileTable_typeColumn.setCellFactory(param -> new TextFieldTableCell<>(new ContractFileTypeLocalStringConverter()));
// 监听 type map 变化
fileTypeLocalMap.addListener((MapChangeListener<? super ContractFileType, ? super ContractFileTypeLocal>) change -> {
List<ContractFileTypeLocal> types = fileTypeLocalMap.values().stream().filter(typeLocal -> {
ContractFileType type = typeLocal.getType();
if (type == null) {
return false;
}
if (isCustomer && !type.isSupportCustomer()) {
return false;
}
return !isVendor || type.isSupportVendor();
}).toList();
fileTable_menu_change_type.getItems().setAll(types.stream()
.map(typeLocal -> {
MenuItem item = new MenuItem();
item.setText(typeLocal.getValue());
item.getProperties().put("typeLocal", typeLocal);
item.setOnAction(this::onFileTableContextMenuChangeTypeAndNameAction);
return item;
}).toList());
fileTable_menu_change_type_and_name.getItems().setAll(types.stream()
.filter(typeLocal -> StringUtils.hasText(typeLocal.getSuggestFileName()))
.map(typeLocal -> {
MenuItem item = new MenuItem();
item.setText(typeLocal.getValue());
item.getProperties().put("typeLocal", typeLocal);
item.getProperties().put("rename", true);
item.setOnAction(this::onFileTableContextMenuChangeTypeAndNameAction);
return item;
}).toList());
});
fileTable_typeColumn
.setCellValueFactory(param -> param.getValue().getType());
fileTable_typeColumn
.setCellFactory(ContractFileTypeTableCell.forTableColumn(getCachedBean(ContractFileTypeService.class)));
fileTable_typeColumn.setEditable(false);
/* 文件名编辑器 */
fileTable_filePathColumn.setCellValueFactory(param -> param.getValue().getFileName());
fileTable_filePathColumn.setCellFactory(TextFieldTableCell.forTableColumn());
@@ -232,17 +171,41 @@ public class ContractTabSkinFiles
System.out.println("fileTable_menu_change_type_and_name:setOnMenuValidation");
});
fileTable_menu_update.getItems().setAll(
createCustomerContractCostByTemplateUpdateMenuItem(),
createCustomerContractBidByTemplateUpdateMenuItem(),
createCustomerContractApplyByTemplateUpdateMenuItem(),
createVendorContractRequestByTemplateUpdateMenuItem(),
createVendorContractApplyByTemplateUpdateMenuItem()
);
createVendorContractApplyByTemplateUpdateMenuItem());
fileTypeLocalMap.putAll(getContractFileService().findAllFileTypes(getLocale().toLanguageTag()));
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();
}
@@ -266,7 +229,7 @@ public class ContractTabSkinFiles
* @param event 菜单事件
*/
private void onFileTableComposePDFAction(ActionEvent event) {
Contract contract = getParent();
ContractVo contract = getParent();
if (!StringUtils.hasText(contract.getPath())) {
setStatus("合同未设置路径");
return;
@@ -296,7 +259,6 @@ public class ContractTabSkinFiles
return;
}
PDFMergerUtility merger = new PDFMergerUtility();
File dest = new File(contractPath, "合并的" + rows.getFirst().getFileName().get());
merger.setDestinationFileName(dest.getAbsolutePath());
@@ -323,7 +285,7 @@ public class ContractTabSkinFiles
}
ContractFileViewModel append = new ContractFileViewModel();
append.getContract().set(contract);
append.getContract().set(contract.getId());
append.getFileName().set(dest.getName());
append.getType().set(ContractFileType.General);
saveRow(append);
@@ -337,7 +299,7 @@ public class ContractTabSkinFiles
}
private void onFileTableComposePDFAction_V2(ActionEvent event) {
Contract contract = getParent();
ContractVo contract = getParent();
if (!StringUtils.hasText(contract.getPath())) {
setStatus("合同未设置路径");
return;
@@ -399,8 +361,7 @@ public class ContractTabSkinFiles
// 加载 PNG 图片
BufferedImage image = ImageIO.read(file);
// 将图片添加到 PDF
try (PDPageContentStream contentStream =
new PDPageContentStream(destination, page)) {
try (PDPageContentStream contentStream = new PDPageContentStream(destination, page)) {
PDImageXObject pdImage = LosslessFactory.createFromImage(destination, image);
float scale = 1f; // 图片缩放比例
contentStream.drawImage(pdImage, 0, 0,
@@ -421,7 +382,7 @@ public class ContractTabSkinFiles
}
ContractFileViewModel append = new ContractFileViewModel();
append.getContract().set(contract);
append.getContract().set(contract.getId());
append.getFileName().set(dest.getName());
append.getType().set(ContractFileType.General);
saveRow(append);
@@ -464,7 +425,6 @@ public class ContractTabSkinFiles
return item;
}
private MenuItem createCustomerContractBidByTemplateUpdateMenuItem() {
MenuItem item = new MenuItem();
item.setText("投标报价审批表");
@@ -478,7 +438,7 @@ public class ContractTabSkinFiles
item.setText("成本核算审批表");
item.visibleProperty().bind(viewModel.getPayWay().isEqualTo(ContractPayWay.RECEIVE));
item.setOnAction(event -> {
Contract contract = getContractService().findById(viewModel.getId().get());
ContractVo contract = getContractService().findById(viewModel.getId().get());
if (contract == null) {
setStatus("异常,对应的合同不存在#" + viewModel.getId().get());
return;
@@ -488,7 +448,7 @@ public class ContractTabSkinFiles
return;
}
String template = controller.getConfService().getString(ContractFileService.KEY_CUSTOMER_COST_TEMPLATE);
String template = controller.getConfService().getString(ContractConstant.KEY_CUSTOMER_COST_TEMPLATE);
if (!StringUtils.hasText(template)) {
setStatus("模板文件未配置");
return;
@@ -498,14 +458,13 @@ public class ContractTabSkinFiles
.filter(v -> v.getType().get().equals(ContractFileType.Cost))
.findFirst().orElse(null);
if (model == null) {
ContractFile file = new ContractFile();
file.setContract(contract);
ContractFileVo file = new ContractFileVo();
file.setContractId(contract.getId());
file.setType(ContractFileType.Cost);
file.setApplyDate(LocalDate.now());
String fileName = item.getText();
ContractFileTypeLocal local = fileTypeLocalMap.get(ContractFileType.CostForm);
ContractFileTypeLocalVo local = getCachedBean(ContractFileTypeService.class).findByType(getLocale(), ContractFileType.CostForm);
if (local != null) {
if (StringUtils.hasText(local.getSuggestFileName())) {
fileName = local.getSuggestFileName();
@@ -530,16 +489,17 @@ public class ContractTabSkinFiles
UITools.showTaskDialogAndWait("更新 " + model.getFileName().get(), task, null);
});
return item;
}
private void onFileTableDescriptionColumnEditCommitAction(TableColumn.CellEditEvent<ContractFileViewModel, String> event) {
private void onFileTableDescriptionColumnEditCommitAction(
TableColumn.CellEditEvent<ContractFileViewModel, String> event) {
acceptCellEditEvent(event, ContractFileViewModel::getDescription);
}
private void onFileTableApplyDateColumnEditCommitAction(TableColumn.CellEditEvent<ContractFileViewModel, LocalDate> event) {
private void onFileTableApplyDateColumnEditCommitAction(
TableColumn.CellEditEvent<ContractFileViewModel, LocalDate> event) {
acceptCellEditEvent(event, ContractFileViewModel::getApplyDate);
}
@@ -563,7 +523,7 @@ public class ContractTabSkinFiles
@Override
protected void onTableRowDoubleClickedAction(ContractFileViewModel item) {
String parent = viewModel.getPath().get();
if (!CompanyFileUtils.exists(parent)) {
if (!getContractService().existsContractPath(getEntity())) {
setStatus("合同的目录未设置或者无效,请检查!");
return;
}
@@ -582,7 +542,7 @@ public class ContractTabSkinFiles
if (selectedItem == null) {
// 未选择行
windowEvent.consume();
//contextMenu.hide();
// contextMenu.hide();
fileTable_menu_del.setVisible(false);
fileTable_menu_change_type.setVisible(false);
fileTable_menu_change_type_and_name.setVisible(false);
@@ -593,19 +553,18 @@ public class ContractTabSkinFiles
//
fileTable_menu_change_type.setVisible(true);
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());
}
fileTable_menu_change_type_and_name.setVisible(true);
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());
}
}
private void onFileTableContextMenuChangeTypeAndNameAction(ActionEvent event) {
MenuItem item = (MenuItem) event.getSource();
@@ -613,7 +572,7 @@ public class ContractTabSkinFiles
if (selectedItems == null || selectedItems.isEmpty()) {
return;
}
ContractFileTypeLocal typeLocal = (ContractFileTypeLocal) item.getProperties().get("typeLocal");
ContractFileTypeLocalVo typeLocal = (ContractFileTypeLocalVo) item.getProperties().get("typeLocal");
if (typeLocal == null) {
return;
}
@@ -630,7 +589,8 @@ public class ContractTabSkinFiles
String extension = StringUtils.getFilenameExtension(fileName);
LocalDate localDate = selectedItem.getApplyDate().get();
String contractCode = viewModel.getCode().get();
String newFileName = contractCode + "-" + suggestFileName + (localDate == null ? "" : ("-" + MyDateTimeUtils.format(localDate))) + "." + extension;
String newFileName = contractCode + "-" + suggestFileName
+ (localDate == null ? "" : ("-" + MyDateTimeUtils.format(localDate))) + "." + extension;
// 新旧文件名不一样需要改名
if (!Objects.equals(newFileName, fileName)) {
@@ -653,30 +613,18 @@ public class ContractTabSkinFiles
}
@Override
public ContractFile loadRowData(ContractFileViewModel row) {
public ContractFileVo loadRowData(ContractFileViewModel row) {
return getContractFileService().findById(row.getId().get());
}
@Override
public ContractFile saveRowData(ContractFile entity) {
return getContractFileService().save(entity);
}
@Override
public void deleteRowData(ContractFile entity) {
getContractFileService().delete(entity);
}
/**
* 重置文件
* <br>
* 依据已存在的文件重建
*/
public void onFileReBuildingAction(ActionEvent event) {
Contract contract = getParent();
ContractVo contract = getParent();
ContractFilesRebuildTasker task = new ContractFilesRebuildTasker();
task.setContractService(getContractService());
task.setContract(contract);
UITools.showTaskDialogAndWait("文件重置", task, null);
@@ -696,13 +644,13 @@ public class ContractTabSkinFiles
setStatus("目录错误,不存在");
return;
}
Contract contract = getParent();
ContractVo contract = getParent();
for (File file : files) {
File dest = new File(dir, file.getName());
if (file.renameTo(dest)) {
ContractFile ccf = new ContractFile();
ccf.setContract(contract);
ContractFileVo ccf = new ContractFileVo();
ccf.setContractId(contract.getId());
ccf.setType(ContractFileType.General);
ccf.setFileName(dest.getName());
getContractFileService().save(ccf);
@@ -719,7 +667,7 @@ public class ContractTabSkinFiles
return;
}
Contract contract = getParent();
ContractVo contract = getParent();
if (!StringUtils.hasText(contract.getPath())) {
setStatus("合同未设置路径");
return;
@@ -746,10 +694,6 @@ public class ContractTabSkinFiles
return;
}
// TODO 放到其他线程中 UI线程卡了
ContractCtx contractCtx = new ContractCtx();
try (PDDocument pdDocument = Loader.loadPDF(pdfFile)) {
Splitter splitter = new Splitter();
List<PDDocument> pages = splitter.split(pdDocument);
@@ -759,12 +703,12 @@ public class ContractTabSkinFiles
File outputFile = new File(contractPath, name + "-" + (i + 1) + ".pdf");
page.save(outputFile);
page.close();
ContractFile contractFile = new ContractFile();
contractFile.setContract(contract);
contractCtx.syncContractFile(contractFile, outputFile, (lv, message) -> {
ContractFileVo contractFile = new ContractFileVo();
contractFile.setContractId(contract.getId());
getContractService().syncContractFile(contractFile, outputFile, (lv, message) -> {
setStatus(message);
});
ContractFile saved = getContractFileService().save(contractFile);
ContractFileVo saved = getContractFileService().save(contractFile);
dataSet.add(ContractFileViewModel.from(saved));
}
} catch (Exception e) {
@@ -777,7 +721,7 @@ public class ContractTabSkinFiles
@Override
protected void onTableDeleteAction(ActionEvent event) {
Contract contract = getParent();
ContractVo contract = getParent();
String path = contract.getPath();
if (!StringUtils.hasText(path)) {
setStatus("未设置目录");
@@ -794,7 +738,7 @@ public class ContractTabSkinFiles
protected boolean deleteRow(ContractFileViewModel row, boolean confirm) {
boolean deleted = super.deleteRow(row);
if (deleted) {
Contract contract = getParent();
ContractVo contract = getParent();
File file = new File(contract.getPath(), row.getFileName().get());
if (!file.exists()) {
setStatus(file.getAbsolutePath() + " 文件不存在, 无法删除");

View File

@@ -1,11 +1,10 @@
package com.ecep.contract.manager.ds.contract.controller;
package com.ecep.contract.controller.contract;
import com.ecep.contract.manager.ds.contract.model.ContractPayPlan;
import com.ecep.contract.manager.ds.contract.service.ContractPayPlanService;
import com.ecep.contract.manager.ds.contract.vo.ContractPayPlanViewModel;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.ui.ViewModelService;
import com.ecep.contract.manager.ui.table.cell.LocalDateTimeTableCell;
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
import com.ecep.contract.service.ContractPayPlanService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vm.ContractPayPlanViewModel;
import com.ecep.contract.vo.ContractPayPlanVo;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Tab;
import javafx.scene.control.TableColumn;
@@ -18,7 +17,7 @@ import java.time.LocalDate;
import java.time.LocalDateTime;
@FxmlPath("/ui/contract/contract-tab-pay-plan.fxml")
public class ContractTabSkinPayPlan extends AbstContractTableTabSkin<ContractPayPlan, ContractPayPlanViewModel> {
public class ContractTabSkinPayPlan extends AbstContractTableTabSkin<ContractPayPlanVo, ContractPayPlanViewModel> {
public TableColumn<ContractPayPlanViewModel, Number> idColumn;
public TableColumn<ContractPayPlanViewModel, LocalDate> payDateColumn;
@@ -47,7 +46,7 @@ public class ContractTabSkinPayPlan extends AbstContractTableTabSkin<ContractPay
}
@Override
protected ViewModelService<ContractPayPlan, ContractPayPlanViewModel> getViewModelService() {
protected ContractPayPlanService getViewModelService() {
return getPayPlanService();
}

View File

@@ -1,37 +1,38 @@
package com.ecep.contract.manager.ds.contract.controller;
import com.ecep.contract.manager.ds.contract.ContractPayWay;
import com.ecep.contract.manager.ds.contract.controller.purchase_order.PurchaseOrderWindowController;
import com.ecep.contract.manager.ds.contract.model.PurchaseOrder;
import com.ecep.contract.manager.ds.contract.service.PurchaseOrdersService;
import com.ecep.contract.manager.ds.contract.vo.PurchaseOrderViewModel;
import com.ecep.contract.manager.ds.other.EmployeeStringConverter;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.ui.tab.TabSkin;
import com.ecep.contract.manager.ui.table.cell.LocalDateTimeTableCell;
import javafx.scene.control.*;
package com.ecep.contract.controller.contract;
import java.time.LocalDateTime;
import com.ecep.contract.ContractPayWay;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.table.cell.EmployeeTableCell;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.controller.vendor.purchase.order.PurchaseOrderWindowController;
import com.ecep.contract.converter.EmployeeStringConverter;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.PurchaseOrdersService;
import com.ecep.contract.vm.PurchaseOrderViewModel;
import com.ecep.contract.vo.PurchaseOrderVo;
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab;
import javafx.scene.control.TableColumn;
/**
* 子合同
*/
@FxmlPath("/ui/contract/contract-tab-purchase-orders.fxml")
public class ContractTabSkinPurchaseOrders
extends AbstContractTableTabSkin<PurchaseOrder, PurchaseOrderViewModel>
extends AbstContractTableTabSkin<PurchaseOrderVo, PurchaseOrderViewModel>
implements TabSkin {
private PurchaseOrdersService purchaseOrdersService;
private EmployeeStringConverter employeeStringConverter;
public TableColumn<PurchaseOrderViewModel, Number> idColumn;
public TableColumn<PurchaseOrderViewModel, String> codeColumn;
public TableColumn<PurchaseOrderViewModel, String> table_makerColumn;
public TableColumn<PurchaseOrderViewModel, Integer> table_makerColumn;
public TableColumn<PurchaseOrderViewModel, LocalDateTime> table_makerDateColumn;
public TableColumn<PurchaseOrderViewModel, String> table_verifierColumn;
public TableColumn<PurchaseOrderViewModel, Integer> table_verifierColumn;
public TableColumn<PurchaseOrderViewModel, LocalDateTime> table_verifierDateColumn;
public TableColumn<PurchaseOrderViewModel, String> table_closerColumn;
public TableColumn<PurchaseOrderViewModel, Integer> table_closerColumn;
public TableColumn<PurchaseOrderViewModel, LocalDateTime> table_closerDateColumn;
public TableColumn<PurchaseOrderViewModel, String> table_descriptionColumn;
@@ -68,14 +69,16 @@ public class ContractTabSkinPurchaseOrders
idColumn.setCellValueFactory(param -> param.getValue().getId());
codeColumn.setCellValueFactory(param -> param.getValue().getCode());
EmployeeStringConverter converter = getEmployeeStringConverter();
table_makerColumn.setCellValueFactory(param -> param.getValue().getMaker().map(converter::toString));
table_makerColumn.setCellValueFactory(param -> param.getValue().getMaker());
table_makerColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
table_makerDateColumn.setCellValueFactory(param -> param.getValue().getMakerDate());
table_makerDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
table_verifierColumn.setCellValueFactory(param -> param.getValue().getMaker().map(converter::toString));
table_verifierColumn.setCellValueFactory(param -> param.getValue().getMaker());
table_verifierColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
table_verifierDateColumn.setCellValueFactory(param -> param.getValue().getVerifierDate());
table_verifierDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
table_closerColumn.setCellValueFactory(param -> param.getValue().getMaker().map(converter::toString));
table_closerColumn.setCellValueFactory(param -> param.getValue().getMaker());
table_closerColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
table_closerDateColumn.setCellValueFactory(param -> param.getValue().getCloserDate());
table_closerDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
@@ -89,16 +92,14 @@ public class ContractTabSkinPurchaseOrders
}
PurchaseOrdersService getPurchaseOrdersService() {
if (purchaseOrdersService == null) {
purchaseOrdersService = getBean(PurchaseOrdersService.class);
}
return purchaseOrdersService;
return getBean(PurchaseOrdersService.class);
}
EmployeeStringConverter getEmployeeStringConverter() {
if (employeeStringConverter == null) {
employeeStringConverter = getBean(EmployeeStringConverter.class);
}
return employeeStringConverter;
return getBean(EmployeeStringConverter.class);
}
CompanyService getCompanyService() {
return controller.getCompanyService();
}
}

View File

@@ -0,0 +1,146 @@
package com.ecep.contract.controller.contract;
import java.time.LocalDate;
import com.ecep.contract.ContractPayWay;
import com.ecep.contract.controller.contract.sale_order.SalesOrderWindowController;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.table.cell.CompanyTableCell;
import com.ecep.contract.controller.table.cell.EmployeeTableCell;
import com.ecep.contract.controller.table.cell.LocalDateFieldTableCell;
import com.ecep.contract.converter.EmployeeStringConverter;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.SaleOrdersService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vm.SalesOrderViewModel;
import com.ecep.contract.vo.SalesOrderVo;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab;
import javafx.scene.control.TableColumn;
import lombok.Setter;
/**
* 子合同
*/
@FxmlPath("/ui/contract/contract-tab-sale-orders.fxml")
public class ContractTabSkinSaleOrders
extends AbstContractTableTabSkin<SalesOrderVo, SalesOrderViewModel>
implements TabSkin {
@Setter
private SaleOrdersService saleOrdersService;
private CompanyService companyService;
@Setter
private EmployeeStringConverter employeeStringConverter;
public TableColumn<SalesOrderViewModel, Number> idColumn;
public TableColumn<SalesOrderViewModel, String> codeColumn;
/**
* 业务员, Employee
*/
public TableColumn<SalesOrderViewModel, Integer> employeeColumn;
/**
* 创建人, Employee
*/
public TableColumn<SalesOrderViewModel, Integer> makerColumn;
public TableColumn<SalesOrderViewModel, LocalDate> makerDateColumn;
/**
* 审核人, Employee
*/
public TableColumn<SalesOrderViewModel, Integer> verifierColumn;
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 MenuItem subContractTable_menu_refresh;
private Tab tab;
public ContractTabSkinSaleOrders(ContractWindowController controller, Tab tab) {
super(controller);
this.tab = tab;
}
@Override
public void initializeUIComponents() {
super.initializeUIComponents();
// 合同是收时,有关联子合同
getTab().disableProperty().bind(viewModel.getPayWay().isEqualTo(ContractPayWay.RECEIVE).not());
}
@Override
public Tab getTab() {
return tab;
}
@Override
protected SaleOrdersService getViewModelService() {
return getSaleOrdersService();
}
@Override
public void initializeTab() {
super.initializeTab();
subContractTable_menu_refresh.setOnAction(this::onTableRefreshAction);
idColumn.setCellValueFactory(param -> param.getValue().getId());
codeColumn.setCellValueFactory(param -> param.getValue().getCode());
employeeColumn.setCellValueFactory(param -> param.getValue().getEmployee());
employeeColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
makerColumn.setCellValueFactory(param -> param.getValue().getMaker());
makerColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
makerDateColumn.setCellValueFactory(param -> param.getValue().getMakerDate());
makerDateColumn.setCellFactory(LocalDateFieldTableCell.forTableColumn());
verifierColumn.setCellValueFactory(param -> param.getValue().getVerifier());
verifierColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
verifierDateColumn.setCellValueFactory(param -> param.getValue().getVerifierDate());
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());
}
@Override
protected void onTableRowDoubleClickedAction(SalesOrderViewModel item) {
SalesOrderWindowController.show(item, getTableView().getScene().getWindow());
}
SaleOrdersService getSaleOrdersService() {
if (saleOrdersService == null) {
saleOrdersService = getBean(SaleOrdersService.class);
}
return saleOrdersService;
}
CompanyService getCompanyService() {
if (companyService == null) {
companyService = getBean(CompanyService.class);
}
return companyService;
}
}

View File

@@ -1,25 +1,31 @@
package com.ecep.contract.manager.ds.contract.controller;
import com.ecep.contract.manager.ds.contract.ContractPayWay;
import com.ecep.contract.manager.ds.contract.model.Contract;
import com.ecep.contract.manager.ds.contract.service.ContractService;
import com.ecep.contract.manager.ds.contract.vo.ContractViewModel;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.ui.tab.TabSkin;
import com.ecep.contract.manager.util.SpecificationUtils;
import javafx.application.Platform;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import org.springframework.data.jpa.domain.Specification;
package com.ecep.contract.controller.contract;
import java.time.LocalDate;
import com.ecep.contract.ContractPayWay;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.table.cell.CompanyTableCell;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.ContractService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.vm.ContractViewModel;
import com.ecep.contract.vo.ContractVo;
import javafx.application.Platform;
import javafx.scene.control.Button;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
/**
* 子合同
*/
@FxmlPath("/ui/contract/contract-tab-sub-contract.fxml")
public class ContractTabSkinSubContract
extends AbstContractTableTabSkin<Contract, ContractViewModel>
extends AbstContractTableTabSkin<ContractVo, ContractViewModel>
implements TabSkin {
public TableColumn<ContractViewModel, Number> subContractTable_idColumn;
@@ -29,6 +35,7 @@ public class ContractTabSkinSubContract
public TableColumn<ContractViewModel, LocalDate> subContractTable_inureDateColumn;
public TableColumn<ContractViewModel, LocalDate> subContractTable_orderDateColumn;
public TableColumn<ContractViewModel, LocalDate> subContractTable_varyDateColumn;
public TableColumn<ContractViewModel, Integer> subContractTable_companyColumn;
public MenuItem subContractTable_menu_refresh;
public TextField contractSearchKeyField;
public Button contractSearchBtn;
@@ -54,12 +61,11 @@ public class ContractTabSkinSubContract
return getContractService();
}
@Override
public Specification<Contract> getSpecification(Contract parent) {
return SpecificationUtils.and(getSpecification(), (root, query, builder) -> {
return builder.equal(root.get("parentCode"), parent.getCode());
});
public ParamUtils.Builder getSpecification(ContractVo parent) {
ParamUtils.Builder params = getSpecification();
params.equals("parentCode", parent.getCode());
return params;
}
@Override
@@ -82,6 +88,8 @@ public class ContractTabSkinSubContract
subContractTable_inureDateColumn.setCellValueFactory(param -> param.getValue().getInureDate());
subContractTable_orderDateColumn.setCellValueFactory(param -> param.getValue().getOrderDate());
subContractTable_varyDateColumn.setCellValueFactory(param -> param.getValue().getVaryDate());
subContractTable_companyColumn.setCellValueFactory(param -> param.getValue().getCompany());
subContractTable_companyColumn.setCellFactory(CompanyTableCell.forTableColumn(getCachedBean(CompanyService.class)));
Platform.runLater(() -> {
getTableView().getSortOrder().add(subContractTable_codeColumn);

View File

@@ -1,40 +1,52 @@
package com.ecep.contract.manager.ds.contract.controller;
import com.ecep.contract.manager.SpringApp;
import com.ecep.contract.manager.ds.company.model.Company;
import com.ecep.contract.manager.ds.company.service.CompanyService;
import com.ecep.contract.manager.ds.company.vo.CompanyViewModel;
import com.ecep.contract.manager.ds.contract.ContractPayWay;
import com.ecep.contract.manager.ds.contract.model.Contract;
import com.ecep.contract.manager.ds.contract.model.ContractBidVendor;
import com.ecep.contract.manager.ds.contract.model.ContractFile;
import com.ecep.contract.manager.ds.contract.service.ContractBidVendorService;
import com.ecep.contract.manager.ds.contract.service.ContractFileService;
import com.ecep.contract.manager.ds.contract.vo.ContractBidVendorViewModel;
import com.ecep.contract.manager.ui.table.EditableEntityTableTabSkin;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.ui.tab.TabSkin;
import javafx.event.ActionEvent;
import javafx.scene.control.*;
import javafx.util.StringConverter;
import lombok.Setter;
import org.controlsfx.control.textfield.TextFields;
import org.hibernate.Hibernate;
package com.ecep.contract.controller.contract;
import java.util.List;
import com.ecep.contract.service.ContractFileService;
import org.controlsfx.control.textfield.TextFields;
import com.ecep.contract.ContractPayWay;
import com.ecep.contract.SpringApp;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.table.EditableEntityTableTabSkin;
import com.ecep.contract.controller.table.cell.CompanyTableCell;
import com.ecep.contract.controller.table.cell.ContractFileTableCell;
import com.ecep.contract.controller.vendor.bid.VendorBidWindowController;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.ContractBidVendorService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vm.CompanyViewModel;
import com.ecep.contract.vm.ContractBidVendorViewModel;
import com.ecep.contract.vo.CompanyVo;
import com.ecep.contract.vo.ContractBidVendorVo;
import com.ecep.contract.vo.ContractVo;
import javafx.event.ActionEvent;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceDialog;
import javafx.scene.control.ComboBox;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab;
import javafx.scene.control.TableColumn;
import javafx.util.StringConverter;
import lombok.Setter;
/**
* 以下是比价供应商
*/
@FxmlPath("/ui/contract/contract-tab-bid.fxml")
public class ContractTabSkinVendorBid
extends AbstContractTableTabSkin<ContractBidVendor, ContractBidVendorViewModel>
implements TabSkin, EditableEntityTableTabSkin<ContractBidVendor, ContractBidVendorViewModel> {
extends AbstContractTableTabSkin<ContractBidVendorVo, ContractBidVendorViewModel>
implements TabSkin, EditableEntityTableTabSkin<ContractBidVendorVo, ContractBidVendorViewModel> {
@Setter
private ContractBidVendorService service;
public TableColumn<ContractBidVendorViewModel, Number> bidVendorTable_idColumn;
public TableColumn<ContractBidVendorViewModel, String> bidVendorTable_companyColumn;
public TableColumn<ContractBidVendorViewModel, ContractFile> bidVendorTable_quotationSheetColumn;
public TableColumn<ContractBidVendorViewModel, Integer> bidVendorTable_companyColumn;
/**
* 报价单, 合同文件 ContractFile
*/
public TableColumn<ContractBidVendorViewModel, Integer> bidVendorTable_quotationSheetColumn;
public Button bidVendorCreateBtn;
public MenuItem bidVendorTable_menu_refresh;
@@ -62,32 +74,6 @@ public class ContractTabSkinVendorBid
return controller.bidVendorTab;
}
static class QuotationSheetColumnTableCell extends TableCell<ContractBidVendorViewModel, ContractFile> {
private ContractFileService contractFileService;
public ContractFileService getContractFileService() {
if (contractFileService == null) {
contractFileService = SpringApp.getBean(ContractFileService.class);
}
return contractFileService;
}
@Override
protected void updateItem(ContractFile item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
} else {
if (!Hibernate.isInitialized(item)) {
item = getContractFileService().findById(item.getId());
ContractBidVendorViewModel viewModel = getTableRow().getItem();
viewModel.getQuotationSheet().set(item);
}
setText(item.getFileName());
}
}
}
@Override
public void initializeTab() {
bidVendorCreateBtn.setOnAction(this::onBidVendorTableCreateAction);
@@ -96,15 +82,10 @@ public class ContractTabSkinVendorBid
bidVendorTable_menu_chose_sheet.setOnAction(this::onBidVendorTableChoseQuotationSheetAction);
bidVendorTable_idColumn.setCellValueFactory(param -> param.getValue().getId());
bidVendorTable_companyColumn.setCellValueFactory(param -> param.getValue().getCompany().map(c -> {
if (c == null) {
return null;
} else {
return c.getName();
}
}));
bidVendorTable_quotationSheetColumn.setCellValueFactory(param -> param.getValue().getQuotationSheet());
bidVendorTable_quotationSheetColumn.setCellFactory(param -> new QuotationSheetColumnTableCell());
bidVendorTable_companyColumn.setCellValueFactory(param -> param.getValue().getCompanyId());
bidVendorTable_companyColumn.setCellFactory(CompanyTableCell.forTableColumn(getCompanyService()));
bidVendorTable_quotationSheetColumn.setCellValueFactory(param -> param.getValue().getQuotationSheetFileId());
bidVendorTable_quotationSheetColumn.setCellFactory(ContractFileTableCell.forTableColumn(SpringApp.getBean(ContractFileService.class)));
super.initializeTab();
}
@@ -114,9 +95,9 @@ public class ContractTabSkinVendorBid
showInOwner(VendorBidWindowController.class, item);
}
@SuppressWarnings("unchecked")
public void onBidVendorTableCreateAction(ActionEvent event) {
Contract contract = getParent();
ContractVo contract = getParent();
ChoiceDialog<String> dialog = new ChoiceDialog<>();
dialog.setTitle("添加比价供应商");
@@ -143,13 +124,13 @@ public class ContractTabSkinVendorBid
comboBox.setEditable(true);
dialog.showAndWait().ifPresent(selectedItem -> {
Company company = getCompanyService().findAllByName(selectedItem).getFirst();
CompanyVo company = getCompanyService().findAllByName(selectedItem).getFirst();
List<ContractBidVendor> list = getService().findByContractAndCompany(contract, company);
List<ContractBidVendorVo> list = getService().findByContractAndCompany(contract, company);
if (list == null || list.isEmpty()) {
ContractBidVendor bidVendor = new ContractBidVendor();
bidVendor.setContract(contract);
bidVendor.setCompany(company);
ContractBidVendorVo bidVendor = new ContractBidVendorVo();
bidVendor.setContractId(contract.getId());
bidVendor.setCompanyId(company.getId());
getService().save(bidVendor);
loadTableDataSet();
} else {
@@ -161,7 +142,6 @@ public class ContractTabSkinVendorBid
});
}
/**
* 匹配报价表
*
@@ -184,8 +164,7 @@ public class ContractTabSkinVendorBid
}
private CompanyService getCompanyService() {
return controller.companyService;
return controller.getCompanyService();
}
}

View File

@@ -1,37 +1,20 @@
package com.ecep.contract.manager.ds.contract.controller;
package com.ecep.contract.controller.contract;
import com.ecep.contract.manager.ds.contract.ContractPayWay;
import com.ecep.contract.manager.ds.contract.model.Contract;
import com.ecep.contract.manager.ds.contract.service.ContractService;
import com.ecep.contract.manager.ds.contract.tasker.ContractVerifyComm;
import com.ecep.contract.manager.ds.contract.tasker.ContractVerifyResultExportAsExcelFile;
import com.ecep.contract.manager.ui.BaseController;
import com.ecep.contract.manager.ds.other.model.Employee;
import com.ecep.contract.manager.ds.other.service.EmployeeService;
import com.ecep.contract.manager.ds.project.service.SaleTypeService;
import com.ecep.contract.manager.ds.vendor.service.VendorGroupService;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.ui.Message;
import com.ecep.contract.manager.ui.MessageHolder;
import com.ecep.contract.manager.ui.table.cell.EmployeeTableCell;
import com.ecep.contract.manager.util.UITools;
import javafx.application.Platform;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXMLLoader;
import javafx.fxml.FXML;
import static com.ecep.contract.util.TableViewUtils.bindDoubleClicked;
import static com.ecep.contract.util.TableViewUtils.bindEnterPressed;
import java.io.File;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.stage.*;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.Hibernate;
import org.controlsfx.control.PopOver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -40,21 +23,45 @@ import org.springframework.context.annotation.Scope;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.io.File;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import com.ecep.contract.Message;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.controller.BaseController;
import com.ecep.contract.controller.table.cell.EmployeeTableCell;
import com.ecep.contract.model.Employee;
import com.ecep.contract.service.ContractService;
import com.ecep.contract.service.EmployeeService;
import com.ecep.contract.service.ProjectSaleTypeService;
import com.ecep.contract.service.VendorGroupService;
import com.ecep.contract.task.ContractVerifyComm;
import com.ecep.contract.task.ContractVerifyResultExportAsExcelFileTasker;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vo.ContractVo;
import com.ecep.contract.vo.EmployeeVo;
import static com.ecep.contract.manager.util.TableViewUtils.bindDoubleClicked;
import static com.ecep.contract.manager.util.TableViewUtils.bindEnterPressed;
import static java.util.concurrent.CompletableFuture.runAsync;
import javafx.application.Platform;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.HBox;
import javafx.stage.FileChooser;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
@Lazy
@Scope("prototype")
@@ -68,6 +75,7 @@ public class ContractVerifyWindowController extends BaseController {
return super.show(loader, owner, modality);
}
@Setter
@Getter
public static class MessageExt extends Message {
@@ -78,14 +86,17 @@ public class ContractVerifyWindowController extends BaseController {
}
}
@Data
public static class Model implements MessageHolder {
public static class Model {
private SimpleStringProperty code = new SimpleStringProperty();
private SimpleStringProperty name = new SimpleStringProperty();
private SimpleObjectProperty<Employee> employee = new SimpleObjectProperty<>();
private SimpleObjectProperty<Integer> employee = new SimpleObjectProperty<>();
private SimpleObjectProperty<LocalDate> setupDate = new SimpleObjectProperty<>();
private SimpleListProperty<MessageExt> messages = new SimpleListProperty<>(FXCollections.observableArrayList());
}
static class MessageHolderImpl implements MessageHolder {
List<MessageExt> messages = new ArrayList<>();
@Override
public void addMessage(Level level, String message) {
@@ -104,9 +115,6 @@ public class ContractVerifyWindowController extends BaseController {
}
}
static class StateTableCell extends TableCell<Model, ObservableList<MessageExt>> {
Label message2Label(MessageExt message) {
Label label = new Label(message.getMessage());
@@ -151,16 +159,10 @@ public class ContractVerifyWindowController extends BaseController {
}
}
ContractVerifyComm comm = new ContractVerifyComm();
ContractVerifyComm comm = new ContractVerifyComm(this);
@Autowired
private SaleTypeService saleTypeService;
@Autowired
private VendorGroupService vendorGroupService;
@Autowired
private ContractService contractService;
@Autowired
private EmployeeService employeeService;
@FXML
public DatePicker setupDateBeginSelector;
@@ -189,7 +191,6 @@ public class ContractVerifyWindowController extends BaseController {
@FXML
public CheckMenuItem onlyShowVerifiedChecker;
@FXML
public TableView<Model> viewTable;
private final ObservableList<Model> viewTableDataSet = FXCollections.observableArrayList();
@@ -198,7 +199,7 @@ public class ContractVerifyWindowController extends BaseController {
@FXML
public TableColumn<Model, String> viewTable_nameColumn;
@FXML
public TableColumn<Model, Employee> viewTable_employeeColumn;
public TableColumn<Model, Integer> viewTable_employeeColumn;
@FXML
public TableColumn<Model, LocalDate> viewTable_setupDateColumn;
@FXML
@@ -212,7 +213,6 @@ public class ContractVerifyWindowController extends BaseController {
public void onShown(WindowEvent windowEvent) {
super.onShown(windowEvent);
comm.setContractService(contractService);
viewTable.getScene().getStylesheets().add("/ui/contract/contract-verify.css");
LocalDate now = LocalDate.now();
@@ -224,9 +224,7 @@ public class ContractVerifyWindowController extends BaseController {
viewTable_employeeColumn.setCellValueFactory(param -> param.getValue().getEmployee());
viewTable_employeeColumn.setCellFactory(param -> new EmployeeTableCell<>(getEmployeeService()));
viewTable_setupDateColumn.setCellValueFactory(param -> param.getValue().getSetupDate());
// viewTable_stateColumn.setCellValueFactory(param -> param.getValue().getMessages().map(messages -> {
// return messages.stream().map(Message::getMessage).collect(Collectors.joining(", "));
// }));
viewTable_stateColumn.setCellValueFactory(param -> param.getValue().getMessages());
viewTable_stateColumn.setCellFactory(param -> new StateTableCell());
@@ -241,11 +239,9 @@ public class ContractVerifyWindowController extends BaseController {
comm.getVerifyCustomerFiles().bind(verifyCustomerFileChecker.selectedProperty());
comm.getVerifyCustomerSubContractDate().bind(verifyCustomerSubContractDateChecker.selectedProperty());
bindDoubleClicked(viewTable, this::showContract);
bindEnterPressed(viewTable, this::reVerifyContract);
}
/**
@@ -257,40 +253,34 @@ public class ContractVerifyWindowController extends BaseController {
viewTableDataSet.clear();
Pageable pageRequest = PageRequest.ofSize(200);
AtomicInteger counter = new AtomicInteger(0);
Specification<Contract> spec = (root, query, builder) -> {
return builder.and(
builder.or(
builder.equal(root.get("payWay"), ContractPayWay.RECEIVE),
builder.and(
builder.equal(root.get("payWay"), ContractPayWay.PAY),
builder.or(
builder.isNull(root.get("parentCode")),
builder.equal(root.get("parentCode"), "")
)
)
),
builder.between(root.get("setupDate"), setupDateBeginSelector.getValue(), setupDateEndSelector.getValue())
);
};
long total = contractService.count(spec);
Map<String, Object> params = ParamUtils.builder().
between("setupDate", setupDateBeginSelector.getValue(), setupDateEndSelector.getValue())
.build();
long total = contractService.count(params);
setStatus("合同:" + total + "");
MessageHolderImpl messageHolder = new MessageHolderImpl();
while (true) {
if (isCloseRequested()) {
break;
}
Page<Contract> page = contractService.findAll(spec, pageRequest);
for (Contract contract : page) {
Page<ContractVo> page = contractService.findAll(params, pageRequest);
for (ContractVo contract : page) {
messageHolder.messages.clear();
if (isCloseRequested()) {
break;
}
counter.incrementAndGet();
Model model = new Model();
viewTableDataSet.add(model);
Employee handler = contract.getHandler();
Integer handler = contract.getHandlerId();
if (handler == null) {
model.getEmployee().set(contract.getEmployee());
model.getEmployee().set(contract.getEmployeeId());
} else {
model.getEmployee().set(handler);
}
@@ -298,11 +288,12 @@ public class ContractVerifyWindowController extends BaseController {
model.getCode().set(contract.getCode());
model.getName().set(contract.getName());
model.getSetupDate().set(contract.getSetupDate());
comm.verify(contract, model);
comm.verify(contract, messageHolder);
setStatus("合同验证进度:" + counter.get() + " / " + total);
// 移除中间消息
if (!model.getMessages().isEmpty()) {
model.getMessages().removeIf(msg -> msg.getLevel().intValue() <= Level.INFO.intValue());
if (!messageHolder.messages.isEmpty()) {
model.getMessages().setAll(messageHolder.messages.stream().filter(msg -> msg.getLevel().intValue() > Level.INFO.intValue()).limit(50).toList());
}
if (model.getMessages().isEmpty()) {
if (onlyShowVerifiedChecker.isSelected()) {
@@ -323,7 +314,6 @@ public class ContractVerifyWindowController extends BaseController {
});
}
public void onContractReVerifyAction(ActionEvent event) {
Model selectedItem = viewTable.getSelectionModel().getSelectedItem();
if (selectedItem == null) {
@@ -338,23 +328,31 @@ public class ContractVerifyWindowController extends BaseController {
return;
}
runAsync(() -> {
Contract contract = contractService.findByCode(contractCode);
ContractVo contract = null;
MessageHolderImpl messageHolder = new MessageHolderImpl();
try {
contract = contractService.findByCode(contractCode);
} catch (Exception e) {
handleException("查找合同 " + contractCode + " 时发生错误", e);
return;
}
if (contract == null) {
return;
}
model.getMessages().clear();
try {
comm.verify(contract, model);
// 移除中间消息
if (!model.getMessages().isEmpty()) {
model.getMessages().removeIf(msg -> msg.getLevel().intValue() <= Level.INFO.intValue());
}
comm.verify(contract, messageHolder);
} catch (Exception 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(() -> {
viewTableDataSet.remove(model);
});
@@ -362,7 +360,6 @@ public class ContractVerifyWindowController extends BaseController {
});
}
public void onShowContractDetailAction(ActionEvent event) {
Model selectedItem = viewTable.getSelectionModel().getSelectedItem();
if (selectedItem == null) {
@@ -376,20 +373,58 @@ public class ContractVerifyWindowController extends BaseController {
if (!StringUtils.hasText(contractCode)) {
return;
}
Contract contract = contractService.findByCode(contractCode);
ContractVo contract = null;
try {
contract = contractService.findByCode(contractCode);
} catch (Exception e) {
handleException("查找合同 " + contractCode + " 时发生错误", e);
return;
}
if (contract == null) {
return;
}
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) {
FileChooser chooser = new FileChooser();
chooser.setTitle("导出核验结果");
chooser.setInitialFileName("核验结果.xlsx");
File selected = chooser.showSaveDialog(viewTable.getScene().getWindow());
if (selected != null) {
ContractVerifyResultExportAsExcelFile task = new ContractVerifyResultExportAsExcelFile();
ContractVerifyResultExportAsExcelFileTasker task = new ContractVerifyResultExportAsExcelFileTasker();
task.setDestFile(selected);
task.setModels(new ArrayList<>(viewTableDataSet));
UITools.showTaskDialogAndWait("导出中...", task, null);

View File

@@ -1,46 +1,47 @@
package com.ecep.contract.manager.ds.contract.controller;
package com.ecep.contract.controller.contract;
import java.io.File;
import com.ecep.contract.manager.ds.company.controller.CompanyWindowController;
import com.ecep.contract.manager.ds.company.model.Company;
import com.ecep.contract.manager.ds.company.service.CompanyService;
import com.ecep.contract.manager.ds.contract.model.Contract;
import com.ecep.contract.manager.ds.contract.service.ContractService;
import com.ecep.contract.manager.ds.contract.tasker.ContractRepairTask;
import com.ecep.contract.manager.ds.contract.tasker.ContractVerifyTasker;
import com.ecep.contract.manager.ds.contract.vo.ContractViewModel;
import com.ecep.contract.manager.ds.project.service.ProjectService;
import com.ecep.contract.manager.ui.AbstEntityController;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.util.DesktopUtils;
import com.ecep.contract.manager.util.UITools;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
import javafx.util.converter.LocalDateStringConverter;
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 org.springframework.util.StringUtils;
import java.io.File;
import java.time.format.DateTimeFormatter;
import com.ecep.contract.ContractPayWay;
import com.ecep.contract.DesktopUtils;
import com.ecep.contract.controller.AbstEntityController;
import com.ecep.contract.controller.company.CompanyWindowController;
import com.ecep.contract.controller.tab.ContractTabSkinBase;
import com.ecep.contract.controller.tab.ContractTabSkinInvoices;
import com.ecep.contract.controller.tab.ContractTabSkinItemsV2;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.ContractService;
import com.ecep.contract.task.ContractRepairTask;
import com.ecep.contract.task.ContractVerifyTasker;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.ContractViewModel;
import com.ecep.contract.vo.CompanyVo;
import com.ecep.contract.vo.ContractVo;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.scene.layout.BorderPane;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
@Lazy
@Scope("prototype")
@Component
@FxmlPath("/ui/contract/contract.fxml")
public class ContractWindowController
extends AbstEntityController<Contract, ContractViewModel> {
extends AbstEntityController<ContractVo, ContractViewModel> {
public static void show(Contract contract, Window owner) {
ContractViewModel model = new ContractViewModel();
model.update(contract);
show(model, owner);
public static void show(ContractVo contract, Window owner) {
show(ContractViewModel.from(contract), owner);
}
/**
@@ -63,17 +64,8 @@ public class ContractWindowController
public Button openRelativeCompanyCustomerBtn;
public Button openRelativeCompanyVendorBtn;
@Autowired
CompanyService companyService;
@Autowired
ContractService contractService;
@Autowired
ProjectService projectService;
LocalDateStringConverter localDateStringConverter = new LocalDateStringConverter(DateTimeFormatter.ISO_LOCAL_DATE, null);
public TextField nameField;
public CheckBox contractNameLockedCk;
public TextField guidField;
public TextField codeField;
public TextField parentCodeField;
@@ -124,12 +116,17 @@ public class ContractWindowController
@Override
public ContractService getViewModelService() {
return contractService;
return getCachedBean(ContractService.class);
}
public CompanyService getCompanyService() {
return getCachedBean(CompanyService.class);
}
public void onShown(WindowEvent windowEvent) {
super.onShown(windowEvent);
getTitle().bind(viewModel.getName().map(name -> "[" + viewModel.getId().get() + "] " + viewModel.getCode().get() + " " + name + " 合同详情"));
getTitle().bind(viewModel.getName()
.map(name -> "[" + viewModel.getId().get() + "] " + viewModel.getCode().get() + " " + name + " 合同详情"));
root.getScene().getStylesheets().add("/ui/contract/contract.css");
}
@@ -139,31 +136,35 @@ public class ContractWindowController
ObservableList<Tab> tabs = tabPane.getTabs();
registerTabSkin(baseInfoTab, tab -> new ContractTabSkinBase(this));
switch (viewModel.getPayWay().get()) {
case RECEIVE -> {
tabs.remove(extendVendorInfo);
registerTabSkin(contractTab, t -> new ContractTabSkinSubContract(this));
tabs.remove(bidVendorTab);
Tab saleOrderTab = new Tab("销售订单");
tabs.add(saleOrderTab);
registerTabSkin(saleOrderTab, tab -> new ContractTabSkinSaleOrders(this, tab));
tabs.add(new Tab("票据"));
break;
}
case PAY -> {
registerTabSkin(extendVendorInfo, t -> new ContractTabSkinExtendVendorInfo(this));
tabs.remove(contractTab);
registerTabSkin(bidVendorTab, t -> new ContractTabSkinVendorBid(this));
ContractPayWay payWay = viewModel.getPayWay().get();
if (payWay == ContractPayWay.RECEIVE) {
tabs.remove(extendVendorInfo);
registerTabSkin(contractTab, t -> new ContractTabSkinSubContract(this));
tabs.remove(bidVendorTab);
Tab saleOrderTab = new Tab("销售订单");
payPlanTab.setText("收款计划");
tabs.add(saleOrderTab);
registerTabSkin(saleOrderTab, tab -> new ContractTabSkinSaleOrders(this, 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("签收单"));
Tab purchaseOrderTab = new Tab("采购订单");
tabs.add(purchaseOrderTab);
registerTabSkin(purchaseOrderTab, tab -> new ContractTabSkinPurchaseOrders(this, tab));
} else if (payWay == ContractPayWay.PAY) {
registerTabSkin(extendVendorInfo, t -> new ContractTabSkinExtendVendorInfo(this));
tabs.remove(contractTab);
registerTabSkin(bidVendorTab, t -> new ContractTabSkinVendorBid(this));
tabs.add(new Tab("发货"));
tabs.add(new Tab("签收单"));
tabs.add(new Tab("付款单"));
break;
}
Tab purchaseOrderTab = new Tab("采购订");
tabs.add(purchaseOrderTab);
registerTabSkin(purchaseOrderTab, tab -> new ContractTabSkinPurchaseOrders(this, tab));
tabs.add(new Tab("入库单"));
tabs.add(new Tab("付款单"));
payPlanTab.setText("付款计划");
}
registerTabSkin(itemTab, tab -> new ContractTabSkinItemsV2(this));
@@ -171,9 +172,8 @@ public class ContractWindowController
registerTabSkin(fileTab, tab -> new ContractTabSkinFiles(this));
}
public void onContractOpenInExplorerAction(ActionEvent event) {
Contract contract = getEntity();
ContractVo contract = getEntity();
String path = contract.getPath();
if (!StringUtils.hasText(path)) {
setStatus("未设置目录");
@@ -188,13 +188,13 @@ public class ContractWindowController
}
public void onContractOpenRelativeCompanyAction(ActionEvent event) {
Contract contract = getEntity();
if (contract.getCompany() == null) {
ContractVo contract = getEntity();
if (contract.getCompanyId() == null) {
UITools.showAlertAndWait("没有关联的公司,你可以尝试同步修复异常。");
return;
}
Integer companyId = contract.getCompany().getId();
Company company = companyService.findById(companyId);
Integer companyId = contract.getCompanyId();
CompanyVo company = getCompanyService().findById(companyId);
if (company != null) {
CompanyWindowController.show(company, root.getScene().getWindow());
}
@@ -202,7 +202,6 @@ public class ContractWindowController
public void onSyncContractAction(ActionEvent event) {
ContractRepairTask task = new ContractRepairTask();
task.setContractService(contractService);
task.setContract(getEntity());
UITools.showTaskDialogAndWait("同步合同", task, null);
if (task.isRepaired()) {
@@ -221,7 +220,7 @@ public class ContractWindowController
}
}
if (task.isItemsUpdated()) {
ContractTabSkinItems tabSkin = getTabSkin(ContractTabSkinItems.class);
ContractTabSkinItemsV2 tabSkin = getTabSkin(ContractTabSkinItemsV2.class);
if (tabSkin != null) {
tabSkin.loadTableDataSet();
}
@@ -234,14 +233,12 @@ public class ContractWindowController
}
}
/**
* 验证合同合规性
*/
public void onContractVerifyAction(ActionEvent event) {
Contract contract = getEntity();
ContractVo contract = getEntity();
ContractVerifyTasker task = new ContractVerifyTasker();
task.setContractService(contractService);
task.setContract(contract);
UITools.showTaskDialogAndWait("同步合规性验证", task, null);
}

View File

@@ -1,25 +1,33 @@
package com.ecep.contract.manager.ds.contract.controller.sale_order;
package com.ecep.contract.controller.contract.sale_order;
import com.ecep.contract.manager.ds.contract.model.SalesOrder;
import com.ecep.contract.manager.ds.contract.service.SaleOrdersService;
import com.ecep.contract.manager.ds.contract.vo.SalesOrderViewModel;
import com.ecep.contract.manager.ui.AbstEntityController;
import com.ecep.contract.manager.ui.FxmlPath;
import javafx.scene.control.*;
import javafx.stage.Stage;
import javafx.stage.Window;
import com.ecep.contract.vo.SalesOrderVo;
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.util.FxmlPath;
import com.ecep.contract.controller.tab.SalesOrderTabSkinBase;
import com.ecep.contract.controller.tab.SalesOrderTabSkinBillVoucher;
import com.ecep.contract.controller.tab.SalesOrderTabSkinItems;
import com.ecep.contract.service.SaleOrdersService;
import com.ecep.contract.vm.SalesOrderViewModel;
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.Stage;
import javafx.stage.Window;
@Lazy
@Scope("prototype")
@Component
@FxmlPath("/ui/contract/sale-orders.fxml")
public class SalesOrderWindowController extends AbstEntityController<SalesOrder, SalesOrderViewModel> {
public class SalesOrderWindowController extends AbstEntityController<SalesOrderVo, SalesOrderViewModel> {
private static final Logger logger = LoggerFactory.getLogger(SalesOrderWindowController.class);
public TabPane tabPane;
public Button saveBtn;
@@ -35,25 +43,21 @@ public class SalesOrderWindowController extends AbstEntityController<SalesOrder,
public TextField makeDateField;
public TextField makerField;
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) {
show(SalesOrderWindowController.class, viewModel, window);
}
@Autowired
private SaleOrdersService service;
@Override
protected SalesOrder loadEntity() {
return service.findById(viewModel.getId().get());
}
@Override
protected SalesOrder saveEntity(SalesOrder entity) {
return service.save(entity);
}
@Override
public void show(Stage stage) {
super.show(stage);
@@ -69,6 +73,6 @@ public class SalesOrderWindowController extends AbstEntityController<SalesOrder,
@Override
public SaleOrdersService getViewModelService() {
return service;
return getCachedBean(SaleOrdersService.class);
}
}

View File

@@ -0,0 +1,29 @@
package com.ecep.contract.controller.customer;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.table.AbstEntityTableTabSkin;
import com.ecep.contract.model.IdentityEntity;
import com.ecep.contract.service.CustomerService;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.vm.CompanyCustomerViewModel;
import com.ecep.contract.vm.IdentityViewModel;
import com.ecep.contract.vo.CustomerVo;
public abstract class AbstCompanyCustomerTableTabSkin<T extends IdentityEntity, TV extends IdentityViewModel<T>>
extends
AbstEntityTableTabSkin<CompanyCustomerWindowController, CustomerVo, CompanyCustomerViewModel, T, TV>
implements TabSkin {
public AbstCompanyCustomerTableTabSkin(CompanyCustomerWindowController controller) {
super(controller);
}
public CompanyService getCompanyService() {
return controller.getCompanyService();
}
protected CustomerService getCompanyCustomerService() {
return getCachedBean(CustomerService.class);
}
}

View File

@@ -0,0 +1,549 @@
package com.ecep.contract.controller.customer;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
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.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
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 org.springframework.util.FileSystemUtils;
import org.springframework.util.StringUtils;
import com.ecep.contract.controller.BaseController;
import com.ecep.contract.vo.CompanyCustomerEvaluationFormFileVo;
import com.ecep.contract.vo.CustomerFileVo;
import com.ecep.contract.service.CompanyCustomerFileService;
import com.ecep.contract.service.CompanyCustomerEvaluationFormFileService;
import com.ecep.contract.util.FileUtils;
import com.ecep.contract.util.FxmlUtils;
import com.ecep.contract.vm.CompanyCustomerEvaluationFormFileViewModel;
import com.ecep.contract.vm.CustomerFileViewModel;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.event.ActionEvent;
import javafx.geometry.Bounds;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.CheckBox;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TextField;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
@Lazy
@Scope("prototype")
@Component
@FxmlPath("/ui/company/customer/customer_evaluation_form.fxml")
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) {
show(CompanyCustomerEvaluationFormFileViewModel.from(saved), window);
}
public static void show(CompanyCustomerEvaluationFormFileViewModel viewModel, Window window) {
show(CompanyCustomerEvaluationFormFileWindowController.class, viewModel, window);
}
public Label idField;
public TextField filePathField;
public CheckBox validField;
public TextField editFilePathField;
public DatePicker signDateField;
public ToggleGroup catalog;
public ToggleGroup level;
public ToggleGroup score1;
public ToggleGroup score2;
public ToggleGroup score3;
public ToggleGroup score4;
public ToggleGroup score5;
public ToggleGroup creditLevel;
public ImageView imageView;
public SplitPane splitPane;
public ScrollPane leftPane;
public Label totalCreditScoreLabel;
private final SimpleIntegerProperty totalCreditScoreProperty = new SimpleIntegerProperty(-1);
private CustomerFileViewModel fileViewModel;
private SimpleBooleanProperty changed = new SimpleBooleanProperty(false);
@Lazy
@Autowired
private CompanyCustomerEvaluationFormFileService evaluationFormFileService;
@Override
public void show(Stage stage) {
super.show(stage);
stage.setFullScreen(false);
stage.setMaximized(true);
}
@Override
public void onShown(WindowEvent windowEvent) {
super.onShown(windowEvent);
getTitle().set("客户评估表单");
}
@Override
public ViewModelService<CompanyCustomerEvaluationFormFileVo, CompanyCustomerEvaluationFormFileViewModel> getViewModelService() {
return evaluationFormFileService;
}
@Override
protected void initializeData() {
CompanyCustomerEvaluationFormFileViewModel viewModel = new CompanyCustomerEvaluationFormFileViewModel();
setViewModel(viewModel);
runAsync(() -> {
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) -> {
if (newValue != null) {
for (Toggle toggle : group.getToggles()) {
String data = (String) toggle.getUserData();
if (data == null) {
if (logger.isWarnEnabled()) {
logger.warn("toggle unset userData: {}", toggle);
}
continue;
}
if (newValue.equals(data)) {
toggle.setSelected(true);
return;
}
}
changed.set(true);
}
Platform.runLater(() -> {
group.selectToggle(null);
// Toggle first = group.getToggles().getFirst();
// first.setSelected(true);
// first.setSelected(false);
// RadioButton btn = (RadioButton) first;
// btn.setText(newValue);
});
};
BiConsumer<SimpleStringProperty, Toggle> stringPropertyUpdater = (property, toggle) -> {
if (toggle == null) {
property.set("");
return;
}
String data = (String) toggle.getUserData();
property.set(data);
changed.set(true);
};
BiConsumer<ToggleGroup, Number> numberRadioGroupUpdater = (group, newValue) -> {
System.out.println("group = " + group + ", newValue = " + newValue);
String value = String.valueOf(newValue);
for (Toggle toggle : group.getToggles()) {
String data = (String) toggle.getUserData();
if (value.equals(data)) {
toggle.setSelected(true);
return;
}
}
Platform.runLater(() -> {
group.selectToggle(null);
// Toggle first = group.getToggles().getFirst();
// first.setSelected(true);
// first.setSelected(false);
// RadioButton btn = (RadioButton) first;
// btn.setText(String.valueOf(newValue));
});
changed.set(true);
};
BiConsumer<SimpleIntegerProperty, Toggle> numberPropertyUpdater = (property, toggle) -> {
if (toggle == null) {
property.set(0);
return;
}
String data = (String) toggle.getUserData();
property.set(Integer.parseInt(data));
changed.set(true);
};
int totalScoreToLevel(int score) {
if (score >= 200) {
return 4;
} else if (score >= 150) {
return 3;
} else if (score >= 100) {
return 2;
} else if (score >= 60) {
return 1;
}
return 0;
}
boolean calcValid() {
if (viewModel.getCreditLevel().get() <= 0) {
return false;
}
if (!StringUtils.hasText(viewModel.getCatalog().get())) {
return false;
}
if (!StringUtils.hasText(viewModel.getLevel().get())) {
return false;
}
if (viewModel.getScore1().get() <= 0) {
return false;
}
if (viewModel.getScore2().get() <= 0) {
return false;
}
if (viewModel.getScore3().get() <= 0) {
return false;
}
if (viewModel.getScore4().get() <= 0) {
return false;
}
if (viewModel.getScore5().get() <= 0) {
return false;
}
if (viewModel.getCreditLevel().get() <= 0) {
return false;
}
return true;
}
private void initializePane() {
setStatus("");
idField.textProperty().bind(viewModel.getId().asString());
filePathField.textProperty().bind(fileViewModel.getFilePath());
editFilePathField.textProperty().bind(fileViewModel.getEditFilePath());
String pattern = "yyyy-MM-dd";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
signDateField.setConverter(new LocalDateStringConverter(formatter, null));
signDateField.valueProperty().bindBidirectional(fileViewModel.getSignDate());
signDateField.valueProperty().addListener((observable, oldValue, newValue) -> {
changed.set(true);
});
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(() -> {
int total = 0;
for (SimpleIntegerProperty score : scores) {
total += score.get();
}
viewModel.getCreditLevel().set(totalScoreToLevel(total));
return total;
}, scores));
totalCreditScoreLabel.textProperty().bind(totalCreditScoreProperty.map(score -> {
return "合计总分:" + score;
}));
Bindings.createBooleanBinding(this::calcValid, viewModel.getCatalog(), viewModel.getLevel(),
viewModel.getScore1(), viewModel.getScore2(), viewModel.getScore3(), viewModel.getScore4(),
viewModel.getScore5(), viewModel.getCreditLevel())
.addListener((observable, oldValue, newValue) -> {
fileViewModel.getValid().set(newValue);
changed.set(true);
});
validField.selectedProperty().bindBidirectional(fileViewModel.getValid());
validField.setSelected(fileViewModel.getValid().getValue());
fileViewModel.getFilePath().addListener((observable, oldValue, newValue) -> {
File file = new File(newValue);
loadFile(file);
});
if (StringUtils.hasText(fileViewModel.getFilePath().get())) {
loadFile(new File(fileViewModel.getFilePath().get()));
}
leftPane.widthProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
imageView.setFitWidth(leftPane.getWidth());
imageView.setFitHeight(-1);
});
});
imageView.setFitWidth(leftPane.getWidth());
imageView.setFitHeight(-1);
imageView.setOnScroll(event -> {
Bounds bounds = imageView.getBoundsInLocal();
imageView.setFitWidth(bounds.getWidth() + event.getDeltaY());
imageView.setFitHeight(-1);
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());
}
});
}
private void initializeRadioGroup(ToggleGroup toggleGroup, SimpleStringProperty property) {
property.addListener((observable, oldValue, newValue) -> {
stringRadioGroupUpdater.accept(toggleGroup, newValue);
});
toggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
stringPropertyUpdater.accept(property, newValue);
});
stringRadioGroupUpdater.accept(toggleGroup, property.getValue());
}
private void initializeRadioGroup(ToggleGroup toggleGroup, SimpleIntegerProperty property) {
property.addListener((observable, oldValue, newValue) -> {
numberRadioGroupUpdater.accept(toggleGroup, newValue);
});
toggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
numberPropertyUpdater.accept(property, newValue);
});
numberRadioGroupUpdater.accept(toggleGroup, property.getValue());
}
}

View File

@@ -1,52 +1,65 @@
package com.ecep.contract.manager.ds.customer.controller;
package com.ecep.contract.controller.customer;
import com.ecep.contract.manager.ds.company.model.Company;
import com.ecep.contract.manager.ds.customer.model.CompanyCustomer;
import com.ecep.contract.manager.ds.customer.model.CompanyCustomerEntity;
import com.ecep.contract.manager.ds.customer.model.CompanyCustomerEvaluationFormFile;
import com.ecep.contract.manager.ds.customer.model.CompanyCustomerFile;
import com.ecep.contract.manager.ds.customer.service.CompanyCustomerEntityService;
import com.ecep.contract.manager.ds.customer.service.CompanyCustomerFileService;
import com.ecep.contract.manager.ds.customer.service.CompanyCustomerService;
import com.ecep.contract.manager.ui.MessageHolder;
import com.ecep.contract.manager.ui.Tasker;
import com.ecep.contract.manager.util.MyDateTimeUtils;
import com.ecep.contract.manager.util.UITools;
import lombok.Setter;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFTable;
import org.hibernate.Hibernate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Pageable;
import org.springframework.util.StringUtils;
import static com.ecep.contract.util.ExcelUtils.setCellValue;
import java.io.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import static com.ecep.contract.manager.util.ExcelUtils.*;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.PrintSetup;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.ss.util.AreaReference;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFTable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Pageable;
import org.springframework.util.StringUtils;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.MyDateTimeUtils;
import com.ecep.contract.service.CompanyCustomerEntityService;
import com.ecep.contract.service.CompanyCustomerEvaluationFormFileService;
import com.ecep.contract.service.CompanyCustomerFileService;
import com.ecep.contract.service.CustomerService;
import com.ecep.contract.task.Tasker;
import com.ecep.contract.util.ExcelUtils;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vo.CompanyCustomerEntityVo;
import com.ecep.contract.vo.CompanyCustomerEvaluationFormFileVo;
import com.ecep.contract.vo.CustomerFileVo;
import com.ecep.contract.vo.CustomerVo;
import com.ecep.contract.vo.CompanyVo;
import lombok.Setter;
public class CompanyCustomerExportExcelTasker extends Tasker<Object> {
private static final Logger logger = LoggerFactory.getLogger(CompanyCustomerExportExcelTasker.class);
@Setter
File destFile;
CompanyCustomerService customerService;
CustomerService customerService;
CompanyCustomerEntityService customerEntityService;
CompanyCustomerFileService customerFileService;
CompanyCustomerEvaluationFormFileService customerEvaluationFormFileService;
CompanyCustomerService getCustomerService() {
CustomerService getCustomerService() {
if (customerService == null)
customerService = getBean(CompanyCustomerService.class);
customerService = getBean(CustomerService.class);
return customerService;
}
@@ -55,12 +68,19 @@ public class CompanyCustomerExportExcelTasker extends Tasker<Object> {
customerFileService = getBean(CompanyCustomerFileService.class);
return customerFileService;
}
CompanyCustomerEntityService getCustomerEntityService() {
if (customerEntityService == null)
customerEntityService = getBean(CompanyCustomerEntityService.class);
return customerEntityService;
}
CompanyCustomerEvaluationFormFileService getCustomerEvaluationFormFileService() {
if (customerEvaluationFormFileService == null)
customerEvaluationFormFileService = getBean(CompanyCustomerEvaluationFormFileService.class);
return customerEvaluationFormFileService;
}
@Override
protected Object execute(MessageHolder holder) throws Exception {
if (destFile.exists()) {
@@ -78,8 +98,7 @@ public class CompanyCustomerExportExcelTasker extends Tasker<Object> {
}
try (
InputStream inp = new FileInputStream(template);
Workbook wb = WorkbookFactory.create(inp);
) {
Workbook wb = WorkbookFactory.create(inp);) {
String sheetName = "客户资信台账";
Sheet sheet = null;
for (int i = 0; i < wb.getNumberOfSheets(); i++) {
@@ -99,55 +118,49 @@ public class CompanyCustomerExportExcelTasker extends Tasker<Object> {
setCellValue(sheet, "A19", "Build by CMS @ " + MyDateTimeUtils.format(LocalDateTime.now()));
int rowIndex = 0;
for (CompanyCustomer customer : getCustomerService().findAll(null, Pageable.unpaged())) {
Company company = customer.getCompany();
for (CustomerVo customer : getCustomerService().findAll(null, Pageable.unpaged())) {
Integer companyId = customer.getCompanyId();
;
CompanyVo company = getCompanyService().findById(companyId);
if (company == null) {
holder.warn("客户 #" + customer.getId() + " 不存在对应公司");
continue;
}
if (!Hibernate.isInitialized(company)) {
company = getCompanyService().findById(company.getId());
}
// VO类不需要延迟加载代理直接使用即可
LocalDate devDate = null;
List<CompanyCustomerEntity> entities = getCustomerEntityService() .findAllByCustomer(customer);
for (CompanyCustomerEntity entity : entities) {
List<CompanyCustomerEntityVo> entities = getCustomerEntityService().findAllByCustomer(customer);
for (CompanyCustomerEntityVo entity : entities) {
if (devDate == null || devDate.isAfter(entity.getDevelopDate())) {
devDate = entity.getDevelopDate();
}
}
CustomerFileVo customerFile = getCustomerFileService()
.findAllByCustomer(customer).stream().filter(v -> v.isValid())
.max(Comparator.comparing(v -> v.getSignDate())).orElse(null);
CompanyCustomerEvaluationFormFile evaluationFormFile = getCustomerFileService().findAllCustomerEvaluationFormFiles(customer).stream().filter(v -> {
CompanyCustomerFile customerFile = v.getCustomerFile();
if (customerFile == null) {
return false;
}
return customerFile.isValid();
}).max(Comparator.comparing(v -> v.getCustomerFile().getSignDate())).orElse(null);
if (evaluationFormFile == null) {
if (customerFile == null) {
holder.warn(company.getName() + " 未匹配的客户评估");
continue;
}
CompanyCustomerFile customerFile = evaluationFormFile.getCustomerFile();
if (devDate != null && devDate.isAfter(customerFile.getSignDate())) {
holder.debug(company.getName() + " 最新评估日期早于客户开发日期,评估日期:" + customerFile.getSignDate() + ", 开发日期:" + devDate);
holder.debug(company.getName() + " 最新评估日期早于客户开发日期,评估日期:" + customerFile.getSignDate() + ", 开发日期:"
+ devDate);
}
rowIndex++;
if (rowIndex > 11) {
// 插入行并复制行的格式
sheet.shiftRows(rowIndex + 3, sheet.getLastRowNum(), 1);
Row templateRow = getRow(sheet, rowIndex + 2, true);
Row newRow = getRow(sheet, rowIndex + 3, true);
Row templateRow = ExcelUtils.getRow(sheet, rowIndex + 2, true);
Row newRow = ExcelUtils.getRow(sheet, rowIndex + 3, true);
if (templateRow != null && newRow != null) {
for (int i = 0; i < templateRow.getLastCellNum(); i++) {
Cell templateCell = templateRow.getCell(i);
Cell newCell = getCell(newRow, i, true);
Cell newCell = ExcelUtils.getCell(newRow, i, true);
if (templateCell != null && newCell != null) {
newCell.setCellStyle(templateCell.getCellStyle());
}
@@ -155,8 +168,9 @@ public class CompanyCustomerExportExcelTasker extends Tasker<Object> {
}
}
Row row = getRow(sheet, rowIndex + 3, true);
Row row = ExcelUtils.getRow(sheet, rowIndex + 3, true);
CompanyCustomerEvaluationFormFileVo evaluationFormFile = getCustomerEvaluationFormFileService()
.findByCustomerFile(customerFile);
setCellValue(row, 0, rowIndex);
setCellValue(row, 1, company.getName());
@@ -173,10 +187,11 @@ public class CompanyCustomerExportExcelTasker extends Tasker<Object> {
for (XSSFTable table : ((XSSFSheet) sheet).getTables()) {
if ("表2".equals(table.getName())) {
holder.info("找到表=" + table.getName() + ", style=" + table.getStyleName());
table.setCellReferences(new AreaReference("$A$4:$H$" + (rowIndex + 4), SpreadsheetVersion.EXCEL2007));
table.setCellReferences(
new AreaReference("$A$4:$H$" + (rowIndex + 4), SpreadsheetVersion.EXCEL2007));
// table.setDataRowCount(rowIndex);
//table.getCTTable().setRef("$A$4:$H$" + (rowIndex + 4));
// table.setDataRowCount(rowIndex);
// table.getCTTable().setRef("$A$4:$H$" + (rowIndex + 4));
}
}
@@ -200,7 +215,6 @@ public class CompanyCustomerExportExcelTasker extends Tasker<Object> {
UITools.showExceptionAndWait("保存失败", e);
}
return null;
}
}

View File

@@ -1,35 +1,31 @@
package com.ecep.contract.manager.ds.customer.controller;
package com.ecep.contract.controller.customer;
import com.ecep.contract.manager.ui.table.cell.CompanyTableCell;
import com.ecep.contract.manager.ds.company.service.CompanyService;
import com.ecep.contract.manager.ds.customer.model.CompanyCustomer;
import com.ecep.contract.manager.ds.customer.service.CompanyCustomerService;
import com.ecep.contract.manager.ds.customer.vo.CompanyCustomerViewModel;
import com.ecep.contract.manager.ui.AbstEntityManagerSkin;
import com.ecep.contract.manager.util.MyDateTimeUtils;
import com.ecep.contract.MyDateTimeUtils;
import com.ecep.contract.controller.AbstEntityManagerSkin;
import com.ecep.contract.controller.table.cell.CompanyTableCell;
import com.ecep.contract.controller.table.cell.CustomerCatalogTableCell;
import com.ecep.contract.service.CustomerService;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.CustomerCatalogService;
import com.ecep.contract.vm.CompanyCustomerViewModel;
import com.ecep.contract.vo.CustomerVo;
import com.ecep.contract.vo.CompanyVo;
import javafx.application.Platform;
import lombok.Setter;
public class CompanyCustomerManagerSkin
extends
AbstEntityManagerSkin<CompanyCustomer, CompanyCustomerViewModel, CompanyCustomerManagerSkin, CompanyCustomerManagerWindowController> {
@Setter
private CompanyService companyService;
AbstEntityManagerSkin<CustomerVo, CompanyCustomerViewModel, CompanyCustomerManagerSkin, CompanyCustomerManagerWindowController> {
public CompanyCustomerManagerSkin(CompanyCustomerManagerWindowController controller) {
super(controller);
}
public CompanyService getCompanyService() {
if (companyService == null) {
companyService = getBean(CompanyService.class);
}
return companyService;
return getBean(CompanyService.class);
}
public CompanyCustomerService getCompanyCustomerService() {
public CustomerService getCompanyCustomerService() {
return controller.getViewModelService();
}
@@ -37,7 +33,10 @@ public class CompanyCustomerManagerSkin
public void initializeTable() {
controller.idColumn.setCellValueFactory(param -> param.getValue().getId());
controller.companyColumn.setCellValueFactory(param -> param.getValue().getCompany());
controller.companyColumn.setCellFactory(param-> new CompanyTableCell<>(getCompanyService()));
controller.companyColumn.setCellFactory(CompanyTableCell.forTableColumn(getCompanyService()));
controller.catalogColumn.setCellValueFactory(param -> param.getValue().getCatalog());
controller.catalogColumn.setCellFactory(CustomerCatalogTableCell.forTableColumn(getBean(CustomerCatalogService.class)));
controller.developDateColumn.setCellValueFactory(param -> param.getValue().getDevelopDate());
controller.pathColumn.setCellValueFactory(param -> param.getValue().getPath());

View File

@@ -1,11 +1,12 @@
package com.ecep.contract.manager.ds.customer.controller;
package com.ecep.contract.controller.customer;
import java.io.File;
import java.time.LocalDate;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import org.hibernate.Hibernate;
import com.ecep.contract.vo.CustomerVo;
import com.ecep.contract.vo.CompanyVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
@@ -14,15 +15,13 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;
import com.ecep.contract.manager.ds.company.model.Company;
import com.ecep.contract.manager.ds.company.service.CompanyService;
import com.ecep.contract.manager.ds.customer.model.CompanyCustomer;
import com.ecep.contract.manager.ds.customer.service.CompanyCustomerService;
import com.ecep.contract.manager.ds.customer.vo.CompanyCustomerViewModel;
import com.ecep.contract.manager.ui.AbstManagerWindowController;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.util.MyDateTimeUtils;
import com.ecep.contract.manager.util.UITools;
import com.ecep.contract.MyDateTimeUtils;
import com.ecep.contract.controller.AbstManagerWindowController;
import com.ecep.contract.service.CustomerService;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.CompanyCustomerViewModel;
import javafx.application.Platform;
import javafx.collections.FXCollections;
@@ -45,24 +44,27 @@ import javafx.stage.Stage;
@Component
@FxmlPath("/ui/company/customer/customer_manager.fxml")
public class CompanyCustomerManagerWindowController
extends AbstManagerWindowController<CompanyCustomer, CompanyCustomerViewModel, CompanyCustomerManagerSkin> {
extends AbstManagerWindowController<CustomerVo, CompanyCustomerViewModel, CompanyCustomerManagerSkin> {
// columns
public TableColumn<CompanyCustomerViewModel, Number> idColumn;
public TableColumn<CompanyCustomerViewModel, Company> companyColumn;
public TableColumn<CompanyCustomerViewModel, String> catalogColumn;
/**
* 客户所属公司,Company
*/
public TableColumn<CompanyCustomerViewModel, Integer> companyColumn;
public TableColumn<CompanyCustomerViewModel, Integer> catalogColumn;
public TableColumn<CompanyCustomerViewModel, LocalDate> developDateColumn;
public TableColumn<CompanyCustomerViewModel, String> pathColumn;
public TableColumn<CompanyCustomerViewModel, String> createdColumn;
@Autowired
private CompanyService companyService;
@Autowired
private CompanyCustomerService companyCustomerService;
@Override
public CompanyCustomerService getViewModelService() {
return companyCustomerService;
public CustomerService getViewModelService() {
return getCachedBean(CustomerService.class);
}
private CompanyService getCompanyService() {
return getCachedBean(CompanyService.class);
}
@Override
@@ -74,7 +76,6 @@ public class CompanyCustomerManagerWindowController
@Override
protected CompanyCustomerManagerSkin createDefaultSkin() {
CompanyCustomerManagerSkin skin = new CompanyCustomerManagerSkin(this);
skin.setCompanyService(companyService);
return skin;
}
@@ -116,21 +117,18 @@ public class CompanyCustomerManagerWindowController
CompletableFuture.runAsync(() -> {
Pageable pageRequest = PageRequest.ofSize(50);
while (!canceled.get()) {
Page<CompanyCustomer> page = companyCustomerService.findAll(null, pageRequest);
Page<CustomerVo> page = getViewModelService().findAll(null, pageRequest);
int index = page.getNumber() * page.getSize();
int i = 1;
for (CompanyCustomer companyCustomer : page) {
for (CustomerVo companyCustomer : page) {
if (canceled.get()) {
return;
}
Company company = companyCustomer.getCompany();
if (!Hibernate.isInitialized(company)) {
company = companyService.findById(company.getId());
}
CompanyVo company = getCompanyService().findById(companyCustomer.getCompanyId());
String prefix = (index + i) + "/" + page.getTotalElements() + ", " + company.getName() + "> ";
companyCustomerService.reBuildingFiles(companyCustomer, msg -> {
getViewModelService().reBuildingFiles(companyCustomer, (level, msg) -> {
Platform.runLater(() -> {
listViewDataSet.add(prefix + msg);
listView.scrollTo(listViewDataSet.size() - 1);
@@ -164,7 +162,7 @@ public class CompanyCustomerManagerWindowController
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("导出");
fileChooser.setInitialFileName("客户资信台账-" + MyDateTimeUtils.format(LocalDate.now()) + ".xlsx");
fileChooser.setInitialDirectory(companyCustomerService.getBasePath());
fileChooser.setInitialDirectory(getViewModelService().getBasePath());
File destFile = fileChooser.showSaveDialog(table.getScene().getWindow());
tasker.setDestFile(destFile);
UITools.showTaskDialogAndWait("导出Excel", tasker, null);

View File

@@ -1,45 +1,49 @@
package com.ecep.contract.manager.ds.customer.controller;
package com.ecep.contract.controller.customer;
import com.ecep.contract.manager.ds.company.service.CompanyContactService;
import com.ecep.contract.manager.ds.company.service.CompanyService;
import com.ecep.contract.manager.ds.customer.model.CompanyCustomer;
import com.ecep.contract.manager.ds.customer.service.CompanyCustomerService;
import com.ecep.contract.manager.ds.customer.vo.CompanyCustomerViewModel;
import com.ecep.contract.manager.ui.AbstEntityController;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.ui.ViewModelService;
import com.ecep.contract.manager.util.DesktopUtils;
import java.io.File;
import javafx.event.ActionEvent;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
import org.hibernate.Hibernate;
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 org.springframework.util.StringUtils;
import java.io.File;
import com.ecep.contract.DesktopUtils;
import com.ecep.contract.controller.AbstEntityController;
import com.ecep.contract.service.CompanyContactService;
import com.ecep.contract.service.CustomerService;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vm.CompanyCustomerViewModel;
import com.ecep.contract.vo.CustomerVo;
import com.ecep.contract.vo.CompanyVo;
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.Stage;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
@Lazy
@Scope("prototype")
@Component
@FxmlPath("/ui/company/customer/customer.fxml")
public class CompanyCustomerWindowController extends AbstEntityController<CompanyCustomer, CompanyCustomerViewModel> {
public class CompanyCustomerWindowController extends AbstEntityController<CustomerVo, CompanyCustomerViewModel> {
private static final Logger logger = LoggerFactory.getLogger(CompanyCustomerWindowController.class);
/**
* 显示界面
*/
public static void show(CompanyCustomer customer, Window window) {
CompanyCustomerViewModel viewModel = new CompanyCustomerViewModel();
viewModel.update(customer);
show(CompanyCustomerWindowController.class, viewModel, window);
public static void show(CustomerVo customer, Window window) {
show(CompanyCustomerWindowController.class, CompanyCustomerViewModel.from(customer), window);
}
public Tab baseInfoTab;
@@ -48,14 +52,6 @@ public class CompanyCustomerWindowController extends AbstEntityController<Compan
public Tab fileTab;
public Tab entityTab;
public Tab satisfactionTab;
@Autowired
CompanyService companyService;
@Autowired
CompanyCustomerService companyCustomerService;
@Autowired
CompanyContactService companyContactService;
public TextField companyField;
public TextField contactField;
public TextField pathField;
@@ -70,33 +66,37 @@ public class CompanyCustomerWindowController extends AbstEntityController<Compan
public Button pathAsNameBtn;
public Button OpenCustomerPathInExplorerBtn;
@Override
public void show(Stage stage) {
super.show(stage);
getTitle().bind(viewModel.getCompany().map(company -> {
if (company == null) {
getTitle().bind(viewModel.getCompany().map(companyId -> {
if (companyId == null) {
return "-";
}
if (!Hibernate.isInitialized(company)) {
company = companyService.findById(company.getId());
viewModel.getCompany().set(company);
}
CompanyVo company = getCompanyService().findById(companyId);
return getMessage("ui.customer.title", String.valueOf(viewModel.getId().get()), company.getName());
}));
}
@Override
protected void registerTabSkins() {
registerTabSkin(baseInfoTab, tab -> new CompanyCustomerTabSkinBase(this));
registerTabSkin(baseInfoTab, tab -> new CustomerTabSkinBase(this));
registerTabSkin(fileTab, tab -> new CustomerTabSkinFile(this));
registerTabSkin(entityTab, tab -> new CustomerTabSkinEntity(this));
registerTabSkin(satisfactionTab, tab -> new CustomerTabSkinSatisfactionSurvey(this));
}
@Override
public CompanyCustomerService getViewModelService() {
return companyCustomerService;
public CustomerService getViewModelService() {
return getCachedBean(CustomerService.class);
}
public CompanyService getCompanyService() {
return getCachedBean(CompanyService.class);
}
public CompanyContactService getCompanyContactService() {
return getCachedBean(CompanyContactService.class);
}
@Override
@@ -119,6 +119,4 @@ public class CompanyCustomerWindowController extends AbstEntityController<Compan
DesktopUtils.showInExplorer(file);
}
}

View File

@@ -0,0 +1,173 @@
package com.ecep.contract.controller.customer;
import java.io.File;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import org.springframework.util.StringUtils;
import com.ecep.contract.MyDateTimeUtils;
import com.ecep.contract.controller.company.CompanyWindowController;
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.service.CompanyContactService;
import com.ecep.contract.service.CustomerService;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.QueryService;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.CompanyContactViewModel;
import com.ecep.contract.vm.CompanyCustomerViewModel;
import com.ecep.contract.vo.CompanyContactVo;
import com.ecep.contract.vo.CustomerVo;
import com.ecep.contract.vo.CompanyVo;
import com.ecep.contract.vo.VendorVo;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.scene.control.Tab;
import javafx.scene.control.TextField;
import javafx.stage.DirectoryChooser;
import javafx.util.StringConverter;
import javafx.util.converter.LocalDateStringConverter;
public class CustomerTabSkinBase
extends AbstEntityBasedTabSkin<CompanyCustomerWindowController, CustomerVo, CompanyCustomerViewModel>
implements TabSkin {
public CustomerTabSkinBase(CompanyCustomerWindowController controller) {
super(controller);
}
@Override
public Tab getTab() {
return controller.baseInfoTab;
}
@Override
public void initializeTab() {
initializeCompanyFieldAutoCompletion(controller.companyField);
initializeContactFieldAutoCompletion(controller.contactField);
UITools.autoCompletion(controller.contactField, viewModel.getContact(),
new QueryService<CompanyContactVo, CompanyContactViewModel>() {
@Override
public CompanyContactVo findById(Integer id) {
return getCompanyContactService().findById(id);
}
@Override
public List<CompanyContactVo> search(String searchText) {
CustomerVo vendor = getEntity();
CompanyVo company = controller.getCompanyService().findById(vendor.getCompanyId());
return getCompanyContactService().searchByCompany(company, searchText);
}
@Override
public StringConverter<CompanyContactVo> getStringConverter() {
return getCompanyContactService().getStringConverter();
}
});
LocalDateStringConverter converter = new LocalDateStringConverter(DateTimeFormatter.ISO_LOCAL_DATE, null);
controller.developDateField.setConverter(converter);
controller.developDateField.valueProperty().bindBidirectional(viewModel.getDevelopDate());
controller.pathField.textProperty().bind(viewModel.getPath());
controller.descriptionField.textProperty().bindBidirectional(viewModel.getDescription());
controller.createdField.textProperty().bind(
Bindings.createStringBinding(
() -> localDateTimeFormatter(viewModel.getCreated()),
viewModel.getCreated()));
controller.versionLabel.textProperty().bind(viewModel.getVersion().asString());
controller.relativeCompanyBtn.disableProperty().bind(viewModel.getCompany().isNull());
controller.relativeCompanyBtn.setOnAction(event -> {
Integer companyId = viewModel.getCompany().get();
if (companyId != null) {
CompanyVo company = getCompanyService().findById(companyId);
if (company != null) {
CompanyWindowController.show(company, controller.root.getScene().getWindow());
}
}
});
controller.createPathBtn.setOnAction(this::onCompanyCustomerCreatePathAction);
controller.changePathBtn.setOnAction(this::onCompanyCustomerChangePathAction);
controller.pathAsNameBtn.setOnAction(this::onCompanyCustomerPathSameAsNameAction);
}
protected String localDateTimeFormatter(SimpleObjectProperty<LocalDateTime> property) {
LocalDateTime dateTime = property.get();
if (dateTime == null) {
return "";
}
return MyDateTimeUtils.format(dateTime);
}
private void initializeContactFieldAutoCompletion(TextField textField) {
UITools.autoCompletion(textField, viewModel.getContact(), getCompanyContactService());
}
private void initializeCompanyFieldAutoCompletion(TextField textField) {
UITools.autoCompletion(textField, viewModel.getCompany(), getCompanyService());
}
public void onCompanyCustomerCreatePathAction(ActionEvent event) {
CustomerVo companyCustomer = getCompanyCustomerService().findById(viewModel.getId().get());
if (getCompanyCustomerService().makePathAbsent(companyCustomer)) {
companyCustomer = getCompanyCustomerService().save(companyCustomer);
viewModel.update(companyCustomer);
} else {
setStatus("目录存在或创建失败");
}
}
public void onCompanyCustomerChangePathAction(ActionEvent event) {
DirectoryChooser chooser = new DirectoryChooser();
CustomerVo entity = getEntity();
String path = entity.getPath();
File initialDirectory = null;
// 如果当前已经设置了目录并且路径有效,则设置初始目录为该目录
if (StringUtils.hasText(path)) {
File dir = new File(path);
if (dir.exists()) {
initialDirectory = dir;
}
}
// 如果没有有效的初始目录,则使用基础路径
if (initialDirectory == null) {
initialDirectory = getCompanyCustomerService().getBasePath();
}
if (initialDirectory != null) {
chooser.setInitialDirectory(initialDirectory);
}
File newDirectory = chooser.showDialog(getTab().getContent().getScene().getWindow());
if (newDirectory != null) {
entity.setPath(newDirectory.getAbsolutePath());
save(entity);
}
}
public void onCompanyCustomerPathSameAsNameAction(ActionEvent event) {
setStatus("未实现");
}
public CustomerService getCompanyCustomerService() {
return controller.getCachedBean(CustomerService.class);
}
public CompanyContactService getCompanyContactService() {
return controller.getCachedBean(CompanyContactService.class);
}
public CompanyService getCompanyService() {
return controller.getCachedBean(CompanyService.class);
}
}

View File

@@ -1,38 +1,36 @@
package com.ecep.contract.manager.ds.customer.controller;
import com.ecep.contract.manager.SpringApp;
import com.ecep.contract.manager.ds.customer.model.CompanyCustomer;
import com.ecep.contract.manager.ds.customer.model.CompanyCustomerEntity;
import com.ecep.contract.manager.ds.customer.model.CustomerCatalog;
import com.ecep.contract.manager.ds.customer.service.CompanyCustomerEntityService;
import com.ecep.contract.manager.ds.customer.vo.CustomerEntityViewModel;
import com.ecep.contract.manager.ds.other.EmployeeStringConverter;
import com.ecep.contract.manager.ds.other.EntityStringConverter;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.ui.table.cell.LocalDateTimeTableCell;
import com.ecep.contract.manager.util.SpecificationUtils;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab;
import javafx.scene.control.TableColumn;
import lombok.Setter;
import org.springframework.data.jpa.domain.Specification;
package com.ecep.contract.controller.customer;
import java.time.LocalDate;
import java.time.LocalDateTime;
import com.ecep.contract.controller.table.cell.CustomerCatalogTableCell;
import com.ecep.contract.controller.table.cell.EmployeeTableCell;
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
import com.ecep.contract.service.CompanyCustomerEntityService;
import com.ecep.contract.service.CustomerCatalogService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.vm.CustomerEntityViewModel;
import com.ecep.contract.vo.CompanyCustomerEntityVo;
import com.ecep.contract.vo.CustomerVo;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab;
import javafx.scene.control.TableColumn;
@FxmlPath("/ui/company/customer/customer-tab-entity.fxml")
public class CustomerTabSkinEntity
extends AbstCompanyCustomerTableTabSkin<CompanyCustomerEntity, CustomerEntityViewModel> {
extends AbstCompanyCustomerTableTabSkin<CompanyCustomerEntityVo, CustomerEntityViewModel> {
// 关联项 tab
public TableColumn<CustomerEntityViewModel, Number> entityTable_idColumn;
public TableColumn<CustomerEntityViewModel, String> entityTable_catalogColumn;
public TableColumn<CustomerEntityViewModel, Integer> entityTable_catalogColumn;
public TableColumn<CustomerEntityViewModel, String> entityTable_nameColumn;
public TableColumn<CustomerEntityViewModel, String> entityTable_abbNameColumn;
public TableColumn<CustomerEntityViewModel, String> entityTable_codeColumn;
public TableColumn<CustomerEntityViewModel, String> entityTable_creatorColumn;
public TableColumn<CustomerEntityViewModel, Integer> entityTable_creatorColumn;
public TableColumn<CustomerEntityViewModel, LocalDate> entityTable_developDateColumn;
public TableColumn<CustomerEntityViewModel, String> entityTable_modifierColumn;
public TableColumn<CustomerEntityViewModel, Integer> entityTable_modifierColumn;
public TableColumn<CustomerEntityViewModel, LocalDate> entityTable_modifyDateColumn;
public TableColumn<CustomerEntityViewModel, LocalDate> entityTable_updatedDateColumn;
public TableColumn<CustomerEntityViewModel, LocalDateTime> fetchedTimeColumn;
@@ -40,9 +38,6 @@ public class CustomerTabSkinEntity
public MenuItem entityTable_menu_refresh;
public MenuItem entityTable_menu_del;
@Setter
private CompanyCustomerEntityService customerEntityService;
public CustomerTabSkinEntity(CompanyCustomerWindowController controller) {
super(controller);
}
@@ -52,7 +47,6 @@ public class CustomerTabSkinEntity
return controller.entityTab;
}
@Override
public void initializeTab() {
super.initializeTab();
@@ -61,34 +55,28 @@ public class CustomerTabSkinEntity
entityTable_nameColumn.setCellValueFactory(param -> param.getValue().getName());
entityTable_abbNameColumn.setCellValueFactory(param -> param.getValue().getAbbName());
entityTable_codeColumn.setCellValueFactory(param -> param.getValue().getCode());
initializeEntityTabCatalogColumn(entityTable_catalogColumn);
EmployeeStringConverter stringConverter = SpringApp.getBean(EmployeeStringConverter.class);
entityTable_catalogColumn.setCellValueFactory(param -> param.getValue().getCatalog());
entityTable_catalogColumn.setCellFactory(CustomerCatalogTableCell.forTableColumn(getCustomerCatalogService()));
entityTable_developDateColumn.setCellValueFactory(param -> param.getValue().getDevelopDate());
entityTable_modifyDateColumn.setCellValueFactory(param -> param.getValue().getModifyDate());
entityTable_creatorColumn.setCellValueFactory(param -> param.getValue().getCreator().map(stringConverter::toString));
entityTable_modifierColumn.setCellValueFactory(param -> param.getValue().getModifier().map(stringConverter::toString));
entityTable_updatedDateColumn.setCellValueFactory(param -> param.getValue().getUpdatedDate());
fetchedTimeColumn.setCellValueFactory(param -> param.getValue().getFetchedTime());
fetchedTimeColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
entityTable_creatorColumn.setCellValueFactory(param -> param.getValue().getCreator());
entityTable_creatorColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
entityTable_modifierColumn.setCellValueFactory(param -> param.getValue().getModifier());
entityTable_modifierColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
entityTable_menu_refresh.setOnAction(this::onTableRefreshAction);
entityTable_menu_del.setOnAction(this::onTableDeleteAction);
}
private void initializeEntityTabCatalogColumn(TableColumn<CustomerEntityViewModel, String> column) {
EntityStringConverter<CustomerCatalog> converter = new EntityStringConverter<>();
converter.setInitialized(v -> getCompanyCustomerService().findCatalogById(v.getId()));
column.setCellValueFactory(param -> param.getValue().getCatalog().map(converter::toString));
}
CompanyCustomerEntityService getCompanyCustomerEntityService() {
if (customerEntityService == null) {
customerEntityService = getBean(CompanyCustomerEntityService.class);
}
return customerEntityService;
return getCachedBean(CompanyCustomerEntityService.class);
}
@Override
@@ -96,11 +84,15 @@ public class CustomerTabSkinEntity
return getCompanyCustomerEntityService();
}
CustomerCatalogService getCustomerCatalogService() {
return getCachedBean(CustomerCatalogService.class);
}
@Override
public Specification<CompanyCustomerEntity> getSpecification(CompanyCustomer parent) {
return SpecificationUtils.and(getSpecification(), (root, query, builder) -> {
return builder.equal(root.get("customer"), parent);
});
public ParamUtils.Builder getSpecification(CustomerVo parent) {
ParamUtils.Builder params = getSpecification();
params.equals("customer", parent.getId());
return params;
}
}

View File

@@ -0,0 +1,318 @@
package com.ecep.contract.controller.customer;
import java.io.File;
import java.time.LocalDate;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.springframework.util.StringUtils;
import com.ecep.contract.CustomerFileType;
import com.ecep.contract.DesktopUtils;
import com.ecep.contract.MyDateTimeUtils;
import com.ecep.contract.SpringApp;
import com.ecep.contract.constant.CompanyCustomerConstant;
import com.ecep.contract.controller.customer.tasker.CustomerEvaluationFormUpdateTask;
import com.ecep.contract.controller.customer.tasker.CustomerNextSignDateTask;
import com.ecep.contract.controller.table.EditableEntityTableTabSkin;
import com.ecep.contract.controller.table.cell.CompanyCustomerFileTableTypeTableCell;
import com.ecep.contract.controller.table.cell.FilePathTableCell;
import com.ecep.contract.service.CompanyCustomerEvaluationFormFileService;
import com.ecep.contract.service.CompanyCustomerFileService;
import com.ecep.contract.service.CompanyCustomerFileTypeService;
import com.ecep.contract.service.CustomerService;
import com.ecep.contract.task.CustomerFileMoveTasker;
import com.ecep.contract.util.FileUtils;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.CustomerFileViewModel;
import com.ecep.contract.vo.CompanyCustomerEvaluationFormFileVo;
import com.ecep.contract.vo.CustomerFileVo;
import com.ecep.contract.vo.CompanyVo;
import com.ecep.contract.vo.CustomerVo;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
import lombok.Setter;
@FxmlPath("/ui/company/customer/customer-tab-file.fxml")
public class CustomerTabSkinFile
extends AbstCompanyCustomerTableTabSkin<CustomerFileVo, CustomerFileViewModel>
implements EditableEntityTableTabSkin<CustomerFileVo, CustomerFileViewModel> {
@Setter
private CompanyCustomerFileService companyCustomerFileService;
public TableColumn<CustomerFileViewModel, Number> fileTable_idColumn;
public TableColumn<CustomerFileViewModel, CustomerFileType> fileTable_typeColumn;
public TableColumn<CustomerFileViewModel, String> fileTable_filePathColumn;
public TableColumn<CustomerFileViewModel, String> fileTable_editFilePathColumn;
public TableColumn<CustomerFileViewModel, LocalDate> fileTable_signDateColumn;
public TableColumn<CustomerFileViewModel, Boolean> fileTable_validColumn;
public TableColumn<CustomerFileViewModel, String> fileTable_descriptionColumn;
public Button fileTable_reBuildBtn;
public Button fileTable_updateEvaluationFormBuildBtn;
public Button fileTable_calcNextSignDateBtn;
public MenuItem fileTable_menu_refresh;
public MenuItem fileTable_menu_add;
public MenuItem fileTable_menu_del;
public CustomerTabSkinFile(CompanyCustomerWindowController controller) {
super(controller);
setDragAndDrop(true);
setDragAndDropFileHandler(this::moveFileToCustomer);
}
// 文件 tab
@Override
public Tab getTab() {
return controller.fileTab;
}
@Override
protected CompanyCustomerFileService getViewModelService() {
return getCompanyCustomerFileService();
}
public CompanyCustomerEvaluationFormFileService getEvaluationFormFileService() {
return getCachedBean(CompanyCustomerEvaluationFormFileService.class);
}
@Override
public ParamUtils.Builder getSpecification(CustomerVo parent) {
ParamUtils.Builder params = getSpecification();
params.equals("customer", parent.getId());
return params;
}
@Override
public void initializeTable() {
super.initializeTable();
TableView<CustomerFileViewModel> table = getTableView();
table.disableProperty().bind(viewModel.getPath().isEmpty());
fileTable_idColumn.setCellValueFactory(param -> param.getValue().getId());
CompanyCustomerFileTypeService fileTypeService = getCachedBean(CompanyCustomerFileTypeService.class);
fileTable_typeColumn.setCellValueFactory(param -> param.getValue().getType());
fileTable_typeColumn.setCellFactory(CompanyCustomerFileTableTypeTableCell.forTableColumn(fileTypeService));
fileTable_filePathColumn.setCellValueFactory(param -> param.getValue().getFilePath());
fileTable_filePathColumn.setCellFactory(FilePathTableCell.forTableColumn(viewModel.getPath()));
fileTable_editFilePathColumn.setCellValueFactory(param -> param.getValue().getEditFilePath());
fileTable_editFilePathColumn.setCellFactory(FilePathTableCell.forTableColumn(viewModel.getPath()));
fileTable_signDateColumn.setCellValueFactory(param -> param.getValue().getSignDate());
fileTable_validColumn.setEditable(true);
fileTable_validColumn.setCellValueFactory(param -> param.getValue().getValid());
fileTable_validColumn.setCellFactory(param -> new CheckBoxTableCell<>());
table.setOnDragOver(event -> {
Dragboard dragboard = event.getDragboard();
if (dragboard.hasFiles()) {
event.acceptTransferModes(TransferMode.MOVE);
}
event.consume();
});
table.setOnDragDropped(event -> {
Dragboard dragboard = event.getDragboard();
boolean success = false;
if (dragboard.hasFiles()) {
List<File> files = dragboard.getFiles();
CompletableFuture.runAsync(() -> {
try {
moveFileToCustomer(files);
} catch (Exception e) {
UITools.showExceptionAndWait("移动文件出错", e);
}
});
}
event.setDropCompleted(success);
event.consume();
});
fileTable_reBuildBtn.setOnAction(this::onFileReBuildingAction);
fileTable_updateEvaluationFormBuildBtn.setOnAction(this::onUpdateEvaluationFormAction);
fileTable_calcNextSignDateBtn.setOnAction(this::onCalcNextSignDateAction);
fileTable_menu_refresh.setOnAction(this::onTableRefreshAction);
fileTable_menu_add.setOnAction(this::onTableDeleteAction);
fileTable_menu_del.setOnAction(this::onFileTableMoveToCompanyPathAction);
}
@Override
protected void onTableRowDoubleClickedAction(CustomerFileViewModel item) {
CustomerFileType fileType = item.getType().get();
if (fileType == CustomerFileType.EvaluationForm) {
// 文件不是 Excel 文件时打开编辑UI
if (!FileUtils.withExtensions(item.getFilePath().get(), FileUtils.XLS,
FileUtils.XLSX)) {
CompanyCustomerEvaluationFormFileWindowController.show(item,
controller.root.getScene().getWindow());
return;
}
}
File file = new File(item.getFilePath().get());
if (!file.exists()) {
setStatus("文件不存在 " + file.getName());
return;
}
DesktopUtils.showInExplorer(file);
}
private void moveFileToCustomer(List<File> files) {
String path = viewModel.getPath().get();
if (!StringUtils.hasText(path)) {
setStatus("未设置目录");
return;
}
File dir = new File(path);
if (!dir.exists()) {
setStatus("目录错误,不存在");
return;
}
CustomerVo companyCustomer = getParent();
LocalDate nextSignDate = getCompanyCustomerFileService().getNextSignDate(companyCustomer,
((level, message) -> setStatus(message)));
if (nextSignDate != null && files.size() == 1) {
File file = files.getFirst();
String fileName = file.getName();
if (fileName.startsWith("S")) {
String destFileName = CompanyCustomerConstant.EVALUATION_FORM_NAME2 + "_"
+ MyDateTimeUtils.format(nextSignDate)
+ "." + StringUtils.getFilenameExtension(fileName);
File dest = new File(dir, destFileName);
if (file.renameTo(dest)) {
CustomerFileVo ccf = new CustomerFileVo();
ccf.setCustomer(companyCustomer.getId());
ccf.setType(CustomerFileType.EvaluationForm);
ccf.setFilePath(dest.getAbsolutePath());
ccf.setSignDate(nextSignDate);
ccf.setValid(false);
CustomerFileVo saved = getCompanyCustomerFileService().save(ccf);
Platform.runLater(() -> {
CustomerFileViewModel model = new CustomerFileViewModel();
model.update(saved);
dataSet.add(model);
CompanyCustomerEvaluationFormFileWindowController.show(model,
getTableView().getScene().getWindow());
});
return;
}
}
}
for (File file : files) {
File dest = new File(dir, file.getName());
if (file.renameTo(dest)) {
CustomerFileVo ccf = new CustomerFileVo();
ccf.setCustomer(companyCustomer.getId());
ccf.setType(CustomerFileType.General);
ccf.setFilePath(dest.getAbsolutePath());
ccf.setValid(false);
getCompanyCustomerFileService().save(ccf);
}
}
loadTableDataSet();
}
public void onFileReBuildingAction(ActionEvent event) {
CustomerService customerService = getCompanyCustomerService();
try {
CustomerVo companyCustomer = customerService.findById(viewModel.getId().get());
if (customerService.reBuildingFiles(companyCustomer, (level, message) -> setStatus(message))) {
loadTableDataSet();
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected boolean deleteRow(CustomerFileViewModel row) {
String path = row.getFilePath().get();
if (super.deleteRow(row)) {
File file = new File(path);
if (file.exists()) {
UITools.showConfirmation("数据记录已经删除,请确认是否删除物理文件", path)
.thenAccept(buttonType -> {
if (buttonType == ButtonType.OK) {
if (file.delete()) {
setStatus("删除文件 " + path);
}
}
});
}
return true;
}
return false;
}
public void onFileTableMoveToCompanyPathAction(ActionEvent event) {
CustomerFileViewModel selectedItem = getSelectedItem();
if (selectedItem == null) {
return;
}
// 检查公司目录设置
Integer companyId = viewModel.getCompany().get();
CompanyVo company = getCompanyService().findById(companyId);
if (!StringUtils.hasText(company.getPath())) {
setStatus("公司目录未设置");
return;
}
File companyPath = new File(company.getPath());
if (!companyPath.exists()) {
setStatus("公司目录设置异常,无法访问");
return;
}
// 创建并启动任务
CustomerFileMoveTasker task = new CustomerFileMoveTasker();
task.setFileId(selectedItem.getId().get());
UITools.showTaskDialogAndWait("移动文件到公司目录", task, null);
// 刷新表格数据
loadTableDataSet();
}
public void onUpdateEvaluationFormAction(ActionEvent event) {
CustomerEvaluationFormUpdateTask task = new CustomerEvaluationFormUpdateTask();
task.setCustomer(getCompanyCustomerService().findById(viewModel.getId().get()));
UITools.showTaskDialogAndWait("更新评价表", task, null);
loadTableDataSet();
}
public void onCalcNextSignDateAction(ActionEvent event) {
CustomerNextSignDateTask task = new CustomerNextSignDateTask();
task.setCustomer(getEntity());
UITools.showTaskDialogAndWait("计算客户的下一个评价日期", task, null);
}
private CompanyCustomerFileService getCompanyCustomerFileService() {
if (companyCustomerFileService == null) {
companyCustomerFileService = SpringApp.getBean(CompanyCustomerFileService.class);
}
return companyCustomerFileService;
}
}

View File

@@ -1,48 +1,49 @@
package com.ecep.contract.manager.ds.customer.controller;
package com.ecep.contract.controller.customer;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Map;
import com.ecep.contract.service.CustomerCatalogService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.controller.project.satisfaction_survey.CustomerSatisfactionSurveyWindowController;
import com.ecep.contract.controller.table.cell.EmployeeTableCell;
import com.ecep.contract.controller.table.cell.ProjectTableCell;
import com.ecep.contract.converter.EntityStringConverter;
import com.ecep.contract.vo.CustomerVo;
import com.ecep.contract.vo.CustomerCatalogVo;
import com.ecep.contract.vo.CustomerSatisfactionSurveyVo;
import com.ecep.contract.vo.EmployeeVo;
import com.ecep.contract.vo.ProjectVo;
import com.ecep.contract.service.CustomerSatisfactionSurveyService;
import com.ecep.contract.service.ProjectService;
import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.vm.CustomerEntityViewModel;
import com.ecep.contract.vm.CustomerSatisfactionSurveyViewModel;
import com.ecep.contract.manager.ds.company.model.Company;
import com.ecep.contract.manager.ds.customer.model.CompanyCustomer;
import com.ecep.contract.manager.ds.customer.model.CustomerCatalog;
import com.ecep.contract.manager.ds.customer.vo.CustomerEntityViewModel;
import com.ecep.contract.manager.ds.other.EntityStringConverter;
import com.ecep.contract.manager.ds.project.service.ProjectService;
import com.ecep.contract.manager.ui.table.cell.EmployeeTableCell;
import com.ecep.contract.manager.ds.other.model.Employee;
import com.ecep.contract.manager.ds.project.controller.satisfaction_survey.CustomerSatisfactionSurveyWindowController;
import com.ecep.contract.manager.ds.project.model.CustomerSatisfactionSurvey;
import com.ecep.contract.manager.ds.project.model.Project;
import com.ecep.contract.manager.ds.project.service.CustomerSatisfactionSurveyService;
import com.ecep.contract.manager.ds.project.vo.CustomerSatisfactionSurveyViewModel;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.ui.table.cell.ProjectTableCell;
import com.ecep.contract.manager.util.SpecificationUtils;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab;
import javafx.scene.control.TableColumn;
import lombok.Setter;
import org.springframework.data.jpa.domain.Specification;
import java.time.LocalDate;
import java.time.LocalDateTime;
@FxmlPath("/ui/company/customer/customer-tab-satisfaction-survey.fxml")
public class CustomerTabSkinSatisfactionSurvey
extends AbstCompanyCustomerTableTabSkin<CustomerSatisfactionSurvey, CustomerSatisfactionSurveyViewModel> {
extends AbstCompanyCustomerTableTabSkin<CustomerSatisfactionSurveyVo, CustomerSatisfactionSurveyViewModel> {
// 关联项 tab
public TableColumn<CustomerSatisfactionSurveyViewModel, Number> idColumn;
public TableColumn<CustomerSatisfactionSurveyViewModel, Project> projectColumn;
public TableColumn<CustomerSatisfactionSurveyViewModel, Integer> projectColumn;
public TableColumn<CustomerSatisfactionSurveyViewModel, String> codeColumn;
public TableColumn<CustomerSatisfactionSurveyViewModel, Number> totalScoreColumn;
public TableColumn<CustomerSatisfactionSurveyViewModel, Employee> applicantColumn;
public TableColumn<CustomerSatisfactionSurveyViewModel, Integer> applicantColumn;
public TableColumn<CustomerSatisfactionSurveyViewModel, LocalDateTime> applyTimeColumn;
public TableColumn<CustomerSatisfactionSurveyViewModel, String> descriptionColumn;
public TableColumn<CustomerSatisfactionSurveyViewModel, LocalDate> dateColumn;
public MenuItem entityTable_menu_refresh;
public MenuItem entityTable_menu_del;
@Setter
private ProjectService projectService;
@Setter
private ProjectService projectService;
@Setter
private CustomerSatisfactionSurveyService satisfactionSurveyService;
@@ -55,19 +56,19 @@ private ProjectService projectService;
return controller.satisfactionTab;
}
@Override
public void initializeTab() {
super.initializeTab();
bindNumberColumn(idColumn, CustomerSatisfactionSurveyViewModel::getId);
bindColumn(codeColumn, CustomerSatisfactionSurveyViewModel::getCode);
projectColumn.setCellValueFactory(param -> param.getValue().getProject());
projectColumn.setCellFactory(cell -> new ProjectTableCell<>(getProjectService()));
projectColumn.setCellFactory(ProjectTableCell.forTableColumn(getProjectService()));
bindLocalDateColumn(dateColumn, CustomerSatisfactionSurveyViewModel::getDate);
bindNumberColumn(totalScoreColumn, CustomerSatisfactionSurveyViewModel::getTotalScore);
applicantColumn.setCellValueFactory(param -> param.getValue().getApplicant());
applicantColumn.setCellFactory(cell -> new EmployeeTableCell<>(getEmployeeService()));
applicantColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
bindLocalDateTimeColumn(applyTimeColumn, CustomerSatisfactionSurveyViewModel::getApplyTime);
bindColumn(descriptionColumn, CustomerSatisfactionSurveyViewModel::getDescription);
@@ -75,13 +76,6 @@ private ProjectService projectService;
entityTable_menu_del.setOnAction(this::onTableDeleteAction);
}
private void initializeEntityTabCatalogColumn(TableColumn<CustomerEntityViewModel, String> column) {
EntityStringConverter<CustomerCatalog> converter = new EntityStringConverter<>();
converter.setInitialized(v -> getCompanyCustomerService().findCatalogById(v.getId()));
column.setCellValueFactory(param -> param.getValue().getCatalog().map(converter::toString));
}
private CustomerSatisfactionSurveyService getCustomerSatisfactionSurveyService() {
if (satisfactionSurveyService == null) {
satisfactionSurveyService = getBean(CustomerSatisfactionSurveyService.class);
@@ -102,14 +96,10 @@ private ProjectService projectService;
}
@Override
public Specification<CustomerSatisfactionSurvey> getSpecification(CompanyCustomer parent) {
return SpecificationUtils.and(getSpecification(), (root, query, builder) -> {
Company company = parent.getCompany();
if (company == null) {
return null;
}
return builder.equal(root.get("project").get("customer"), company);
});
public ParamUtils.Builder getSpecification(CustomerVo parent) {
ParamUtils.Builder params = getSpecification();
params.equals("project.customer", parent.getId());
return params;
}
@Override

View File

@@ -0,0 +1,33 @@
package com.ecep.contract.controller.customer.tasker;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.WebSocketClientTasker;
import com.ecep.contract.task.Tasker;
import com.ecep.contract.vo.CustomerVo;
import lombok.Setter;
/**
* 客户评估表更新任务
*/
public class CustomerEvaluationFormUpdateTask extends Tasker<Object> implements WebSocketClientTasker {
@Setter
private CustomerVo customer;
@Override
public String getTaskName() {
return "CustomerEvaluationFormUpdateTask";
}
@Override
public void updateProgress(long current, long total) {
super.updateProgress(current, total);
}
@Override
protected Object execute(MessageHolder holder) throws Exception {
updateTitle("客户评估表更新任务");
return callRemoteTask(holder, getLocale(), customer.getId());
}
}

View File

@@ -0,0 +1,34 @@
package com.ecep.contract.controller.customer.tasker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.WebSocketClientTasker;
import com.ecep.contract.task.Tasker;
import com.ecep.contract.vo.CustomerVo;
import lombok.Setter;
public class CustomerNextSignDateTask extends Tasker<Object> implements WebSocketClientTasker {
private static final Logger logger = LoggerFactory.getLogger(CustomerNextSignDateTask.class);
@Setter
private CustomerVo customer;
@Override
public String getTaskName() {
return "CustomerNextSignDateTask";
}
@Override
public void updateProgress(long current, long total) {
super.updateProgress(current, total);
}
@Override
protected Object execute(MessageHolder holder) throws Exception {
updateTitle("计算客户下一个评价日期");
return callRemoteTask(holder, getLocale(), customer.getId());
}
}

View File

@@ -0,0 +1,39 @@
package com.ecep.contract.controller.customer.tasker;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.WebSocketClientTasker;
import com.ecep.contract.task.Tasker;
import com.ecep.contract.vo.CustomerVo;
import lombok.Getter;
import lombok.Setter;
/**
* 客户文件重建任务类
* 用于通过WebSocket与服务器通信重建客户相关文件
*/
public class CustomerRebuildFilesTasker extends Tasker<Object> implements WebSocketClientTasker {
@Getter
@Setter
private CustomerVo companyCustomer;
@Getter
@Setter
protected boolean filesUpdated = false;
@Override
public String getTaskName() {
return "CustomerRebuildFilesTasker";
}
@Override
public void updateProgress(long current, long total) {
super.updateProgress(current, total);
}
@Override
protected Object execute(MessageHolder holder) throws Exception {
updateTitle("重建客户文件");
return callRemoteTask(holder, getLocale(), companyCustomer.getId());
}
}

View File

@@ -1,27 +1,30 @@
package com.ecep.contract.manager.ds.other.controller.department;
package com.ecep.contract.controller.department;
import java.util.List;
import org.springframework.data.domain.Pageable;
import com.ecep.contract.controller.AbstEntityManagerSkin;
import com.ecep.contract.controller.ManagerSkin;
import com.ecep.contract.controller.table.EditableEntityTableTabSkin;
import com.ecep.contract.controller.table.cell.EmployeeTableCell;
import com.ecep.contract.converter.EmployeeStringConverter;
import com.ecep.contract.model.Employee;
import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.vo.DepartmentVo;
import com.ecep.contract.vm.DepartmentViewModel;
import com.ecep.contract.manager.ds.other.EmployeeStringConverter;
import com.ecep.contract.manager.ds.other.model.Department;
import com.ecep.contract.manager.ds.other.model.Employee;
import com.ecep.contract.manager.ds.other.vo.DepartmentViewModel;
import com.ecep.contract.manager.ui.AbstEntityManagerSkin;
import com.ecep.contract.manager.ui.table.EditableEntityTableTabSkin;
import com.ecep.contract.manager.ui.ManagerSkin;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.scene.control.TableColumn;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.control.cell.ComboBoxTableCell;
import javafx.scene.control.cell.TextFieldTableCell;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import java.util.List;
public class DepartmentManagerSkin
extends AbstEntityManagerSkin<Department, DepartmentViewModel, DepartmentManagerSkin, DepartmentManagerWindowController>
implements ManagerSkin, EditableEntityTableTabSkin<Department, DepartmentViewModel> {
extends
AbstEntityManagerSkin<DepartmentVo, DepartmentViewModel, DepartmentManagerSkin, DepartmentManagerWindowController>
implements ManagerSkin, EditableEntityTableTabSkin<DepartmentVo, DepartmentViewModel> {
public DepartmentManagerSkin(DepartmentManagerWindowController controller) {
super(controller);
@@ -31,10 +34,7 @@ public class DepartmentManagerSkin
public void initializeTable() {
getTableView().setEditable(true);
Specification<Employee> spec = (root, query, cb) -> {
return cb.equal(root.get("isActive"), true);
};
List<Employee> employees = controller.getEmployeeService().findAll(spec, Pageable.ofSize(30)).getContent();
// 不再需要获取所有员工列表因为现在使用的是leaderId和leaderName
controller.idColumn.setCellValueFactory(param -> param.getValue().getId());
@@ -47,8 +47,7 @@ public class DepartmentManagerSkin
controller.codeColumn.setOnEditCommit(this::onCodeColumnEditCommit);
controller.leaderColumn.setCellValueFactory(param -> param.getValue().getLeader());
controller.leaderColumn.setCellFactory(ComboBoxTableCell.forTableColumn(getBean(EmployeeStringConverter.class), FXCollections.observableArrayList(employees)));
controller.leaderColumn.setOnEditCommit(this::onLeaderColumnEditCommit);
controller.leaderColumn.setCellFactory(param -> new EmployeeTableCell<>(controller.getEmployeeService()));
controller.activeColumn.setCellValueFactory(param -> param.getValue().getIsActive());
controller.activeColumn.setEditable(true);
@@ -56,7 +55,6 @@ public class DepartmentManagerSkin
controller.activeColumn.setOnEditCommit(this::onActiveColumnEditCommit);
}
private void onCodeColumnEditCommit(TableColumn.CellEditEvent<DepartmentViewModel, String> event) {
DepartmentViewModel row = event.getRowValue();
row.getCode().set(event.getNewValue());
@@ -69,9 +67,11 @@ public class DepartmentManagerSkin
saveRowData(row);
}
private void onLeaderColumnEditCommit(TableColumn.CellEditEvent<DepartmentViewModel, Employee> event) {
private void onLeaderColumnEditCommit(TableColumn.CellEditEvent<DepartmentViewModel, Integer> event) {
DepartmentViewModel row = event.getRowValue();
row.getLeader().set(event.getNewValue());
// 注意这里我们只设置了leaderName但没有设置leaderId
// 在实际应用中您可能需要根据leaderName查找对应的leaderId
saveRowData(row);
}
@@ -83,14 +83,14 @@ public class DepartmentManagerSkin
@Override
protected void onTableRowDoubleClickedAction(DepartmentViewModel item) {
//TODO 显示详情
// TODO 显示详情
}
@Override
protected void onTableCreateNewAction(ActionEvent event) {
Department employee = new Department();
employee = controller.getViewModelService().save(employee);
DepartmentViewModel viewModel = DepartmentViewModel.from(employee);
DepartmentVo department = new DepartmentVo();
department = controller.getViewModelService().save(department);
DepartmentViewModel viewModel = DepartmentViewModel.from(department);
dataSet.add(viewModel);
}
}

View File

@@ -1,31 +1,31 @@
package com.ecep.contract.manager.ds.other.controller.department;
package com.ecep.contract.controller.department;
import com.ecep.contract.manager.ds.other.model.Department;
import com.ecep.contract.manager.ds.other.model.Employee;
import com.ecep.contract.manager.ds.other.service.DepartmentService;
import com.ecep.contract.manager.ds.other.vo.DepartmentViewModel;
import com.ecep.contract.manager.ui.AbstManagerWindowController;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.ui.ViewModelService;
import javafx.event.ActionEvent;
import javafx.scene.control.TableColumn;
import javafx.stage.Stage;
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.DepartmentService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vm.DepartmentViewModel;
import com.ecep.contract.vo.DepartmentVo;
import javafx.event.ActionEvent;
import javafx.scene.control.TableColumn;
import javafx.stage.Stage;
@Lazy
@Scope("prototype")
@Component
@FxmlPath(value = "/ui/employee/department-manager.fxml")
public class DepartmentManagerWindowController
extends AbstManagerWindowController<Department, DepartmentViewModel, DepartmentManagerSkin> {
extends AbstManagerWindowController<DepartmentVo, DepartmentViewModel, DepartmentManagerSkin> {
public TableColumn<DepartmentViewModel, Number> idColumn;
public TableColumn<DepartmentViewModel, String> nameColumn;
public TableColumn<DepartmentViewModel, String> codeColumn;
public TableColumn<DepartmentViewModel, Employee> leaderColumn;
public TableColumn<DepartmentViewModel, Integer> leaderColumn;
public TableColumn<DepartmentViewModel, Boolean> activeColumn;
@Autowired

View File

@@ -0,0 +1,29 @@
package com.ecep.contract.controller.employee;
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.model.Employee;
import com.ecep.contract.service.EmployeeRoleService;
import com.ecep.contract.service.EmployeeService;
import com.ecep.contract.service.PermissionService;
import com.ecep.contract.vm.EmployeeViewModel;
import com.ecep.contract.vo.EmployeeVo;
import lombok.Setter;
public abstract class AbstEmployeeBasedTabSkin
extends AbstEntityBasedTabSkin<EmployeeWindowController, EmployeeVo, EmployeeViewModel>
implements TabSkin {
public AbstEmployeeBasedTabSkin(EmployeeWindowController controller) {
super(controller);
}
protected EmployeeRoleService getEmployeeRoleService() {
return getCachedBean(EmployeeRoleService.class);
}
protected PermissionService getPermissionService() {
return getCachedBean(PermissionService.class);
}
}

View File

@@ -0,0 +1,44 @@
package com.ecep.contract.controller.employee;
import java.util.Map;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.table.AbstEntityTableTabSkin;
import com.ecep.contract.model.Employee;
import com.ecep.contract.model.IdentityEntity;
import com.ecep.contract.service.EmployeeService;
import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.vm.EmployeeBasedViewModel;
import com.ecep.contract.vm.EmployeeViewModel;
import com.ecep.contract.vm.IdentityViewModel;
import com.ecep.contract.vo.EmployeeVo;
public abstract class AbstEmployeeTableTabSkin<T extends IdentityEntity, TV extends IdentityViewModel<T>>
extends AbstEntityTableTabSkin<EmployeeWindowController, EmployeeVo, EmployeeViewModel, T, TV>
implements TabSkin {
public AbstEmployeeTableTabSkin(EmployeeWindowController controller) {
super(controller);
}
public EmployeeService getEmployeeService() {
return controller.employeeService;
}
@Override
protected TV createNewViewModel() {
TV model = super.createNewViewModel();
if (model instanceof EmployeeBasedViewModel m) {
m.getEmployee().set(getEntity().getId());
}
return model;
}
@Override
public ParamUtils.Builder getSpecification(EmployeeVo parent) {
ParamUtils.Builder params = getSpecification();
params.equals("employee", parent.getId());
return params;
}
}

View File

@@ -1,20 +1,20 @@
package com.ecep.contract.manager.ds.other.controller.employee;
package com.ecep.contract.controller.employee;
import com.ecep.contract.controller.AbstEntityManagerSkin;
import com.ecep.contract.controller.ManagerSkin;
import com.ecep.contract.controller.table.cell.DepartmentTableCell;
import com.ecep.contract.model.Employee;
import com.ecep.contract.service.DepartmentService;
import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.vm.EmployeeViewModel;
import com.ecep.contract.vo.EmployeeVo;
import com.ecep.contract.manager.ds.other.EntityStringConverter;
import com.ecep.contract.manager.ds.other.model.Department;
import com.ecep.contract.manager.ds.other.model.Employee;
import com.ecep.contract.manager.ds.other.service.DepartmentService;
import com.ecep.contract.manager.ds.other.vo.EmployeeViewModel;
import com.ecep.contract.manager.ui.AbstEntityManagerSkin;
import com.ecep.contract.manager.ui.ManagerSkin;
import com.ecep.contract.manager.ui.table.cell.DepartmentTableCell;
import com.ecep.contract.manager.util.SpecificationUtils;
import javafx.event.ActionEvent;
import javafx.scene.control.cell.CheckBoxTableCell;
import org.springframework.data.jpa.domain.Specification;
public class EmployeeManagerSkin
extends AbstEntityManagerSkin<Employee, EmployeeViewModel, EmployeeManagerSkin, EmployeeManagerWindowController>
extends
AbstEntityManagerSkin<EmployeeVo, EmployeeViewModel, EmployeeManagerSkin, EmployeeManagerWindowController>
implements ManagerSkin {
public EmployeeManagerSkin(EmployeeManagerWindowController controller) {
super(controller);
@@ -30,14 +30,12 @@ public class EmployeeManagerSkin
}
@Override
public Specification<Employee> getSpecification() {
Specification<Employee> spec = super.getSpecification();
public ParamUtils.Builder getSpecification() {
ParamUtils.Builder params = super.getSpecification();
if (controller.activeCheckBox.isSelected()) {
spec = SpecificationUtils.and(spec, (root, query, builder) -> {
return builder.isTrue(root.get("isActive"));
});
params.equals("isActive", true);
}
return spec;
return params;
}
@Override
@@ -49,8 +47,7 @@ public class EmployeeManagerSkin
controller.accountColumn.setCellValueFactory(param -> param.getValue().getAccount());
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.createdColumn.setCellValueFactory(param -> param.getValue().getCreated());
@@ -68,7 +65,7 @@ public class EmployeeManagerSkin
@Override
protected void onTableCreateNewAction(ActionEvent event) {
Employee employee = new Employee();
EmployeeVo employee = new EmployeeVo();
employee = controller.getViewModelService().save(employee);
EmployeeViewModel viewModel = EmployeeViewModel.from(employee);
dataSet.add(viewModel);

View File

@@ -1,39 +1,40 @@
package com.ecep.contract.manager.ds.other.controller.employee;
package com.ecep.contract.controller.employee;
import java.time.LocalDate;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.ecep.contract.constant.CloudServiceConstant;
import com.ecep.contract.controller.AbstManagerWindowController;
import com.ecep.contract.model.Department;
import com.ecep.contract.service.EmployeeService;
import com.ecep.contract.task.EmployeesSyncTask;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.EmployeeViewModel;
import com.ecep.contract.vo.EmployeeVo;
import com.ecep.contract.manager.cloud.u8.EmployeesSyncTask;
import com.ecep.contract.manager.cloud.u8.YongYouU8Service;
import com.ecep.contract.manager.ds.other.model.Department;
import com.ecep.contract.manager.ds.other.model.Employee;
import com.ecep.contract.manager.ds.other.service.EmployeeService;
import com.ecep.contract.manager.ds.other.vo.EmployeeViewModel;
import com.ecep.contract.manager.ui.AbstManagerWindowController;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.util.UITools;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TableColumn;
import javafx.stage.Stage;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
@Lazy
@Scope("prototype")
@Component
@FxmlPath("/ui/employee/employee-manager.fxml")
public class EmployeeManagerWindowController
extends AbstManagerWindowController<Employee, EmployeeViewModel, EmployeeManagerSkin> {
extends AbstManagerWindowController<EmployeeVo, EmployeeViewModel, EmployeeManagerSkin> {
@FXML
public TableColumn<EmployeeViewModel, Number> idColumn;
@FXML
public TableColumn<EmployeeViewModel, String> accountColumn;
@FXML
public TableColumn<EmployeeViewModel, Department> departmentColumn;
public TableColumn<EmployeeViewModel, Integer> departmentColumn;
@FXML
public TableColumn<EmployeeViewModel, String> nameColumn;
@FXML
@@ -68,8 +69,8 @@ public class EmployeeManagerWindowController
* U8系统 同步员工数据
*/
public void onSyncFromU8Action(ActionEvent event) {
Task<Object> task = new EmployeesSyncTask();
UITools.showTaskDialogAndWait("" + YongYouU8Service.NAME + " 同步员工数据", task, null);
EmployeesSyncTask task = new EmployeesSyncTask();
UITools.showTaskDialogAndWait("" + CloudServiceConstant.U8_NAME + " 同步员工数据", task, null);
}

View File

@@ -1,31 +1,32 @@
package com.ecep.contract.manager.ds.other.controller.employee;
package com.ecep.contract.controller.employee;
import com.ecep.contract.manager.ds.other.model.Employee;
import com.ecep.contract.manager.ds.other.model.EmployeeAuthBind;
import com.ecep.contract.manager.ds.other.service.EmployeeAuthBindService;
import com.ecep.contract.manager.ds.other.vo.EmployeeAuthBindViewModel;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.ui.table.cell.EmployeeTableCell;
import com.ecep.contract.Desktop;
import com.ecep.contract.controller.table.cell.EmployeeTableCell;
import com.ecep.contract.model.Employee;
import com.ecep.contract.model.EmployeeAuthBind;
import com.ecep.contract.service.EmployeeAuthBindService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vm.EmployeeAuthBindViewModel;
import com.ecep.contract.vo.EmployeeAuthBindVo;
import javafx.application.Platform;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab;
import javafx.scene.control.TableColumn;
import lombok.Setter;
import org.springframework.data.domain.Sort;
import java.time.LocalDateTime;
import java.util.List;
@FxmlPath("/ui/employee/employee-auth-bind.fxml")
public class EmployeeTabSkinAuthBind
extends AbstEmployeeTableTabSkin<EmployeeAuthBind, EmployeeAuthBindViewModel> {
extends AbstEmployeeTableTabSkin<EmployeeAuthBindVo, EmployeeAuthBindViewModel> {
public TableColumn<EmployeeAuthBindViewModel, Number> idColumn;
public TableColumn<EmployeeAuthBindViewModel, String> ipColumn;
public TableColumn<EmployeeAuthBindViewModel, String> macColumn;
public TableColumn<EmployeeAuthBindViewModel, LocalDateTime> createTime;
public TableColumn<EmployeeAuthBindViewModel, LocalDateTime> updateTimeColumn;
public TableColumn<EmployeeAuthBindViewModel, Employee> updaterColumn;
public TableColumn<EmployeeAuthBindViewModel, Integer> updaterColumn;
public TableColumn<EmployeeAuthBindViewModel, String> descriptionColumn;
@Setter
@@ -79,13 +80,18 @@ public class EmployeeTabSkinAuthBind
protected void createContextMenu(ContextMenu contextMenu) {
super.createContextMenu(contextMenu);
MenuItem menuItem = new MenuItem("导入未关联");
int activeEmployeeId = Desktop.instance.getActiveEmployeeId();
if (activeEmployeeId <= 0) {
logger.warn("未登录员工{}", activeEmployeeId);
return;
}
menuItem.setOnAction(event -> {
EmployeeAuthBindService service = getEmployeeAuthBindService();
List<EmployeeAuthBind> authBinds = service.findAllByEmployee(null, Sort.unsorted());
for (EmployeeAuthBind authBind : authBinds) {
authBind.setEmployee(getEntity());
List<EmployeeAuthBindVo> authBinds = service.findAllByEmployee(null);
for (EmployeeAuthBindVo authBind : authBinds) {
authBind.setEmployeeId(getEntity().getId());
authBind.setUpdateTime(LocalDateTime.now());
authBind.setUpdater(controller.getCurrentUser());
authBind.setUpdaterId(activeEmployeeId);
service.save(authBind);
}
});

View File

@@ -1,15 +1,14 @@
package com.ecep.contract.manager.ds.other.controller.employee;
import com.ecep.contract.manager.ds.other.EntityStringConverter;
import com.ecep.contract.manager.ds.other.model.Department;
import com.ecep.contract.manager.ds.other.service.DepartmentService;
import com.ecep.contract.manager.ui.tab.TabSkin;
import com.ecep.contract.manager.util.UITools;
import javafx.scene.control.Tab;
import javafx.util.converter.LocalDateStringConverter;
package com.ecep.contract.controller.employee;
import java.time.format.DateTimeFormatter;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.service.DepartmentService;
import com.ecep.contract.util.UITools;
import javafx.scene.control.Tab;
import javafx.util.converter.LocalDateStringConverter;
public class EmployeeTabSkinBase
extends AbstEmployeeBasedTabSkin
implements TabSkin {
@@ -24,11 +23,8 @@ public class EmployeeTabSkinBase
@Override
public void initializeTab() {
EntityStringConverter<Department> departmentEntityStringConverter = new EntityStringConverter<>();
DepartmentService departmentService = getBean(DepartmentService.class);
departmentEntityStringConverter.setInitialized(department -> departmentService.findById(department.getId()));
UITools.autoCompletion(controller.departmentField, viewModel.getDepartment(),
p -> departmentService.search(p.getUserText()), departmentEntityStringConverter);
getCachedBean(DepartmentService.class));
controller.nameField.textProperty().bindBidirectional(viewModel.getName());
controller.aliasField.textProperty().bindBidirectional(viewModel.getAlias());
@@ -48,23 +44,25 @@ public class EmployeeTabSkinBase
controller.isActiveField.selectedProperty().bindBidirectional(viewModel.getIsActive());
// Callback<ListView<EmployeeRole>, ListCell<EmployeeRole>> cellFactory =
// controller.rolesField.getCellFactory();
// StringConverter<EmployeeRole> employeeRoleStringConverter = new
// EntityStringConverter<>();
// controller.rolesField.setCellFactory(param -> {
// ListCell<EmployeeRole> cell = cellFactory.call(param);
// if (cell instanceof CheckBoxListCell<EmployeeRole> list) {
// list.setConverter(employeeRoleStringConverter);
// }
// return cell;
// });
// Callback<ListView<EmployeeRole>, ListCell<EmployeeRole>> cellFactory = controller.rolesField.getCellFactory();
// StringConverter<EmployeeRole> employeeRoleStringConverter = new EntityStringConverter<>();
// controller.rolesField.setCellFactory(param -> {
// ListCell<EmployeeRole> cell = cellFactory.call(param);
// if (cell instanceof CheckBoxListCell<EmployeeRole> list) {
// list.setConverter(employeeRoleStringConverter);
// }
// return cell;
// });
// controller.rolesField.getCheckModel().getCheckedItems().setAll();
// Property<IndexedCheckModel<EmployeeRole>> selectedRoles = new SimpleObjectProperty<>();
// controller.rolesField.getCheckModel().getCheckedItems().addListener((ListChangeListener<? super EmployeeRole>) changed -> {
// System.out.println("newValue = " + changed);
// });
// controller.rolesField.getCheckModel().getCheckedItems().setAll();
// Property<IndexedCheckModel<EmployeeRole>> selectedRoles = new
// SimpleObjectProperty<>();
// controller.rolesField.getCheckModel().getCheckedItems().addListener((ListChangeListener<?
// super EmployeeRole>) changed -> {
// System.out.println("newValue = " + changed);
// });
}
}

View File

@@ -1,20 +1,21 @@
package com.ecep.contract.manager.ds.other.controller.employee;
package com.ecep.contract.controller.employee;
import java.time.LocalDateTime;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
import com.ecep.contract.service.EmployeeLoginHistoryService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vo.EmployeeLoginHistoryVo;
import com.ecep.contract.vm.EmployeeLoginHistoryViewModel;
import com.ecep.contract.manager.ds.other.model.EmployeeLoginHistory;
import com.ecep.contract.manager.ds.other.service.EmployeeLoginHistoryService;
import com.ecep.contract.manager.ds.other.vo.EmployeeLoginHistoryViewModel;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.ui.tab.TabSkin;
import com.ecep.contract.manager.ui.table.cell.LocalDateTimeTableCell;
import javafx.application.Platform;
import javafx.scene.control.Tab;
import javafx.scene.control.TableColumn;
import java.time.LocalDateTime;
@FxmlPath("/ui/employee/employee-login-history.fxml")
public class EmployeeTabSkinLoginHistory
extends AbstEmployeeTableTabSkin<EmployeeLoginHistory, EmployeeLoginHistoryViewModel>
extends AbstEmployeeTableTabSkin<EmployeeLoginHistoryVo, EmployeeLoginHistoryViewModel>
implements TabSkin {
public TableColumn<EmployeeLoginHistoryViewModel, Number> idColumn;
public TableColumn<EmployeeLoginHistoryViewModel, String> ipColumn;

View File

@@ -1,18 +1,20 @@
package com.ecep.contract.manager.ds.other.controller.employee;
package com.ecep.contract.controller.employee;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.ecep.contract.util.ParamUtils;
import org.springframework.data.domain.Pageable;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.vo.EmployeeRoleVo;
import com.ecep.contract.manager.Desktop;
import com.ecep.contract.manager.ds.other.model.Employee;
import com.ecep.contract.manager.ds.other.model.EmployeeRole;
import com.ecep.contract.manager.ui.tab.TabSkin;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.collections.ListChangeListener;
import javafx.scene.control.ListCell;
import javafx.scene.control.Tab;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import java.util.List;
public class EmployeeTabSkinRole
extends AbstEmployeeBasedTabSkin
@@ -41,24 +43,21 @@ public class EmployeeTabSkinRole
}
private void loadSelectedRoles() {
List<EmployeeRole> selectedRoles = getEmployeeService().getRolesByEmployeeId(viewModel.getId().get());
List<EmployeeRoleVo> selectedRoles = getEmployeeService().getRolesByEmployeeId(viewModel.getId().get());
controller.rolesField.getTargetItems().setAll(selectedRoles);
changed.set(false);
}
private void initializeListView() {
// 非系统内置账户
Specification<EmployeeRole> spec = null;
if (!Desktop.instance.getActiveEmployee().isSystemAdministrator()) {
spec = (root, query, cb) -> cb.equal(root.get("systemAdministrator"), false);
}
List<EmployeeRole> roles = getEmployeeRoleService().findAll(spec, Pageable.ofSize(500)).getContent();
Map<String, Object> params = ParamUtils.builder().build();
List<EmployeeRoleVo> roles = getEmployeeRoleService().findAll(params, Pageable.ofSize(500)).getContent();
controller.rolesField.getSourceItems().setAll(roles);
controller.rolesField.setCellFactory(param -> {
return new ListCell<>() {
return new ListCell<EmployeeRoleVo>() {
@Override
protected void updateItem(EmployeeRole item, boolean empty) {
protected void updateItem(EmployeeRoleVo item, boolean empty) {
super.updateItem(item, empty);
if (item == null || empty) {
setText(null);
@@ -69,10 +68,10 @@ public class EmployeeTabSkinRole
};
});
controller.rolesField.getTargetItems().addListener((ListChangeListener<EmployeeRole>) change -> {
controller.rolesField.getTargetItems().addListener((ListChangeListener<EmployeeRoleVo>) change -> {
while (change.next()) {
List<? extends EmployeeRole> added = change.getAddedSubList();
List<? extends EmployeeRole> removed = change.getRemoved();
List<? extends EmployeeRoleVo> added = change.getAddedSubList();
List<? extends EmployeeRoleVo> removed = change.getRemoved();
if (!added.isEmpty() || !removed.isEmpty()) {
changed.set(true);
}
@@ -82,9 +81,7 @@ public class EmployeeTabSkinRole
@Override
public void save() {
Employee entity = getEntity();
entity.setRoles(controller.rolesField.getTargetItems());
save(entity);
getEmployeeService().getUpdateEmployeeRoles(viewModel.getId().get(), controller.rolesField.getTargetItems());
loadSelectedRoles();
}
}

View File

@@ -1,18 +1,5 @@
package com.ecep.contract.manager.ds.other.controller.employee;
package com.ecep.contract.controller.employee;
import com.ecep.contract.manager.ds.other.model.Employee;
import com.ecep.contract.manager.ds.other.model.EmployeeRole;
import com.ecep.contract.manager.ds.other.service.EmployeeService;
import com.ecep.contract.manager.ds.other.vo.EmployeeViewModel;
import com.ecep.contract.manager.ui.AbstEntityController;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.ui.ViewModelService;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
import lombok.Getter;
import org.controlsfx.control.ListSelectionView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -21,11 +8,29 @@ 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.EmployeeService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vm.EmployeeViewModel;
import com.ecep.contract.vo.EmployeeRoleVo;
import com.ecep.contract.vo.EmployeeVo;
import javafx.scene.control.CheckBox;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.stage.Window;
import javafx.stage.WindowEvent;
import lombok.Getter;
@Lazy
@Scope("prototype")
@Component
@FxmlPath("/ui/employee/employee.fxml")
public class EmployeeWindowController extends AbstEntityController<Employee, EmployeeViewModel> {
public class EmployeeWindowController extends AbstEntityController<EmployeeVo, EmployeeViewModel> {
private static final Logger logger = LoggerFactory.getLogger(EmployeeWindowController.class);
/**
@@ -55,7 +60,7 @@ public class EmployeeWindowController extends AbstEntityController<Employee, Emp
*/
public Tab rolesTab;
public ListSelectionView<EmployeeRole> rolesField;
public ListSelectionView<EmployeeRoleVo> rolesField;
public Tab loginHistoryTab;
public Tab authBindTab;
@@ -66,7 +71,7 @@ public class EmployeeWindowController extends AbstEntityController<Employee, Emp
public TableView<Tab> permissionsTable;
public static void show(Employee employee, Window owner) {
public static void show(EmployeeVo employee, Window owner) {
EmployeeViewModel model = EmployeeViewModel.from(employee);
show(model, owner);
}

View File

@@ -1,16 +1,18 @@
package com.ecep.contract.manager.ds.other.controller.inventory;
package com.ecep.contract.controller.inventory;
import java.util.function.Function;
import com.ecep.contract.controller.AbstEntityManagerSkin;
import com.ecep.contract.controller.table.cell.InventoryCatalogTableCell;
import com.ecep.contract.controller.table.cell.LocalDateFieldTableCell;
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
import com.ecep.contract.converter.EntityStringConverter;
import com.ecep.contract.service.InventoryCatalogService;
import com.ecep.contract.service.InventoryService;
import com.ecep.contract.vm.InventoryViewModel;
import com.ecep.contract.vo.InventoryCatalogVo;
import com.ecep.contract.vo.InventoryVo;
import com.ecep.contract.manager.ds.other.EntityStringConverter;
import com.ecep.contract.manager.ds.other.model.Inventory;
import com.ecep.contract.manager.ds.other.model.InventoryCatalog;
import com.ecep.contract.manager.ds.other.service.InventoryCatalogService;
import com.ecep.contract.manager.ds.other.service.InventoryService;
import com.ecep.contract.manager.ds.other.vo.InventoryViewModel;
import com.ecep.contract.manager.ui.AbstEntityManagerSkin;
import com.ecep.contract.manager.ui.table.EditableEntityTableTabSkin;
import com.ecep.contract.manager.ui.ManagerSkin;
import com.ecep.contract.manager.ui.util.LocalDateFieldTableCell;
import com.ecep.contract.manager.ui.table.cell.LocalDateTimeTableCell;
import javafx.beans.property.Property;
import javafx.event.ActionEvent;
import javafx.scene.control.TableColumn;
@@ -19,11 +21,8 @@ import javafx.util.converter.CurrencyStringConverter;
import javafx.util.converter.NumberStringConverter;
import lombok.Setter;
import java.util.function.Function;
public class InventoryManagerSkin
extends AbstEntityManagerSkin<Inventory, InventoryViewModel, InventoryManagerSkin, InventoryManagerWindowController>
implements ManagerSkin, EditableEntityTableTabSkin<Inventory, InventoryViewModel> {
public class InventoryManagerSkin extends
AbstEntityManagerSkin<InventoryVo, InventoryViewModel, InventoryManagerSkin, InventoryManagerWindowController> {
@Setter
private InventoryCatalogService catalogService;
@@ -58,18 +57,13 @@ public class InventoryManagerSkin
controller.codeColumn.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getCode));
controller.catalogColumn.setCellValueFactory(param -> param.getValue().getCatalog());
EntityStringConverter<InventoryCatalog> catalogStringConverter = new EntityStringConverter<>();
catalogStringConverter.setInitialized(v -> getInventoryCatalogService().findById(v.getId()));
catalogStringConverter.setFormater(InventoryCatalog::getName);
catalogStringConverter.setFromString(v -> getInventoryCatalogService().findByName(v));
catalogStringConverter.setSuggestion(getInventoryCatalogService()::search);
controller.catalogColumn.setCellFactory(TextFieldTableCell.forTableColumn(catalogStringConverter));
controller.catalogColumn.setCellFactory(param-> new InventoryCatalogTableCell<>(getInventoryCatalogService()));
controller.catalogColumn.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getCatalog));
controller.specificationColumn.setCellValueFactory(param -> param.getValue().getSpecification());
controller.specificationColumn.setCellFactory(TextFieldTableCell.forTableColumn());
controller.specificationColumn.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getSpecification));
controller.specificationColumn
.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getSpecification));
controller.unitColumn.setCellValueFactory(param -> param.getValue().getUnit());
controller.unitColumn.setCellFactory(TextFieldTableCell.forTableColumn());
@@ -77,44 +71,48 @@ public class InventoryManagerSkin
controller.purchaseTaxRateColumn.setCellValueFactory(param -> param.getValue().getPurchaseTaxRate());
controller.purchaseTaxRateColumn.setCellFactory(TextFieldTableCell.forTableColumn(new NumberStringConverter()));
controller.purchaseTaxRateColumn.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getPurchaseTaxRate));
controller.purchaseTaxRateColumn
.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getPurchaseTaxRate));
controller.purchasePriceColumn.setCellValueFactory(param -> param.getValue().getPurchasePrice());
controller.purchasePriceColumn.setCellFactory(TextFieldTableCell.forTableColumn(new CurrencyStringConverter()));
// controller.purchasePriceColumn.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getPurchasePrice));
// controller.purchasePriceColumn.setOnEditCommit(event ->
// onColumnEditCommit(event, InventoryViewModel::getPurchasePrice));
controller.saleTaxRateColumn.setCellValueFactory(param -> param.getValue().getSaleTaxRate());
controller.saleTaxRateColumn.setCellFactory(TextFieldTableCell.forTableColumn(new NumberStringConverter()));
controller.saleTaxRateColumn.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getSaleTaxRate));
controller.saleTaxRateColumn
.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getSaleTaxRate));
controller.salePriceColumn.setCellValueFactory(param -> param.getValue().getSalePrice());
controller.salePriceColumn.setCellFactory(TextFieldTableCell.forTableColumn(new CurrencyStringConverter()));
// controller.salePriceColumn.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getSalePrice));
// controller.salePriceColumn.setOnEditCommit(event -> onColumnEditCommit(event,
// InventoryViewModel::getSalePrice));
controller.createTimeColumn.setCellValueFactory(param -> param.getValue().getCreateTime());
controller.createTimeColumn.setCellFactory(LocalDateFieldTableCell.forTableColumn());
controller.createTimeColumn.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getCreateTime));
controller.createTimeColumn
.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getCreateTime));
controller.updateDateColumn.setCellValueFactory(param -> param.getValue().getUpdateDate());
controller.updateDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
controller.updateDateColumn.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getUpdateDate));
controller.updateDateColumn
.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getUpdateDate));
controller.descriptionColumn.setCellValueFactory(param -> param.getValue().getDescription());
controller.descriptionColumn.setCellFactory(TextFieldTableCell.forTableColumn());
controller.descriptionColumn.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getDescription));
controller.descriptionColumn
.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getDescription));
}
private <T> void onColumnEditCommit(
TableColumn.CellEditEvent<InventoryViewModel, T> event,
Function<InventoryViewModel, Property<T>> supplier
) {
Function<InventoryViewModel, Property<T>> supplier) {
InventoryViewModel row = event.getRowValue();
supplier.apply(row).setValue(event.getNewValue());
saveRowData(row);
}
@Override
protected void onTableRowDoubleClickedAction(InventoryViewModel item) {
showInOwner(InventoryWindowController.class, item);
@@ -122,7 +120,7 @@ public class InventoryManagerSkin
@Override
protected void onTableCreateNewAction(ActionEvent event) {
Inventory inventory = getService().save(getService().createNewInstance());
InventoryVo inventory = getService().save(getService().createNewEntity());
InventoryViewModel viewModel = InventoryViewModel.from(inventory);
dataSet.add(viewModel);
}

View File

@@ -1,4 +1,4 @@
package com.ecep.contract.manager.ds.other.controller.inventory;
package com.ecep.contract.controller.inventory;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -8,13 +8,15 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import com.ecep.contract.manager.ds.other.model.Inventory;
import com.ecep.contract.manager.ds.other.model.InventoryCatalog;
import com.ecep.contract.manager.ds.other.service.InventoryService;
import com.ecep.contract.manager.ds.other.vo.InventoryViewModel;
import com.ecep.contract.manager.ui.AbstManagerWindowController;
import com.ecep.contract.manager.ui.FxmlPath;
import com.ecep.contract.manager.util.UITools;
import com.ecep.contract.controller.AbstManagerWindowController;
import com.ecep.contract.service.InventoryService;
import com.ecep.contract.task.InventoryAllSyncTask;
import com.ecep.contract.task.InventorySyncTask;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.InventoryViewModel;
import com.ecep.contract.vo.InventoryCatalogVo;
import com.ecep.contract.vo.InventoryVo;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
@@ -26,7 +28,7 @@ import javafx.stage.Stage;
@Component
@FxmlPath(value = "/ui/inventory/inventory-manager.fxml")
public class InventoryManagerWindowController
extends AbstManagerWindowController<Inventory, InventoryViewModel, InventoryManagerSkin> {
extends AbstManagerWindowController<InventoryVo, InventoryViewModel, InventoryManagerSkin> {
@FXML
public TableColumn<InventoryViewModel, Number> idColumn;
@@ -34,8 +36,11 @@ public class InventoryManagerWindowController
public TableColumn<InventoryViewModel, String> nameColumn;
@FXML
public TableColumn<InventoryViewModel, String> codeColumn;
/**
* InventoryCatalogVo
*/
@FXML
public TableColumn<InventoryViewModel, InventoryCatalog> catalogColumn;
public TableColumn<InventoryViewModel, Integer> catalogColumn;
@FXML
public TableColumn<InventoryViewModel, String> specificationColumn;
@FXML
@@ -75,7 +80,7 @@ public class InventoryManagerWindowController
}
public void onSyncAction(ActionEvent event) {
InventorySyncTask task = new InventorySyncTask();
InventoryAllSyncTask task = new InventoryAllSyncTask();
UITools.showTaskDialogAndWait("同步数据", task, null);
}

View File

@@ -0,0 +1,180 @@
package com.ecep.contract.controller.inventory;
import java.text.NumberFormat;
import java.time.format.DateTimeFormatter;
import java.util.function.Consumer;
import com.ecep.contract.task.InventorySyncTask;
import org.springframework.util.StringUtils;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.MyDateTimeUtils;
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.table.EditableEntityTableTabSkin;
import com.ecep.contract.converter.EmployeeStringConverter;
import com.ecep.contract.converter.EntityStringConverter;
import com.ecep.contract.service.InventoryCatalogService;
import com.ecep.contract.service.InventoryService;
import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.InventoryViewModel;
import com.ecep.contract.vo.InventoryCatalogVo;
import com.ecep.contract.vo.InventoryVo;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.event.ActionEvent;
import javafx.scene.control.Tab;
import javafx.scene.control.TextField;
import javafx.util.converter.CurrencyStringConverter;
import javafx.util.converter.LocalDateStringConverter;
import javafx.util.converter.LocalDateTimeStringConverter;
import javafx.util.converter.NumberStringConverter;
public class InventoryTabSkinBase
extends AbstEntityBasedTabSkin<InventoryWindowController, InventoryVo, InventoryViewModel>
implements TabSkin, EditableEntityTableTabSkin<InventoryVo, InventoryViewModel> {
public InventoryTabSkinBase(InventoryWindowController controller) {
super(controller);
}
InventoryService getService() {
return getCachedBean(InventoryService.class);
}
InventoryCatalogService getCatalogService() {
return getCachedBean(InventoryCatalogService.class);
}
@Override
public Tab getTab() {
return controller.baseInfoTab;
}
@Override
public void initializeTab() {
controller.syncBtn.setOnAction(this::onSyncAction);
NumberFormat numberInstance = NumberFormat.getNumberInstance(getLocale());
numberInstance.setMaximumFractionDigits(2);
numberInstance.setMinimumFractionDigits(2);
CurrencyStringConverter currencyStringConverter = new CurrencyStringConverter(numberInstance);
controller.nameField.textProperty().bindBidirectional(viewModel.getName());
controller.nameLockField.selectedProperty().bindBidirectional(viewModel.getNameLock());
controller.codeField.textProperty().bind(viewModel.getCode());
controller.unitField.textProperty().bindBidirectional(viewModel.getUnit());
controller.specificationField.textProperty().bindBidirectional(viewModel.getSpecification());
controller.specificationLockField.selectedProperty().bindBidirectional(viewModel.getSpecificationLock());
UITools.autoCompletion(controller.catalogField, viewModel.getCatalog(), getCatalogService());
controller.purchaseTaxRateField.textProperty().bindBidirectional(viewModel.getPurchaseTaxRate(),
new NumberStringConverter());
// 采购价
bindPriceField(controller.purchasePriceField, viewModel.getPurchasePrice(), currencyStringConverter,
viewModel::updatePurchasePrice);
bindPriceField(controller.purchaseTaxPriceField, viewModel.getPurchaseTaxPrice(), currencyStringConverter,
viewModel::updatePurchaseTaxPrice);
controller.saleTaxRateField.textProperty().bindBidirectional(viewModel.getSaleTaxRate(),
new NumberStringConverter());
// 销售价
bindPriceField(controller.salePriceField, viewModel.getSalePrice(), currencyStringConverter,
viewModel::updateSalePrice);
bindPriceField(controller.saleTaxPriceField, viewModel.getSaleTaxPrice(), currencyStringConverter,
viewModel::updateSaleTaxPrice);
// 采购价不能大于销售价
Bindings.greaterThan(viewModel.getPurchasePrice(), viewModel.getSalePrice())
.addListener((observable, oldValue, newValue) -> {
if (newValue) {
controller.purchasePriceField.getStyleClass().add("error");
controller.salePriceField.getStyleClass().add("error");
} else {
controller.purchasePriceField.getStyleClass().remove("error");
controller.salePriceField.getStyleClass().remove("error");
}
});
Bindings.greaterThan(viewModel.getPurchaseTaxPrice(), viewModel.getSaleTaxPrice())
.addListener((observable, oldValue, newValue) -> {
if (newValue) {
controller.purchaseTaxPriceField.getStyleClass().add("error");
controller.saleTaxPriceField.getStyleClass().add("error");
} else {
controller.purchaseTaxPriceField.getStyleClass().remove("error");
controller.saleTaxPriceField.getStyleClass().remove("error");
}
});
UITools.autoCompletion(controller.creatorField, viewModel.getCreator(), controller.getEmployeeService());
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(MyDateTimeUtils.DEFAULT_DATE_FORMAT_PATTERN);
controller.createTimeField.textProperty().bindBidirectional(viewModel.getCreateTime(),
new LocalDateStringConverter(dateFormatter, null));
UITools.autoCompletion(controller.updaterField, viewModel.getUpdater(), controller.getEmployeeService());
DateTimeFormatter dateTimeFormatter = DateTimeFormatter
.ofPattern(MyDateTimeUtils.DEFAULT_DATETIME_FORMAT_PATTERN);
controller.updateDateField.textProperty().bindBidirectional(viewModel.getUpdateDate(),
new LocalDateTimeStringConverter(dateTimeFormatter, null));
controller.weightUnitField.textProperty().bindBidirectional(viewModel.getWeightUnit());
controller.sizeUnitField.textProperty().bindBidirectional(viewModel.getSizeUnit());
controller.volumeUnitField.textProperty().bindBidirectional(viewModel.getVolumeUnit());
controller.weightField.textProperty().bindBidirectional(viewModel.getWeight(), new NumberStringConverter());
controller.packagedWeightField.textProperty().bindBidirectional(viewModel.getPackagedWeight(),
new NumberStringConverter());
controller.sizeLengthField.textProperty().bindBidirectional(viewModel.getSizeLength(),
new NumberStringConverter());
controller.sizeWidthField.textProperty().bindBidirectional(viewModel.getSizeWidth(),
new NumberStringConverter());
controller.sizeHeightField.textProperty().bindBidirectional(viewModel.getSizeHeight(),
new NumberStringConverter());
controller.volumeField.textProperty().bindBidirectional(viewModel.getVolume(), new NumberStringConverter());
controller.packagedSizeLengthField.textProperty().bindBidirectional(viewModel.getPackagedSizeLength(),
new NumberStringConverter());
controller.packagedSizeWidthField.textProperty().bindBidirectional(viewModel.getPackagedSizeWidth(),
new NumberStringConverter());
controller.packagedSizeHeightField.textProperty().bindBidirectional(viewModel.getPackagedSizeHeight(),
new NumberStringConverter());
controller.packagedVolumeField.textProperty().bindBidirectional(viewModel.getPackagedVolume(),
new NumberStringConverter());
controller.descriptionField.textProperty().bindBidirectional(viewModel.getDescription());
}
private void bindPriceField(TextField textField, SimpleDoubleProperty property,
CurrencyStringConverter stringConverter, Consumer<Double> updater) {
textField.setText(stringConverter.toString(property.get()));
property.addListener((observable, oldValue, newValue) -> {
textField.setText(stringConverter.toString(newValue));
});
textField.setOnKeyTyped(event -> {
Number number = stringConverter.fromString(textField.getText());
updater.accept(number.doubleValue());
});
}
private void onSyncAction(ActionEvent event) {
InventoryVo inventory = getEntity();
InventorySyncTask task = new InventorySyncTask();
task.setInventory(inventory);
UITools.showTaskDialogAndWait("同步数据", task, null);
}
@Override
public void deleteRowData(InventoryVo entity) {
getService().delete(entity);
}
@Override
public InventoryVo loadRowData(InventoryViewModel row) {
return getService().findById(row.getId().get());
}
@Override
public InventoryVo saveRowData(InventoryVo entity) {
return getService().save(entity);
}
}

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