Compare commits

..

72 Commits

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

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

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

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

1
.env Normal file
View File

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

2
.gitignore vendored
View File

@@ -36,3 +36,5 @@ build/
.vscode/ .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,23 +1,284 @@
# 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 模块通信
### 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: ignore:
- .idea - .idea
- target - 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方法实体类可能包含更多业务相关方法

203
README.md
View File

@@ -1,13 +1,198 @@
# 项目介绍 # Contract-Manager 项目报告
分模块: ## 项目概述
- common: Contract-Manager是一个企业级合同管理系统提供完整的合同生命周期管理包括合同的创建、审批、执行、归档等功能同时集成了供应商管理、客户管理、项目管理等相关业务模块。
公共模块,包含实体类、公共的类、枚举、异常、工具类等
- server:
服务端模块包含服务端的代码依赖common模块
- client:
客户端模块包含客户端的代码依赖common模块
UI 的fxml文件在 /client/src/main/resources/ui/
## 项目架构
### 模块划分
项目采用模块化设计,分为三个主要模块:
- **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. 扩展更多云端服务集成

View File

@@ -6,12 +6,12 @@
<parent> <parent>
<groupId>com.ecep.contract</groupId> <groupId>com.ecep.contract</groupId>
<artifactId>Contract-Manager</artifactId> <artifactId>Contract-Manager</artifactId>
<version>0.0.84-SNAPSHOT</version> <version>0.0.135-SNAPSHOT</version>
</parent> </parent>
<groupId>com.ecep.contract</groupId> <groupId>com.ecep.contract</groupId>
<artifactId>client</artifactId> <artifactId>client</artifactId>
<version>0.0.84-SNAPSHOT</version> <version>0.0.135-SNAPSHOT</version>
<properties> <properties>
<maven.compiler.source>${java.version}</maven.compiler.source> <maven.compiler.source>${java.version}</maven.compiler.source>
@@ -22,7 +22,7 @@
<dependency> <dependency>
<groupId>com.ecep.contract</groupId> <groupId>com.ecep.contract</groupId>
<artifactId>common</artifactId> <artifactId>common</artifactId>
<version>0.0.84-SNAPSHOT</version> <version>0.0.135-SNAPSHOT</version>
<exclusions> <exclusions>
<exclusion> <exclusion>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@@ -120,6 +120,23 @@
</execution> </execution>
</executions> </executions>
</plugin> </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> </plugins>
</build> </build>

View File

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

View File

@@ -203,7 +203,9 @@ public class Desktop extends Application {
controller.setHttpClient(this.httpClient); controller.setHttpClient(this.httpClient);
controller.setHolder(holder); controller.setHolder(holder);
controller.setPrimaryStage(primaryStage); controller.setPrimaryStage(primaryStage);
controller.setProperties(properties); MyProperties myProperties = new MyProperties();
myProperties.loadFromProperties(properties);
controller.setProperties(myProperties);
while (true) { while (true) {
try { try {
controller.tryLogin().get(); controller.tryLogin().get();

View File

@@ -1,22 +1,192 @@
package com.ecep.contract; package com.ecep.contract;
import lombok.Getter; import java.io.File;
import lombok.Setter; import java.io.FileInputStream;
import org.springframework.boot.context.properties.ConfigurationProperties; 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.stereotype.Component;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.io.File; import lombok.Getter;
import java.nio.file.Path; import lombok.Setter;
import java.nio.file.Paths;
/**
* 应用程序配置类,用于管理系统配置信息
*/
@Component @Component
@ConfigurationProperties(prefix = "my") public class MyProperties implements InitializingBean {
public class MyProperties { private static final Logger logger = LoggerFactory.getLogger(MyProperties.class);
private static final String FILE_NAME = "config.properties";
@Getter @Getter
@Setter @Setter
private String downloadsPath; 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");
}
}
/** /**
* 尝试返回当前用户的下载文件夹 * 尝试返回当前用户的下载文件夹
@@ -24,7 +194,13 @@ public class MyProperties {
public File getDownloadDirectory() { public File getDownloadDirectory() {
String downloadsPath = getDownloadsPath(); String downloadsPath = getDownloadsPath();
if (StringUtils.hasText(downloadsPath)) { if (StringUtils.hasText(downloadsPath)) {
return new File(downloadsPath); // 确保目录存在
File dir = new File(downloadsPath);
if (!dir.exists()) {
boolean created = dir.mkdirs();
logger.debug("创建下载目录: {}, 结果: {}", downloadsPath, created);
}
return dir;
} }
// 没有配置下载目录时,尝试使用默认设置 // 没有配置下载目录时,尝试使用默认设置
@@ -32,4 +208,29 @@ public class MyProperties {
Path path = Paths.get(home, "Downloads"); Path path = Paths.get(home, "Downloads");
return path.toFile(); 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

@@ -98,6 +98,17 @@ public class SpringApp {
context = application.run(); context = application.run();
logger.debug("SpringApp.launch application.run()."); logger.debug("SpringApp.launch application.run().");
Duration between = Duration.between(startup.getBufferedTimeline().getStartTime(), Instant.now()); 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); holder.info("应用程序环境加载完成... " + between);
}); });
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {

View File

@@ -53,7 +53,6 @@ public class WebSocketClientService {
@Getter @Getter
@Setter @Setter
private long readTimeout = 30000; private long readTimeout = 30000;
private String webSocketUrl = "ws://localhost:8080/ws";
private boolean isActive = false; // 标记连接是否活跃 private boolean isActive = false; // 标记连接是否活跃
private ScheduledFuture<?> heartbeatTask; // 心跳任务 private ScheduledFuture<?> heartbeatTask; // 心跳任务
private ScheduledFuture<?> reconnectFuture; // 修改类型为CompletableFuture<Void> private ScheduledFuture<?> reconnectFuture; // 修改类型为CompletableFuture<Void>
@@ -118,10 +117,25 @@ public class WebSocketClientService {
String errorMsg = node.get(WebSocketConstant.MESSAGE_FIELD_NAME).asText(); String errorMsg = node.get(WebSocketConstant.MESSAGE_FIELD_NAME).asText();
logger.error("收到错误消息: 错误码={}, 错误信息={}", errorCode, errorMsg); logger.error("收到错误消息: 错误码={}, 错误信息={}", errorCode, errorMsg);
if (errorCode == WebSocketConstant.ERROR_CODE_UNAUTHORIZED) { 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(); OkHttpLoginController controller = new OkHttpLoginController();
controller.tryLogin(); controller.setHttpClient(Desktop.instance.getHttpClient());
controller.setProperties(SpringApp.getBean(MyProperties.class));
controller.tryLogin().get();
// 需要把窗口顶置 // 需要把窗口顶置
initWebSocket();
} }
return; return;
} }
@@ -164,8 +178,8 @@ public class WebSocketClientService {
}; };
private void onCallbackMessage(CompletableFuture<JsonNode> future, JsonNode node) { private void onCallbackMessage(CompletableFuture<JsonNode> future, JsonNode node) {
if (node.has(WebSocketConstant.SUCCESS_FIELD_VALUE)) { if (node.has(WebSocketConstant.SUCCESS_FIELD_NAME)) {
if (!node.get(WebSocketConstant.SUCCESS_FIELD_VALUE).asBoolean()) { if (!node.get(WebSocketConstant.SUCCESS_FIELD_NAME).asBoolean()) {
future.completeExceptionally( future.completeExceptionally(
new RuntimeException( new RuntimeException(
"请求失败:来自服务器的消息=" + node.get(WebSocketConstant.MESSAGE_FIELD_NAME).asText())); "请求失败:来自服务器的消息=" + node.get(WebSocketConstant.MESSAGE_FIELD_NAME).asText()));
@@ -192,6 +206,12 @@ public class WebSocketClientService {
send(objectMapper.writeValueAsString(message)); send(objectMapper.writeValueAsString(message));
} }
/**
* WebSocketServerCallbackManage#onMessage 负责接收处理
*
* @param msg
* @return
*/
public CompletableFuture<JsonNode> send(SimpleMessage msg) { public CompletableFuture<JsonNode> send(SimpleMessage msg) {
CompletableFuture<JsonNode> future = new CompletableFuture<>(); CompletableFuture<JsonNode> future = new CompletableFuture<>();
try { try {
@@ -204,7 +224,7 @@ public class WebSocketClientService {
String json = objectMapper.writeValueAsString(msg); String json = objectMapper.writeValueAsString(msg);
callbacks.put(msg.getMessageId(), future); callbacks.put(msg.getMessageId(), future);
if (webSocket.send(json)) { if (webSocket.send(json)) {
logger.debug("send message success:{}", json); logger.debug("send json success:{}", json);
} else { } else {
if (isActive) { if (isActive) {
future.completeExceptionally(new RuntimeException("Failed to send WebSocket message")); future.completeExceptionally(new RuntimeException("Failed to send WebSocket message"));
@@ -233,6 +253,8 @@ public class WebSocketClientService {
try { try {
// 构建WebSocket请求包含认证信息 // 构建WebSocket请求包含认证信息
var myProperties = SpringApp.getBean(MyProperties.class);
String webSocketUrl = "ws://" + myProperties.getServerHost() + ":" + myProperties.getServerPort() + "/ws";
Request request = new Request.Builder() Request request = new Request.Builder()
.url(webSocketUrl) .url(webSocketUrl)
.build(); .build();
@@ -337,14 +359,6 @@ public class WebSocketClientService {
return online; return online;
} }
public void withSession(Consumer<WebSocketClientSession> sessionConsumer) {
WebSocketClientSession session = createSession();
try {
sessionConsumer.accept(session);
} finally {
// closeSession(session);
}
}
public void closeSession(WebSocketClientSession session) { public void closeSession(WebSocketClientSession session) {
if (session != null) { if (session != null) {

View File

@@ -4,6 +4,8 @@ import com.ecep.contract.constant.WebSocketConstant;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter; import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import java.beans.PropertyDescriptor; import java.beans.PropertyDescriptor;
@@ -12,9 +14,18 @@ import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.logging.Level; import java.util.logging.Level;
/**
*
*/
public class WebSocketClientSession { public class WebSocketClientSession {
private static final Logger logger = LoggerFactory.getLogger(WebSocketClientSession.class);
/**
* 会话ID由客户端创建服务器不保存会话只回传会话ID
*/
@Getter @Getter
private String sessionId = UUID.randomUUID().toString(); private final String sessionId = UUID.randomUUID().toString();
@Getter
private boolean done = false;
private WebSocketClientTasker tasker; private WebSocketClientTasker tasker;
@@ -41,7 +52,6 @@ public class WebSocketClientSession {
webSocketService.send(arguments); webSocketService.send(arguments);
} }
public void onMessage(JsonNode node) { public void onMessage(JsonNode node) {
if (node.has("type")) { if (node.has("type")) {
String type = node.get("type").asText(); String type = node.get("type").asText();
@@ -58,6 +68,8 @@ public class WebSocketClientSession {
handleAsStart(args); handleAsStart(args);
} else if (type.equals("done")) { } else if (type.equals("done")) {
handleAsDone(args); handleAsDone(args);
done = true;
close();
} else { } else {
tasker.updateMessage(java.util.logging.Level.INFO, "未知的消息类型: " + node.toString()); tasker.updateMessage(java.util.logging.Level.INFO, "未知的消息类型: " + node.toString());
} }
@@ -66,15 +78,14 @@ public class WebSocketClientSession {
} }
} }
private void handleAsDone(JsonNode args) {
tasker.updateMessage(java.util.logging.Level.INFO, "任务完成");
close();
}
private void handleAsStart(JsonNode args) { private void handleAsStart(JsonNode args) {
tasker.updateMessage(java.util.logging.Level.INFO, "任务开始"); tasker.updateMessage(java.util.logging.Level.INFO, "任务开始");
} }
private void handleAsDone(JsonNode args) {
tasker.updateMessage(java.util.logging.Level.INFO, "任务完成");
}
private void handleAsProgress(JsonNode args) { private void handleAsProgress(JsonNode args) {
long current = args.get(0).asLong(); long current = args.get(0).asLong();
long total = args.get(1).asLong(); long total = args.get(1).asLong();
@@ -86,15 +97,19 @@ public class WebSocketClientSession {
Object value = args.get(1); Object value = args.get(1);
try { try {
PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(tasker.getClass(), name); PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(tasker.getClass(), name);
System.out.println("descriptor = " + descriptor); if (descriptor == null) {
System.out.println("descriptor.getPropertyType() = " + descriptor.getPropertyType()); tasker.updateMessage(java.util.logging.Level.SEVERE, "属性 " + name + " 不存在");
return;
}
Object object = webSocketService.getObjectMapper().convertValue(value, descriptor.getPropertyType()); Object object = webSocketService.getObjectMapper().convertValue(value, descriptor.getPropertyType());
System.out.println("object = " + object); if (descriptor.getWriteMethod() == null) {
System.out.println("descriptor.getWriteMethod() = " + descriptor.getWriteMethod()); tasker.updateMessage(java.util.logging.Level.SEVERE, "属性 " + name + " 不可写");
System.out.println("descriptor.getWriteMethod().getParameterTypes() = " + descriptor.getWriteMethod().getParameterTypes()); } else {
descriptor.getWriteMethod().invoke(tasker, object); descriptor.getWriteMethod().invoke(tasker, object);
}
} catch (Exception e) { } catch (Exception e) {
tasker.updateMessage(java.util.logging.Level.SEVERE, "属性设置失败: " + name + " = " + value); tasker.updateMessage(java.util.logging.Level.SEVERE, "属性设置失败: " + name + " = " + value);
logger.error("set {} = {}", name, value, e);
} }
} }
@@ -106,7 +121,7 @@ public class WebSocketClientSession {
private void handleAsMessage(JsonNode args) { private void handleAsMessage(JsonNode args) {
String level = args.get(0).asText(); String level = args.get(0).asText();
String message = args.get(1).asText(); String message = args.get(1).asText();
updateMessage(java.util.logging.Level.parse(level), message); updateMessage(java.util.logging.Level.parse(level), "[R] " + message);
} }
public void updateMessage(Level level, String message) { public void updateMessage(Level level, String message) {

View File

@@ -3,27 +3,134 @@ package com.ecep.contract;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import java.util.Locale; import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.logging.Level;
/**
* WebSocket客户端任务接口
* 定义了所有通过WebSocket与服务器通信的任务的通用方法
* 包括任务名称、更新消息、更新标题、更新进度等操作
* <p>
* 所有通过WebSocket与服务器通信的任务类都应实现此接口, 文档参考 .trace/rules/client_task_rules.md
*/
public interface WebSocketClientTasker { public interface WebSocketClientTasker {
/**
* s
* 获取任务名称
* 任务名称用于唯一标识任务, 服务器端会根据任务名称来调用对应的任务处理函数
*
* @return 任务名称
*/
String getTaskName(); String getTaskName();
/**
* 更新任务执行过程中的消息
* 客户端可以通过此方法向用户展示任务执行过程中的重要信息或错误提示
*
* @param level 消息级别, 用于区分不同类型的消息, 如INFO, WARNING, SEVERE等
* @param message 消息内容, 可以是任意字符串, 用于展示给用户
*/
void updateMessage(Level level, String message); void updateMessage(Level level, String message);
/**
* 更新任务标题
* 客户端可以通过此方法向用户展示任务的当前执行状态或重要信息
*
* @param title 任务标题
*/
void updateTitle(String title); void updateTitle(String title);
/**
* 更新任务进度
* 客户端可以通过此方法向用户展示任务的执行进度, 如文件上传进度、数据库操作进度等
*
* @param current 当前进度
* @param total 总进度
*/
void updateProgress(long current, long 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) { default Object callRemoteTask(MessageHolder holder, Locale locale, Object... args) {
WebSocketClientService webSocketService = SpringApp.getBean(WebSocketClientService.class); WebSocketClientService webSocketService = SpringApp.getBean(WebSocketClientService.class);
webSocketService.withSession(session -> {
// 检查WebSocket连接是否可用
if (!webSocketService.getOnlineProperty().get()) {
String errorMsg = "WebSocket连接不可用请检查网络连接或服务器状态";
holder.addMessage(Level.SEVERE, errorMsg);
return null;
}
WebSocketClientSession session = webSocketService.createSession();
try { try {
session.submitTask(this, locale, args); session.submitTask(this, locale, args);
holder.info("已提交任务到服务器: " + getTaskName());
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
throw new RuntimeException(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; 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

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

View File

@@ -9,6 +9,8 @@ import java.util.Locale;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer; import java.util.function.Consumer;
import com.ecep.contract.util.BeanContext;
import com.ecep.contract.util.DefaultBeanContext;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.FxmlUtils; import com.ecep.contract.util.FxmlUtils;
import com.ecep.contract.vo.EmployeeVo; import com.ecep.contract.vo.EmployeeVo;
@@ -41,7 +43,7 @@ import javafx.stage.Window;
import javafx.stage.WindowEvent; import javafx.stage.WindowEvent;
import lombok.Getter; import lombok.Getter;
public class BaseController { public class BaseController implements BeanContext {
private static final Logger logger = LoggerFactory.getLogger(BaseController.class); private static final Logger logger = LoggerFactory.getLogger(BaseController.class);
public static HashMap<String, Stage> stages = new HashMap<>(); public static HashMap<String, Stage> stages = new HashMap<>();
@@ -187,20 +189,13 @@ public class BaseController {
public Label rightStatusLabel; public Label rightStatusLabel;
private EmployeeVo currentUser; private EmployeeVo currentUser;
private HashMap<Class<?>, Object> cachedBeans = new HashMap<>(); private BeanContext beanContext = new DefaultBeanContext();
public <T> T getBean(Class<T> requiredType) throws BeansException { public <T> T getBean(Class<T> requiredType) throws BeansException {
return SpringApp.getBean(requiredType); return beanContext.getBean(requiredType);
} }
@SuppressWarnings("unchecked")
public <T> T getCachedBean(Class<T> requiredType) throws BeansException { public <T> T getCachedBean(Class<T> requiredType) throws BeansException {
Object object = cachedBeans.get(requiredType); return beanContext.getCachedBean(requiredType);
if (object == null) {
object = getBean(requiredType);
cachedBeans.put(requiredType, object);
}
return (T) object;
} }
public SysConfService getConfService() { public SysConfService getConfService() {

View File

@@ -1,12 +0,0 @@
package com.ecep.contract.controller;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.task.Tasker;
public class ContractGroupSyncTask extends Tasker<Object>{
@Override
public Object execute(MessageHolder holder) {
return null;
}
}

View File

@@ -1,11 +0,0 @@
package com.ecep.contract.controller;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.task.Tasker;
public class ContractKindSyncTask extends Tasker<Object> {
@Override
public Object execute(MessageHolder holder) {
return null;
}
}

View File

@@ -1,12 +0,0 @@
package com.ecep.contract.controller;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.task.Tasker;
public class ContractSyncAllTask extends Tasker<Object> {
@Override
public Object execute(MessageHolder holder) {
return null;
}
}

View File

@@ -1,11 +0,0 @@
package com.ecep.contract.controller;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.task.Tasker;
public class ContractTypeSyncTask extends Tasker<Object> {
@Override
public Object execute(MessageHolder holder) {
return null;
}
}

View File

@@ -1,14 +0,0 @@
package com.ecep.contract.controller;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.task.Tasker;
public class CustomerClassSyncTask extends Tasker<Object> {
@Override
protected Object execute(MessageHolder holder) throws Exception {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'execute'");
}
}

View File

@@ -6,6 +6,9 @@ import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; 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.control.TaskProgressView;
import org.controlsfx.glyphfont.Glyph; import org.controlsfx.glyphfont.Glyph;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -29,7 +32,7 @@ import com.ecep.contract.controller.inventory.InventoryManagerWindowController;
import com.ecep.contract.controller.permission.EmployeeFunctionsManagerWindowController; import com.ecep.contract.controller.permission.EmployeeFunctionsManagerWindowController;
import com.ecep.contract.controller.permission.EmployeeRoleManagerWindowController; import com.ecep.contract.controller.permission.EmployeeRoleManagerWindowController;
import com.ecep.contract.controller.project.ProjectManagerWindowController; import com.ecep.contract.controller.project.ProjectManagerWindowController;
import com.ecep.contract.controller.vendor.CompanyVendorManagerWindowController; import com.ecep.contract.controller.vendor.VendorManagerWindowController;
import com.ecep.contract.service.CloudRkService; import com.ecep.contract.service.CloudRkService;
import com.ecep.contract.service.YongYouU8Service; import com.ecep.contract.service.YongYouU8Service;
import com.ecep.contract.task.ContractSyncTask; import com.ecep.contract.task.ContractSyncTask;
@@ -79,7 +82,7 @@ public class HomeWindowController extends BaseController {
openCompanyManagerWindow.setOnAction(event -> showInOwner(CompanyManagerWindowController.class)); openCompanyManagerWindow.setOnAction(event -> showInOwner(CompanyManagerWindowController.class));
openProjectManagerWindow.setOnAction(event -> showInOwner(ProjectManagerWindowController.class)); openProjectManagerWindow.setOnAction(event -> showInOwner(ProjectManagerWindowController.class));
openContractManagerWindow.setOnAction(event -> showInOwner(ContractManagerWindowController.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)); openCustomManagerWindow.setOnAction(event -> showInOwner(CompanyCustomerManagerWindowController.class));
} }
@@ -113,6 +116,7 @@ public class HomeWindowController extends BaseController {
@EventListener @EventListener
public void onCurrentEmployeeInitialed(CurrentEmployeeInitialedEvent event) { public void onCurrentEmployeeInitialed(CurrentEmployeeInitialedEvent event) {
System.out.println("event = " + event);
CurrentEmployee currentEmployee = event.getEmployee(); CurrentEmployee currentEmployee = event.getEmployee();
if (currentEmployee.isSystemAdministrator()) { if (currentEmployee.isSystemAdministrator()) {
if (logger.isInfoEnabled()) { if (logger.isInfoEnabled()) {

View File

@@ -1,544 +0,0 @@
package com.ecep.contract.controller;
import java.io.FileOutputStream;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Level;
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.SpringApp;
import javafx.application.Platform;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.event.EventHandler;
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.ListView;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import lombok.Setter;
public class LoginWidowController implements MessageHolder {
private static final Logger logger = LoggerFactory.getLogger(LoginWidowController.class);
@Setter
MessageHolder holder;
@Setter
Stage primaryStage;
@Setter
Properties properties;
@Override
public void addMessage(Level level, String message) {
holder.addMessage(level, message);
}
private void storeProperties() {
try (FileOutputStream fos = new FileOutputStream("config.properties")) {
// 保存到文件
properties.store(fos, "Updated config.properties");
info("配置文件已更新!");
} catch (java.io.IOException e) {
error("保存配置文件失败:" + e.getMessage());
}
}
String getHost() {
return properties.getProperty("server.host");
}
public void tryLogin() {
// CompletableFuture<ButtonType> future = new CompletableFuture<>();
// 检查配置文件中是否保存用户名和密码
String userName = getUserName();
if (StringUtils.hasText(userName)) {
try {
EmployeeInfo employeeInfo = tryToConnect(userName, getPassword());
if (employeeInfo.errorCode < 0) {
error("登录失败:错误代码=" + employeeInfo.errorCode);
} else {
logined(employeeInfo);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
} else {
showUserNameLoginDialog(null);
}
}
private String getPassword() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getPassword'");
}
private String getUserName() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getUserName'");
}
CompletableFuture<List<LoginWidowController.MacIP>> getMacAndIP() {
return CompletableFuture.supplyAsync(() -> {
// mac ip
List<LoginWidowController.MacIP> list = new ArrayList<>();
try {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface anInterface = interfaces.nextElement();
if (anInterface.isLoopback()) {
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 ? "-" : ""));
}
while (inetAddresses.hasMoreElements()) {
InetAddress inetAddress = inetAddresses.nextElement();
if (inetAddress instanceof Inet4Address) {
list.add(new LoginWidowController.MacIP(sb.toString(), inetAddress.getHostAddress()));
}
}
}
} catch (SocketException e) {
throw new RuntimeException(e);
}
return list;
});
}
private EmployeeInfo tryToConnect(String userName, String password) throws SQLException {
String host = getHost();
String port = getPort();
String database = getDatabase();
if (logger.isDebugEnabled()) {
logger.debug("try to connect db server host:{},port:{},database:{},user:{},pwd:{}", host, port, database,
userName, "*");
}
return null;
}
private String getDatabase() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getDatabase'");
}
private String getPort() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getPort'");
}
private void createSession(Connection connection, EmployeeInfo employeeInfo) {
employeeInfo.sessionId = addHistory(connection, employeeInfo.employeeId, employeeInfo.binds.getFirst());
}
private int addHistory(Connection connection, int employeeId, MacIP macIP) {
try {
String sql = "INSERT INTO EMPLOYEE_LOGIN_HISTORY (IP, MAC, DT, EMPLOYEE_ID) VALUES (?, ?, ?, ?)";
try (PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, macIP.ip);
ps.setString(2, macIP.mac);
ps.setObject(3, LocalDateTime.now()); // 根据数据库字段类型调整
ps.setInt(4, employeeId);
ps.executeUpdate();
// 返回 新的主键值
ResultSet generatedKeys = ps.getGeneratedKeys();
if (generatedKeys.next()) {
return generatedKeys.getInt(1);
}
}
} catch (SQLException e) {
holder.error("申请新会话编号失败");
logger.error("unable insert EMPLOYEE_LOGIN_HISTORY, ", e);
}
return -1;
}
static class MacIP {
String ip;
String mac;
public MacIP(String mac, String ip) {
this.mac = mac;
this.ip = ip;
}
}
static class EmployeeInfo {
Integer employeeId;
List<MacIP> binds = new ArrayList<>();
SimpleStringProperty name = new SimpleStringProperty();
SimpleBooleanProperty active = new SimpleBooleanProperty();
int sessionId;
int errorCode = 0;
public EmployeeInfo(Integer employeeId) {
this.employeeId = employeeId;
}
public static EmployeeInfo error(int code) {
EmployeeInfo employeeInfo = new EmployeeInfo(null);
employeeInfo.errorCode = code;
return employeeInfo;
}
public void addBind(MacIP macIP) {
binds.add(macIP);
}
}
private CompletableFuture<EmployeeInfo> tryLoginWithEmployeeBind(Connection connection,
CompletableFuture<List<MacIP>> macAndIP) {
CompletableFuture<EmployeeInfo> future = new CompletableFuture<>();
macAndIP.thenAccept(macIPS -> {
if (macIPS.isEmpty()) {
future.complete(EmployeeInfo.error(-1));
return;
}
HashMap<Integer, EmployeeInfo> employeeMap = new HashMap<>();
for (MacIP macIP : macIPS) {
for (Integer employeeId : findAllBindEmployee(connection, macIP)) {
employeeMap.computeIfAbsent(employeeId, k -> new EmployeeInfo(employeeId)).addBind(macIP);
}
}
if (employeeMap.isEmpty()) {
error("本机未绑定登录信息,请联系管理员更新.");
// 当前计算机的信息,如用户名,计算机名等
String username = System.getProperty("user.name");
String computerName = System.getenv("COMPUTERNAME");
for (MacIP macIP : macIPS) {
if (macIP.ip.equals("127.0.0.1")) {
continue;
}
registerComputer(username, computerName, connection, macIP);
}
future.complete(EmployeeInfo.error(-2));
return;
}
if (employeeMap.size() == 1) {
// 直接登录
EmployeeInfo employeeInfo = employeeMap.values().stream().findFirst().get();
// issue #1 登录成功后没有更新员工信息
fill(connection, employeeInfo);
future.complete(employeeInfo);
} else {
List<EmployeeInfo> list = employeeMap.values().stream().toList();
// 选择登录
Platform.runLater(() -> {
EmployeeInfo info = showEmployeeSelectDialog(list);
future.complete(Objects.requireNonNullElseGet(info, () -> EmployeeInfo.error(-3)));
});
for (EmployeeInfo info : list) {
fill(connection, info);
}
}
});
return future;
}
private EmployeeInfo showEmployeeSelectDialog(List<EmployeeInfo> list) {
Stage stage = new Stage();
stage.initOwner(primaryStage);
stage.setTitle("请选择账户登录系统");
stage.setWidth(360);
stage.setHeight(280);
Label label = new Label("您的主机关联了以下账户,请选择一个登录");
label.setPadding(new Insets(10, 0, 10, 10));
ListView<Label> listView = new ListView<>();
EventHandler<MouseEvent> eventHandler = event -> {
if (event.getClickCount() == 2) {
// listView.getSelectionModel().select(cb);
stage.close();
}
};
for (EmployeeInfo employeeInfo : list) {
Label cb = new Label();
cb.setUserData(employeeInfo);
cb.textProperty().bind(employeeInfo.name);
cb.setPadding(new Insets(5));
cb.setOnMouseClicked(eventHandler);
listView.getItems().add(cb);
}
// 创建 BorderPane 并设置边距
BorderPane borderPane = new BorderPane();
borderPane.setPadding(new Insets(10));
borderPane.setTop(label);
borderPane.setCenter(listView);
Button bottom = new Button("确定");
bottom.setDefaultButton(true);
bottom.setOnAction(event -> {
Label selectedItem = listView.getSelectionModel().getSelectedItem();
if (selectedItem == null) {
// 没选中,退出继续选择
return;
}
stage.close();
});
BorderPane.setAlignment(bottom, javafx.geometry.Pos.CENTER);
borderPane.setBottom(bottom);
stage.setScene(new Scene(borderPane));
stage.setOnCloseRequest(event -> {
Label selectedItem = listView.getSelectionModel().getSelectedItem();
if (selectedItem == null) {
// 关闭时,如何没有做选择,不关闭窗口
event.consume();
}
});
stage.showAndWait();
Label selectedItem = listView.getSelectionModel().getSelectedItem();
if (selectedItem == null) {
throw new NoSuchElementException("请选择工号登录系统");
}
return (EmployeeInfo) selectedItem.getUserData();
}
private void fill(Connection connection, EmployeeInfo info) {
try {
ResultSet rs = connection.createStatement()
.executeQuery("SELECT * FROM EMPLOYEE where ID = " + info.employeeId);
if (rs.next()) {
String name = rs.getString("NAME");
boolean isActive = rs.getBoolean("IS_ACTIVE");
Platform.runLater(() -> {
info.name.set(name);
info.active.set(isActive);
});
}
} catch (SQLException e) {
logger.error("查询{}失败", info.employeeId, e);
}
}
private void registerComputer(String username, String computerName, Connection connection, MacIP macIP) {
info("正在注册本机信息(MAC:" + macIP.mac + ", IP:" + macIP.ip + ")...");
String sql = "INSERT INTO EMPLOYEE_AUTH_BIND (IP,MAC,DESCRIPTION)VALUES(?,?,?)";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setString(1, macIP.ip);
stmt.setString(2, macIP.mac);
// 当前计算机的信息,如用户名,计算机名等
stmt.setString(3, username + "," + computerName);
if (stmt.execute()) {
info("注册成功");
}
} catch (SQLException e) {
error(String.format("注册失败请联系管理员或重启应用MAC: %s, IP: %s", macIP.mac, macIP.ip));
logger.error("注册失败 mac:{}, ip:{}", macIP.mac, macIP.ip, e);
}
}
List<Integer> findAllBindEmployee(Connection connection, MacIP macIP) {
List<Integer> list = new ArrayList<>();
// 优化后代码
String sql = "SELECT * FROM EMPLOYEE_AUTH_BIND WHERE IP = ? AND MAC = ?";
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
stmt.setString(1, macIP.ip);
stmt.setString(2, macIP.mac);
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
int id = rs.getInt("EMPLOYEE_ID");
if (id > 0) {
list.add(id);
}
}
}
} catch (SQLException e) {
throw new RuntimeException(
String.format("查询本机绑定信息异常请联系管理员或重启应用MAC: %s, IP: %s", macIP.mac, macIP.ip),
e);
}
return list;
}
private void showUserNameLoginDialog(Exception exception) {
Stage stage = new Stage();
stage.initOwner(primaryStage);
stage.setTitle("登录");
// 创建 BorderPane 并设置边距
BorderPane borderPane = new BorderPane();
borderPane.setPadding(new Insets(10));
// 创建布局
GridPane grid = new GridPane();
grid.setHgap(10);
grid.setVgap(10);
// 为整个 GridPane 设置外边距
// GridPane.setMargin(grid, new Insets(10));
// 账户输入框
Label userLabel = new Label("账户:");
TextField userField = new TextField();
{
String username = getUserName();
if (StringUtils.hasText(username)) {
userField.setText(username);
}
grid.add(userLabel, 0, 0);
grid.add(userField, 1, 0);
}
// 密码输入框
Label passwordLabel = new Label("密码:");
PasswordField passwordField = new PasswordField();
{
String password = getPassword();
if (StringUtils.hasText(password)) {
passwordField.setText(password);
}
grid.add(passwordLabel, 0, 1);
grid.add(passwordField, 1, 1);
}
// 记住密码复选框
CheckBox rememberCheckBox = new CheckBox("记住账户密码");
{
String property = properties.getProperty("username_password.remember", "false");
if (Boolean.parseBoolean(property)) {
rememberCheckBox.setSelected(true);
}
}
grid.add(rememberCheckBox, 1, 2);
// 错误消息提示
Label exceptionLabel = new Label();
grid.add(exceptionLabel, 0, 3);
exceptionLabel.setWrapText(true);
GridPane.setColumnSpan(exceptionLabel, 2);
if (exception == null) {
exceptionLabel.setVisible(false);
} else {
exceptionLabel.setText(exception.getMessage());
exceptionLabel.setVisible(true);
}
borderPane.setCenter(grid);
// 登录按钮
Button loginButton = new Button("登录");
loginButton.setDefaultButton(true);
borderPane.setBottom(loginButton);
BorderPane.setAlignment(loginButton, javafx.geometry.Pos.CENTER);
// 登录按钮点击事件
loginButton.setOnAction(event -> {
String username = userField.getText();
String password = passwordField.getText();
boolean remember = rememberCheckBox.isSelected();
// 尝试连接数据库
exceptionLabel.setText("");
exceptionLabel.setVisible(false);
try {
EmployeeInfo employeeInfo = tryToConnect(username, password);
if (employeeInfo.errorCode < 0) {
exceptionLabel.setText("登录失败:错误代码=" + employeeInfo.errorCode);
exceptionLabel.setVisible(true);
} else {
logined(employeeInfo);
}
} catch (SQLException ex) {
//
exceptionLabel.setText("数据库错误:" + ex.getMessage());
exceptionLabel.setVisible(true);
return;
}
properties.setProperty("db.server.username", username);
properties.setProperty("db.server.password", password);
properties.setProperty("username_password.remember", Boolean.toString(remember));
// 如果勾选了“记住密码”,则更新配置文件
if (remember) {
CompletableFuture.runAsync(() -> {
storeProperties();
});
}
// 关闭登录窗口
stage.close();
});
// 创建场景并设置到窗口
Scene scene = new Scene(borderPane, 400, 260);
stage.setScene(scene);
// stage.setAlwaysOnTop(true);
stage.setResizable(false);
stage.showAndWait();
}
private void logined(EmployeeInfo employeeInfo) {
info("欢迎 " + employeeInfo.name.get());
if (!SpringApp.isRunning()) {
info("请稍后...");
}
Desktop.instance.setActiveEmployeeId(employeeInfo.employeeId);
// Desktop.instance.setSessionId(employeeInfo.sessionId);
tryShowHomeWindow();
}
void tryShowHomeWindow() {
try {
while (!SpringApp.isRunning()) {
System.out.println("等待启动");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 必须要等待启动成功后才能关闭主场景,否则进程结束程序退出
HomeWindowController.show().thenRun(() -> Platform.runLater(primaryStage::close));
}
}

View File

@@ -11,12 +11,14 @@ import java.util.Properties;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.logging.Level; import java.util.logging.Level;
import javafx.beans.property.SimpleStringProperty;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import com.ecep.contract.Desktop; import com.ecep.contract.Desktop;
import com.ecep.contract.MessageHolder; import com.ecep.contract.MessageHolder;
import com.ecep.contract.MyProperties;
import com.ecep.contract.SpringApp; import com.ecep.contract.SpringApp;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
@@ -56,31 +58,13 @@ public class OkHttpLoginController implements MessageHolder {
@Setter @Setter
private Stage primaryStage; private Stage primaryStage;
@Setter @Setter
private Properties properties; private MyProperties properties;
@Setter @Setter
private OkHttpClient httpClient; private OkHttpClient httpClient;
private WebSocket webSocket; private SimpleStringProperty serverUrl = new SimpleStringProperty();
private String serverUrl;
private String webSocketUrl; private String webSocketUrl;
public OkHttpLoginController() { public OkHttpLoginController() {
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 @Override
@@ -91,9 +75,9 @@ public class OkHttpLoginController implements MessageHolder {
} }
private void initServerUrls() { private void initServerUrls() {
String host = properties.getProperty("server.host", "localhost"); String host = properties.getServerHost();
String port = properties.getProperty("server.port", "8080"); String port = properties.getServerPort();
this.serverUrl = "http://" + host + ":" + port; serverUrl.set("http://" + host + ":" + port);
this.webSocketUrl = "ws://" + host + ":" + port + "/ws"; this.webSocketUrl = "ws://" + host + ":" + port + "/ws";
} }
@@ -117,11 +101,11 @@ public class OkHttpLoginController implements MessageHolder {
} }
private String getUserName() { private String getUserName() {
return properties.getProperty("user.name", ""); return properties.getUserName();
} }
private String getPassword() { private String getPassword() {
return properties.getProperty("user.password", ""); return properties.getPassword();
} }
private void showLoginDialog() { private void showLoginDialog() {
@@ -138,6 +122,14 @@ public class OkHttpLoginController implements MessageHolder {
grid.setHgap(10); grid.setHgap(10);
grid.setVgap(15); 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("用户名:"); Label userLabel = new Label("用户名:");
TextField userField = new TextField(); TextField userField = new TextField();
@@ -146,8 +138,8 @@ public class OkHttpLoginController implements MessageHolder {
if (StringUtils.hasText(username)) { if (StringUtils.hasText(username)) {
userField.setText(username); userField.setText(username);
} }
grid.add(userLabel, 0, 0); grid.add(userLabel, 0, 1);
grid.add(userField, 1, 0); grid.add(userField, 1, 1);
} }
// 密码输入框 // 密码输入框
@@ -158,25 +150,25 @@ public class OkHttpLoginController implements MessageHolder {
if (StringUtils.hasText(password)) { if (StringUtils.hasText(password)) {
passwordField.setText(password); passwordField.setText(password);
} }
grid.add(passwordLabel, 0, 1); grid.add(passwordLabel, 0, 2);
grid.add(passwordField, 1, 1); grid.add(passwordField, 1, 2);
} }
// 记住密码复选框 // 记住密码复选框
CheckBox rememberCheckBox = new CheckBox("记住密码"); CheckBox rememberCheckBox = new CheckBox("记住密码");
{ {
String property = properties.getProperty("remember.password", "false"); boolean remember = properties.isRememberPassword();
if (Boolean.parseBoolean(property)) { if (remember) {
rememberCheckBox.setSelected(true); rememberCheckBox.setSelected(true);
} }
} }
grid.add(rememberCheckBox, 1, 2); grid.add(rememberCheckBox, 1, 3);
// 错误消息提示 // 错误消息提示
Label errorLabel = new Label(); Label errorLabel = new Label();
errorLabel.setStyle("-fx-text-fill: red;"); errorLabel.setStyle("-fx-text-fill: red;");
errorLabel.setVisible(false); errorLabel.setVisible(false);
grid.add(errorLabel, 0, 3); grid.add(errorLabel, 0, 4);
GridPane.setColumnSpan(errorLabel, 2); GridPane.setColumnSpan(errorLabel, 2);
borderPane.setCenter(grid); borderPane.setCenter(grid);
@@ -204,14 +196,16 @@ public class OkHttpLoginController implements MessageHolder {
errorLabel.setVisible(false); errorLabel.setVisible(false);
// 保存配置 // 保存配置
properties.setProperty("user.name", username);
if (remember) { if (remember) {
properties.setProperty("user.password", password); properties.setUserName(username);
properties.setProperty("remember.password", "true"); properties.setPassword(password);
properties.setRememberPassword(true);
} else { } else {
properties.setProperty("user.password", ""); properties.setUserName(username);
properties.setProperty("remember.password", "false"); properties.setPassword("");
properties.setRememberPassword(false);
} }
properties.save();
// 执行登录 // 执行登录
login(username, password).whenComplete((v, e) -> { login(username, password).whenComplete((v, e) -> {
@@ -240,8 +234,8 @@ public class OkHttpLoginController implements MessageHolder {
private CompletableFuture<Void> login(String username, String password) { private CompletableFuture<Void> login(String username, String password) {
// 添加详细日志记录服务器URL和请求准备情况 // 添加详细日志记录服务器URL和请求准备情况
info("正在连接服务器: " + serverUrl); info("正在连接服务器: " + serverUrl.get());
logger.debug("login方法被调用用户名: " + username); logger.debug("用户名: {}", username);
CompletableFuture<Void> future = new CompletableFuture<>(); CompletableFuture<Void> future = new CompletableFuture<>();
try { try {
@@ -268,7 +262,7 @@ public class OkHttpLoginController implements MessageHolder {
RequestBody body = RequestBody.create(objectNode.toString(), JSON); RequestBody body = RequestBody.create(objectNode.toString(), JSON);
// 构建并记录完整的请求URL // 构建并记录完整的请求URL
String loginUrl = serverUrl + "/api/login"; String loginUrl = serverUrl.get() + "/api/login";
logger.debug("构建登录请求URL: " + loginUrl); logger.debug("构建登录请求URL: " + loginUrl);
Request request = new Request.Builder() Request request = new Request.Builder()
.url(loginUrl) .url(loginUrl)
@@ -363,21 +357,6 @@ public class OkHttpLoginController implements MessageHolder {
}); });
} }
// WebSocket消息发送方法
public void sendMessage(String message) {
if (webSocket != null) {
webSocket.send(message);
}
}
// 关闭WebSocket连接
public void closeWebSocket() {
if (webSocket != null) {
webSocket.close(1000, "正常关闭");
webSocket = null;
}
}
static class MacIP { static class MacIP {
String mac; String mac;
String ip; String ip;

View File

@@ -1,11 +0,0 @@
package com.ecep.contract.controller;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.task.Tasker;
public class VendorClassSyncTask extends Tasker<Object> {
@Override
public Object execute(MessageHolder holder) {
return null;
}
}

View File

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

View File

@@ -1,6 +1,7 @@
package com.ecep.contract.controller; package com.ecep.contract.controller.cloud.rk;
import com.ecep.contract.SpringApp; import com.ecep.contract.SpringApp;
import com.ecep.contract.controller.AbstEntityManagerSkin;
import com.ecep.contract.controller.company.CompanyWindowController; import com.ecep.contract.controller.company.CompanyWindowController;
import com.ecep.contract.controller.table.cell.CompanyTableCell; import com.ecep.contract.controller.table.cell.CompanyTableCell;
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell; import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;

View File

@@ -1,9 +1,10 @@
package com.ecep.contract.controller; package com.ecep.contract.controller.cloud.rk;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer; import java.util.function.Consumer;
import com.ecep.contract.controller.AbstManagerWindowController;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -64,32 +65,12 @@ public class CloudRkManagerWindowController
getTitle().set("数据源:集团相关方"); getTitle().set("数据源:集团相关方");
} }
private void initializeTask(Task<Object> task, String prefix, Consumer<String> consumer) {
task.setOnScheduled(e -> {
consumer.accept("正在从相关方平台同步" + prefix + ",请稍后...");
});
task.setOnRunning(e -> {
consumer.accept("开始" + prefix + "...");
});
task.setOnSucceeded(e -> {
consumer.accept(prefix + "完成...");
});
task.exceptionProperty().addListener((observable, oldValue, newValue) -> {
consumer.accept(newValue.getMessage());
logger.error("{} 发生异常", prefix, newValue);
});
SpringApp.getBean(ScheduledExecutorService.class).submit(task);
consumer.accept("任务已创建...");
}
public void onDataRepairAction(ActionEvent event) { public void onDataRepairAction(ActionEvent event) {
} }
public void onSyncAction(ActionEvent event) { public void onSyncAction(ActionEvent event) {
CloudRkSyncTask task = new CloudRkSyncTask(); CloudRkSyncTask task = new CloudRkSyncTask();
UITools.showTaskDialogAndWait("同步数据", task, consumer -> { UITools.showTaskDialogAndWait("同步数据", task, null);
initializeTask(task, "同步数据", msg -> consumer.accept(Message.info(msg)));
});
} }
@Override @Override

View File

@@ -1,6 +1,7 @@
package com.ecep.contract.controller; package com.ecep.contract.controller.cloud.tyc;
import com.ecep.contract.SpringApp; import com.ecep.contract.SpringApp;
import com.ecep.contract.controller.AbstEntityManagerSkin;
import com.ecep.contract.controller.company.CompanyWindowController; import com.ecep.contract.controller.company.CompanyWindowController;
import com.ecep.contract.controller.table.cell.CompanyTableCell; import com.ecep.contract.controller.table.cell.CompanyTableCell;
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell; import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
@@ -51,7 +52,7 @@ public class CloudTycManagerSkin
controller.cloudIdColumn.setCellValueFactory(param -> param.getValue().getCloudId()); 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.latestUpdateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
controller.cloudLatestColumn.setCellValueFactory(param -> param.getValue().getCloudLatest()); controller.cloudLatestColumn.setCellValueFactory(param -> param.getValue().getCloudLatest());

View File

@@ -1,7 +1,8 @@
package com.ecep.contract.controller; package com.ecep.contract.controller.cloud.tyc;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import com.ecep.contract.controller.AbstManagerWindowController;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;

View File

@@ -1,5 +1,6 @@
package com.ecep.contract.controller; package com.ecep.contract.controller.cloud.u8;
import com.ecep.contract.controller.BaseController;
import org.controlsfx.control.ToggleSwitch; import org.controlsfx.control.ToggleSwitch;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;

View File

@@ -1,12 +1,13 @@
package com.ecep.contract.controller; 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.company.CompanyWindowController;
import com.ecep.contract.controller.table.cell.CompanyTableCell; import com.ecep.contract.controller.table.cell.CompanyTableCell;
import com.ecep.contract.controller.table.cell.LocalDateFieldTableCell;
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell; import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
import com.ecep.contract.model.Company;
import com.ecep.contract.service.CompanyService; import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.YongYouU8Service; import com.ecep.contract.service.YongYouU8Service;
import com.ecep.contract.util.ProxyUtils;
import com.ecep.contract.vm.CloudYuInfoViewModel; import com.ecep.contract.vm.CloudYuInfoViewModel;
import com.ecep.contract.vo.CloudYuVo; import com.ecep.contract.vo.CloudYuVo;
import com.ecep.contract.vo.CompanyVo; import com.ecep.contract.vo.CompanyVo;
@@ -15,6 +16,7 @@ import javafx.collections.ObservableList;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.scene.control.ContextMenu; import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
import javafx.scene.control.cell.CheckBoxTableCell;
public class YongYouU8ManagerSkin public class YongYouU8ManagerSkin
extends extends
@@ -36,18 +38,24 @@ public class YongYouU8ManagerSkin
@Override @Override
public void initializeTable() { public void initializeTable() {
controller.idColumn.setCellValueFactory(param -> param.getValue().getId()); controller.idColumn.setCellValueFactory(param -> param.getValue().getId());
controller.companyColumn.setCellValueFactory(param -> param.getValue().getCompany()); controller.companyColumn.setCellValueFactory(param -> param.getValue().getCompany());
controller.companyColumn.setCellFactory(param -> new CompanyTableCell<>(getCompanyService())); controller.companyColumn.setCellFactory( CompanyTableCell.forTableColumn(getCompanyService()));
controller.cloudIdColumn.setCellValueFactory(param -> param.getValue().getCloudId()); controller.latestUpdateColumn.setCellValueFactory(param -> param.getValue().getLatestUpdate());
controller.latestUpdateColumn.setCellValueFactory(param -> param.getValue().getLatest());
controller.latestUpdateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); controller.latestUpdateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
controller.cloudLatestColumn.setCellValueFactory(param -> param.getValue().getCloudLatest()); controller.cloudLatestColumn.setCellValueFactory(param -> param.getValue().getCloudLatest());
controller.cloudLatestColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); controller.cloudLatestColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
controller.descriptionColumn.setCellValueFactory(param -> param.getValue().getVendorCode()); controller.cloudVendorUpdateDateColumn.setCellValueFactory(param -> param.getValue().getVendorUpdateDate());
controller.cloudCustomerUpdateDateColumn.setCellValueFactory(param -> param.getValue().getCustomerUpdateDate());
controller.activeColumn.setCellValueFactory(param -> param.getValue().getActive());
controller.activeColumn.setCellFactory(CheckBoxTableCell.forTableColumn(controller.activeColumn));
controller.descriptionColumn.setCellValueFactory(param -> param.getValue().getExceptionMessage());
} }
@Override @Override
@@ -73,7 +81,7 @@ public class YongYouU8ManagerSkin
} }
for (CloudYuInfoViewModel selectedItem : selectedItems) { for (CloudYuInfoViewModel selectedItem : selectedItems) {
CloudYuVo yongYouU8Vo = getU8Service().findById(selectedItem.getId().get()); CloudYuVo yongYouU8Vo = getU8Service().findById(selectedItem.getId().get());
selectedItem.getCustomerCode().set(""); selectedItem.getExceptionMessage().set("");
if (selectedItem.copyTo(yongYouU8Vo)) { if (selectedItem.copyTo(yongYouU8Vo)) {
CloudYuVo saved = getU8Service().save(yongYouU8Vo); CloudYuVo saved = getU8Service().save(yongYouU8Vo);
selectedItem.update(saved); selectedItem.update(saved);

View File

@@ -1,7 +1,10 @@
package com.ecep.contract.controller; package com.ecep.contract.controller.cloud.u8;
import java.time.LocalDateTime; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
@@ -10,10 +13,6 @@ import org.springframework.stereotype.Component;
import com.ecep.contract.Desktop; import com.ecep.contract.Desktop;
import com.ecep.contract.service.YongYouU8Service; import com.ecep.contract.service.YongYouU8Service;
import com.ecep.contract.task.ContractSyncTask;
import com.ecep.contract.task.CustomerSyncTask;
import com.ecep.contract.task.EmployeesSyncTask;
import com.ecep.contract.task.VendorSyncTask;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.UITools; import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.CloudYuInfoViewModel; import com.ecep.contract.vm.CloudYuInfoViewModel;
@@ -38,8 +37,10 @@ public class YongYouU8ManagerWindowController
public TableColumn<CloudYuInfoViewModel, Number> idColumn; public TableColumn<CloudYuInfoViewModel, Number> idColumn;
public TableColumn<CloudYuInfoViewModel, LocalDateTime> latestUpdateColumn; public TableColumn<CloudYuInfoViewModel, LocalDateTime> latestUpdateColumn;
public TableColumn<CloudYuInfoViewModel, Integer> companyColumn; public TableColumn<CloudYuInfoViewModel, Integer> companyColumn;
public TableColumn<CloudYuInfoViewModel, String> cloudIdColumn;
public TableColumn<CloudYuInfoViewModel, LocalDateTime> cloudLatestColumn; public TableColumn<CloudYuInfoViewModel, LocalDateTime> cloudLatestColumn;
public TableColumn<CloudYuInfoViewModel, java.time.LocalDate> cloudVendorUpdateDateColumn;
public TableColumn<CloudYuInfoViewModel, java.time.LocalDate> cloudCustomerUpdateDateColumn;
public TableColumn<CloudYuInfoViewModel, Boolean> activeColumn;
public TableColumn<CloudYuInfoViewModel, String> descriptionColumn; public TableColumn<CloudYuInfoViewModel, String> descriptionColumn;
@Override @Override
@@ -92,27 +93,32 @@ public class YongYouU8ManagerWindowController
public void onContractGroupSyncAction(ActionEvent event) { public void onContractGroupSyncAction(ActionEvent event) {
ContractGroupSyncTask task = new ContractGroupSyncTask(); ContractGroupSyncTask task = new ContractGroupSyncTask();
Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task); UITools.showTaskDialogAndWait("合同组数据同步", task, null);
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
} }
public void onContractTypeSyncAction(ActionEvent event) { public void onContractTypeSyncAction(ActionEvent event) {
ContractTypeSyncTask task = new ContractTypeSyncTask(); ContractTypeSyncTask task = new ContractTypeSyncTask();
Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task); UITools.showTaskDialogAndWait("合同类型数据同步", task, null);
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
} }
public void onContractKindSyncAction(ActionEvent event) { public void onContractKindSyncAction(ActionEvent event) {
ContractKindSyncTask task = new ContractKindSyncTask(); ContractKindSyncTask task = new ContractKindSyncTask();
Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task); UITools.showTaskDialogAndWait("合同类型数据同步", task, null);
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
} }
public void onVendorClassSyncAction(ActionEvent event) { public void onVendorClassSyncAction(ActionEvent event) {
VendorClassSyncTask task = new VendorClassSyncTask(); VendorClassSyncTask task = new VendorClassSyncTask();
Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task); UITools.showTaskDialogAndWait("客户分类数据同步", task, null);
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
} }
public void onCustomerClassSyncAction(ActionEvent event) { public void onCustomerClassSyncAction(ActionEvent event) {
CustomerClassSyncTask task = new CustomerClassSyncTask(); CustomerClassSyncTask task = new CustomerClassSyncTask();
Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task); UITools.showTaskDialogAndWait("客户分类数据同步", task, null);
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
} }
} }

View File

@@ -1,32 +1,13 @@
package com.ecep.contract.controller.company; package com.ecep.contract.controller.company;
import java.lang.reflect.InvocationTargetException;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.CompletableFuture;
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.BaseController; import com.ecep.contract.controller.BaseController;
import com.ecep.contract.model.CompanyContact;
import com.ecep.contract.service.CompanyContactService; import com.ecep.contract.service.CompanyContactService;
import com.ecep.contract.util.FxmlUtils; import com.ecep.contract.util.FxmlUtils;
import com.ecep.contract.util.UITools; import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.CompanyContactViewModel; import com.ecep.contract.vm.CompanyContactViewModel;
import com.ecep.contract.vo.CompanyContactVo; import com.ecep.contract.vo.CompanyContactVo;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.scene.control.Button; import javafx.scene.control.*;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane; import javafx.scene.layout.BorderPane;
import javafx.stage.Modality; import javafx.stage.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
@@ -35,6 +16,16 @@ import javafx.stage.WindowEvent;
import javafx.util.converter.LocalDateStringConverter; import javafx.util.converter.LocalDateStringConverter;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
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 java.lang.reflect.InvocationTargetException;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.CompletableFuture;
@Lazy @Lazy
@Scope("prototype") @Scope("prototype")

View File

@@ -25,17 +25,17 @@ import com.ecep.contract.controller.tab.CompanyTabSkinInvoice;
import com.ecep.contract.controller.tab.CompanyTabSkinOldName; import com.ecep.contract.controller.tab.CompanyTabSkinOldName;
import com.ecep.contract.controller.tab.CompanyTabSkinOther; import com.ecep.contract.controller.tab.CompanyTabSkinOther;
import com.ecep.contract.controller.tab.CompanyTabSkinPurchaseBillVoucher; import com.ecep.contract.controller.tab.CompanyTabSkinPurchaseBillVoucher;
import com.ecep.contract.controller.vendor.CompanyVendorWindowController; import com.ecep.contract.controller.vendor.VendorWindowController;
import com.ecep.contract.service.CompanyCustomerService; import com.ecep.contract.service.CustomerService;
import com.ecep.contract.service.CompanyService; import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.CompanyVendorService; import com.ecep.contract.service.VendorService;
import com.ecep.contract.task.CompanyCompositeUpdateTasker; import com.ecep.contract.task.CompanyCompositeUpdateTasker;
import com.ecep.contract.task.CompanyVerifyTasker; import com.ecep.contract.task.CompanyVerifyTasker;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.UITools; import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.CompanyViewModel; import com.ecep.contract.vm.CompanyViewModel;
import com.ecep.contract.vo.CompanyCustomerVo; import com.ecep.contract.vo.CustomerVo;
import com.ecep.contract.vo.CompanyVendorVo; import com.ecep.contract.vo.VendorVo;
import com.ecep.contract.vo.CompanyVo; import com.ecep.contract.vo.CompanyVo;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
@@ -75,10 +75,6 @@ public class CompanyWindowController
@Autowired @Autowired
private CompanyService companyService; private CompanyService companyService;
@Autowired
private CompanyCustomerService companyCustomerService;
@Autowired
private CompanyVendorService companyVendorService;
public BorderPane root; public BorderPane root;
public TabPane tabPane; public TabPane tabPane;
@@ -128,8 +124,8 @@ public class CompanyWindowController
// private final CompanyVendorViewModel companyVendorViewModel = new // private final CompanyVendorViewModel companyVendorViewModel = new
// CompanyVendorViewModel(); // CompanyVendorViewModel();
private final SimpleObjectProperty<CompanyCustomerVo> companyCustomerProperty = new SimpleObjectProperty<>(); private final SimpleObjectProperty<CustomerVo> companyCustomerProperty = new SimpleObjectProperty<>();
private final SimpleObjectProperty<CompanyVendorVo> companyVendorProperty = new SimpleObjectProperty<>(); private final SimpleObjectProperty<VendorVo> companyVendorProperty = new SimpleObjectProperty<>();
public Pane customerTab_pane1; public Pane customerTab_pane1;
public Button customerTab_openBtn; public Button customerTab_openBtn;
@@ -206,7 +202,8 @@ public class CompanyWindowController
logger.debug("onCustomerTabShown"); logger.debug("onCustomerTabShown");
} }
getLoadedFuture().thenAcceptAsync(company -> { getLoadedFuture().thenAcceptAsync(company -> {
companyCustomerProperty.set(companyCustomerService.findByCompany(company)); CustomerVo customerVo = getCachedBean(CustomerService.class).findByCompany(company);
companyCustomerProperty.set(customerVo);
}).exceptionally(ex -> { }).exceptionally(ex -> {
UITools.showExceptionAndWait(ex.getMessage(), ex); UITools.showExceptionAndWait(ex.getMessage(), ex);
return null; return null;
@@ -230,7 +227,7 @@ public class CompanyWindowController
}); });
vendorTab_openBtn.setOnAction(event -> { vendorTab_openBtn.setOnAction(event -> {
CompanyVendorWindowController.show(companyVendorProperty.get(), root.getScene().getWindow()); VendorWindowController.show(companyVendorProperty.get(), root.getScene().getWindow());
}); });
} }
@@ -242,7 +239,8 @@ public class CompanyWindowController
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("onVendorTabShown company {}", company.getName()); logger.debug("onVendorTabShown company {}", company.getName());
} }
companyVendorProperty.set(companyVendorService.findByCompany(company)); VendorService vendorService = getBean(VendorService.class);
companyVendorProperty.set(vendorService.findByCompany(company));
}).exceptionally(ex -> { }).exceptionally(ex -> {
UITools.showExceptionAndWait(ex.getMessage(), ex); UITools.showExceptionAndWait(ex.getMessage(), ex);
return null; return null;
@@ -256,6 +254,7 @@ public class CompanyWindowController
CompanyCompositeUpdateTasker task = new CompanyCompositeUpdateTasker(); CompanyCompositeUpdateTasker task = new CompanyCompositeUpdateTasker();
task.setCompany(getEntity()); task.setCompany(getEntity());
UITools.showTaskDialogAndWait("更新企业信息", task, null); UITools.showTaskDialogAndWait("更新企业信息", task, null);
refresh();
} }
/** /**
@@ -264,9 +263,9 @@ public class CompanyWindowController
public void onCompanyVerifyAction(ActionEvent event) { public void onCompanyVerifyAction(ActionEvent event) {
CompanyVo company = getEntity(); CompanyVo company = getEntity();
CompanyVerifyTasker task = new CompanyVerifyTasker(); CompanyVerifyTasker task = new CompanyVerifyTasker();
task.setCompanyService(companyService);
task.setCompany(company); task.setCompany(company);
UITools.showTaskDialogAndWait("企业合规性验证", task, null); UITools.showTaskDialogAndWait("企业合规性验证", task, null);
refresh();
} }
public void onCompanyOpenInExplorerAction(ActionEvent event) { public void onCompanyOpenInExplorerAction(ActionEvent event) {
@@ -277,7 +276,7 @@ public class CompanyWindowController
if (!StringUtils.hasText(path)) { if (!StringUtils.hasText(path)) {
ButtonType buttonType = UITools.showConfirmation("目录未设置", "是否创建目录").join(); ButtonType buttonType = UITools.showConfirmation("目录未设置", "是否创建目录").join();
if (buttonType == ButtonType.OK) { if (buttonType == ButtonType.OK) {
if (companyService.makePathAbsent(company)) { if (companyService.makePathAbsent(company, (level, message) -> setStatus(message))) {
save(company); save(company);
} }
} else { } else {

View File

@@ -1,4 +1,4 @@
package com.ecep.contract.controller.bank.account; package com.ecep.contract.controller.company.bank_account;
import com.ecep.contract.controller.company.CompanyWindowController; import com.ecep.contract.controller.company.CompanyWindowController;
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin; import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;

View File

@@ -1,4 +1,4 @@
package com.ecep.contract.controller.bank.account; package com.ecep.contract.controller.company.bank_account;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
@@ -6,7 +6,6 @@ import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import com.ecep.contract.controller.AbstEntityController; import com.ecep.contract.controller.AbstEntityController;
import com.ecep.contract.model.CompanyBankAccount;
import com.ecep.contract.service.CompanyBankAccountService; import com.ecep.contract.service.CompanyBankAccountService;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vm.CompanyBankAccountViewModel; import com.ecep.contract.vm.CompanyBankAccountViewModel;

View File

@@ -1,4 +1,4 @@
package com.ecep.contract.controller.company_old_name; package com.ecep.contract.controller.company.old_name;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;

View File

@@ -1,9 +1,7 @@
package com.ecep.contract.controller.company_old_name; package com.ecep.contract.controller.company.old_name;
import java.io.File; import java.io.File;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
@@ -12,7 +10,6 @@ import com.ecep.contract.CompanyFileType;
import com.ecep.contract.DesktopUtils; import com.ecep.contract.DesktopUtils;
import com.ecep.contract.controller.tab.TabSkin; import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.table.AbstEntityTableTabSkin; import com.ecep.contract.controller.table.AbstEntityTableTabSkin;
import com.ecep.contract.model.CompanyOldName;
import com.ecep.contract.service.CompanyFileService; import com.ecep.contract.service.CompanyFileService;
import com.ecep.contract.service.CompanyOldNameService; import com.ecep.contract.service.CompanyOldNameService;
import com.ecep.contract.util.ParamUtils; import com.ecep.contract.util.ParamUtils;

View File

@@ -1,4 +1,4 @@
package com.ecep.contract.controller.company_old_name; package com.ecep.contract.controller.company.old_name;
import java.io.File; import java.io.File;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;

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,11 +1,8 @@
package com.ecep.contract.controller.contract; package com.ecep.contract.controller.contract;
import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import com.ecep.contract.SpringApp; import com.ecep.contract.util.ComboBoxUtils;
import com.ecep.contract.controller.ComboBoxUtils;
import com.ecep.contract.model.VendorGroup;
import com.ecep.contract.service.ExtendVendorInfoService; import com.ecep.contract.service.ExtendVendorInfoService;
import com.ecep.contract.service.VendorGroupService; import com.ecep.contract.service.VendorGroupService;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
@@ -25,7 +22,6 @@ import javafx.scene.control.Label;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.util.converter.NumberStringConverter; import javafx.util.converter.NumberStringConverter;
import lombok.Setter;
@FxmlPath("/ui/contract/contract-tab-ext-vendor-info.fxml") @FxmlPath("/ui/contract/contract-tab-ext-vendor-info.fxml")
public class ContractTabSkinExtendVendorInfo extends AbstContractBasedTabSkin { public class ContractTabSkinExtendVendorInfo extends AbstContractBasedTabSkin {
@@ -119,6 +115,9 @@ public class ContractTabSkinExtendVendorInfo extends AbstContractBasedTabSkin {
assignedProviderField.selectedProperty().bindBidirectional(viewModel.getAssignedProvider()); assignedProviderField.selectedProperty().bindBidirectional(viewModel.getAssignedProvider());
assignedProviderField.disableProperty().bind(Bindings.createBooleanBinding(() -> { assignedProviderField.disableProperty().bind(Bindings.createBooleanBinding(() -> {
Integer groupId = viewModel.getGroup().get(); Integer groupId = viewModel.getGroup().get();
if (groupId == null) {
return false;
}
VendorGroupVo group = getVendorGroupService().findById(groupId); VendorGroupVo group = getVendorGroupService().findById(groupId);
if (group == null) { if (group == null) {
return false; return false;
@@ -135,9 +134,9 @@ public class ContractTabSkinExtendVendorInfo extends AbstContractBasedTabSkin {
ExtendVendorInfoVo vendorInfo = loadedFuture.join(); ExtendVendorInfoVo vendorInfo = loadedFuture.join();
if (viewModel.copyTo(vendorInfo)) { if (viewModel.copyTo(vendorInfo)) {
// 注意这里需要根据实际service接口实现调整可能需要调用不同的方法 // 注意这里需要根据实际service接口实现调整可能需要调用不同的方法
// ExtendVendorInfoVo saved = getExtendVendorInfoService().saveVo(vendorInfo); ExtendVendorInfoVo saved = getExtendVendorInfoService().save(vendorInfo);
// updateViewModel(saved); updateViewModel(saved);
// loadedFuture = CompletableFuture.completedFuture(saved); loadedFuture = CompletableFuture.completedFuture(saved);
} }
} }
} }

View File

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

View File

@@ -1,19 +1,10 @@
package com.ecep.contract.controller.tab; package com.ecep.contract.controller.contract;
import java.text.NumberFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import com.ecep.contract.controller.contract.AbstContractTableTabSkin;
import com.ecep.contract.controller.contract.ContractWindowController;
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell; import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
import com.ecep.contract.model.ContractPayPlan;
import com.ecep.contract.service.ContractPayPlanService; import com.ecep.contract.service.ContractPayPlanService;
import com.ecep.contract.service.ViewModelService;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vm.ContractPayPlanViewModel; import com.ecep.contract.vm.ContractPayPlanViewModel;
import com.ecep.contract.vo.ContractPayPlanVo; import com.ecep.contract.vo.ContractPayPlanVo;
import javafx.scene.control.ContextMenu; import javafx.scene.control.ContextMenu;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
import javafx.scene.control.TableColumn; import javafx.scene.control.TableColumn;
@@ -21,6 +12,10 @@ import javafx.scene.control.cell.TextFieldTableCell;
import javafx.util.converter.CurrencyStringConverter; import javafx.util.converter.CurrencyStringConverter;
import javafx.util.converter.NumberStringConverter; import javafx.util.converter.NumberStringConverter;
import java.text.NumberFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
@FxmlPath("/ui/contract/contract-tab-pay-plan.fxml") @FxmlPath("/ui/contract/contract-tab-pay-plan.fxml")
public class ContractTabSkinPayPlan extends AbstContractTableTabSkin<ContractPayPlanVo, ContractPayPlanViewModel> { public class ContractTabSkinPayPlan extends AbstContractTableTabSkin<ContractPayPlanVo, ContractPayPlanViewModel> {

View File

@@ -1,19 +1,17 @@
package com.ecep.contract.controller.tab; package com.ecep.contract.controller.contract;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import com.ecep.contract.ContractPayWay; import com.ecep.contract.ContractPayWay;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.table.cell.EmployeeTableCell;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.controller.contract.AbstContractTableTabSkin; import com.ecep.contract.controller.vendor.purchase.order.PurchaseOrderWindowController;
import com.ecep.contract.controller.contract.ContractWindowController;
import com.ecep.contract.controller.vendor.PurchaseOrderWindowController;
import com.ecep.contract.converter.EmployeeStringConverter; import com.ecep.contract.converter.EmployeeStringConverter;
import com.ecep.contract.service.CompanyService; import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.PurchaseOrdersService; import com.ecep.contract.service.PurchaseOrdersService;
import com.ecep.contract.vm.PurchaseOrderViewModel; import com.ecep.contract.vm.PurchaseOrderViewModel;
import com.ecep.contract.vo.PurchaseOrderVo; import com.ecep.contract.vo.PurchaseOrderVo;
import com.ecep.contract.model.PurchaseOrder;
import com.ecep.contract.controller.table.cell.CompanyTableCell;
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell; import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
@@ -71,17 +69,16 @@ public class ContractTabSkinPurchaseOrders
idColumn.setCellValueFactory(param -> param.getValue().getId()); idColumn.setCellValueFactory(param -> param.getValue().getId());
codeColumn.setCellValueFactory(param -> param.getValue().getCode()); codeColumn.setCellValueFactory(param -> param.getValue().getCode());
EmployeeStringConverter converter = getEmployeeStringConverter();
table_makerColumn.setCellValueFactory(param -> param.getValue().getMaker()); table_makerColumn.setCellValueFactory(param -> param.getValue().getMaker());
table_makerColumn.setCellFactory(CompanyTableCell.forTableColumn(getCompanyService())); table_makerColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
table_makerDateColumn.setCellValueFactory(param -> param.getValue().getMakerDate()); table_makerDateColumn.setCellValueFactory(param -> param.getValue().getMakerDate());
table_makerDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); table_makerDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
table_verifierColumn.setCellValueFactory(param -> param.getValue().getMaker()); table_verifierColumn.setCellValueFactory(param -> param.getValue().getMaker());
table_verifierColumn.setCellFactory(CompanyTableCell.forTableColumn(getCompanyService())); table_verifierColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
table_verifierDateColumn.setCellValueFactory(param -> param.getValue().getVerifierDate()); table_verifierDateColumn.setCellValueFactory(param -> param.getValue().getVerifierDate());
table_verifierDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); table_verifierDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
table_closerColumn.setCellValueFactory(param -> param.getValue().getMaker()); table_closerColumn.setCellValueFactory(param -> param.getValue().getMaker());
table_closerColumn.setCellFactory(CompanyTableCell.forTableColumn(getCompanyService())); table_closerColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
table_closerDateColumn.setCellValueFactory(param -> param.getValue().getCloserDate()); table_closerDateColumn.setCellValueFactory(param -> param.getValue().getCloserDate());
table_closerDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); table_closerDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());

View File

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

View File

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

View File

@@ -1,24 +1,20 @@
package com.ecep.contract.controller.tab; package com.ecep.contract.controller.contract;
import java.util.List; import java.util.List;
import com.ecep.contract.service.ContractFileService;
import org.controlsfx.control.textfield.TextFields; import org.controlsfx.control.textfield.TextFields;
import com.ecep.contract.ContractPayWay; import com.ecep.contract.ContractPayWay;
import com.ecep.contract.SpringApp; import com.ecep.contract.SpringApp;
import com.ecep.contract.controller.contract.AbstContractTableTabSkin; import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.contract.ContractWindowController;
import com.ecep.contract.controller.table.EditableEntityTableTabSkin; import com.ecep.contract.controller.table.EditableEntityTableTabSkin;
import com.ecep.contract.controller.table.cell.CompanyTableCell; import com.ecep.contract.controller.table.cell.CompanyTableCell;
import com.ecep.contract.controller.table.cell.ContractFileTableCell; import com.ecep.contract.controller.table.cell.ContractFileTableCell;
import com.ecep.contract.controller.vendor.VendorBidWindowController; import com.ecep.contract.controller.vendor.bid.VendorBidWindowController;
import com.ecep.contract.model.ContractBidVendor;
import com.ecep.contract.model.ContractFile;
import com.ecep.contract.service.CompanyService; import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.ContractBidVendorService; import com.ecep.contract.service.ContractBidVendorService;
import com.ecep.contract.service.ContractFileService;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.ProxyUtils;
import com.ecep.contract.vm.CompanyViewModel; import com.ecep.contract.vm.CompanyViewModel;
import com.ecep.contract.vm.ContractBidVendorViewModel; import com.ecep.contract.vm.ContractBidVendorViewModel;
import com.ecep.contract.vo.CompanyVo; import com.ecep.contract.vo.CompanyVo;
@@ -32,7 +28,6 @@ import javafx.scene.control.ChoiceDialog;
import javafx.scene.control.ComboBox; import javafx.scene.control.ComboBox;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn; import javafx.scene.control.TableColumn;
import javafx.util.StringConverter; import javafx.util.StringConverter;
import lombok.Setter; import lombok.Setter;
@@ -90,7 +85,7 @@ public class ContractTabSkinVendorBid
bidVendorTable_companyColumn.setCellValueFactory(param -> param.getValue().getCompanyId()); bidVendorTable_companyColumn.setCellValueFactory(param -> param.getValue().getCompanyId());
bidVendorTable_companyColumn.setCellFactory(CompanyTableCell.forTableColumn(getCompanyService())); bidVendorTable_companyColumn.setCellFactory(CompanyTableCell.forTableColumn(getCompanyService()));
bidVendorTable_quotationSheetColumn.setCellValueFactory(param -> param.getValue().getQuotationSheetFileId()); bidVendorTable_quotationSheetColumn.setCellValueFactory(param -> param.getValue().getQuotationSheetFileId());
bidVendorTable_quotationSheetColumn.setCellFactory(ContractFileTableCell.forTableColumn(getContractService())); bidVendorTable_quotationSheetColumn.setCellFactory(ContractFileTableCell.forTableColumn(SpringApp.getBean(ContractFileService.class)));
super.initializeTab(); super.initializeTab();
} }
@@ -100,6 +95,7 @@ public class ContractTabSkinVendorBid
showInOwner(VendorBidWindowController.class, item); showInOwner(VendorBidWindowController.class, item);
} }
@SuppressWarnings("unchecked")
public void onBidVendorTableCreateAction(ActionEvent event) { public void onBidVendorTableCreateAction(ActionEvent event) {
ContractVo contract = getParent(); ContractVo contract = getParent();

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
package com.ecep.contract.controller.customer; package com.ecep.contract.controller.contract.sale_order;
import com.ecep.contract.vo.SalesOrderVo; import com.ecep.contract.vo.SalesOrderVo;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -43,6 +43,15 @@ public class SalesOrderWindowController extends AbstEntityController<SalesOrderV
public TextField makeDateField; public TextField makeDateField;
public TextField makerField; public TextField makerField;
public TextArea descriptionField; public TextArea descriptionField;
public TextField refIdField;
public TextField taxRateField;
public TextField customerField;
public TextField customerAddressField;
public TextField modifierField;
public TextField modifierDateField;
public TextField closerField;
public TextField closerDateField;
public TextField contractField;
public static void show(SalesOrderViewModel viewModel, Window window) { public static void show(SalesOrderViewModel viewModel, Window window) {

View File

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

View File

@@ -1,11 +1,26 @@
package com.ecep.contract.controller.customer; package com.ecep.contract.controller.customer;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.time.format.DateTimeFormatter;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer;
import com.ecep.contract.controller.AbstEntityController;
import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.service.CompanyCustomerFileTypeService;
import com.ecep.contract.service.ViewModelService;
import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vo.CustomerFileTypeLocalVo;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.property.*;
import javafx.scene.input.MouseButton;
import javafx.scene.paint.Color;
import javafx.scene.text.FontWeight;
import javafx.util.converter.LocalDateStringConverter;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
@@ -15,25 +30,24 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope; import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import com.ecep.contract.controller.BaseController; import com.ecep.contract.controller.BaseController;
import com.ecep.contract.vo.CompanyCustomerEvaluationFormFileVo; import com.ecep.contract.vo.CompanyCustomerEvaluationFormFileVo;
import com.ecep.contract.vo.CompanyCustomerFileVo; import com.ecep.contract.vo.CustomerFileVo;
import com.ecep.contract.service.CompanyCustomerFileService; import com.ecep.contract.service.CompanyCustomerFileService;
import com.ecep.contract.service.CompanyCustomerEvaluationFormFileService; import com.ecep.contract.service.CompanyCustomerEvaluationFormFileService;
import com.ecep.contract.util.FileUtils; import com.ecep.contract.util.FileUtils;
import com.ecep.contract.util.FxmlUtils; import com.ecep.contract.util.FxmlUtils;
import com.ecep.contract.vm.CompanyCustomerEvaluationFormFileViewModel; import com.ecep.contract.vm.CompanyCustomerEvaluationFormFileViewModel;
import com.ecep.contract.vm.CompanyCustomerFileViewModel; import com.ecep.contract.vm.CustomerFileViewModel;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.geometry.Bounds; import javafx.geometry.Bounds;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.CheckBox; import javafx.scene.control.CheckBox;
import javafx.scene.control.DatePicker; import javafx.scene.control.DatePicker;
import javafx.scene.control.Label; import javafx.scene.control.Label;
@@ -50,28 +64,29 @@ import javafx.stage.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.stage.Window; import javafx.stage.Window;
import javafx.stage.WindowEvent; import javafx.stage.WindowEvent;
@Lazy @Lazy
@Scope("prototype") @Scope("prototype")
@Component @Component
public class CompanyCustomerEvaluationFormFileWindowController extends BaseController { @FxmlPath("/ui/company/customer/customer_evaluation_form.fxml")
private static final Logger logger = LoggerFactory.getLogger(CompanyCustomerEvaluationFormFileWindowController.class); public class CompanyCustomerEvaluationFormFileWindowController
extends AbstEntityController<CompanyCustomerEvaluationFormFileVo, CompanyCustomerEvaluationFormFileViewModel> {
private static final Logger logger = LoggerFactory
.getLogger(CompanyCustomerEvaluationFormFileWindowController.class);
public static void show(CustomerFileViewModel item, Window window) {
show(CompanyCustomerEvaluationFormFileWindowController.class, window, controller -> {
controller.fileViewModel = item;
});
}
public static void show(CompanyCustomerEvaluationFormFileVo saved, Window window) { public static void show(CompanyCustomerEvaluationFormFileVo saved, Window window) {
show(CompanyCustomerEvaluationFormFileViewModel.from(saved), window); show(CompanyCustomerEvaluationFormFileViewModel.from(saved), window);
} }
public static void show(CompanyCustomerEvaluationFormFileViewModel viewModel, Window window) { public static void show(CompanyCustomerEvaluationFormFileViewModel viewModel, Window window) {
String key = viewModel.getClass().getName() + "-" + viewModel.getId().get(); show(CompanyCustomerEvaluationFormFileWindowController.class, viewModel, window);
if (toFront(key)) {
return;
} }
FxmlUtils.newLoaderAsyncWithRunLater("/ui/company/customer/customer_evaluation_form.fxml", null, loader -> {
CompanyCustomerEvaluationFormFileWindowController controller = loader.getController();
controller.viewModel = viewModel;
controller.show(loader, window, Modality.NONE, key);
});
}
public Label idField; public Label idField;
public TextField filePathField; public TextField filePathField;
@@ -91,64 +106,66 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
public ScrollPane leftPane; public ScrollPane leftPane;
public Label totalCreditScoreLabel; public Label totalCreditScoreLabel;
private CompanyCustomerEvaluationFormFileViewModel viewModel;
private final SimpleStringProperty catalogProperty = new SimpleStringProperty("");
private final SimpleStringProperty levelProperty = new SimpleStringProperty("");
private final SimpleIntegerProperty score1Property = new SimpleIntegerProperty(-1);
private final SimpleIntegerProperty score2Property = new SimpleIntegerProperty(-1);
private final SimpleIntegerProperty score3Property = new SimpleIntegerProperty(-1);
private final SimpleIntegerProperty score4Property = new SimpleIntegerProperty(-1);
private final SimpleIntegerProperty score5Property = new SimpleIntegerProperty(-1);
private final SimpleIntegerProperty creditLevelProperty = new SimpleIntegerProperty(-1);
private final SimpleIntegerProperty totalCreditScoreProperty = new SimpleIntegerProperty(-1); private final SimpleIntegerProperty totalCreditScoreProperty = new SimpleIntegerProperty(-1);
private SimpleObjectProperty<Image> imageProperty = new SimpleObjectProperty<>(); private CustomerFileViewModel fileViewModel;
private CompletableFuture<CompanyCustomerEvaluationFormFileVo> loadedFuture; private SimpleBooleanProperty changed = new SimpleBooleanProperty(false);
@Lazy
@Autowired
private CompanyCustomerFileService companyCustomerFileService;
@Lazy @Lazy
@Autowired @Autowired
private CompanyCustomerEvaluationFormFileService evaluationFormFileService; private CompanyCustomerEvaluationFormFileService evaluationFormFileService;
@Override @Override
public void show(Stage stage) { public void show(Stage stage) {
super.show(stage); super.show(stage);
stage.setFullScreen(false); stage.setFullScreen(false);
// Rectangle2D screenBounds = Screen.getPrimary().getVisualBounds();
//
// stage.setX(screenBounds.getMinX());
// stage.setY(screenBounds.getMinY());
// stage.setWidth(screenBounds.getWidth());
// stage.setHeight(screenBounds.getHeight());
//
// stage.isMaximized();
stage.setMaximized(true); stage.setMaximized(true);
getTitle().set("客户评估表单");
} }
@Override @Override
public void onShown(WindowEvent windowEvent) { public void onShown(WindowEvent windowEvent) {
super.onShown(windowEvent); super.onShown(windowEvent);
if (logger.isDebugEnabled()) { getTitle().set("客户评估表单");
logger.debug("onShown");
} }
initializePane(); @Override
public ViewModelService<CompanyCustomerEvaluationFormFileVo, CompanyCustomerEvaluationFormFileViewModel> getViewModelService() {
return evaluationFormFileService;
}
loadedFuture = CompletableFuture.supplyAsync(() -> { @Override
int id = viewModel.getId().get(); protected void initializeData() {
CompanyCustomerFileVo customerFile = companyCustomerFileService.findById(id); CompanyCustomerEvaluationFormFileViewModel viewModel = new CompanyCustomerEvaluationFormFileViewModel();
CompanyCustomerEvaluationFormFileVo formFile = evaluationFormFileService.findByCustomerFile(customerFile); setViewModel(viewModel);
Platform.runLater(() -> update(formFile)); runAsync(() -> {
return formFile; CompanyCustomerEvaluationFormFileVo item = getCachedBean(CompanyCustomerEvaluationFormFileService.class)
.findByCustomerFile(fileViewModel.getId().get());
viewModel.getId().set(item.getId());
updateViewModel(item);
super.initializeData();
Platform.runLater(this::initializePane);
}); });
} }
@Override
protected void updateViewModel(CompanyCustomerEvaluationFormFileVo entity) {
super.updateViewModel(entity);
changed.set(false);
}
@Override
public BooleanBinding createTabSkinChangedBindings() {
return viewModel.getChanged().or(fileViewModel.getChanged()).or(changed);
}
@Override
public void saveTabSkins() {
save();
changed.setValue(false);
}
BiConsumer<ToggleGroup, String> stringRadioGroupUpdater = (group, newValue) -> { BiConsumer<ToggleGroup, String> stringRadioGroupUpdater = (group, newValue) -> {
if (newValue != null) { if (newValue != null) {
for (Toggle toggle : group.getToggles()) { for (Toggle toggle : group.getToggles()) {
@@ -164,14 +181,15 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
return; return;
} }
} }
changed.set(true);
} }
Platform.runLater(() -> { Platform.runLater(() -> {
group.selectToggle(null); group.selectToggle(null);
// Toggle first = group.getToggles().getFirst(); // Toggle first = group.getToggles().getFirst();
// first.setSelected(true); // first.setSelected(true);
// first.setSelected(false); // first.setSelected(false);
// RadioButton btn = (RadioButton) first; // RadioButton btn = (RadioButton) first;
// btn.setText(newValue); // btn.setText(newValue);
}); });
}; };
@@ -182,9 +200,11 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
} }
String data = (String) toggle.getUserData(); String data = (String) toggle.getUserData();
property.set(data); property.set(data);
changed.set(true);
}; };
BiConsumer<ToggleGroup, Number> numberRadioGroupUpdater = (group, newValue) -> { BiConsumer<ToggleGroup, Number> numberRadioGroupUpdater = (group, newValue) -> {
System.out.println("group = " + group + ", newValue = " + newValue);
String value = String.valueOf(newValue); String value = String.valueOf(newValue);
for (Toggle toggle : group.getToggles()) { for (Toggle toggle : group.getToggles()) {
String data = (String) toggle.getUserData(); String data = (String) toggle.getUserData();
@@ -195,12 +215,13 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
} }
Platform.runLater(() -> { Platform.runLater(() -> {
group.selectToggle(null); group.selectToggle(null);
// Toggle first = group.getToggles().getFirst(); // Toggle first = group.getToggles().getFirst();
// first.setSelected(true); // first.setSelected(true);
// first.setSelected(false); // first.setSelected(false);
// RadioButton btn = (RadioButton) first; // RadioButton btn = (RadioButton) first;
// btn.setText(String.valueOf(newValue)); // btn.setText(String.valueOf(newValue));
}); });
changed.set(true);
}; };
BiConsumer<SimpleIntegerProperty, Toggle> numberPropertyUpdater = (property, toggle) -> { BiConsumer<SimpleIntegerProperty, Toggle> numberPropertyUpdater = (property, toggle) -> {
@@ -210,9 +231,9 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
} }
String data = (String) toggle.getUserData(); String data = (String) toggle.getUserData();
property.set(Integer.parseInt(data)); property.set(Integer.parseInt(data));
changed.set(true);
}; };
int totalScoreToLevel(int score) { int totalScoreToLevel(int score) {
if (score >= 200) { if (score >= 200) {
return 4; return 4;
@@ -227,63 +248,73 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
} }
boolean calcValid() { boolean calcValid() {
if (creditLevelProperty.get() <= 0) { if (viewModel.getCreditLevel().get() <= 0) {
return false; return false;
} }
if (!StringUtils.hasText(catalogProperty.get())) { if (!StringUtils.hasText(viewModel.getCatalog().get())) {
return false; return false;
} }
if (!StringUtils.hasText(levelProperty.get())) { if (!StringUtils.hasText(viewModel.getLevel().get())) {
return false; return false;
} }
if (score1Property.get() <= 0) {
if (viewModel.getScore1().get() <= 0) {
return false; return false;
} }
if (score2Property.get() <= 0) { if (viewModel.getScore2().get() <= 0) {
return false; return false;
} }
if (score3Property.get() <= 0) { if (viewModel.getScore3().get() <= 0) {
return false; return false;
} }
if (score4Property.get() <= 0) { if (viewModel.getScore4().get() <= 0) {
return false; return false;
} }
if (score5Property.get() <= 0) { if (viewModel.getScore5().get() <= 0) {
return false; return false;
} }
if (creditLevelProperty.get() <= 0) { if (viewModel.getCreditLevel().get() <= 0) {
return false; return false;
} }
return true; return true;
} }
private void initializePane() { private void initializePane() {
setStatus("");
idField.textProperty().bind(viewModel.getId().asString()); idField.textProperty().bind(viewModel.getId().asString());
// filePathField.textProperty().bind(viewModel.getFilePath()); filePathField.textProperty().bind(fileViewModel.getFilePath());
// editFilePathField.textProperty().bind(viewModel.getEditFilePath()); editFilePathField.textProperty().bind(fileViewModel.getEditFilePath());
// signDateField.valueProperty().bindBidirectional(viewModel.getSignDate()); String pattern = "yyyy-MM-dd";
// validField.selectedProperty().bindBidirectional(viewModel.getValid()); DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
signDateField.setConverter(new LocalDateStringConverter(formatter, null));
initializeRadioGroup(catalog, catalogProperty); signDateField.valueProperty().bindBidirectional(fileViewModel.getSignDate());
initializeRadioGroup(level, levelProperty); signDateField.valueProperty().addListener((observable, oldValue, newValue) -> {
changed.set(true);
initializeRadioGroup(score1, score1Property);
initializeRadioGroup(score2, score2Property);
initializeRadioGroup(score3, score3Property);
initializeRadioGroup(score4, score4Property);
initializeRadioGroup(score5, score5Property);
creditLevelProperty.addListener((observable, oldValue, newValue) -> {
numberRadioGroupUpdater.accept(creditLevel, newValue);
}); });
SimpleIntegerProperty[] scores = new SimpleIntegerProperty[]{score1Property, score2Property, score3Property, score4Property, score5Property}; initializeRadioGroup(catalog, viewModel.getCatalog());
initializeRadioGroup(level, viewModel.getLevel());
initializeRadioGroup(score1, viewModel.getScore1());
initializeRadioGroup(score2, viewModel.getScore2());
initializeRadioGroup(score3, viewModel.getScore3());
initializeRadioGroup(score4, viewModel.getScore4());
initializeRadioGroup(score5, viewModel.getScore5());
// 信用等级
viewModel.getCreditLevel().addListener((observable, oldValue, newValue) -> {
numberRadioGroupUpdater.accept(creditLevel, newValue);
});
numberRadioGroupUpdater.accept(creditLevel, viewModel.getCreditLevel().get());
SimpleIntegerProperty[] scores = new SimpleIntegerProperty[]{viewModel.getScore1(), viewModel.getScore2(),
viewModel.getScore3(), viewModel.getScore4(), viewModel.getScore5()};
totalCreditScoreProperty.bind(Bindings.createIntegerBinding(() -> { totalCreditScoreProperty.bind(Bindings.createIntegerBinding(() -> {
int total = 0; int total = 0;
for (SimpleIntegerProperty score : scores) { for (SimpleIntegerProperty score : scores) {
total += score.get(); total += score.get();
} }
creditLevelProperty.set(totalScoreToLevel(total)); viewModel.getCreditLevel().set(totalScoreToLevel(total));
return total; return total;
}, scores)); }, scores));
@@ -291,27 +322,191 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
return "合计总分:" + score; return "合计总分:" + score;
})); }));
Bindings.createBooleanBinding(() -> { Bindings.createBooleanBinding(this::calcValid, viewModel.getCatalog(), viewModel.getLevel(),
boolean valid = calcValid(); viewModel.getScore1(), viewModel.getScore2(), viewModel.getScore3(), viewModel.getScore4(),
// viewModel.getValid().set(valid); viewModel.getScore5(), viewModel.getCreditLevel())
return valid; .addListener((observable, oldValue, newValue) -> {
}, catalogProperty, levelProperty, score1Property, score2Property, score3Property, score4Property, score5Property, creditLevelProperty).addListener(((observable, oldValue, newValue) -> { fileViewModel.getValid().set(newValue);
logger.info("valid:{}", newValue); changed.set(true);
})); });
validField.selectedProperty().bindBidirectional(fileViewModel.getValid());
validField.setSelected(fileViewModel.getValid().getValue());
fileViewModel.getFilePath().addListener((observable, oldValue, newValue) -> {
File file = new File(newValue);
loadFile(file);
});
if (StringUtils.hasText(fileViewModel.getFilePath().get())) {
loadFile(new File(fileViewModel.getFilePath().get()));
}
imageView.imageProperty().bind(viewModel.getFilePath().map(path -> { leftPane.widthProperty().addListener((observable, oldValue, newValue) -> {
if (FileUtils.withExtensions(path, FileUtils.PDF)) { Platform.runLater(() -> {
File pdfFile = new File(path); imageView.setFitWidth(leftPane.getWidth());
try (PDDocument pdDocument = Loader.loadPDF(pdfFile)) { 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); PDFRenderer pdfRenderer = new PDFRenderer(pdDocument);
BufferedImage bufferedImage = pdfRenderer.renderImageWithDPI(0, 300); BufferedImage bufferedImage = pdfRenderer.renderImageWithDPI(0, 300);
// 获取 BufferedImage 的宽度和高度 // 获取 BufferedImage 的宽度和高度
int width = bufferedImage.getWidth(); int width = bufferedImage.getWidth();
int height = bufferedImage.getHeight(); int height = bufferedImage.getHeight();
WritableImage writableImage = new WritableImage(width, height); canvas.resize(width, height);
PixelWriter pixelWriter = writableImage.getPixelWriter(); GraphicsContext graphic = canvas.getGraphicsContext2D();
WritableImage writableImage1 = new WritableImage(width, height);
PixelWriter pixelWriter = writableImage1.getPixelWriter();
// 将 BufferedImage 的像素数据复制到 WritableImage // 将 BufferedImage 的像素数据复制到 WritableImage
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) { for (int x = 0; x < width; x++) {
@@ -319,42 +514,13 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
pixelWriter.setArgb(x, y, argb); pixelWriter.setArgb(x, y, argb);
} }
} }
return writableImage; setStatus();
Platform.runLater(() -> {
imageView.setImage(writableImage1);
});
} catch (Exception e) { } catch (Exception e) {
setStatus(e.getMessage()); setStatus(e.getMessage());
} }
return null;
} else {
File file = new File(path);
Image image = new Image(file.toURI().toString());
return image;
}
}));
leftPane.widthProperty().addListener((observable, oldValue, newValue) -> {
Platform.runLater(() -> {
imageView.setFitWidth(leftPane.getWidth());
imageView.setFitHeight(leftPane.getHeight());
});
});
imageView.setFitWidth(leftPane.getWidth());
imageView.setFitHeight(leftPane.getHeight());
imageView.setOnScroll(event -> {
System.out.println("event = " + event);
System.out.println("event.getDeltaY() = " + event.getDeltaY());
Bounds bounds = imageView.getBoundsInLocal();
// Bounds latestBounds = (Bounds) imageView.getProperties().get("latestBounds");
// if (latestBounds != null) {
// double latestBoundsWidth = latestBounds.getWidth();
// }
// if (bounds.getWidth() < leftPane.getWidth()) {
imageView.setFitWidth(bounds.getWidth() + event.getDeltaY());
// } else {
imageView.setFitHeight(bounds.getHeight() + event.getDeltaY());
// }
}); });
} }
@@ -365,6 +531,7 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
toggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> { toggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
stringPropertyUpdater.accept(property, newValue); stringPropertyUpdater.accept(property, newValue);
}); });
stringRadioGroupUpdater.accept(toggleGroup, property.getValue());
} }
private void initializeRadioGroup(ToggleGroup toggleGroup, SimpleIntegerProperty property) { private void initializeRadioGroup(ToggleGroup toggleGroup, SimpleIntegerProperty property) {
@@ -375,21 +542,8 @@ public class CompanyCustomerEvaluationFormFileWindowController extends BaseContr
toggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> { toggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
numberPropertyUpdater.accept(property, newValue); numberPropertyUpdater.accept(property, newValue);
}); });
numberRadioGroupUpdater.accept(toggleGroup, property.getValue());
} }
private void update(CompanyCustomerEvaluationFormFileVo formFile) {
viewModel.update(formFile);
// formFile.getScoreTemplateVersion();
catalogProperty.set(formFile.getCatalog());
levelProperty.set(formFile.getLevel());
score1Property.set(formFile.getScore1());
score2Property.set(formFile.getScore2());
score3Property.set(formFile.getScore3());
score4Property.set(formFile.getScore4());
score5Property.set(formFile.getScore5());
creditLevelProperty.set(formFile.getCreditLevel());
}
} }

View File

@@ -1,240 +0,0 @@
package com.ecep.contract.controller.customer;
import static com.ecep.contract.util.ExcelUtils.setCellValue;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.time.LocalDate;
import java.util.Comparator;
import java.util.List;
import com.ecep.contract.service.*;
import com.ecep.contract.task.Tasker;
import com.ecep.contract.util.CompanyUtils;
import com.ecep.contract.vo.CompanyCustomerFileVo;
import com.ecep.contract.vo.CompanyCustomerVo;
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.CellRangeAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import com.ecep.contract.CustomerFileType;
import com.ecep.contract.MessageHolder;
import com.ecep.contract.SpringApp;
import com.ecep.contract.vo.CloudTycVo;
import com.ecep.contract.vo.CompanyVo;
import com.ecep.contract.vo.CompanyCustomerEvaluationFormFileVo;
import lombok.Setter;
public class CompanyCustomerEvaluationFormUpdateTask extends Tasker<Object> {
private static final Logger logger = LoggerFactory.getLogger(CompanyCustomerEvaluationFormUpdateTask.class);
@Setter
private CompanyCustomerVo customer;
@Setter
private CompanyService companyService;
private CompanyContactService companyContactService;
private CompanyCustomerService companyCustomerService;
@Setter
private CompanyCustomerFileService companyCustomerFileService;
private CompanyCustomerService getCompanyCustomerService() {
if (companyCustomerService == null) {
companyCustomerService = SpringApp.getBean(CompanyCustomerService.class);
}
return companyCustomerService;
}
private CompanyContactService getCompanyContactService() {
if (companyContactService == null) {
companyContactService = SpringApp.getBean(CompanyContactService.class);
}
return companyContactService;
}
private CompanyCustomerFileService getCompanyCustomerFileService() {
if (companyCustomerFileService == null) {
companyCustomerFileService = SpringApp.getBean(CompanyCustomerFileService.class);
}
return companyCustomerFileService;
}
private CompanyCustomerEvaluationFormFileService getCompanyCustomerEvaluationFormFileService() {
return getBean(CompanyCustomerEvaluationFormFileService.class);
}
private File getEvaluationFormTemplate() {
return getCompanyCustomerFileService().getEvaluationFormTemplate();
}
@Override
protected Object execute(MessageHolder holder) throws Exception {
try {
updateEvaluationForm(holder);
} catch (Exception ex) {
updateMessage(ex.getMessage());
}
return null;
}
public void updateEvaluationForm(MessageHolder holder) {
if (!StringUtils.hasText(customer.getPath())) {
holder.error("供应商目录未设置,请先设置供应商目录");
return;
}
File template = getEvaluationFormTemplate();
if (template == null) {
holder.error("评价表模板文件未设置,请先设置评价表模板文件");
return;
}
if (!template.exists()) {
holder.error("评价表模板文件 " + template.getAbsolutePath() + " 不存在,请检查");
return;
}
File dir = new File(customer.getPath());
String template_file_name = template.getName();
File destFile = new File(dir, template_file_name);
if (destFile.exists()) {
holder.info("表单文件已经存在," + destFile.getName());
try (
InputStream inp = new FileInputStream(destFile);
Workbook wb = WorkbookFactory.create(inp)) {
updateEvaluationForm(wb, destFile, holder);
holder.info("评价表已更新");
} catch (Exception e) {
holder.error(e.getMessage());
logger.error(e.getMessage(), e);
}
} else {
holder.info("根据模板 " + template_file_name + " 创建表单 " + destFile.getName());
try (
InputStream inp = new FileInputStream(template);
Workbook wb = WorkbookFactory.create(inp)) {
updateEvaluationForm(wb, destFile, holder);
holder.info("评价表已创建");
CompanyCustomerFileVo customerFile = new CompanyCustomerFileVo();
customerFile.setCustomer(customer.getId());
customerFile.setFilePath(destFile.getAbsolutePath());
customerFile.setType(CustomerFileType.General);
save(customerFile);
} catch (Exception e) {
holder.error(e.getMessage());
logger.error(e.getMessage(), e);
}
}
updateProgress(1, 1);
}
private void save(CompanyCustomerFileVo customerFile) {
getCompanyCustomerFileService().save(customerFile);
}
/**
* 更新客户评估表,依据模板创建,如果已经存在生成的文件,则更新评估表
*
* @param wb work book
* @param destFile 目标文件
*/
public void updateEvaluationForm(Workbook wb, File destFile, MessageHolder holder) throws IOException {
Integer companyId = customer.getCompanyId();
CompanyVo company = getCompanyService().findById(companyId);
Sheet sheet = wb.getSheetAt(0);
updateSheet(company, sheet, holder.sub(" - "));
updateProgress(900, 1000);
// 输出到文件
try (OutputStream fileOut = new FileOutputStream(destFile)) {
wb.write(fileOut);
} catch (FileNotFoundException e) {
holder.error("写评估表时发生文件错误,请检查评估表是否被打开中:" + e.getMessage());
logger.error(e.getMessage(), e);
}
}
private void updateSheet(CompanyVo company, Sheet sheet, MessageHolder holder) {
setCellValue(sheet, "B3", "客户编号:" + CompanyUtils.formatCompanyVendorId(customer.getId()));
setCellValue(sheet, "B4", "客户名称:" + company.getName());
LocalDate suggestDate = getCompanyCustomerFileService().getNextSignDate(customer, holder);
if (suggestDate == null) {
suggestDate = LocalDate.now();
}
setCellValue(sheet, "H3", "评定时间:" + suggestDate);
setCellValue(sheet, "H4", "统一社会信用代码:");
setCellValue(sheet, "H5", company.getUniscid());
// 注册所属地
setCellValue(sheet, "B5", "注册所属地:" + company.getDistrict());
// 经营状态
setCellValue(sheet, "D6", "经营状态:" + company.getEntStatus());
// 成立日期
setCellValue(sheet, "H6", "成立日期:" + company.getSetupDate());
// 所属行业
setCellValue(sheet, "D7", "所属行业:" + company.getIndustry());
setCellValue(sheet, "D8",
"注册资金:" + company.getRegisteredCapital() + " " + company.getRegisteredCapitalCurrency());
// 企业类型
setCellValue(sheet, "H10", "企业类型:" + company.getEntType());
// 天眼评分
CloudTycService cloudTycService = SpringApp.getBean(CloudTycService.class);
CloudTycVo cloudTyc = cloudTycService.getOrCreateCloudTyc(company);
setCellValue(sheet, "D10", "天眼评分:" + (cloudTyc.getScore() > 0 ? cloudTyc.getScore() : ""));
// 检索评估表
List<CompanyCustomerFileVo> customerFiles = getCompanyCustomerFileService().findAllByCustomerAndType(customer,
CustomerFileType.EvaluationForm);
List<CompanyCustomerEvaluationFormFileVo> filteredList = customerFiles.stream().filter(file -> {
return file.getSignDate() != null && file.isValid();
})
.sorted(Comparator.comparing(CompanyCustomerFileVo::getSignDate))
.map(getCompanyCustomerEvaluationFormFileService()::findByCustomerFile)
.toList();
if (filteredList.isEmpty()) {
setCellValue(sheet, "C40", "首次评价");
try {
sheet.addMergedRegion(CellRangeAddress.valueOf("C40:K40"));
} catch (Exception ignored) {
}
} else {
setCellValue(sheet, "C40", "评价日期");
try {
sheet.addMergedRegion(CellRangeAddress.valueOf("C40:D40"));
} catch (Exception ignored) {
}
setCellValue(sheet, "E40", "经济指标");
setCellValue(sheet, "F40", "综合指标");
setCellValue(sheet, "G40", "资信等级");
String[] CreditLevelTitles = new String[] { "-", "差★", " 一般★★", " 较好★★★", " 好★★★★", " " };
int baseRow = 40;
for (CompanyCustomerEvaluationFormFileVo form : filteredList) {
CompanyCustomerFileVo customerFile = getCompanyCustomerFileService().findById(form.getCustomerFile());
setCellValue(sheet, baseRow, 2, String.valueOf(customerFile.getSignDate()));
setCellValue(sheet, baseRow, 4, form.getCatalog());
setCellValue(sheet, baseRow, 5, form.getLevel());
if (form.getCreditLevel() == null) {
setCellValue(sheet, baseRow, 6, "-");
} else {
setCellValue(sheet, baseRow, 6, CreditLevelTitles[form.getCreditLevel()]);
}
try {
sheet.addMergedRegion(new CellRangeAddress(baseRow, baseRow, 2, 3));
} catch (Exception ignored) {
}
baseRow++;
}
}
}
}

View File

@@ -34,14 +34,14 @@ import com.ecep.contract.MyDateTimeUtils;
import com.ecep.contract.service.CompanyCustomerEntityService; import com.ecep.contract.service.CompanyCustomerEntityService;
import com.ecep.contract.service.CompanyCustomerEvaluationFormFileService; import com.ecep.contract.service.CompanyCustomerEvaluationFormFileService;
import com.ecep.contract.service.CompanyCustomerFileService; import com.ecep.contract.service.CompanyCustomerFileService;
import com.ecep.contract.service.CompanyCustomerService; import com.ecep.contract.service.CustomerService;
import com.ecep.contract.task.Tasker; import com.ecep.contract.task.Tasker;
import com.ecep.contract.util.ExcelUtils; import com.ecep.contract.util.ExcelUtils;
import com.ecep.contract.util.UITools; import com.ecep.contract.util.UITools;
import com.ecep.contract.vo.CompanyCustomerEntityVo; import com.ecep.contract.vo.CompanyCustomerEntityVo;
import com.ecep.contract.vo.CompanyCustomerEvaluationFormFileVo; import com.ecep.contract.vo.CompanyCustomerEvaluationFormFileVo;
import com.ecep.contract.vo.CompanyCustomerFileVo; import com.ecep.contract.vo.CustomerFileVo;
import com.ecep.contract.vo.CompanyCustomerVo; import com.ecep.contract.vo.CustomerVo;
import com.ecep.contract.vo.CompanyVo; import com.ecep.contract.vo.CompanyVo;
import lombok.Setter; import lombok.Setter;
@@ -52,14 +52,14 @@ public class CompanyCustomerExportExcelTasker extends Tasker<Object> {
@Setter @Setter
File destFile; File destFile;
CompanyCustomerService customerService; CustomerService customerService;
CompanyCustomerEntityService customerEntityService; CompanyCustomerEntityService customerEntityService;
CompanyCustomerFileService customerFileService; CompanyCustomerFileService customerFileService;
CompanyCustomerEvaluationFormFileService customerEvaluationFormFileService; CompanyCustomerEvaluationFormFileService customerEvaluationFormFileService;
CompanyCustomerService getCustomerService() { CustomerService getCustomerService() {
if (customerService == null) if (customerService == null)
customerService = getBean(CompanyCustomerService.class); customerService = getBean(CustomerService.class);
return customerService; return customerService;
} }
@@ -118,7 +118,7 @@ public class CompanyCustomerExportExcelTasker extends Tasker<Object> {
setCellValue(sheet, "A19", "Build by CMS @ " + MyDateTimeUtils.format(LocalDateTime.now())); setCellValue(sheet, "A19", "Build by CMS @ " + MyDateTimeUtils.format(LocalDateTime.now()));
int rowIndex = 0; int rowIndex = 0;
for (CompanyCustomerVo customer : getCustomerService().findAll(null, Pageable.unpaged())) { for (CustomerVo customer : getCustomerService().findAll(null, Pageable.unpaged())) {
Integer companyId = customer.getCompanyId(); Integer companyId = customer.getCompanyId();
; ;
CompanyVo company = getCompanyService().findById(companyId); CompanyVo company = getCompanyService().findById(companyId);
@@ -136,7 +136,7 @@ public class CompanyCustomerExportExcelTasker extends Tasker<Object> {
} }
} }
CompanyCustomerFileVo customerFile = getCustomerFileService() CustomerFileVo customerFile = getCustomerFileService()
.findAllByCustomer(customer).stream().filter(v -> v.isValid()) .findAllByCustomer(customer).stream().filter(v -> v.isValid())
.max(Comparator.comparing(v -> v.getSignDate())).orElse(null); .max(Comparator.comparing(v -> v.getSignDate())).orElse(null);

View File

@@ -3,17 +3,19 @@ package com.ecep.contract.controller.customer;
import com.ecep.contract.MyDateTimeUtils; import com.ecep.contract.MyDateTimeUtils;
import com.ecep.contract.controller.AbstEntityManagerSkin; import com.ecep.contract.controller.AbstEntityManagerSkin;
import com.ecep.contract.controller.table.cell.CompanyTableCell; import com.ecep.contract.controller.table.cell.CompanyTableCell;
import com.ecep.contract.service.CompanyCustomerService; 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.CompanyService;
import com.ecep.contract.service.CustomerCatalogService;
import com.ecep.contract.vm.CompanyCustomerViewModel; import com.ecep.contract.vm.CompanyCustomerViewModel;
import com.ecep.contract.vo.CompanyCustomerVo; import com.ecep.contract.vo.CustomerVo;
import com.ecep.contract.vo.CompanyVo; import com.ecep.contract.vo.CompanyVo;
import javafx.application.Platform; import javafx.application.Platform;
public class CompanyCustomerManagerSkin public class CompanyCustomerManagerSkin
extends extends
AbstEntityManagerSkin<CompanyCustomerVo, CompanyCustomerViewModel, CompanyCustomerManagerSkin, CompanyCustomerManagerWindowController> { AbstEntityManagerSkin<CustomerVo, CompanyCustomerViewModel, CompanyCustomerManagerSkin, CompanyCustomerManagerWindowController> {
public CompanyCustomerManagerSkin(CompanyCustomerManagerWindowController controller) { public CompanyCustomerManagerSkin(CompanyCustomerManagerWindowController controller) {
super(controller); super(controller);
@@ -23,7 +25,7 @@ public class CompanyCustomerManagerSkin
return getBean(CompanyService.class); return getBean(CompanyService.class);
} }
public CompanyCustomerService getCompanyCustomerService() { public CustomerService getCompanyCustomerService() {
return controller.getViewModelService(); return controller.getViewModelService();
} }
@@ -31,7 +33,10 @@ public class CompanyCustomerManagerSkin
public void initializeTable() { public void initializeTable() {
controller.idColumn.setCellValueFactory(param -> param.getValue().getId()); controller.idColumn.setCellValueFactory(param -> param.getValue().getId());
controller.companyColumn.setCellValueFactory(param -> param.getValue().getCompany()); controller.companyColumn.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.developDateColumn.setCellValueFactory(param -> param.getValue().getDevelopDate());
controller.pathColumn.setCellValueFactory(param -> param.getValue().getPath()); controller.pathColumn.setCellValueFactory(param -> param.getValue().getPath());

View File

@@ -5,7 +5,7 @@ import java.time.LocalDate;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import com.ecep.contract.vo.CompanyCustomerVo; import com.ecep.contract.vo.CustomerVo;
import com.ecep.contract.vo.CompanyVo; import com.ecep.contract.vo.CompanyVo;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
@@ -17,7 +17,7 @@ import org.springframework.stereotype.Component;
import com.ecep.contract.MyDateTimeUtils; import com.ecep.contract.MyDateTimeUtils;
import com.ecep.contract.controller.AbstManagerWindowController; import com.ecep.contract.controller.AbstManagerWindowController;
import com.ecep.contract.service.CompanyCustomerService; import com.ecep.contract.service.CustomerService;
import com.ecep.contract.service.CompanyService; import com.ecep.contract.service.CompanyService;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.UITools; import com.ecep.contract.util.UITools;
@@ -44,7 +44,7 @@ import javafx.stage.Stage;
@Component @Component
@FxmlPath("/ui/company/customer/customer_manager.fxml") @FxmlPath("/ui/company/customer/customer_manager.fxml")
public class CompanyCustomerManagerWindowController public class CompanyCustomerManagerWindowController
extends AbstManagerWindowController<CompanyCustomerVo, CompanyCustomerViewModel, CompanyCustomerManagerSkin> { extends AbstManagerWindowController<CustomerVo, CompanyCustomerViewModel, CompanyCustomerManagerSkin> {
// columns // columns
public TableColumn<CompanyCustomerViewModel, Number> idColumn; public TableColumn<CompanyCustomerViewModel, Number> idColumn;
@@ -52,18 +52,17 @@ public class CompanyCustomerManagerWindowController
* 客户所属公司,Company * 客户所属公司,Company
*/ */
public TableColumn<CompanyCustomerViewModel, Integer> companyColumn; public TableColumn<CompanyCustomerViewModel, Integer> companyColumn;
public TableColumn<CompanyCustomerViewModel, String> catalogColumn; public TableColumn<CompanyCustomerViewModel, Integer> catalogColumn;
public TableColumn<CompanyCustomerViewModel, LocalDate> developDateColumn; public TableColumn<CompanyCustomerViewModel, LocalDate> developDateColumn;
public TableColumn<CompanyCustomerViewModel, String> pathColumn; public TableColumn<CompanyCustomerViewModel, String> pathColumn;
public TableColumn<CompanyCustomerViewModel, String> createdColumn; public TableColumn<CompanyCustomerViewModel, String> createdColumn;
@Override @Override
public CompanyCustomerService getViewModelService() { public CustomerService getViewModelService() {
return getCachedBean(CompanyCustomerService.class); return getCachedBean(CustomerService.class);
} }
@Autowired
private CompanyService getCompanyService() { private CompanyService getCompanyService() {
return getCachedBean(CompanyService.class); return getCachedBean(CompanyService.class);
} }
@@ -118,11 +117,11 @@ public class CompanyCustomerManagerWindowController
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {
Pageable pageRequest = PageRequest.ofSize(50); Pageable pageRequest = PageRequest.ofSize(50);
while (!canceled.get()) { while (!canceled.get()) {
Page<CompanyCustomerVo> page = getViewModelService().findAll(null, pageRequest); Page<CustomerVo> page = getViewModelService().findAll(null, pageRequest);
int index = page.getNumber() * page.getSize(); int index = page.getNumber() * page.getSize();
int i = 1; int i = 1;
for (CompanyCustomerVo companyCustomer : page) { for (CustomerVo companyCustomer : page) {
if (canceled.get()) { if (canceled.get()) {
return; return;
} }

View File

@@ -12,11 +12,11 @@ import org.springframework.util.StringUtils;
import com.ecep.contract.DesktopUtils; import com.ecep.contract.DesktopUtils;
import com.ecep.contract.controller.AbstEntityController; import com.ecep.contract.controller.AbstEntityController;
import com.ecep.contract.service.CompanyContactService; import com.ecep.contract.service.CompanyContactService;
import com.ecep.contract.service.CompanyCustomerService; import com.ecep.contract.service.CustomerService;
import com.ecep.contract.service.CompanyService; import com.ecep.contract.service.CompanyService;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.vm.CompanyCustomerViewModel; import com.ecep.contract.vm.CompanyCustomerViewModel;
import com.ecep.contract.vo.CompanyCustomerVo; import com.ecep.contract.vo.CustomerVo;
import com.ecep.contract.vo.CompanyVo; import com.ecep.contract.vo.CompanyVo;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
@@ -36,13 +36,13 @@ import javafx.stage.WindowEvent;
@Scope("prototype") @Scope("prototype")
@Component @Component
@FxmlPath("/ui/company/customer/customer.fxml") @FxmlPath("/ui/company/customer/customer.fxml")
public class CompanyCustomerWindowController extends AbstEntityController<CompanyCustomerVo, CompanyCustomerViewModel> { public class CompanyCustomerWindowController extends AbstEntityController<CustomerVo, CompanyCustomerViewModel> {
private static final Logger logger = LoggerFactory.getLogger(CompanyCustomerWindowController.class); private static final Logger logger = LoggerFactory.getLogger(CompanyCustomerWindowController.class);
/** /**
* 显示界面 * 显示界面
*/ */
public static void show(CompanyCustomerVo customer, Window window) { public static void show(CustomerVo customer, Window window) {
show(CompanyCustomerWindowController.class, CompanyCustomerViewModel.from(customer), window); show(CompanyCustomerWindowController.class, CompanyCustomerViewModel.from(customer), window);
} }
@@ -80,15 +80,15 @@ public class CompanyCustomerWindowController extends AbstEntityController<Compan
@Override @Override
protected void registerTabSkins() { protected void registerTabSkins() {
registerTabSkin(baseInfoTab, tab -> new CompanyCustomerTabSkinBase(this)); registerTabSkin(baseInfoTab, tab -> new CustomerTabSkinBase(this));
registerTabSkin(fileTab, tab -> new CustomerTabSkinFile(this)); registerTabSkin(fileTab, tab -> new CustomerTabSkinFile(this));
registerTabSkin(entityTab, tab -> new CustomerTabSkinEntity(this)); registerTabSkin(entityTab, tab -> new CustomerTabSkinEntity(this));
registerTabSkin(satisfactionTab, tab -> new CustomerTabSkinSatisfactionSurvey(this)); registerTabSkin(satisfactionTab, tab -> new CustomerTabSkinSatisfactionSurvey(this));
} }
@Override @Override
public CompanyCustomerService getViewModelService() { public CustomerService getViewModelService() {
return getCachedBean(CompanyCustomerService.class); return getCachedBean(CustomerService.class);
} }
public CompanyService getCompanyService() { public CompanyService getCompanyService() {

View File

@@ -1,36 +1,42 @@
package com.ecep.contract.controller.customer; package com.ecep.contract.controller.customer;
import java.io.File;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.List;
import org.springframework.util.StringUtils;
import com.ecep.contract.MyDateTimeUtils; import com.ecep.contract.MyDateTimeUtils;
import com.ecep.contract.SpringApp;
import com.ecep.contract.controller.company.CompanyWindowController; import com.ecep.contract.controller.company.CompanyWindowController;
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin; import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
import com.ecep.contract.controller.tab.TabSkin; import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.converter.CompanyStringConverter;
import com.ecep.contract.converter.EntityStringConverter;
import com.ecep.contract.vo.CompanyVo;
import com.ecep.contract.vo.CompanyContactVo;
import com.ecep.contract.vo.CompanyCustomerVo;
import com.ecep.contract.service.CompanyContactService; import com.ecep.contract.service.CompanyContactService;
import com.ecep.contract.service.CompanyCustomerService; import com.ecep.contract.service.CustomerService;
import com.ecep.contract.service.CompanyService; import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.QueryService;
import com.ecep.contract.util.UITools; import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.CompanyContactViewModel;
import com.ecep.contract.vm.CompanyCustomerViewModel; 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.binding.Bindings;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.stage.DirectoryChooser;
import javafx.util.StringConverter;
import javafx.util.converter.LocalDateStringConverter; import javafx.util.converter.LocalDateStringConverter;
public class CompanyCustomerTabSkinBase public class CustomerTabSkinBase
extends AbstEntityBasedTabSkin<CompanyCustomerWindowController, CompanyCustomerVo, CompanyCustomerViewModel> extends AbstEntityBasedTabSkin<CompanyCustomerWindowController, CustomerVo, CompanyCustomerViewModel>
implements TabSkin { implements TabSkin {
public CompanyCustomerTabSkinBase(CompanyCustomerWindowController controller) { public CustomerTabSkinBase(CompanyCustomerWindowController controller) {
super(controller); super(controller);
} }
@@ -44,7 +50,25 @@ public class CompanyCustomerTabSkinBase
initializeCompanyFieldAutoCompletion(controller.companyField); initializeCompanyFieldAutoCompletion(controller.companyField);
initializeContactFieldAutoCompletion(controller.contactField); initializeContactFieldAutoCompletion(controller.contactField);
UITools.autoCompletion(controller.contactField, viewModel.getContact(), getCompanyContactService()); 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); LocalDateStringConverter converter = new LocalDateStringConverter(DateTimeFormatter.ISO_LOCAL_DATE, null);
@@ -92,7 +116,7 @@ public class CompanyCustomerTabSkinBase
} }
public void onCompanyCustomerCreatePathAction(ActionEvent event) { public void onCompanyCustomerCreatePathAction(ActionEvent event) {
CompanyCustomerVo companyCustomer = getCompanyCustomerService().findById(viewModel.getId().get()); CustomerVo companyCustomer = getCompanyCustomerService().findById(viewModel.getId().get());
if (getCompanyCustomerService().makePathAbsent(companyCustomer)) { if (getCompanyCustomerService().makePathAbsent(companyCustomer)) {
companyCustomer = getCompanyCustomerService().save(companyCustomer); companyCustomer = getCompanyCustomerService().save(companyCustomer);
viewModel.update(companyCustomer); viewModel.update(companyCustomer);
@@ -102,15 +126,41 @@ public class CompanyCustomerTabSkinBase
} }
public void onCompanyCustomerChangePathAction(ActionEvent event) { public void onCompanyCustomerChangePathAction(ActionEvent event) {
setStatus("未实现"); 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) { public void onCompanyCustomerPathSameAsNameAction(ActionEvent event) {
setStatus("未实现"); setStatus("未实现");
} }
public CompanyCustomerService getCompanyCustomerService() { public CustomerService getCompanyCustomerService() {
return controller.getCachedBean(CompanyCustomerService.class); return controller.getCachedBean(CustomerService.class);
} }
public CompanyContactService getCompanyContactService() { public CompanyContactService getCompanyContactService() {

View File

@@ -12,7 +12,7 @@ import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.ParamUtils; import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.vm.CustomerEntityViewModel; import com.ecep.contract.vm.CustomerEntityViewModel;
import com.ecep.contract.vo.CompanyCustomerEntityVo; import com.ecep.contract.vo.CompanyCustomerEntityVo;
import com.ecep.contract.vo.CompanyCustomerVo; import com.ecep.contract.vo.CustomerVo;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
@@ -89,7 +89,7 @@ public class CustomerTabSkinEntity
} }
@Override @Override
public ParamUtils.Builder getSpecification(CompanyCustomerVo parent) { public ParamUtils.Builder getSpecification(CustomerVo parent) {
ParamUtils.Builder params = getSpecification(); ParamUtils.Builder params = getSpecification();
params.equals("customer", parent.getId()); params.equals("customer", parent.getId());
return params; return params;

View File

@@ -4,45 +4,40 @@ import java.io.File;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import com.ecep.contract.CustomerFileType; import com.ecep.contract.CustomerFileType;
import com.ecep.contract.DesktopUtils; import com.ecep.contract.DesktopUtils;
import com.ecep.contract.Message;
import com.ecep.contract.MyDateTimeUtils; import com.ecep.contract.MyDateTimeUtils;
import com.ecep.contract.SpringApp; import com.ecep.contract.SpringApp;
import com.ecep.contract.constant.CompanyCustomerConstant; 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.EditableEntityTableTabSkin;
import com.ecep.contract.model.BaseEnumEntity; 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.CompanyCustomerEvaluationFormFileService;
import com.ecep.contract.service.CompanyCustomerFileService; import com.ecep.contract.service.CompanyCustomerFileService;
import com.ecep.contract.service.CompanyCustomerFileTypeService; import com.ecep.contract.service.CompanyCustomerFileTypeService;
import com.ecep.contract.service.CompanyCustomerService; import com.ecep.contract.service.CustomerService;
import com.ecep.contract.task.CustomerFileMoveTasker;
import com.ecep.contract.util.FileUtils; import com.ecep.contract.util.FileUtils;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.ParamUtils; import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.util.UITools; import com.ecep.contract.util.UITools;
import com.ecep.contract.vm.CompanyCustomerFileViewModel; import com.ecep.contract.vm.CustomerFileViewModel;
import com.ecep.contract.vo.CompanyCustomerEvaluationFormFileVo; import com.ecep.contract.vo.CompanyCustomerEvaluationFormFileVo;
import com.ecep.contract.vo.CompanyCustomerFileVo; import com.ecep.contract.vo.CustomerFileVo;
import com.ecep.contract.vo.CompanyCustomerVo;
import com.ecep.contract.vo.CompanyVo; import com.ecep.contract.vo.CompanyVo;
import com.ecep.contract.vo.CustomerFileTypeLocalVo; import com.ecep.contract.vo.CustomerVo;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.collections.ObservableMap;
import javafx.concurrent.Task;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.ButtonType; import javafx.scene.control.ButtonType;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn; import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView; import javafx.scene.control.TableView;
import javafx.scene.control.cell.CheckBoxTableCell; import javafx.scene.control.cell.CheckBoxTableCell;
@@ -52,19 +47,19 @@ import lombok.Setter;
@FxmlPath("/ui/company/customer/customer-tab-file.fxml") @FxmlPath("/ui/company/customer/customer-tab-file.fxml")
public class CustomerTabSkinFile public class CustomerTabSkinFile
extends AbstCompanyCustomerTableTabSkin<CompanyCustomerFileVo, CompanyCustomerFileViewModel> extends AbstCompanyCustomerTableTabSkin<CustomerFileVo, CustomerFileViewModel>
implements EditableEntityTableTabSkin<CompanyCustomerFileVo, CompanyCustomerFileViewModel> { implements EditableEntityTableTabSkin<CustomerFileVo, CustomerFileViewModel> {
@Setter @Setter
private CompanyCustomerFileService companyCustomerFileService; private CompanyCustomerFileService companyCustomerFileService;
public TableColumn<CompanyCustomerFileViewModel, Number> fileTable_idColumn; public TableColumn<CustomerFileViewModel, Number> fileTable_idColumn;
public TableColumn<CompanyCustomerFileViewModel, String> fileTable_typeColumn; public TableColumn<CustomerFileViewModel, CustomerFileType> fileTable_typeColumn;
public TableColumn<CompanyCustomerFileViewModel, String> fileTable_filePathColumn; public TableColumn<CustomerFileViewModel, String> fileTable_filePathColumn;
public TableColumn<CompanyCustomerFileViewModel, String> fileTable_editFilePathColumn; public TableColumn<CustomerFileViewModel, String> fileTable_editFilePathColumn;
public TableColumn<CompanyCustomerFileViewModel, LocalDate> fileTable_signDateColumn; public TableColumn<CustomerFileViewModel, LocalDate> fileTable_signDateColumn;
public TableColumn<CompanyCustomerFileViewModel, Boolean> fileTable_validColumn; public TableColumn<CustomerFileViewModel, Boolean> fileTable_validColumn;
public TableColumn<CompanyCustomerFileViewModel, String> fileTable_descriptionColumn; public TableColumn<CustomerFileViewModel, String> fileTable_descriptionColumn;
public Button fileTable_reBuildBtn; public Button fileTable_reBuildBtn;
public Button fileTable_updateEvaluationFormBuildBtn; public Button fileTable_updateEvaluationFormBuildBtn;
@@ -90,8 +85,12 @@ public class CustomerTabSkinFile
return getCompanyCustomerFileService(); return getCompanyCustomerFileService();
} }
public CompanyCustomerEvaluationFormFileService getEvaluationFormFileService() {
return getCachedBean(CompanyCustomerEvaluationFormFileService.class);
}
@Override @Override
public ParamUtils.Builder getSpecification(CompanyCustomerVo parent) { public ParamUtils.Builder getSpecification(CustomerVo parent) {
ParamUtils.Builder params = getSpecification(); ParamUtils.Builder params = getSpecification();
params.equals("customer", parent.getId()); params.equals("customer", parent.getId());
return params; return params;
@@ -101,20 +100,20 @@ public class CustomerTabSkinFile
public void initializeTable() { public void initializeTable() {
super.initializeTable(); super.initializeTable();
TableView<CompanyCustomerFileViewModel> table = getTableView(); TableView<CustomerFileViewModel> table = getTableView();
table.disableProperty().bind(viewModel.getPath().isEmpty()); table.disableProperty().bind(viewModel.getPath().isEmpty());
fileTable_idColumn.setCellValueFactory(param -> param.getValue().getId()); fileTable_idColumn.setCellValueFactory(param -> param.getValue().getId());
CompanyCustomerFileTypeService fileTypeService = getCachedBean(CompanyCustomerFileTypeService.class); CompanyCustomerFileTypeService fileTypeService = getCachedBean(CompanyCustomerFileTypeService.class);
ObservableMap<CustomerFileType, CustomerFileTypeLocalVo> observableMapByLocal = FXCollections fileTable_typeColumn.setCellValueFactory(param -> param.getValue().getType());
.observableMap(fileTypeService.findAll(getLocale())); fileTable_typeColumn.setCellFactory(CompanyCustomerFileTableTypeTableCell.forTableColumn(fileTypeService));
fileTable_typeColumn.setCellValueFactory(param -> Bindings.valueAt(observableMapByLocal,
param.getValue().getType()).map(BaseEnumEntity::getValue));
fileTable_filePathColumn.setCellValueFactory(param -> param.getValue().getFilePath()); fileTable_filePathColumn.setCellValueFactory(param -> param.getValue().getFilePath());
fileTable_filePathColumn.setCellFactory(param -> new FileTableFilePathTableCell()); fileTable_filePathColumn.setCellFactory(FilePathTableCell.forTableColumn(viewModel.getPath()));
fileTable_editFilePathColumn.setCellValueFactory(param -> param.getValue().getEditFilePath()); fileTable_editFilePathColumn.setCellValueFactory(param -> param.getValue().getEditFilePath());
fileTable_editFilePathColumn.setCellFactory(param -> new FileTableFilePathTableCell()); fileTable_editFilePathColumn.setCellFactory(FilePathTableCell.forTableColumn(viewModel.getPath()));
fileTable_signDateColumn.setCellValueFactory(param -> param.getValue().getSignDate()); fileTable_signDateColumn.setCellValueFactory(param -> param.getValue().getSignDate());
fileTable_validColumn.setEditable(true); fileTable_validColumn.setEditable(true);
fileTable_validColumn.setCellValueFactory(param -> param.getValue().getValid()); fileTable_validColumn.setCellValueFactory(param -> param.getValue().getValid());
@@ -155,15 +154,14 @@ public class CustomerTabSkinFile
} }
@Override @Override
protected void onTableRowDoubleClickedAction(CompanyCustomerFileViewModel item) { protected void onTableRowDoubleClickedAction(CustomerFileViewModel item) {
CustomerFileType fileType = item.getType().get(); CustomerFileType fileType = item.getType().get();
if (fileType == CustomerFileType.EvaluationForm) { if (fileType == CustomerFileType.EvaluationForm) {
CompanyCustomerEvaluationFormFileVo evaluationFormFile = getCachedBean(
CompanyCustomerEvaluationFormFileService.class).findByCustomerFile(item.getId().get());
// 文件不是 Excel 文件时打开编辑UI // 文件不是 Excel 文件时打开编辑UI
if (!FileUtils.withExtensions(item.getFilePath().get(), FileUtils.XLS, if (!FileUtils.withExtensions(item.getFilePath().get(), FileUtils.XLS,
FileUtils.XLSX)) { FileUtils.XLSX)) {
CompanyCustomerEvaluationFormFileWindowController.show(evaluationFormFile, CompanyCustomerEvaluationFormFileWindowController.show(item,
controller.root.getScene().getWindow()); controller.root.getScene().getWindow());
return; return;
} }
@@ -187,7 +185,7 @@ public class CustomerTabSkinFile
setStatus("目录错误,不存在"); setStatus("目录错误,不存在");
return; return;
} }
CompanyCustomerVo companyCustomer = getParent(); CustomerVo companyCustomer = getParent();
LocalDate nextSignDate = getCompanyCustomerFileService().getNextSignDate(companyCustomer, LocalDate nextSignDate = getCompanyCustomerFileService().getNextSignDate(companyCustomer,
((level, message) -> setStatus(message))); ((level, message) -> setStatus(message)));
if (nextSignDate != null && files.size() == 1) { if (nextSignDate != null && files.size() == 1) {
@@ -199,22 +197,19 @@ public class CustomerTabSkinFile
+ "." + StringUtils.getFilenameExtension(fileName); + "." + StringUtils.getFilenameExtension(fileName);
File dest = new File(dir, destFileName); File dest = new File(dir, destFileName);
if (file.renameTo(dest)) { if (file.renameTo(dest)) {
CompanyCustomerFileVo ccf = new CompanyCustomerFileVo(); CustomerFileVo ccf = new CustomerFileVo();
ccf.setCustomer(companyCustomer.getId()); ccf.setCustomer(companyCustomer.getId());
ccf.setType(CustomerFileType.EvaluationForm); ccf.setType(CustomerFileType.EvaluationForm);
ccf.setFilePath(dest.getAbsolutePath()); ccf.setFilePath(dest.getAbsolutePath());
ccf.setSignDate(nextSignDate); ccf.setSignDate(nextSignDate);
ccf.setValid(false); ccf.setValid(false);
CompanyCustomerFileVo saved = getCompanyCustomerFileService().save(ccf); CustomerFileVo saved = getCompanyCustomerFileService().save(ccf);
Platform.runLater(() -> { Platform.runLater(() -> {
CompanyCustomerFileViewModel model = new CompanyCustomerFileViewModel(); CustomerFileViewModel model = new CustomerFileViewModel();
model.update(saved); model.update(saved);
dataSet.add(model); dataSet.add(model);
CompanyCustomerEvaluationFormFileVo evaluationFormFile = getCachedBean( CompanyCustomerEvaluationFormFileWindowController.show(model,
CompanyCustomerEvaluationFormFileService.class).findByCustomerFile(saved);
CompanyCustomerEvaluationFormFileWindowController.show(evaluationFormFile,
getTableView().getScene().getWindow()); getTableView().getScene().getWindow());
}); });
return; return;
@@ -226,7 +221,7 @@ public class CustomerTabSkinFile
for (File file : files) { for (File file : files) {
File dest = new File(dir, file.getName()); File dest = new File(dir, file.getName());
if (file.renameTo(dest)) { if (file.renameTo(dest)) {
CompanyCustomerFileVo ccf = new CompanyCustomerFileVo(); CustomerFileVo ccf = new CustomerFileVo();
ccf.setCustomer(companyCustomer.getId()); ccf.setCustomer(companyCustomer.getId());
ccf.setType(CustomerFileType.General); ccf.setType(CustomerFileType.General);
ccf.setFilePath(dest.getAbsolutePath()); ccf.setFilePath(dest.getAbsolutePath());
@@ -238,36 +233,20 @@ public class CustomerTabSkinFile
} }
public void onFileReBuildingAction(ActionEvent event) { public void onFileReBuildingAction(ActionEvent event) {
CompletableFuture.runAsync(() -> {
CompanyCustomerService customerService = getCompanyCustomerService(); CustomerService customerService = getCompanyCustomerService();
try { try {
CompanyCustomerVo companyCustomer = customerService.findById(viewModel.getId().get()); CustomerVo companyCustomer = customerService.findById(viewModel.getId().get());
if (customerService.reBuildingFiles(companyCustomer, (level, message) -> setStatus(message))) { if (customerService.reBuildingFiles(companyCustomer, (level, message) -> setStatus(message))) {
loadTableDataSet(); loadTableDataSet();
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
});
} }
@Override @Override
public CompanyCustomerFileVo loadRowData(CompanyCustomerFileViewModel row) { protected boolean deleteRow(CustomerFileViewModel row) {
return getCompanyCustomerFileService().findById(row.getId().get());
}
@Override
public CompanyCustomerFileVo saveRowData(CompanyCustomerFileVo entity) {
return getCompanyCustomerFileService().save(entity);
}
@Override
public void deleteRowData(CompanyCustomerFileVo entity) {
getCompanyCustomerFileService().delete(entity);
}
@Override
protected boolean deleteRow(CompanyCustomerFileViewModel row) {
String path = row.getFilePath().get(); String path = row.getFilePath().get();
if (super.deleteRow(row)) { if (super.deleteRow(row)) {
File file = new File(path); File file = new File(path);
@@ -287,6 +266,12 @@ public class CustomerTabSkinFile
} }
public void onFileTableMoveToCompanyPathAction(ActionEvent event) { public void onFileTableMoveToCompanyPathAction(ActionEvent event) {
CustomerFileViewModel selectedItem = getSelectedItem();
if (selectedItem == null) {
return;
}
// 检查公司目录设置
Integer companyId = viewModel.getCompany().get(); Integer companyId = viewModel.getCompany().get();
CompanyVo company = getCompanyService().findById(companyId); CompanyVo company = getCompanyService().findById(companyId);
@@ -297,104 +282,30 @@ public class CustomerTabSkinFile
File companyPath = new File(company.getPath()); File companyPath = new File(company.getPath());
if (!companyPath.exists()) { if (!companyPath.exists()) {
setStatus("公司目录设置设置异常,无法访问"); setStatus("公司目录设置异常,无法访问");
return; return;
} }
CompanyCustomerFileViewModel selectedItem = getSelectedItem(); // 创建并启动任务
if (selectedItem == null) { CustomerFileMoveTasker task = new CustomerFileMoveTasker();
return; task.setFileId(selectedItem.getId().get());
} UITools.showTaskDialogAndWait("移动文件到公司目录", task, null);
String filePath = selectedItem.getFilePath().get();
String editFilePath = selectedItem.getEditFilePath().get();
if (StringUtils.hasText(filePath)) { // 刷新表格数据
File file = new File(filePath); loadTableDataSet();
if (file.exists()) {
File dest = new File(companyPath, file.getName());
if (file.renameTo(dest)) {
setStatus(file.getAbsolutePath() + " -> " + dest.getAbsolutePath());
}
}
}
if (StringUtils.hasText(editFilePath)) {
File file = new File(editFilePath);
if (file.exists()) {
File dest = new File(companyPath, file.getName());
if (file.renameTo(dest)) {
setStatus(file.getAbsolutePath() + " -> " + dest.getAbsolutePath());
}
}
}
deleteRow(selectedItem);
// getCompanyCustomerService().deleteFileById(selectedItem.getId().get());
// dataSet.remove(selectedItem);
}
private void initializeTask(Task<Object> task, String prefix, Consumer<String> consumer) {
task.setOnScheduled(e -> {
consumer.accept("正在" + prefix + ",请稍后...");
});
task.setOnRunning(e -> {
consumer.accept("开始" + prefix + "...");
});
task.setOnSucceeded(e -> {
consumer.accept(prefix + "同步完成...");
});
task.exceptionProperty().addListener((observable, oldValue, newValue) -> {
consumer.accept(newValue.getMessage());
});
SpringApp.getBean(ScheduledExecutorService.class).submit(task);
consumer.accept("任务已创建...");
} }
public void onUpdateEvaluationFormAction(ActionEvent event) { public void onUpdateEvaluationFormAction(ActionEvent event) {
CompanyCustomerEvaluationFormUpdateTask task = new CompanyCustomerEvaluationFormUpdateTask(); CustomerEvaluationFormUpdateTask task = new CustomerEvaluationFormUpdateTask();
task.setCompanyService(getCompanyService());
task.setCompanyCustomerFileService(getCompanyCustomerFileService());
task.setCustomer(getCompanyCustomerService().findById(viewModel.getId().get())); task.setCustomer(getCompanyCustomerService().findById(viewModel.getId().get()));
UITools.showTaskDialogAndWait("更新评价表", task, consumer -> { UITools.showTaskDialogAndWait("更新评价表", task, null);
initializeTask(task, "更新评价表", msg -> consumer.accept(Message.info(msg)));
});
loadTableDataSet(); loadTableDataSet();
} }
public void onCalcNextSignDateAction(ActionEvent event) { public void onCalcNextSignDateAction(ActionEvent event) {
UITools.showDialogAndWait("计算客户下一个评价日期", "依据已有的客户评估表和登记采购的合同计算下一个评估日期", ds -> { CustomerNextSignDateTask task = new CustomerNextSignDateTask();
CompanyCustomerVo companyCustomer = getCompanyCustomerService().findById(viewModel.getId().get()); task.setCustomer(getEntity());
LocalDate nextSignDate = getCompanyCustomerFileService().getNextSignDate(companyCustomer, (level, msg) -> { UITools.showTaskDialogAndWait("计算客户的下一个评价日期", task, null);
Platform.runLater(() -> {
ds.add(msg);
});
});
if (nextSignDate != null) {
Platform.runLater(() -> {
ds.add("下一个评价日期:" + nextSignDate);
});
}
});
}
class FileTableFilePathTableCell extends TableCell<CompanyCustomerFileViewModel, String> {
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty || !StringUtils.hasText(item)) {
setText("");
return;
}
String path = viewModel.getPath().get();
if (StringUtils.hasText(path)) {
if (item.startsWith(path)) {
item = "~" + item.substring(path.length());
}
}
setText(item);
}
} }
private CompanyCustomerFileService getCompanyCustomerFileService() { private CompanyCustomerFileService getCompanyCustomerFileService() {

View File

@@ -10,7 +10,7 @@ import com.ecep.contract.controller.project.satisfaction_survey.CustomerSatisfac
import com.ecep.contract.controller.table.cell.EmployeeTableCell; import com.ecep.contract.controller.table.cell.EmployeeTableCell;
import com.ecep.contract.controller.table.cell.ProjectTableCell; import com.ecep.contract.controller.table.cell.ProjectTableCell;
import com.ecep.contract.converter.EntityStringConverter; import com.ecep.contract.converter.EntityStringConverter;
import com.ecep.contract.vo.CompanyCustomerVo; import com.ecep.contract.vo.CustomerVo;
import com.ecep.contract.vo.CustomerCatalogVo; import com.ecep.contract.vo.CustomerCatalogVo;
import com.ecep.contract.vo.CustomerSatisfactionSurveyVo; import com.ecep.contract.vo.CustomerSatisfactionSurveyVo;
import com.ecep.contract.vo.EmployeeVo; import com.ecep.contract.vo.EmployeeVo;
@@ -68,7 +68,7 @@ public class CustomerTabSkinSatisfactionSurvey
bindLocalDateColumn(dateColumn, CustomerSatisfactionSurveyViewModel::getDate); bindLocalDateColumn(dateColumn, CustomerSatisfactionSurveyViewModel::getDate);
bindNumberColumn(totalScoreColumn, CustomerSatisfactionSurveyViewModel::getTotalScore); bindNumberColumn(totalScoreColumn, CustomerSatisfactionSurveyViewModel::getTotalScore);
applicantColumn.setCellValueFactory(param -> param.getValue().getApplicant()); applicantColumn.setCellValueFactory(param -> param.getValue().getApplicant());
applicantColumn.setCellFactory(cell -> new EmployeeTableCell<>(getEmployeeService())); applicantColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
bindLocalDateTimeColumn(applyTimeColumn, CustomerSatisfactionSurveyViewModel::getApplyTime); bindLocalDateTimeColumn(applyTimeColumn, CustomerSatisfactionSurveyViewModel::getApplyTime);
bindColumn(descriptionColumn, CustomerSatisfactionSurveyViewModel::getDescription); bindColumn(descriptionColumn, CustomerSatisfactionSurveyViewModel::getDescription);
@@ -96,7 +96,7 @@ public class CustomerTabSkinSatisfactionSurvey
} }
@Override @Override
public ParamUtils.Builder getSpecification(CompanyCustomerVo parent) { public ParamUtils.Builder getSpecification(CustomerVo parent) {
ParamUtils.Builder params = getSpecification(); ParamUtils.Builder params = getSpecification();
params.equals("project.customer", parent.getId()); params.equals("project.customer", parent.getId());
return params; return params;

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

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

View File

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

View File

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

View File

@@ -10,6 +10,7 @@ import org.springframework.stereotype.Component;
import com.ecep.contract.controller.AbstManagerWindowController; import com.ecep.contract.controller.AbstManagerWindowController;
import com.ecep.contract.service.InventoryService; import com.ecep.contract.service.InventoryService;
import com.ecep.contract.task.InventoryAllSyncTask;
import com.ecep.contract.task.InventorySyncTask; import com.ecep.contract.task.InventorySyncTask;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.UITools; import com.ecep.contract.util.UITools;
@@ -79,7 +80,7 @@ public class InventoryManagerWindowController
} }
public void onSyncAction(ActionEvent event) { public void onSyncAction(ActionEvent event) {
InventorySyncTask task = new InventorySyncTask(); InventoryAllSyncTask task = new InventoryAllSyncTask();
UITools.showTaskDialogAndWait("同步数据", task, null); UITools.showTaskDialogAndWait("同步数据", task, null);
} }

View File

@@ -4,6 +4,7 @@ import java.text.NumberFormat;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.function.Consumer; import java.util.function.Consumer;
import com.ecep.contract.task.InventorySyncTask;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import com.ecep.contract.MessageHolder; import com.ecep.contract.MessageHolder;
@@ -157,19 +158,9 @@ public class InventoryTabSkinBase
private void onSyncAction(ActionEvent event) { private void onSyncAction(ActionEvent event) {
InventoryVo inventory = getEntity(); InventoryVo inventory = getEntity();
setStatus("开始同步数据..."); InventorySyncTask task = new InventorySyncTask();
if (inventory == null) { task.setInventory(inventory);
setStatus("请选择要同步数据."); UITools.showTaskDialogAndWait("同步数据", task, null);
return;
}
if (!StringUtils.hasText(inventory.getCode())) {
setStatus("请填写商品编码.");
return;
}
setStatus("正在同步数据...");
MessageHolder holder = (lv, msg) -> setStatus(msg);
getService().syncInventory(inventory, holder);
} }
@Override @Override

View File

@@ -92,7 +92,7 @@ public class InventoryTabSkinContracts
@Override @Override
public ParamUtils.Builder getSpecification(InventoryVo parent) { public ParamUtils.Builder getSpecification(InventoryVo parent) {
ParamUtils.Builder params = getSpecification(); ParamUtils.Builder params = getSpecification();
params.equals("inventory", parent); params.equals("inventory", parent.getId());
return params; return params;
} }

View File

@@ -11,17 +11,12 @@ import java.util.stream.Collectors;
import com.ecep.contract.ContractPayWay; import com.ecep.contract.ContractPayWay;
import com.ecep.contract.controller.tab.TabSkin; import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.table.AbstEntityTableTabSkin; import com.ecep.contract.controller.table.AbstEntityTableTabSkin;
import com.ecep.contract.model.Contract;
import com.ecep.contract.model.ContractItem;
import com.ecep.contract.model.HistoryPrice; import com.ecep.contract.model.HistoryPrice;
import com.ecep.contract.model.Inventory;
import com.ecep.contract.model.InventoryHistoryPrice;
import com.ecep.contract.service.ContractItemService; import com.ecep.contract.service.ContractItemService;
import com.ecep.contract.service.ContractService; import com.ecep.contract.service.ContractService;
import com.ecep.contract.service.InventoryHistoryPriceService; import com.ecep.contract.service.InventoryHistoryPriceService;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.ParamUtils; import com.ecep.contract.util.ParamUtils;
import com.ecep.contract.util.ProxyUtils;
import com.ecep.contract.vm.InventoryHistoryPriceViewModel; import com.ecep.contract.vm.InventoryHistoryPriceViewModel;
import com.ecep.contract.vm.InventoryViewModel; import com.ecep.contract.vm.InventoryViewModel;
import com.ecep.contract.vo.ContractItemVo; import com.ecep.contract.vo.ContractItemVo;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,6 @@ package com.ecep.contract.controller.project;
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin; import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
import com.ecep.contract.controller.tab.TabSkin; import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.model.Project;
import com.ecep.contract.service.ProjectService; import com.ecep.contract.service.ProjectService;
import com.ecep.contract.vm.ProjectViewModel; import com.ecep.contract.vm.ProjectViewModel;
import com.ecep.contract.vo.ProjectVo; import com.ecep.contract.vo.ProjectVo;

View File

@@ -2,7 +2,7 @@ package com.ecep.contract.controller.project;
import com.ecep.contract.controller.AbstEntityManagerSkin; import com.ecep.contract.controller.AbstEntityManagerSkin;
import com.ecep.contract.controller.BaseController; import com.ecep.contract.controller.BaseController;
import com.ecep.contract.controller.ComboBoxUtils; import com.ecep.contract.util.ComboBoxUtils;
import com.ecep.contract.controller.ManagerSkin; import com.ecep.contract.controller.ManagerSkin;
import com.ecep.contract.controller.table.cell.CompanyTableCell; import com.ecep.contract.controller.table.cell.CompanyTableCell;
import com.ecep.contract.controller.table.cell.ProductTypeTableCell; import com.ecep.contract.controller.table.cell.ProductTypeTableCell;

View File

@@ -6,10 +6,9 @@ import java.util.stream.Stream;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import com.ecep.contract.controller.ComboBoxUtils; import com.ecep.contract.util.ComboBoxUtils;
import com.ecep.contract.controller.tab.TabSkin; import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.converter.EmployeeStringConverter; import com.ecep.contract.converter.EmployeeStringConverter;
import com.ecep.contract.converter.EntityStringConverter;
import com.ecep.contract.service.DeliverySignMethodService; import com.ecep.contract.service.DeliverySignMethodService;
import com.ecep.contract.service.EmployeeService; import com.ecep.contract.service.EmployeeService;
import com.ecep.contract.service.ProductTypeService; import com.ecep.contract.service.ProductTypeService;
@@ -29,11 +28,9 @@ import com.ecep.contract.vo.ProjectTypeVo;
import com.ecep.contract.vo.ProjectVo; import com.ecep.contract.vo.ProjectVo;
import javafx.beans.binding.Bindings; import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList; import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
import javafx.util.converter.LocalDateStringConverter; import javafx.util.converter.LocalDateStringConverter;
import javafx.util.converter.NumberStringConverter; import javafx.util.converter.NumberStringConverter;
@@ -43,15 +40,8 @@ import lombok.Setter;
* 基础信息 * 基础信息
*/ */
public class ProjectTabSkinBase extends AbstProjectBasedTabSkin implements TabSkin { public class ProjectTabSkinBase extends AbstProjectBasedTabSkin implements TabSkin {
@Setter
EmployeeStringConverter employeeStringConverter;
@Setter @Setter
LocalDateStringConverter localDateStringConverter; LocalDateStringConverter localDateStringConverter;
@Setter
EmployeeService employeeService;
@Setter
private DeliverySignMethodService deliverySignMethodService;
public ProjectTabSkinBase(ProjectWindowController controller) { public ProjectTabSkinBase(ProjectWindowController controller) {
super(controller); super(controller);
@@ -62,10 +52,6 @@ public class ProjectTabSkinBase extends AbstProjectBasedTabSkin implements TabSk
return controller.baseInfoTab; return controller.baseInfoTab;
} }
private EmployeeService getEmployeeService() {
return getBean(EmployeeService.class);
}
private ProjectTypeService getProjectTypeService() { private ProjectTypeService getProjectTypeService() {
return getBean(ProjectTypeService.class); return getBean(ProjectTypeService.class);
} }
@@ -106,7 +92,6 @@ public class ProjectTabSkinBase extends AbstProjectBasedTabSkin implements TabSk
controller.standardPayWayField.selectedProperty().bindBidirectional(viewModel.getStandardPayWay()); controller.standardPayWayField.selectedProperty().bindBidirectional(viewModel.getStandardPayWay());
Bindings.bindBidirectional(controller.amountField.textProperty(), viewModel.getAmount(), Bindings.bindBidirectional(controller.amountField.textProperty(), viewModel.getAmount(),
new NumberStringConverter()); new NumberStringConverter());
ComboBoxUtils.initialComboBox(controller.saleTypeField, viewModel.getSaleType(), getSaleTypeService(), true); ComboBoxUtils.initialComboBox(controller.saleTypeField, viewModel.getSaleType(), getSaleTypeService(), true);
ComboBoxUtils.initialComboBox(controller.projectTypeField, viewModel.getProjectType(), getProjectTypeService(), ComboBoxUtils.initialComboBox(controller.projectTypeField, viewModel.getProjectType(), getProjectTypeService(),
true); true);
@@ -122,6 +107,9 @@ public class ProjectTabSkinBase extends AbstProjectBasedTabSkin implements TabSk
controller.saleTypeField.valueProperty().addListener((observable, oldValue, newValue) -> { controller.saleTypeField.valueProperty().addListener((observable, oldValue, newValue) -> {
Predicate<DeliverySignMethodVo> predicate = p -> { Predicate<DeliverySignMethodVo> predicate = p -> {
if (newValue == null) {
return true;
}
return p == null || Objects.equals(p.getSaleTypeId(), newValue.getId()); return p == null || Objects.equals(p.getSaleTypeId(), newValue.getId());
}; };

View File

@@ -12,10 +12,8 @@ import com.ecep.contract.controller.table.EditableEntityTableTabSkin;
import com.ecep.contract.controller.table.cell.EmployeeTableCell; import com.ecep.contract.controller.table.cell.EmployeeTableCell;
import com.ecep.contract.converter.CompanyStringConverter; import com.ecep.contract.converter.CompanyStringConverter;
import com.ecep.contract.converter.EmployeeStringConverter; import com.ecep.contract.converter.EmployeeStringConverter;
import com.ecep.contract.model.CompanyCustomerEvaluationFormFile;
import com.ecep.contract.model.Employee;
import com.ecep.contract.service.CompanyCustomerFileService; import com.ecep.contract.service.CompanyCustomerFileService;
import com.ecep.contract.service.CompanyCustomerService; import com.ecep.contract.service.CustomerService;
import com.ecep.contract.service.CompanyService; import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.ProjectBidService; import com.ecep.contract.service.ProjectBidService;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
@@ -36,7 +34,7 @@ public class ProjectTabSkinBid
public TableColumn<ProjectBidViewModel, Number> idColumn; public TableColumn<ProjectBidViewModel, Number> idColumn;
public TableColumn<ProjectBidViewModel, Number> versionColumn; public TableColumn<ProjectBidViewModel, Number> versionColumn;
public TableColumn<ProjectBidViewModel, CompanyCustomerEvaluationFormFile> evaluationFileColumn; public TableColumn<ProjectBidViewModel, Integer> evaluationFileColumn;
public TableColumn<ProjectBidViewModel, String> descriptionColumn; public TableColumn<ProjectBidViewModel, String> descriptionColumn;
public TableColumn<ProjectBidViewModel, Integer> applicantColumn; public TableColumn<ProjectBidViewModel, Integer> applicantColumn;
@@ -58,7 +56,7 @@ public class ProjectTabSkinBid
@Setter @Setter
private CompanyService companyService; private CompanyService companyService;
@Setter @Setter
private CompanyCustomerService customerService; private CustomerService customerService;
@Setter @Setter
private CompanyCustomerFileService customerFileService; private CompanyCustomerFileService customerFileService;
@@ -165,9 +163,9 @@ public class ProjectTabSkinBid
return companyStringConverter; return companyStringConverter;
} }
private CompanyCustomerService getCompanyCustomerService() { private CustomerService getCompanyCustomerService() {
if (customerService == null) { if (customerService == null) {
customerService = getBean(CompanyCustomerService.class); customerService = getBean(CustomerService.class);
} }
return customerService; return customerService;
} }

View File

@@ -7,7 +7,6 @@ import com.ecep.contract.controller.project.cost.ProjectCostWindowController;
import com.ecep.contract.controller.tab.TabSkin; import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.controller.table.cell.EmployeeTableCell; import com.ecep.contract.controller.table.cell.EmployeeTableCell;
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell; import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
import com.ecep.contract.model.ProjectCostItem;
import com.ecep.contract.service.ContractItemService; import com.ecep.contract.service.ContractItemService;
import com.ecep.contract.service.ProjectCostItemService; import com.ecep.contract.service.ProjectCostItemService;
import com.ecep.contract.service.ProjectCostService; import com.ecep.contract.service.ProjectCostService;

View File

@@ -1,25 +1,12 @@
package com.ecep.contract.controller.project; package com.ecep.contract.controller.project;
import org.controlsfx.control.textfield.AutoCompletionBinding;
import org.springframework.util.StringUtils;
import com.ecep.contract.controller.company.CompanyWindowController; import com.ecep.contract.controller.company.CompanyWindowController;
import com.ecep.contract.controller.tab.TabSkin; import com.ecep.contract.controller.tab.TabSkin;
import com.ecep.contract.converter.EntityStringConverter; import com.ecep.contract.converter.EntityStringConverter;
import com.ecep.contract.model.CompanyContact; import com.ecep.contract.service.*;
import com.ecep.contract.model.CompanyInvoiceInfo;
import com.ecep.contract.service.BankService;
import com.ecep.contract.service.CompanyBankAccountService;
import com.ecep.contract.service.CompanyContactService;
import com.ecep.contract.service.CompanyInvoiceInfoService;
import com.ecep.contract.service.CompanyService;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
import com.ecep.contract.util.UITools; import com.ecep.contract.util.UITools;
import com.ecep.contract.vo.BankVo; import com.ecep.contract.vo.*;
import com.ecep.contract.vo.CompanyBankAccountVo;
import com.ecep.contract.vo.CompanyContactVo;
import com.ecep.contract.vo.CompanyVo;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
import javafx.fxml.FXML; import javafx.fxml.FXML;
@@ -27,6 +14,8 @@ import javafx.scene.control.Button;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import org.controlsfx.control.textfield.AutoCompletionBinding;
import org.springframework.util.StringUtils;
@FxmlPath("/ui/project/project-tab-customer.fxml") @FxmlPath("/ui/project/project-tab-customer.fxml")
public class ProjectTabSkinCustomerInfo public class ProjectTabSkinCustomerInfo
@@ -101,7 +90,7 @@ public class ProjectTabSkinCustomerInfo
private void initInvoiceInfoField() { private void initInvoiceInfoField() {
invoiceInfoAutoCompletion(invoiceInfoField, invoiceInfoLabel, viewModel.getInvoiceInfo()) invoiceInfoAutoCompletion(invoiceInfoField, invoiceInfoLabel, viewModel.getInvoiceInfo())
.setOnAutoCompleted(event -> { .setOnAutoCompleted(event -> {
CompanyInvoiceInfo invoiceInfo = event.getCompletion(); CompanyInvoiceInfoVo invoiceInfo = event.getCompletion();
viewModel.getInvoiceInfo().set(invoiceInfo.getId()); viewModel.getInvoiceInfo().set(invoiceInfo.getId());
}); });
} }
@@ -199,16 +188,16 @@ public class ProjectTabSkinCustomerInfo
return UITools.autoCompletion(textField, property, getBankAccountService()); return UITools.autoCompletion(textField, property, getBankAccountService());
} }
private AutoCompletionBinding<CompanyInvoiceInfo> invoiceInfoAutoCompletion(TextField textField, Label label, private AutoCompletionBinding<CompanyInvoiceInfoVo> invoiceInfoAutoCompletion(TextField textField, Label label,
SimpleObjectProperty<Integer> property) { SimpleObjectProperty<Integer> property) {
EntityStringConverter<CompanyInvoiceInfo> converter = new EntityStringConverter<>(); EntityStringConverter<CompanyInvoiceInfoVo> converter = new EntityStringConverter<>();
converter.setInitialized(info -> getInvoiceInfoService().findById(info.getId())); converter.setInitialized(info -> getInvoiceInfoService().findById(info.getId()));
label.textProperty().bind(property.map(infoId -> { label.textProperty().bind(property.map(infoId -> {
if (infoId == null) { if (infoId == null) {
return "未选择"; return "未选择";
} }
CompanyInvoiceInfo info = getInvoiceInfoService().findById(infoId); CompanyInvoiceInfoVo info = getInvoiceInfoService().findById(infoId);
if (info == null) { if (info == null) {
return "#" + infoId; return "#" + infoId;
} }

View File

@@ -12,7 +12,7 @@ import com.ecep.contract.controller.table.cell.EmployeeTableCell;
import com.ecep.contract.converter.CompanyStringConverter; import com.ecep.contract.converter.CompanyStringConverter;
import com.ecep.contract.converter.EmployeeStringConverter; import com.ecep.contract.converter.EmployeeStringConverter;
import com.ecep.contract.service.CompanyCustomerFileService; import com.ecep.contract.service.CompanyCustomerFileService;
import com.ecep.contract.service.CompanyCustomerService; import com.ecep.contract.service.CustomerService;
import com.ecep.contract.service.CompanyService; import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.CustomerSatisfactionSurveyService; import com.ecep.contract.service.CustomerSatisfactionSurveyService;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
@@ -52,7 +52,7 @@ public class ProjectTabSkinCustomerSatisfactionSurvey
@Setter @Setter
private CompanyService companyService; private CompanyService companyService;
@Setter @Setter
private CompanyCustomerService customerService; private CustomerService customerService;
@Setter @Setter
private CompanyCustomerFileService customerFileService; private CompanyCustomerFileService customerFileService;
@@ -141,9 +141,9 @@ public class ProjectTabSkinCustomerSatisfactionSurvey
return companyStringConverter; return companyStringConverter;
} }
private CompanyCustomerService getCompanyCustomerService() { private CustomerService getCompanyCustomerService() {
if (customerService == null) { if (customerService == null) {
customerService = getBean(CompanyCustomerService.class); customerService = getBean(CustomerService.class);
} }
return customerService; return customerService;
} }

View File

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

View File

@@ -15,7 +15,7 @@ import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
import com.ecep.contract.converter.CompanyStringConverter; import com.ecep.contract.converter.CompanyStringConverter;
import com.ecep.contract.converter.EmployeeStringConverter; import com.ecep.contract.converter.EmployeeStringConverter;
import com.ecep.contract.service.CompanyCustomerFileService; import com.ecep.contract.service.CompanyCustomerFileService;
import com.ecep.contract.service.CompanyCustomerService; import com.ecep.contract.service.CustomerService;
import com.ecep.contract.service.CompanyService; import com.ecep.contract.service.CompanyService;
import com.ecep.contract.service.ProjectQuotationService; import com.ecep.contract.service.ProjectQuotationService;
import com.ecep.contract.util.FxmlPath; import com.ecep.contract.util.FxmlPath;
@@ -69,7 +69,7 @@ public class ProjectTabSkinQuotation
@Setter @Setter
private CompanyService companyService; private CompanyService companyService;
@Setter @Setter
private CompanyCustomerService customerService; private CustomerService customerService;
@Setter @Setter
private CompanyCustomerFileService customerFileService; private CompanyCustomerFileService customerFileService;
@@ -90,6 +90,8 @@ public class ProjectTabSkinQuotation
@Override @Override
public void initializeTable() { public void initializeTable() {
super.initializeTable(); super.initializeTable();
idColumn.setCellValueFactory(param -> param.getValue().getId());
levelColumn.setCellValueFactory(param -> param.getValue().getLevel()); levelColumn.setCellValueFactory(param -> param.getValue().getLevel());
levelColumn.setCellFactory(param -> new LevelTableCell()); levelColumn.setCellFactory(param -> new LevelTableCell());
standardPayWayColumn.setCellValueFactory(param -> param.getValue().getStandardPayWay() standardPayWayColumn.setCellValueFactory(param -> param.getValue().getStandardPayWay()
@@ -172,9 +174,9 @@ public class ProjectTabSkinQuotation
return companyStringConverter; return companyStringConverter;
} }
private CompanyCustomerService getCompanyCustomerService() { private CustomerService getCompanyCustomerService() {
if (customerService == null) { if (customerService == null) {
customerService = getBean(CompanyCustomerService.class); customerService = getBean(CustomerService.class);
} }
return customerService; return customerService;
} }

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