commit 65bc67460b84f78a65529eb2f4a1a436ecce1c0a Author: songqq Date: Fri Aug 22 19:55:19 2025 +0800 重新初始化项目 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..07e6956 --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +HELP.md +logs/ +release/ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ +.lock + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/.lingma/rules/project_rule.md b/.lingma/rules/project_rule.md new file mode 100644 index 0000000..1539ab4 --- /dev/null +++ b/.lingma/rules/project_rule.md @@ -0,0 +1,270 @@ +你是一个资深的java专家,请在开发中遵循如下规则: +- 严格遵循 **SOLID、DRY、KISS、YAGNI** 原则 +- 遵循 **OWASP 安全最佳实践**(如输入验证、SQL注入防护) +- 采用 **分层架构设计**,确保职责分离 +- 代码变更需通过 **单元测试覆盖**(测试覆盖率 ≥ 80%) + +--- + +## 二、技术栈规范 +### 技术栈要求 +- **框架**:JavaFx 22 + Spring Boot 3.x + Java 21 +- **依赖**: + - 核心:Spring Data JPA, Lombok + - 数据库:MySQL Driver、MsSQL Driver + - 其他:controlsfx + +--- + +## 三、应用逻辑设计规范 +### 1. 分层架构原则 +| 层级 | 职责 | 约束条件 | +|----------------|------------------------|----------------------------------------------------------| +| **Controller** | 处理 fxml 请求与响应,定义 API 接口 | - 禁止直接操作数据库
- 必须通过 Service 层调用 | +| **Service** | 业务逻辑实现,事务管理,数据校验 | - 必须通过 Repository 访问数据库
- 返回 DTO 而非实体类(除非必要) | +| **Repository** | 数据持久化操作,定义数据库查询逻辑 | - 必须继承 `MyRepository`
- 使用 `@EntityGraph` 避免 N+1 查询问题 | +| **Entity** | 数据库表结构映射对象 | - 仅用于数据库交互
- 禁止直接返回给前端(需通过 DTO 转换) | +| **Tasker** | 运行的任务 | - | +| **ViewModel** | 界面数据模型 | - | +| **.fxml** | UI 界面逻辑 | - 位于 /resources/ui/ | + +--- + +## 四、核心代码规范 +### 1. 实体类(Entity)规范 +```java +@Entity +@Data // Lombok 注解 +@Table(name = "USER") // 数据表名,默认为大写的类名 +public class User { + @Id + @Column(name = "ID", nullable = false) // 字段名大写 + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @Column(name = "USERNAME") // 字段名大写 + @Size(min = 3, max = 50) + private String username; + + // 关联关系使用懒加载 + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "DEPARTMENT_ID") // 关联关系,字段名大写 + private Department department; +} +``` + +### 2. 数据访问层(Repository)规范 +```java +public interface UserRepository extends MyRepository { + // 命名查询 + Optional findByUsername(String username); + + // 自定义 JPQL 查询 + @Query("SELECT u FROM User u JOIN FETCH u.department WHERE u.id = :id") + @EntityGraph(attributePaths = {"department"}) + Optional findUserWithDepartment(@Param("id") Long id); +} +``` + +### 3. 服务层(Service)规范 +```java +@Service +public class UserServiceImpl implements UserService { + @Autowired + private UserRepository userRepository; + + @Transactional + public ApiResponse createUser(UserDTO dto) { + // 业务逻辑实现 + User user = User.builder().username(dto.getUsername()).build(); + User savedUser = userRepository.save(user); + return ApiResponse.success(UserDTO.fromEntity(savedUser)); + } +} +``` + +### 4. 控制器(Controller)规范 +```java +@Lazy +@Scope("prototype") // 单例 +@Component +@FxmlPath("/ui/employee/employee.fxml") // FXML 文件路径 +public class EmployeeWindowController extends AbstEntityController{ + public BorderPane root; + public TabPane tabPane; + public Tab baseInfoTab; + public Tab rolesTab; + public Tab loginHistoryTab; + public Tab authBindTab; + + @Autowired + private EmployeeService employeeService; + + @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 EmployeeTabSkinBase(this)); + registerTabSkin(rolesTab, tab -> new EmployeeTabSkinRole(this)); + registerTabSkin(loginHistoryTab, tab -> new EmployeeTabSkinLoginHistory(this)); + } + + @Override + public EmployeeService getViewModelService() { + return employeeService; + } +} +``` + +--- + +## 五、Tab 界面控制 +```java +public class EmployeeTabSkinBase + extends AbstEmployeeBasedTabSkin + implements TabSkin { + public EmployeeTabSkinBase(EmployeeWindowController controller) { + super(controller); + } + + @Override + public Tab getTab() { + return controller.baseInfoTab; + } + + @Override + public void initializeTab() { + + } +} + +@FxmlPath("/ui/employee/employee-login-history.fxml") +public class EmployeeTabSkinLoginHistory + extends AbstEmployeeTableTabSkin + implements TabSkin { + public TableColumn idColumn; + public TableColumn ipColumn; + public TableColumn macColumn; + public TableColumn loginTimeColumn; + public TableColumn activeTimeColumn; + + private EmployeeLoginHistoryService loginHistoryService; + + public EmployeeTabSkinLoginHistory(EmployeeWindowController controller) { + super(controller); + } + + EmployeeLoginHistoryService getLoginHistoryService() { + if (loginHistoryService == null) { + loginHistoryService = getBean(EmployeeLoginHistoryService.class); + } + return loginHistoryService; + } + + @Override + protected EmployeeLoginHistoryService getViewModelService() { + return getLoginHistoryService(); + } + + @Override + public Tab getTab() { + return controller.loginHistoryTab; + } + + @Override + public void initializeTab() { + super.initializeTab(); + + idColumn.setCellValueFactory(param -> param.getValue().getId()); + ipColumn.setCellValueFactory(param -> param.getValue().getIp()); + macColumn.setCellValueFactory(param -> param.getValue().getMac()); + loginTimeColumn.setCellValueFactory(param -> param.getValue().getLoginTime()); + loginTimeColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); + activeTimeColumn.setCellValueFactory(param -> param.getValue().getActiveTime()); + activeTimeColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); + + Platform.runLater(() -> { + getTableView().getSortOrder().add(activeTimeColumn); + }); + } +} +``` + +--- + +## 六、全局异常处理规范 +```java + +``` + +### 2. 全局异常处理器(GlobalExceptionHandler) +```java +@RestControllerAdvice +public class GlobalExceptionHandler { + @ExceptionHandler(EntityNotFoundException.class) + public ResponseEntity> handleEntityNotFound(EntityNotFoundException ex) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(ApiResponse.error(ex.getMessage())); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity> handleValidationErrors(MethodArgumentNotValidException ex) { + String errorMessage = ex.getBindingResult() + .getFieldErrors() + .stream() + .map(error -> error.getField() + ": " + error.getDefaultMessage()) + .collect(Collectors.joining(", ")); + return ResponseEntity.badRequest().body(ApiResponse.error(errorMessage)); + } +} +``` + +--- + +## 七、安全与性能规范 +1. 输入校验: + +2. **事务管理**: + - `@Transactional` 注解仅标注在 Service 方法上 + - 避免在循环中频繁提交事务 +3. **性能优化**: + - 使用 `@EntityGraph` 预加载关联关系 + - 避免在循环中执行数据库查询(批量操作优先) + +--- + +## 八、代码风格规范 +1. **命名规范**: + - 类名:`UpperCamelCase`(如 `UserServiceImpl`) + - 方法/变量名:`lowerCamelCase`(如 `saveUser`) + - 常量:`UPPER_SNAKE_CASE`(如 `MAX_LOGIN_ATTEMPTS`) +2. **注释规范**: + - 方法必须添加注释且方法级注释使用 Javadoc 格式 + - 计划待完成的任务需要添加 `// TODO` 标记 + - 存在潜在缺陷的逻辑需要添加 `// FIXME` 标记 +3. **代码格式化**: + - 使用 IntelliJ IDEA 默认的 Spring Boot 风格 + - 禁止手动修改代码缩进(依赖 IDE 自动格式化) + +--- + +## 九、部署规范 +1. **部署规范**: + - 生产环境需禁用 `@EnableAutoConfiguration` 的默认配置 + - 敏感信息通过 `application.properties` 外部化配置 + - 使用 `Spring Profiles` 管理环境差异(如 `dev`, `prod`) + +--- + +## 十、扩展性设计规范 +1. **接口优先**: + - 服务层接口(`UserService`)与实现(`UserServiceImpl`)分离 +2. **扩展点预留**: + - 关键业务逻辑需提供 `Strategy` 或 `Template` 模式支持扩展 +3. **日志规范**: + - 使用 `SLF4J` 记录日志(禁止直接使用 `System.out.println`) + - 核心操作需记录 `INFO` 级别日志,异常记录 `ERROR` 级别 +``` \ No newline at end of file diff --git a/.mvn/maven.config b/.mvn/maven.config new file mode 100644 index 0000000..4dc19b5 --- /dev/null +++ b/.mvn/maven.config @@ -0,0 +1,4 @@ +-Dfile.encoding=UTF-8 +-Dmaven.compiler.source=21 +-Dmaven.compiler.target=21 +-Dmaven.compiler.encoding=UTF-8 \ No newline at end of file diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..8f96f52 --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +wrapperVersion=3.3.2 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip diff --git a/.trae/.ignore b/.trae/.ignore new file mode 100644 index 0000000..e69de29 diff --git a/.trae/rules/project_rules.md b/.trae/rules/project_rules.md new file mode 100644 index 0000000..efcec4a --- /dev/null +++ b/.trae/rules/project_rules.md @@ -0,0 +1,13 @@ +Java 21 +Spring Boot 3.3.7 +Spring Data JPA 3.3.7 +JavaFX 21 +ControlsFX 11.1.2 +MySQL 8.0.33 +Lombok 1.18.32 +POI 5.2.5 +PDFBox 3.0.1 + +ignore: + - .idea + - target diff --git a/README.md b/README.md new file mode 100644 index 0000000..3d49bdd --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +## 项目改进与优化分析报告 +### 1. 同步任务与调度优化 +- 调度策略优化 :各同步任务(如 ContractSyncTask 、 VendorSyncTask )使用不同的调度间隔和策略,建议统一调度配置,避免重复执行和资源浪费 +- 增量同步实现 :多数同步任务缺乏断点续传机制,建议添加基于最后同步ID或时间戳的增量同步功能 +- 任务依赖管理 :部分任务存在隐式依赖关系(如 OldVersionSyncVendorTask 中的多步骤执行),建议引入任务依赖管理机制 +### 2. 线程池与异步处理 +- 线程池配置 :当前项目未明确配置线程池参数(核心线程数、最大线程数等),建议在 SpringApp 中添加显式线程池配置 +- 线程池隔离 :不同类型的任务(IO密集型、CPU密集型)应使用不同线程池,避免资源竞争 +- 异步任务监控 :添加异步任务执行状态监控和超时处理机制,避免任务长时间阻塞 +### 3. 缓存机制优化 +- 缓存策略细化 :当前缓存配置较为简单( CaffeineCacheManager ),建议为不同类型数据配置差异化缓存策略(过期时间、最大容量等) +- 缓存一致性 :修复 CloudRkService 中缓存更新问题(注释中提到的"这个可以无法更新缓存") +- 缓存预热 :添加关键数据缓存预热机制,提高系统启动后响应速度 +### 4. 依赖注入与懒加载 +- 依赖注入规范化 :减少 SpringApp.getBean() 手动获取bean的方式,推广构造函数注入或字段注入 +- 懒加载优化 :充分利用 @Lazy 注解和 BootstrapMode.LAZY 配置,优化应用启动性能 +- 服务解耦 :继续推进服务拆分工作(如之前的 ContractService 拆分为专用服务),减少服务间耦合 +### 5. 异常处理与重试机制 +- 异常处理统一 :规范异常处理策略,确保所有异常都被适当记录和处理 +- 重试机制添加 :为网络请求和数据库操作添加重试机制(如使用 Spring Retry ) +- 熔断降级 :对外部系统调用(如用友U8接口)添加熔断降级机制,提高系统稳定性 +### 6. 数据库操作优化 +- 批量操作引入 :对大量数据的CRUD操作,建议使用批量处理API提高性能 +- 查询优化 :添加适当索引,优化复杂查询,避免全表扫描 +- 连接池配置 :优化 HikariDataSource 配置参数(如连接池大小、超时时间等) +### 7. 代码质量与维护性 +- 重复代码消除 :提取同步任务中的共同逻辑(如进度更新、异常处理)为抽象基类 +- 注释完善 :补充关键类和方法的文档注释,特别是复杂业务逻辑和优化点 +- 技术债务清理 :解决代码中的TODO项(如 CloudRkService 中的缓存更新问题) +### 8. 配置管理优化 +- 配置集中管理 :将分散在代码中的配置项(如同步间隔、批处理大小)集中到配置文件 +- 动态配置支持 :添加动态配置更新机制,避免重启应用 +以上优化点已按优先级和影响范围排序,建议逐步实施。实施过程中应注意性能测试和兼容性验证,确保优化不会引入新问题。 \ No newline at end of file diff --git a/config.properties b/config.properties new file mode 100644 index 0000000..a4340a9 --- /dev/null +++ b/config.properties @@ -0,0 +1,9 @@ +#Updated config.properties +#Wed Mar 26 16:33:45 CST 2025 +cloud.u8.enabled=true +db.server.database=supplier_ms +db.server.host=db-server1.ecctrl.com +db.server.password=ecep.62335656 +db.server.port=3306 +db.server.username=ecep +username_password.remember=true diff --git a/cookie.json b/cookie.json new file mode 100644 index 0000000..3c2cbca --- /dev/null +++ b/cookie.json @@ -0,0 +1 @@ +{"domainIndex":{"capi.tianyancha.com":[{"name":"HWWAFSESID","value":"50686102a0afeb7be3a2","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596543736","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"6ba03d032b0699e06ed","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596612564","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"b47aa1dce32787c8b3c1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596737658","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"1d320c371f909352d05","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734597042838","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"55d67229f46a5060b35d","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734597604201","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"07be6171af14b0b5b29b","comment":null,"commentURL":null,"domain":"capi.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"HWWAFSESTIME","value":"1734598144461","comment":null,"commentURL":null,"domain":"capi.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false}],"cloud-gateway.tianyancha.com":[{"name":"HWWAFSESTIME","value":"1734596569303","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"aad2ff46bbd32b773b","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"933a1760e05480a59c","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734597649532","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"da8e6c44b080ef138a","comment":null,"commentURL":null,"domain":"cloud-gateway.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"HWWAFSESTIME","value":"1734598379937","comment":null,"commentURL":null,"domain":"cloud-gateway.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false}],"gcaptcha4.geetest.com":[{"name":"captcha_v4_user","value":"359bd1ab73354d33b25e3dce42898c5e","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"captcha_v4_user","value":"0425c2bb69714a3cb2521448114325c6","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"captcha_v4_user","value":"c3bf54ec0c474360ae95afad40d9205d","comment":null,"commentURL":null,"domain":"gcaptcha4.geetest.com","maxAge":31536000,"path":"/","portlist":null,"secure":true,"httpOnly":false,"version":0,"discard":false}],"www.tianyancha.com":[{"name":"HWWAFSESID","value":"8594582696edad4731","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596543940","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"csrfToken","value":"Zu_1PyVs9IXN5y_h2BbE_gPO","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596609400","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"b24c910ce0099714055","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"csrfToken","value":"VSnPfq_4O3AfxGC1Tlq5c7AB","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596737002","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"8696d16f9dade193375","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"csrfToken","value":"xyfl7cuGYMu2M2l1C7UcRpja","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_ga","value":"GA1.1.228018237.1734596741","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gid","value":"GA1.1.1473480425.1734596741","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gat_gtag_UA_123487620_1","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734597036965","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"d040113394ab3edfb39","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"csrfToken","value":"aRrbYTtiLxUk_U98kKjzAS5t","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_ga","value":"GA1.1.711605108.1734597045","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gid","value":"GA1.1.1426239103.1734597045","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gat_gtag_UA_123487620_1","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"f771679b8c97186bd1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734597607684","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"csrfToken","value":"wi0vTu-FoO4mJtGk055_jNAg","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_ga","value":"GA1.1.1688427152.1734597608","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gid","value":"GA1.1.503345592.1734597608","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"640db82ef4d455aae9","comment":null,"commentURL":null,"domain":"www.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"HWWAFSESTIME","value":"1734598146629","comment":null,"commentURL":null,"domain":"www.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"csrfToken","value":"56i8jgGUedl4rG5-qOZp28Nz","comment":null,"commentURL":null,"domain":"www.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"_ga","value":"GA1.1.2127366020.1734598148","comment":null,"commentURL":null,"domain":"www.tianyancha.com","maxAge":63071999,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"_gid","value":"GA1.1.219896500.1734598148","comment":null,"commentURL":null,"domain":"www.tianyancha.com","maxAge":86399,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"_gat_gtag_UA_123487620_1","value":"1","comment":null,"commentURL":null,"domain":"www.tianyancha.com","maxAge":59,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false}],".tianyancha.com":[{"name":"CUID","value":"17ba8fda93fb2f1707efa9f51831e174","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"TYCID","value":"60283100bde211efa30eaff1b68b8ea3","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sajssdk_2015_cross_new_user","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"bannerFlag","value":"true","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"tyc-user-info","value":"{%22state%22:%223%22%2C%22vipManager%22:%220%22%2C%22mobile%22:%2217717373906%22%2C%22userId%22:%22313432949%22%2C%22isExpired%22:%220%22}","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"tyc-user-info-save-time","value":"1734596568857","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"auth_token","value":"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxNzcxNzM3MzkwNiIsImlhdCI6MTczNDU5NjU2OSwiZXhwIjoxNzM3MTg4NTY5fQ.JZjXteqxMdk9ndGcUgYpTvqKGAreOgBsLJsuR-rGg6xCoYkxleJqdOL7CLiMMZ_CkP_NUJOMNG4jqeHqMUs6hQ","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"tyc-user-phone","value":"%255B%252217717373906%2522%255D","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"searchSessionId","value":"1734596577.64645647","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sensorsdata2015jssdkcross","value":"%7B%22distinct_id%22%3A%22313432949%22%2C%22first_id%22%3A%22193de02c72799e-068e92c991bbdfc-7622565d-3686400-193de02c728c4c%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTkzZGUwMmM3Mjc5OWUtMDY4ZTkyYzk5MWJiZGZjLTc2MjI1NjVkLTM2ODY0MDAtMTkzZGUwMmM3MjhjNGMiLCIkaWRlbnRpdHlfbG9naW5faWQiOiIzMTM0MzI5NDkifQ%3D%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%24identity_login_id%22%2C%22value%22%3A%22313432949%22%7D%2C%22%24device_id%22%3A%22193de02c72799e-068e92c991bbdfc-7622565d-3686400-193de02c728c4c%22%7D","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"CUID","value":"d297fa9ee7602b2d8bee025632a3f27f","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"TYCID","value":"890fd730bde211ef92f88565e8c18ca8","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sajssdk_2015_cross_new_user","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sensorsdata2015jssdkcross","value":"%7B%22distinct_id%22%3A%22193de03d3183bd-035c3758008fd66-7622565d-3686400-193de03d319ca2%22%2C%22first_id%22%3A%22%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTkzZGUwM2QzMTgzYmQtMDM1YzM3NTgwMDhmZDY2LTc2MjI1NjVkLTM2ODY0MDAtMTkzZGUwM2QzMTljYTIifQ%3D%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%22%2C%22value%22%3A%22%22%7D%2C%22%24device_id%22%3A%22193de03d3183bd-035c3758008fd66-7622565d-3686400-193de03d319ca2%22%7D","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"bannerFlag","value":"true","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"CUID","value":"15ac6fafd2ad2497c7cb55570dca9eac","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"TYCID","value":"d53a21b0bde211efa30eaff1b68b8ea3","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sajssdk_2015_cross_new_user","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sensorsdata2015jssdkcross","value":"%7B%22distinct_id%22%3A%22193de05c5d810bd-0d747256f2ddcc-7622565d-3686400-193de05c5d9cc8%22%2C%22first_id%22%3A%22%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTkzZGUwNWM1ZDgxMGJkLTBkNzQ3MjU2ZjJkZGNjLTc2MjI1NjVkLTM2ODY0MDAtMTkzZGUwNWM1ZDljYzgifQ%3D%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%22%2C%22value%22%3A%22%22%7D%2C%22%24device_id%22%3A%22193de05c5d810bd-0d747256f2ddcc-7622565d-3686400-193de05c5d9cc8%22%7D","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"bannerFlag","value":"true","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"CUID","value":"fd64fcaeacf724db19fe717dc1781bee","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"TYCID","value":"880e9e60bde311efb4db233063166be0","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sajssdk_2015_cross_new_user","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sensorsdata2015jssdkcross","value":"%7B%22distinct_id%22%3A%22193de0a6656962-0bed636145e1cc8-7622565d-3686400-193de0a6657c9d%22%2C%22first_id%22%3A%22%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTkzZGUwYTY2NTY5NjItMGJlZDYzNjE0NWUxY2M4LTc2MjI1NjVkLTM2ODY0MDAtMTkzZGUwYTY2NTdjOWQifQ%3D%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%22%2C%22value%22%3A%22%22%7D%2C%22%24device_id%22%3A%22193de0a6656962-0bed636145e1cc8-7622565d-3686400-193de0a6657c9d%22%7D","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"bannerFlag","value":"true","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"CUID","value":"69ebb2181678214280059319600ff749","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"TYCID","value":"d9f797d0bde411ef96107538ca8b55ff","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sajssdk_2015_cross_new_user","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"tyc-user-info","value":"{%22state%22:%223%22%2C%22vipManager%22:%220%22%2C%22mobile%22:%2217717373906%22%2C%22userId%22:%22313432949%22%2C%22isExpired%22:%220%22}","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"tyc-user-info-save-time","value":"1734597646325","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"auth_token","value":"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxNzcxNzM3MzkwNiIsImlhdCI6MTczNDU5NzY0NywiZXhwIjoxNzM3MTg5NjQ3fQ.ZLY_Zyu6IpfMAc8gPkNv2mHdGSFoRSW2ZiUm3e02_h0h7vWtui57P0tqB-PLcCZQ8WLJhtECgF5wOverOrivDg","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"tyc-user-phone","value":"%255B%252217717373906%2522%255D","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"searchSessionId","value":"1734597832.53059832","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sensorsdata2015jssdkcross","value":"%7B%22distinct_id%22%3A%22313432949%22%2C%22first_id%22%3A%22193de1300c3258-0c0f3d9832018b8-7622565d-3686400-193de1300c417ed%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTkzZGUxMzAwYzMyNTgtMGMwZjNkOTgzMjAxOGI4LTc2MjI1NjVkLTM2ODY0MDAtMTkzZGUxMzAwYzQxN2VkIiwiJGlkZW50aXR5X2xvZ2luX2lkIjoiMzEzNDMyOTQ5In0%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%24identity_login_id%22%2C%22value%22%3A%22313432949%22%7D%2C%22%24device_id%22%3A%22193de1300c3258-0c0f3d9832018b8-7622565d-3686400-193de1300c417ed%22%7D","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"bannerFlag","value":"true","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"CUID","value":"18d66202692f243f9718a86239f60492","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":94608000,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"TYCID","value":"1b44b4b0bde611ef8bda23f342e81e81","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":63072000,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"sajssdk_2015_cross_new_user","value":"1","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":25852,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"tyc-user-info","value":"{%22state%22:%223%22%2C%22vipManager%22:%220%22%2C%22mobile%22:%2217717373906%22%2C%22userId%22:%22313432949%22%2C%22isExpired%22:%220%22}","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":2591999,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"tyc-user-info-save-time","value":"1734598367232","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":2591999,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"auth_token","value":"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxNzcxNzM3MzkwNiIsImlhdCI6MTczNDU5ODM2OCwiZXhwIjoxNzM3MTkwMzY4fQ.yqRAdNtp4cwPCu5HTEIGuzTZebaxJu5cMfWwuPAPJxDWrWzXxKLtzDwwclf3eLB6zMI7IPrxwEwK9PJ41LvCyA","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":2591999,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"tyc-user-phone","value":"%255B%252217717373906%2522%255D","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":2591999,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"searchSessionId","value":"1734598379.37311337","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":2592000,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sensorsdata2015jssdkcross","value":"%7B%22distinct_id%22%3A%22313432949%22%2C%22first_id%22%3A%22193de1b3aad1073-0482365c4952ce-7622565d-3686400-193de1b3aaecef%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTkzZGUxYjNhYWQxMDczLTA0ODIzNjVjNDk1MmNlLTc2MjI1NjVkLTM2ODY0MDAtMTkzZGUxYjNhYWVjZWYiLCIkaWRlbnRpdHlfbG9naW5faWQiOiIzMTM0MzI5NDkifQ%3D%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%24identity_login_id%22%2C%22value%22%3A%22313432949%22%7D%2C%22%24device_id%22%3A%22193de1b3aad1073-0482365c4952ce-7622565d-3686400-193de1b3aaecef%22%7D","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":6307199999,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"bannerFlag","value":"true","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false}],"sentry-prod.jindidata.com":[{"name":"HWWAFSESID","value":"7674a2b6e3a68381599","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596567771","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"1173ae5e783b8845db","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734597648473","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734598367961","comment":null,"commentURL":null,"domain":"sentry-prod.jindidata.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"HWWAFSESID","value":"522ae1a20c9be5ea3fe","comment":null,"commentURL":null,"domain":"sentry-prod.jindidata.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false}],"napi-huawei.tianyancha.com":[{"name":"HWWAFSESID","value":"9f6d6580439f013191b","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596567549","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734597649667","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"e79078505d373ef002","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734598378293","comment":null,"commentURL":null,"domain":"napi-huawei.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"HWWAFSESID","value":"d6dc53c4d3edf74c083","comment":null,"commentURL":null,"domain":"napi-huawei.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false}],"tianyancha.com":[{"name":"ssuid","value":"3712781011","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HMACCOUNT","value":"082BC733B131A88C","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_ga","value":"GA1.2.1795641672.1734596545","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gid","value":"GA1.2.988578840.1734596545","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gat_gtag_UA_123487620_1","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"Hm_lvt_e92c8d65d92d534b0fc290df538b4758","value":"1734594291,1734594478,1734594712,1734596545","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"Hm_lpvt_e92c8d65d92d534b0fc290df538b4758","value":"1734596577","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"ssuid","value":"6418765584","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"Hm_lvt_e92c8d65d92d534b0fc290df538b4758","value":"1734594478,1734594712,1734596545,1734596614","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"Hm_lpvt_e92c8d65d92d534b0fc290df538b4758","value":"1734596614","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HMACCOUNT","value":"D144FDA95FC24A30","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_ga","value":"GA1.2.1007349574.1734596614","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gid","value":"GA1.2.372127674.1734596614","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gat_gtag_UA_123487620_1","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false}]},"uriIndex":{"http://gcaptcha4.geetest.com":[{"name":"captcha_v4_user","value":"359bd1ab73354d33b25e3dce42898c5e","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"captcha_v4_user","value":"0425c2bb69714a3cb2521448114325c6","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"captcha_v4_user","value":"c3bf54ec0c474360ae95afad40d9205d","comment":null,"commentURL":null,"domain":"gcaptcha4.geetest.com","maxAge":31536000,"path":"/","portlist":null,"secure":true,"httpOnly":false,"version":0,"discard":false}],"http://www.tianyancha.com":[{"name":"HWWAFSESID","value":"8594582696edad4731","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596543940","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"csrfToken","value":"Zu_1PyVs9IXN5y_h2BbE_gPO","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"CUID","value":"17ba8fda93fb2f1707efa9f51831e174","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"TYCID","value":"60283100bde211efa30eaff1b68b8ea3","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"ssuid","value":"3712781011","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sajssdk_2015_cross_new_user","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"bannerFlag","value":"true","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HMACCOUNT","value":"082BC733B131A88C","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_ga","value":"GA1.2.1795641672.1734596545","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gid","value":"GA1.2.988578840.1734596545","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gat_gtag_UA_123487620_1","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"tyc-user-info","value":"{%22state%22:%223%22%2C%22vipManager%22:%220%22%2C%22mobile%22:%2217717373906%22%2C%22userId%22:%22313432949%22%2C%22isExpired%22:%220%22}","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"tyc-user-info-save-time","value":"1734596568857","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"auth_token","value":"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxNzcxNzM3MzkwNiIsImlhdCI6MTczNDU5NjU2OSwiZXhwIjoxNzM3MTg4NTY5fQ.JZjXteqxMdk9ndGcUgYpTvqKGAreOgBsLJsuR-rGg6xCoYkxleJqdOL7CLiMMZ_CkP_NUJOMNG4jqeHqMUs6hQ","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"tyc-user-phone","value":"%255B%252217717373906%2522%255D","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"searchSessionId","value":"1734596577.64645647","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sensorsdata2015jssdkcross","value":"%7B%22distinct_id%22%3A%22313432949%22%2C%22first_id%22%3A%22193de02c72799e-068e92c991bbdfc-7622565d-3686400-193de02c728c4c%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTkzZGUwMmM3Mjc5OWUtMDY4ZTkyYzk5MWJiZGZjLTc2MjI1NjVkLTM2ODY0MDAtMTkzZGUwMmM3MjhjNGMiLCIkaWRlbnRpdHlfbG9naW5faWQiOiIzMTM0MzI5NDkifQ%3D%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%24identity_login_id%22%2C%22value%22%3A%22313432949%22%7D%2C%22%24device_id%22%3A%22193de02c72799e-068e92c991bbdfc-7622565d-3686400-193de02c728c4c%22%7D","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"Hm_lvt_e92c8d65d92d534b0fc290df538b4758","value":"1734594291,1734594478,1734594712,1734596545","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"Hm_lpvt_e92c8d65d92d534b0fc290df538b4758","value":"1734596577","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596609400","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"b24c910ce0099714055","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"csrfToken","value":"VSnPfq_4O3AfxGC1Tlq5c7AB","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"CUID","value":"d297fa9ee7602b2d8bee025632a3f27f","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"TYCID","value":"890fd730bde211ef92f88565e8c18ca8","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"ssuid","value":"6418765584","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sajssdk_2015_cross_new_user","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sensorsdata2015jssdkcross","value":"%7B%22distinct_id%22%3A%22193de03d3183bd-035c3758008fd66-7622565d-3686400-193de03d319ca2%22%2C%22first_id%22%3A%22%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTkzZGUwM2QzMTgzYmQtMDM1YzM3NTgwMDhmZDY2LTc2MjI1NjVkLTM2ODY0MDAtMTkzZGUwM2QzMTljYTIifQ%3D%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%22%2C%22value%22%3A%22%22%7D%2C%22%24device_id%22%3A%22193de03d3183bd-035c3758008fd66-7622565d-3686400-193de03d319ca2%22%7D","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"bannerFlag","value":"true","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"Hm_lvt_e92c8d65d92d534b0fc290df538b4758","value":"1734594478,1734594712,1734596545,1734596614","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"Hm_lpvt_e92c8d65d92d534b0fc290df538b4758","value":"1734596614","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HMACCOUNT","value":"D144FDA95FC24A30","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_ga","value":"GA1.2.1007349574.1734596614","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gid","value":"GA1.2.372127674.1734596614","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gat_gtag_UA_123487620_1","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596737002","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"8696d16f9dade193375","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"csrfToken","value":"xyfl7cuGYMu2M2l1C7UcRpja","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"CUID","value":"15ac6fafd2ad2497c7cb55570dca9eac","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"TYCID","value":"d53a21b0bde211efa30eaff1b68b8ea3","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sajssdk_2015_cross_new_user","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sensorsdata2015jssdkcross","value":"%7B%22distinct_id%22%3A%22193de05c5d810bd-0d747256f2ddcc-7622565d-3686400-193de05c5d9cc8%22%2C%22first_id%22%3A%22%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTkzZGUwNWM1ZDgxMGJkLTBkNzQ3MjU2ZjJkZGNjLTc2MjI1NjVkLTM2ODY0MDAtMTkzZGUwNWM1ZDljYzgifQ%3D%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%22%2C%22value%22%3A%22%22%7D%2C%22%24device_id%22%3A%22193de05c5d810bd-0d747256f2ddcc-7622565d-3686400-193de05c5d9cc8%22%7D","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"bannerFlag","value":"true","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_ga","value":"GA1.1.228018237.1734596741","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gid","value":"GA1.1.1473480425.1734596741","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gat_gtag_UA_123487620_1","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734597036965","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"d040113394ab3edfb39","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"csrfToken","value":"aRrbYTtiLxUk_U98kKjzAS5t","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"CUID","value":"fd64fcaeacf724db19fe717dc1781bee","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"TYCID","value":"880e9e60bde311efb4db233063166be0","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sajssdk_2015_cross_new_user","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sensorsdata2015jssdkcross","value":"%7B%22distinct_id%22%3A%22193de0a6656962-0bed636145e1cc8-7622565d-3686400-193de0a6657c9d%22%2C%22first_id%22%3A%22%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTkzZGUwYTY2NTY5NjItMGJlZDYzNjE0NWUxY2M4LTc2MjI1NjVkLTM2ODY0MDAtMTkzZGUwYTY2NTdjOWQifQ%3D%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%22%2C%22value%22%3A%22%22%7D%2C%22%24device_id%22%3A%22193de0a6656962-0bed636145e1cc8-7622565d-3686400-193de0a6657c9d%22%7D","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"bannerFlag","value":"true","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_ga","value":"GA1.1.711605108.1734597045","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gid","value":"GA1.1.1426239103.1734597045","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gat_gtag_UA_123487620_1","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"f771679b8c97186bd1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734597607684","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"csrfToken","value":"wi0vTu-FoO4mJtGk055_jNAg","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"CUID","value":"69ebb2181678214280059319600ff749","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"TYCID","value":"d9f797d0bde411ef96107538ca8b55ff","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sajssdk_2015_cross_new_user","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"tyc-user-info","value":"{%22state%22:%223%22%2C%22vipManager%22:%220%22%2C%22mobile%22:%2217717373906%22%2C%22userId%22:%22313432949%22%2C%22isExpired%22:%220%22}","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"tyc-user-info-save-time","value":"1734597646325","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"auth_token","value":"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxNzcxNzM3MzkwNiIsImlhdCI6MTczNDU5NzY0NywiZXhwIjoxNzM3MTg5NjQ3fQ.ZLY_Zyu6IpfMAc8gPkNv2mHdGSFoRSW2ZiUm3e02_h0h7vWtui57P0tqB-PLcCZQ8WLJhtECgF5wOverOrivDg","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"tyc-user-phone","value":"%255B%252217717373906%2522%255D","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"searchSessionId","value":"1734597832.53059832","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sensorsdata2015jssdkcross","value":"%7B%22distinct_id%22%3A%22313432949%22%2C%22first_id%22%3A%22193de1300c3258-0c0f3d9832018b8-7622565d-3686400-193de1300c417ed%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTkzZGUxMzAwYzMyNTgtMGMwZjNkOTgzMjAxOGI4LTc2MjI1NjVkLTM2ODY0MDAtMTkzZGUxMzAwYzQxN2VkIiwiJGlkZW50aXR5X2xvZ2luX2lkIjoiMzEzNDMyOTQ5In0%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%24identity_login_id%22%2C%22value%22%3A%22313432949%22%7D%2C%22%24device_id%22%3A%22193de1300c3258-0c0f3d9832018b8-7622565d-3686400-193de1300c417ed%22%7D","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"bannerFlag","value":"true","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_ga","value":"GA1.1.1688427152.1734597608","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gid","value":"GA1.1.503345592.1734597608","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"640db82ef4d455aae9","comment":null,"commentURL":null,"domain":"www.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"HWWAFSESTIME","value":"1734598146629","comment":null,"commentURL":null,"domain":"www.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"csrfToken","value":"56i8jgGUedl4rG5-qOZp28Nz","comment":null,"commentURL":null,"domain":"www.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"CUID","value":"18d66202692f243f9718a86239f60492","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":94608000,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"TYCID","value":"1b44b4b0bde611ef8bda23f342e81e81","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":63072000,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"sajssdk_2015_cross_new_user","value":"1","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":25852,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"tyc-user-info","value":"{%22state%22:%223%22%2C%22vipManager%22:%220%22%2C%22mobile%22:%2217717373906%22%2C%22userId%22:%22313432949%22%2C%22isExpired%22:%220%22}","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":2591999,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"tyc-user-info-save-time","value":"1734598367232","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":2591999,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"auth_token","value":"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxNzcxNzM3MzkwNiIsImlhdCI6MTczNDU5ODM2OCwiZXhwIjoxNzM3MTkwMzY4fQ.yqRAdNtp4cwPCu5HTEIGuzTZebaxJu5cMfWwuPAPJxDWrWzXxKLtzDwwclf3eLB6zMI7IPrxwEwK9PJ41LvCyA","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":2591999,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"tyc-user-phone","value":"%255B%252217717373906%2522%255D","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":2591999,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"searchSessionId","value":"1734598379.37311337","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":2592000,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sensorsdata2015jssdkcross","value":"%7B%22distinct_id%22%3A%22313432949%22%2C%22first_id%22%3A%22193de1b3aad1073-0482365c4952ce-7622565d-3686400-193de1b3aaecef%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTkzZGUxYjNhYWQxMDczLTA0ODIzNjVjNDk1MmNlLTc2MjI1NjVkLTM2ODY0MDAtMTkzZGUxYjNhYWVjZWYiLCIkaWRlbnRpdHlfbG9naW5faWQiOiIzMTM0MzI5NDkifQ%3D%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%24identity_login_id%22%2C%22value%22%3A%22313432949%22%7D%2C%22%24device_id%22%3A%22193de1b3aad1073-0482365c4952ce-7622565d-3686400-193de1b3aaecef%22%7D","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":6307199999,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"bannerFlag","value":"true","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"_ga","value":"GA1.1.2127366020.1734598148","comment":null,"commentURL":null,"domain":"www.tianyancha.com","maxAge":63071999,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"_gid","value":"GA1.1.219896500.1734598148","comment":null,"commentURL":null,"domain":"www.tianyancha.com","maxAge":86399,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"_gat_gtag_UA_123487620_1","value":"1","comment":null,"commentURL":null,"domain":"www.tianyancha.com","maxAge":59,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false}],"http://sentry-prod.jindidata.com":[{"name":"HWWAFSESID","value":"7674a2b6e3a68381599","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596567771","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"1173ae5e783b8845db","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734597648473","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734598367961","comment":null,"commentURL":null,"domain":"sentry-prod.jindidata.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"HWWAFSESID","value":"522ae1a20c9be5ea3fe","comment":null,"commentURL":null,"domain":"sentry-prod.jindidata.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false}],"http://cloud-gateway.tianyancha.com":[{"name":"HWWAFSESTIME","value":"1734596569303","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"aad2ff46bbd32b773b","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"933a1760e05480a59c","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734597649532","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"da8e6c44b080ef138a","comment":null,"commentURL":null,"domain":"cloud-gateway.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"HWWAFSESTIME","value":"1734598379937","comment":null,"commentURL":null,"domain":"cloud-gateway.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false}],"http://napi-huawei.tianyancha.com":[{"name":"HWWAFSESID","value":"9f6d6580439f013191b","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596567549","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734597649667","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"e79078505d373ef002","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734598378293","comment":null,"commentURL":null,"domain":"napi-huawei.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"HWWAFSESID","value":"d6dc53c4d3edf74c083","comment":null,"commentURL":null,"domain":"napi-huawei.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false}],"http://capi.tianyancha.com":[{"name":"HWWAFSESID","value":"50686102a0afeb7be3a2","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596543736","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"6ba03d032b0699e06ed","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596612564","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"b47aa1dce32787c8b3c1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596737658","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"1d320c371f909352d05","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734597042838","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"55d67229f46a5060b35d","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734597604201","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"07be6171af14b0b5b29b","comment":null,"commentURL":null,"domain":"capi.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"HWWAFSESTIME","value":"1734598144461","comment":null,"commentURL":null,"domain":"capi.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false}]},"cookieJar":[{"name":"HWWAFSESID","value":"8594582696edad4731","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596543940","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"csrfToken","value":"Zu_1PyVs9IXN5y_h2BbE_gPO","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"CUID","value":"17ba8fda93fb2f1707efa9f51831e174","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"TYCID","value":"60283100bde211efa30eaff1b68b8ea3","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"ssuid","value":"3712781011","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sajssdk_2015_cross_new_user","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"bannerFlag","value":"true","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"50686102a0afeb7be3a2","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596543736","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HMACCOUNT","value":"082BC733B131A88C","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_ga","value":"GA1.2.1795641672.1734596545","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gid","value":"GA1.2.988578840.1734596545","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gat_gtag_UA_123487620_1","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"captcha_v4_user","value":"359bd1ab73354d33b25e3dce42898c5e","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"tyc-user-info","value":"{%22state%22:%223%22%2C%22vipManager%22:%220%22%2C%22mobile%22:%2217717373906%22%2C%22userId%22:%22313432949%22%2C%22isExpired%22:%220%22}","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"tyc-user-info-save-time","value":"1734596568857","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"auth_token","value":"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxNzcxNzM3MzkwNiIsImlhdCI6MTczNDU5NjU2OSwiZXhwIjoxNzM3MTg4NTY5fQ.JZjXteqxMdk9ndGcUgYpTvqKGAreOgBsLJsuR-rGg6xCoYkxleJqdOL7CLiMMZ_CkP_NUJOMNG4jqeHqMUs6hQ","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"tyc-user-phone","value":"%255B%252217717373906%2522%255D","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"7674a2b6e3a68381599","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596567771","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596569303","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"aad2ff46bbd32b773b","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"9f6d6580439f013191b","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596567549","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"searchSessionId","value":"1734596577.64645647","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sensorsdata2015jssdkcross","value":"%7B%22distinct_id%22%3A%22313432949%22%2C%22first_id%22%3A%22193de02c72799e-068e92c991bbdfc-7622565d-3686400-193de02c728c4c%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTkzZGUwMmM3Mjc5OWUtMDY4ZTkyYzk5MWJiZGZjLTc2MjI1NjVkLTM2ODY0MDAtMTkzZGUwMmM3MjhjNGMiLCIkaWRlbnRpdHlfbG9naW5faWQiOiIzMTM0MzI5NDkifQ%3D%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%24identity_login_id%22%2C%22value%22%3A%22313432949%22%7D%2C%22%24device_id%22%3A%22193de02c72799e-068e92c991bbdfc-7622565d-3686400-193de02c728c4c%22%7D","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"Hm_lvt_e92c8d65d92d534b0fc290df538b4758","value":"1734594291,1734594478,1734594712,1734596545","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"Hm_lpvt_e92c8d65d92d534b0fc290df538b4758","value":"1734596577","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596609400","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"b24c910ce0099714055","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"csrfToken","value":"VSnPfq_4O3AfxGC1Tlq5c7AB","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"CUID","value":"d297fa9ee7602b2d8bee025632a3f27f","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"TYCID","value":"890fd730bde211ef92f88565e8c18ca8","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"ssuid","value":"6418765584","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sajssdk_2015_cross_new_user","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sensorsdata2015jssdkcross","value":"%7B%22distinct_id%22%3A%22193de03d3183bd-035c3758008fd66-7622565d-3686400-193de03d319ca2%22%2C%22first_id%22%3A%22%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTkzZGUwM2QzMTgzYmQtMDM1YzM3NTgwMDhmZDY2LTc2MjI1NjVkLTM2ODY0MDAtMTkzZGUwM2QzMTljYTIifQ%3D%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%22%2C%22value%22%3A%22%22%7D%2C%22%24device_id%22%3A%22193de03d3183bd-035c3758008fd66-7622565d-3686400-193de03d319ca2%22%7D","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"bannerFlag","value":"true","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"6ba03d032b0699e06ed","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596612564","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"Hm_lvt_e92c8d65d92d534b0fc290df538b4758","value":"1734594478,1734594712,1734596545,1734596614","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"Hm_lpvt_e92c8d65d92d534b0fc290df538b4758","value":"1734596614","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HMACCOUNT","value":"D144FDA95FC24A30","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_ga","value":"GA1.2.1007349574.1734596614","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gid","value":"GA1.2.372127674.1734596614","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gat_gtag_UA_123487620_1","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596737002","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"8696d16f9dade193375","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"csrfToken","value":"xyfl7cuGYMu2M2l1C7UcRpja","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"CUID","value":"15ac6fafd2ad2497c7cb55570dca9eac","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"TYCID","value":"d53a21b0bde211efa30eaff1b68b8ea3","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sajssdk_2015_cross_new_user","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"b47aa1dce32787c8b3c1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734596737658","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sensorsdata2015jssdkcross","value":"%7B%22distinct_id%22%3A%22193de05c5d810bd-0d747256f2ddcc-7622565d-3686400-193de05c5d9cc8%22%2C%22first_id%22%3A%22%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTkzZGUwNWM1ZDgxMGJkLTBkNzQ3MjU2ZjJkZGNjLTc2MjI1NjVkLTM2ODY0MDAtMTkzZGUwNWM1ZDljYzgifQ%3D%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%22%2C%22value%22%3A%22%22%7D%2C%22%24device_id%22%3A%22193de05c5d810bd-0d747256f2ddcc-7622565d-3686400-193de05c5d9cc8%22%7D","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"bannerFlag","value":"true","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_ga","value":"GA1.1.228018237.1734596741","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gid","value":"GA1.1.1473480425.1734596741","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gat_gtag_UA_123487620_1","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734597036965","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"d040113394ab3edfb39","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"csrfToken","value":"aRrbYTtiLxUk_U98kKjzAS5t","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"CUID","value":"fd64fcaeacf724db19fe717dc1781bee","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"TYCID","value":"880e9e60bde311efb4db233063166be0","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sajssdk_2015_cross_new_user","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sensorsdata2015jssdkcross","value":"%7B%22distinct_id%22%3A%22193de0a6656962-0bed636145e1cc8-7622565d-3686400-193de0a6657c9d%22%2C%22first_id%22%3A%22%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTkzZGUwYTY2NTY5NjItMGJlZDYzNjE0NWUxY2M4LTc2MjI1NjVkLTM2ODY0MDAtMTkzZGUwYTY2NTdjOWQifQ%3D%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%22%2C%22value%22%3A%22%22%7D%2C%22%24device_id%22%3A%22193de0a6656962-0bed636145e1cc8-7622565d-3686400-193de0a6657c9d%22%7D","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"bannerFlag","value":"true","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"1d320c371f909352d05","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734597042838","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_ga","value":"GA1.1.711605108.1734597045","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gid","value":"GA1.1.1426239103.1734597045","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gat_gtag_UA_123487620_1","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"f771679b8c97186bd1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734597607684","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"csrfToken","value":"wi0vTu-FoO4mJtGk055_jNAg","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"CUID","value":"69ebb2181678214280059319600ff749","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"TYCID","value":"d9f797d0bde411ef96107538ca8b55ff","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sajssdk_2015_cross_new_user","value":"1","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"55d67229f46a5060b35d","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734597604201","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"captcha_v4_user","value":"0425c2bb69714a3cb2521448114325c6","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"tyc-user-info","value":"{%22state%22:%223%22%2C%22vipManager%22:%220%22%2C%22mobile%22:%2217717373906%22%2C%22userId%22:%22313432949%22%2C%22isExpired%22:%220%22}","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"tyc-user-info-save-time","value":"1734597646325","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"auth_token","value":"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxNzcxNzM3MzkwNiIsImlhdCI6MTczNDU5NzY0NywiZXhwIjoxNzM3MTg5NjQ3fQ.ZLY_Zyu6IpfMAc8gPkNv2mHdGSFoRSW2ZiUm3e02_h0h7vWtui57P0tqB-PLcCZQ8WLJhtECgF5wOverOrivDg","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"tyc-user-phone","value":"%255B%252217717373906%2522%255D","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"1173ae5e783b8845db","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734597648473","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"933a1760e05480a59c","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734597649532","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESTIME","value":"1734597649667","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"e79078505d373ef002","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"searchSessionId","value":"1734597832.53059832","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"sensorsdata2015jssdkcross","value":"%7B%22distinct_id%22%3A%22313432949%22%2C%22first_id%22%3A%22193de1300c3258-0c0f3d9832018b8-7622565d-3686400-193de1300c417ed%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTkzZGUxMzAwYzMyNTgtMGMwZjNkOTgzMjAxOGI4LTc2MjI1NjVkLTM2ODY0MDAtMTkzZGUxMzAwYzQxN2VkIiwiJGlkZW50aXR5X2xvZ2luX2lkIjoiMzEzNDMyOTQ5In0%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%24identity_login_id%22%2C%22value%22%3A%22313432949%22%7D%2C%22%24device_id%22%3A%22193de1300c3258-0c0f3d9832018b8-7622565d-3686400-193de1300c417ed%22%7D","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"bannerFlag","value":"true","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_ga","value":"GA1.1.1688427152.1734597608","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"_gid","value":"GA1.1.503345592.1734597608","comment":null,"commentURL":null,"domain":null,"maxAge":-1,"path":null,"portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"640db82ef4d455aae9","comment":null,"commentURL":null,"domain":"www.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"HWWAFSESTIME","value":"1734598146629","comment":null,"commentURL":null,"domain":"www.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"csrfToken","value":"56i8jgGUedl4rG5-qOZp28Nz","comment":null,"commentURL":null,"domain":"www.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"CUID","value":"18d66202692f243f9718a86239f60492","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":94608000,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"TYCID","value":"1b44b4b0bde611ef8bda23f342e81e81","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":63072000,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"sajssdk_2015_cross_new_user","value":"1","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":25852,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"HWWAFSESID","value":"07be6171af14b0b5b29b","comment":null,"commentURL":null,"domain":"capi.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"HWWAFSESTIME","value":"1734598144461","comment":null,"commentURL":null,"domain":"capi.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"tyc-user-info","value":"{%22state%22:%223%22%2C%22vipManager%22:%220%22%2C%22mobile%22:%2217717373906%22%2C%22userId%22:%22313432949%22%2C%22isExpired%22:%220%22}","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":2591999,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"tyc-user-info-save-time","value":"1734598367232","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":2591999,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"auth_token","value":"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxNzcxNzM3MzkwNiIsImlhdCI6MTczNDU5ODM2OCwiZXhwIjoxNzM3MTkwMzY4fQ.yqRAdNtp4cwPCu5HTEIGuzTZebaxJu5cMfWwuPAPJxDWrWzXxKLtzDwwclf3eLB6zMI7IPrxwEwK9PJ41LvCyA","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":2591999,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"tyc-user-phone","value":"%255B%252217717373906%2522%255D","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":2591999,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"HWWAFSESTIME","value":"1734598367961","comment":null,"commentURL":null,"domain":"sentry-prod.jindidata.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"HWWAFSESID","value":"522ae1a20c9be5ea3fe","comment":null,"commentURL":null,"domain":"sentry-prod.jindidata.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"captcha_v4_user","value":"c3bf54ec0c474360ae95afad40d9205d","comment":null,"commentURL":null,"domain":"gcaptcha4.geetest.com","maxAge":31536000,"path":"/","portlist":null,"secure":true,"httpOnly":false,"version":0,"discard":false},{"name":"searchSessionId","value":"1734598379.37311337","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":2592000,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":1,"discard":false},{"name":"HWWAFSESID","value":"da8e6c44b080ef138a","comment":null,"commentURL":null,"domain":"cloud-gateway.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"HWWAFSESTIME","value":"1734598379937","comment":null,"commentURL":null,"domain":"cloud-gateway.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"HWWAFSESTIME","value":"1734598378293","comment":null,"commentURL":null,"domain":"napi-huawei.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"HWWAFSESID","value":"d6dc53c4d3edf74c083","comment":null,"commentURL":null,"domain":"napi-huawei.tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"sensorsdata2015jssdkcross","value":"%7B%22distinct_id%22%3A%22313432949%22%2C%22first_id%22%3A%22193de1b3aad1073-0482365c4952ce-7622565d-3686400-193de1b3aaecef%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%2C%22%24latest_referrer%22%3A%22%22%7D%2C%22identities%22%3A%22eyIkaWRlbnRpdHlfY29va2llX2lkIjoiMTkzZGUxYjNhYWQxMDczLTA0ODIzNjVjNDk1MmNlLTc2MjI1NjVkLTM2ODY0MDAtMTkzZGUxYjNhYWVjZWYiLCIkaWRlbnRpdHlfbG9naW5faWQiOiIzMTM0MzI5NDkifQ%3D%3D%22%2C%22history_login_id%22%3A%7B%22name%22%3A%22%24identity_login_id%22%2C%22value%22%3A%22313432949%22%7D%2C%22%24device_id%22%3A%22193de1b3aad1073-0482365c4952ce-7622565d-3686400-193de1b3aaecef%22%7D","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":6307199999,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"bannerFlag","value":"true","comment":null,"commentURL":null,"domain":".tianyancha.com","maxAge":-1,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"_ga","value":"GA1.1.2127366020.1734598148","comment":null,"commentURL":null,"domain":"www.tianyancha.com","maxAge":63071999,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"_gid","value":"GA1.1.219896500.1734598148","comment":null,"commentURL":null,"domain":"www.tianyancha.com","maxAge":86399,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false},{"name":"_gat_gtag_UA_123487620_1","value":"1","comment":null,"commentURL":null,"domain":"www.tianyancha.com","maxAge":59,"path":"/","portlist":null,"secure":false,"httpOnly":false,"version":0,"discard":false}]} \ No newline at end of file diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml new file mode 100644 index 0000000..16a57bd --- /dev/null +++ b/dependency-reduced-pom.xml @@ -0,0 +1,256 @@ + + + + spring-boot-starter-parent + org.springframework.boot + 3.3.7 + pom.xml + + 4.0.0 + com.ecep.contract + Contract-Manager + Contract-Manager + 0.0.1-SNAPSHOT + Contract-Manager + + + 1 + 宋其青 + qiqing.song@ecep.com + + + + + + + + + + org.openjfx + javafx-maven-plugin + 0.0.8 + + com.ecep.contract.manager.AppV2 + app + app-jlink + app-jlink-image + true + true + 2 + + + + maven-shade-plugin + 3.3.0 + + + package + + shade + + + + + + + + + org.springframework.boot + spring-boot-starter-data-redis + 3.3.7 + provided + + + org.springframework.boot + spring-boot-starter-mail + 3.3.7 + provided + + + com.microsoft.sqlserver + mssql-jdbc + 12.6.4.jre11 + provided + + + com.github.ben-manes.caffeine + caffeine + 3.1.8 + provided + + + org.projectlombok + lombok + 1.18.36 + provided + true + + + org.springframework.boot + spring-boot-starter-test + 3.3.7 + test + + + spring-boot-test + org.springframework.boot + + + spring-boot-test-autoconfigure + org.springframework.boot + + + json-path + com.jayway.jsonpath + + + json-smart + net.minidev + + + assertj-core + org.assertj + + + awaitility + org.awaitility + + + hamcrest + org.hamcrest + + + junit-jupiter + org.junit.jupiter + + + mockito-core + org.mockito + + + mockito-junit-jupiter + org.mockito + + + jsonassert + org.skyscreamer + + + spring-test + org.springframework + + + xmlunit-core + org.xmlunit + + + + + com.zaxxer + HikariCP + 5.1.0 + provided + + + org.springframework.boot + spring-boot-starter-jdbc + 3.3.7 + provided + + + org.springframework.data + spring-data-jdbc + 3.3.7 + provided + + + com.mysql + mysql-connector-j + 8.3.0 + provided + + + protobuf-java + com.google.protobuf + + + + + org.springframework.boot + spring-boot-starter-data-jpa + 3.3.7 + provided + + + org.springframework.data + spring-data-jpa + 3.3.7 + provided + + + org.openjfx + javafx-controls + 22.0.2 + provided + + + org.openjfx + javafx-fxml + 22.0.2 + provided + + + org.openjfx + javafx-web + 22.0.2 + provided + + + org.controlsfx + controlsfx + 11.2.0 + provided + + + com.fasterxml.jackson.core + jackson-databind + 2.17.3 + provided + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.17.3 + provided + + + ch.qos.logback + logback-access + 1.5.12 + compile + + + org.apache.poi + poi + 5.3.0 + provided + + + org.apache.poi + poi-ooxml + 5.3.0 + provided + + + org.apache.pdfbox + pdfbox + 3.0.3 + provided + + + + 22 + 5.3.0 + 22.0.2 + + diff --git a/docs/Black_Reason.json b/docs/Black_Reason.json new file mode 100644 index 0000000..bb32d55 --- /dev/null +++ b/docs/Black_Reason.json @@ -0,0 +1,29 @@ +{ + "id": 7398596, + "entId": "10275671", + "entName": "上海星宇建设集团有限公司", + "entType": "E", + "blackGrey": "黑", + "blackLevel": "1", + "subordinateBusinessParty": null, + "subordinateBusinessPartyNm": "全集团", + "businessArea": "6", + "includeDate": "2024-06-28", + "dataSources": "4", + "blackReason": "已被列为失信被执行人", + "applyName": "外部", + "applyDate": "2024-06-28", + "processId": null, + "formInstanceId": null, + "description": "案号:(2021)沪0110执1129号 失信被执行人具体行为:有履行能力而拒不履行生效法律文书确定义务 生效法律文书确定义务:无 被执行人履行情况:全部未履行 执行法院:310202", + "balckTemplateId": null, + "removeDate": null, + "blackStatus": "1", + "createTime": "2024-06-28", + "uploadingDocuments": null, + "uploadingName": null, + "source": "外部数据", + "ispublic": "0", + "updateTime": "2024-06-28", + "createByName": null +} \ No newline at end of file diff --git a/docs/Ent_Fuzzy.json b/docs/Ent_Fuzzy.json new file mode 100644 index 0000000..934f2ed --- /dev/null +++ b/docs/Ent_Fuzzy.json @@ -0,0 +1,43 @@ +{ + "data" : [ { + "entid" : "pWjJpZENa", + "entname" : "中国移动通信集团有限公司", + "isNowName" : "0" + }, { + "entid" : "PfMGOgxfEV", + "entname" : "中国石油天然气股份有限公司", + "isNowName" : "0" + }, { + "entid" : "v5asxpqV", + "entname" : "华能国际电力股份有限公司", + "isNowName" : "0" + }, { + "entid" : "VyDNRKw9J89", + "entname" : "国家能源集团宁夏煤业有限责任公司", + "isNowName" : "0" + }, { + "entid" : "pKVZYm8db6t", + "entname" : "宏润建设集团股份有限公司", + "isNowName" : "0" + }, { + "entid" : "LzMLH2winP", + "entname" : "抚顺矿业集团有限责任公司", + "isNowName" : "0" + }, { + "entid" : "jz7PqUtTBK9", + "entname" : "中国移动通信集团湖北有限公司", + "isNowName" : "0" + }, { + "entid" : "ywxksg38oLO", + "entname" : "中国广电四川网络股份有限公司", + "isNowName" : "0" + }, { + "entid" : "faVnctUWKa", + "entname" : "中国石油化工股份有限公司", + "isNowName" : "0" + }, { + "entid" : "j2zbBGHC0QV", + "entname" : "山东莱芜农村商业银行股份有限公司", + "isNowName" : "0" + } ] +} \ No newline at end of file diff --git a/docs/Ent_Report_B1001.json b/docs/Ent_Report_B1001.json new file mode 100644 index 0000000..42e35fb --- /dev/null +++ b/docs/Ent_Report_B1001.json @@ -0,0 +1,157 @@ +{ + "B1001" : { + "data" : { + "esdate" : "1995-03-13", + "nameHistory" : "", + "dom" : "上海市宝山区杨泰路868号", + "reccap" : 37234.2827, + "enttype" : "有限责任公司(台港澳与境内合资)", + "nacaoid" : "607300405", + "opfrom" : "1995-03-13", + "exenum" : null, + "parform" : "", + "localadm" : null, + "insform" : null, + "entname" : "上海一冷开利空调设备有限公司", + "nicfulltitle" : "制造业-通用设备制造业-烘炉、风机、包装等设备制造-制冷、空调设备制造", + "regorg" : "上海市市场监督管理局", + "tel" : "021-26092000", + "abuitem" : "生产空调制冷、采暖、通风设备及相关零部件,销售自产产品;自产产品及同类商品的批发、进出口、佣金代理(拍卖除外);机电设备安装工程、建筑智能化工程专业承包;提供相关技术咨询、售后服务等配套服务(不涉及国营贸易管理商品,涉及配额、许可证管理及建筑业资质要求的,需在取得许可证和相应资质后开展经营业务)。【依法须经批准的项目,经相关部门批准后方可开展经营活动】", + "email" : "ming.jiang@carrier.utc.com", + "frname" : "汤盛锋", + "regno" : "310000400109398", + "opto" : "2045-03-12", + "ancheyear" : "2023", + "seicfulltitle" : null, + "domproright" : null, + "parnum" : null, + "cbuitem" : "生产空调制冷、采暖、通风设备及相关零部件,销售自产产品;自产产品及同类商品的批发、进出口、佣金代理(拍卖除外);机电设备安装工程、建筑智能化工程专业承包;提供相关技术咨询、售后服务等配套服务(不涉及国营贸易管理商品,涉及配额、许可证管理及建筑业资质要求的,需在取得许可证和相应资质后开展经营业务)。【依法须经批准的项目,经相关部门批准后方可开展经营活动】", + "oploc" : "上海市宝山区杨泰路868号", + "regcapcur" : "人民币元", + "empnum" : "331", + "uniscid" : "913100006073004059", + "opscoandform" : "生产空调制冷、采暖、通风设备及相关零部件,销售自产产品;自产产品及同类商品的批发、进出口、佣金代理(拍卖除外);机电设备安装工程、建筑智能化工程专业承包;提供相关技术咨询、售后服务等配套服务(不涉及国营贸易管理商品,涉及配额、许可证管理及建筑业资质要求的,需在取得许可证和相应资质后开展经营业务)。【依法须经批准的项目,经相关部门批准后方可开展经营活动】", + "regcap" : "37234.2827万", + "regorgprovince" : "上海市宝山区", + "limparnum" : null, + "entstatus" : "在营(开业)", + "apprdate" : "2024-07-11" + }, + "count" : 1, + "ishide" : true, + "header" : [ { + "key" : "entName", + "label" : "企业名称" + }, { + "key" : "historyName", + "label" : "曾用名", + "only" : true + }, { + "key" : "regno", + "label" : "工商注册号" + }, { + "key" : "frname", + "label" : "法定代表人" + }, { + "key" : "uniscid", + "label" : "统一社会信用代码", + "only" : true + }, { + "key" : "regcap", + "label" : "注册资本" + }, { + "key" : "regcapcur", + "label" : "注册资本币种" + }, { + "key" : "entstatus", + "label" : "经营状态" + }, { + "key" : "enttype", + "label" : "企业(机构)类型" + }, { + "key" : "esdate", + "label" : "注册日期" + }, { + "key" : "opfrom", + "label" : "经营期限自" + }, { + "key" : "opto", + "label" : "经营期限至" + }, { + "key" : "regorgprovince", + "label" : "所在地" + }, { + "key" : "dom", + "label" : "注册地址", + "only" : true + }, { + "key" : "regorg", + "label" : "注册机关" + }, { + "key" : "reccap", + "label" : "实收资本(万)", + "only" : true + }, { + "key" : "ancheyear", + "label" : "最后年检日期", + "only" : true + }, { + "key" : "empnum", + "label" : "员工人数", + "only" : true + }, { + "key" : "apprdate", + "label" : "核准日期" + }, { + "key" : "oploc", + "label" : "经营场所", + "only" : true + }, { + "key" : "email", + "label" : "邮箱" + }, { + "key" : "tel", + "label" : "电话" + }, { + "key" : "domproright", + "label" : "住所产权", + "only" : true + }, { + "key" : "insform", + "label" : "设立方式", + "only" : true + }, { + "key" : "parnum", + "label" : "合伙人数", + "only" : true + }, { + "key" : "limparnum", + "label" : "有限合伙人数", + "only" : true + }, { + "key" : "parform", + "label" : "合伙方式", + "only" : true + }, { + "key" : "exenum", + "label" : "执行人数", + "only" : true + }, { + "key" : "nicfulltitle", + "label" : "经济行业分类" + }, { + "key" : "seicfulltitle", + "label" : "战兴产业分类", + "only" : true + }, { + "key" : "abuitem", + "label" : "许可经营项目", + "only" : true + }, { + "key" : "opscoandform", + "label" : "经营(业务)范围" + } ], + "text" : "基本注册信息", + "source" : "国家市场监督管理总局" + } +} \ No newline at end of file diff --git a/docs/Ent_Score.json b/docs/Ent_Score.json new file mode 100644 index 0000000..1a172a8 --- /dev/null +++ b/docs/Ent_Score.json @@ -0,0 +1,24 @@ +{ + "success" : true, + "code" : 20000, + "message" : "成功", + "data" : { + "adverses" : [ { + "adverseEvent" : "被执行人", + "score" : 10, + "count" : 1 + } ], + "reduceScore" : 10, + "level" : "C", + "AHP" : false, + "entTypeCheck" : { + "entType" : "国企", + "finalentname" : "开利亚洲有限公司", + "score" : 75, + "ratioroad" : "70.0%", + "id" : null + }, + "adverseChecks" : [ ], + "levelDescription" : "信用资质中等" + } +} \ No newline at end of file diff --git a/docs/Report_Detail.json b/docs/Report_Detail.json new file mode 100644 index 0000000..9feede6 --- /dev/null +++ b/docs/Report_Detail.json @@ -0,0 +1,21 @@ +{ + "success" : true, + "code" : 20000, + "total" : null, + "message" : "成功", + "data" : { + "entid" : 8853120, + "entname" : "上海华启电器设备有限公司", + "externalEnvironment" : 526, + "enterpriseCharacteristics" : 731, + "stability" : 950, + "managementLevel" : 675, + "negativeRisk" : 699, + "associatedRisk" : 980, + "totalScore" : 720, + "grade" : "R2", + "description" : "次优的等级,具有几乎最好的信用质量。", + "updated" : "2024-10-09T07:43:24.000+00:00", + "entUrl" : "https://rk.shanghai-electric.com/rk-platform/unified-query/panoramic/Za0Wi8Dz17V/上海华启电器设备有限公司" + } +} \ No newline at end of file diff --git a/docs/S24036/img.png b/docs/S24036/img.png new file mode 100644 index 0000000..f3605b9 Binary files /dev/null and b/docs/S24036/img.png differ diff --git a/docs/S24036/img_1.png b/docs/S24036/img_1.png new file mode 100644 index 0000000..9e3d496 Binary files /dev/null and b/docs/S24036/img_1.png differ diff --git a/docs/S24036/img_2.png b/docs/S24036/img_2.png new file mode 100644 index 0000000..0e6b059 Binary files /dev/null and b/docs/S24036/img_2.png differ diff --git a/docs/SupplierScore.json b/docs/SupplierScore.json new file mode 100644 index 0000000..dc053d9 --- /dev/null +++ b/docs/SupplierScore.json @@ -0,0 +1,40 @@ +{ + "success" : true, + "code" : 20000, + "total" : null, + "message" : "成功", + "data" : { + "id" : 6302179, + "entid" : "9996400", + "entname" : "上海一冷开利空调设备有限公司", + "score" : 425, + "s1" : 615, + "s2Ys" : 320, + "s3" : 506, + "s4" : 820, + "s101" : 680, + "s102" : 550, + "s201" : 500, + "s202Ys" : 275, + "s301" : 475, + "s302" : 390, + "s303" : 800, + "s401" : 825, + "s402" : 800, + "scoreLevel" : "R3", + "scoreDes" : "次优资质,供应商有较优的企业表现。", + "s1Des" : "较好", + "s2YsDes" : "一般", + "s3Des" : "一般", + "s4Des" : "很好", + "s101Des" : "较好", + "s102Des" : "较好", + "s201Des" : "一般", + "s202YsDes" : "较差", + "s301Des" : "一般", + "s302Des" : "较差", + "s303Des" : "很好", + "s401Des" : "很好", + "s402Des" : "较好" + } +} diff --git a/docs/readme.txt b/docs/readme.txt new file mode 100644 index 0000000..61fb289 --- /dev/null +++ b/docs/readme.txt @@ -0,0 +1,6 @@ + +list = [ + ProjectCost(id=10, applyDate=null, standardPayWay=false, noStandardPayWayText=null, standardContractText=false, noStandardContractText=null, stampTax=0.0, stampTaxFee=0.0, onSiteServiceFee=0.0, assemblyServiceFee=0.0, technicalServiceFee=0.0, bidServiceFee=0.0, freightCost=0.0, guaranteeLetterFee=0.0, taxAndSurcharges=0.0, taxAndSurchargesFee=0.0, inQuantities=0.0, inTaxAmount=0.0, inExclusiveTaxAmount=0.0, outQuantities=0.0, outTaxAmount=0.0, outExclusiveTaxAmount=0.0, grossProfitMargin=0.0), + ProjectCost(id=11, applyDate=null, standardPayWay=false, noStandardPayWayText=null, standardContractText=false, noStandardContractText=null, stampTax=0.0, stampTaxFee=0.0, onSiteServiceFee=0.0, assemblyServiceFee=0.0, technicalServiceFee=0.0, bidServiceFee=0.0, freightCost=0.0, guaranteeLetterFee=0.0, taxAndSurcharges=0.0, taxAndSurchargesFee=0.0, inQuantities=0.0, inTaxAmount=0.0, inExclusiveTaxAmount=0.0, outQuantities=0.0, outTaxAmount=0.0, outExclusiveTaxAmount=0.0, grossProfitMargin=0.0) +] + diff --git a/docs/temp.sql b/docs/temp.sql new file mode 100644 index 0000000..77e9d7f --- /dev/null +++ b/docs/temp.sql @@ -0,0 +1,284 @@ +/** MySQL **/ + +GRANT ALL PRIVILEGES ON *.* TO 'ecep'@'10.84.209.89' WITH GRANT OPTION; + + +/** 修改用户密码 **/ +ALTER USER 'mianli.wang'@'10.84.209.38' IDENTIFIED WITH mysql_native_password BY 'A2025b0512#'; + + +/* 查询CLOUD_YU表中VEN_CODE不以 | 开头的记录 */ +select * +from CLOUD_YU +where EX_MSG not like '|%'; + + +select * +from VENDOR; + +/* 查询一级行业分类 */ +SELECT SUBSTRING_INDEX(INDUSTRY, '-', 1) as title, COUNT(*) as total +FROM `COMPANY` +where COMPANY.INDUSTRY is not null + and length(INDUSTRY) > 0 +GROUP BY title +order by total DESC; + +/* 查询一级二级行业分类 */ +SELECT SUBSTRING_INDEX(INDUSTRY, '-', 2) as title, COUNT(*) as total +FROM `COMPANY` +where SUBSTRING_INDEX(INDUSTRY, '-', 1) = '交通运输、仓储和邮政业' +GROUP BY title +order by total desc; + +select COMPANY_ID, count(*) +from COMPANY_CONTRACT +group by COMPANY_ID; + + +select * +from CONTRACT + left join supplier_ms.COMPANY_CONTRACT CC on CONTRACT.ID = CC.CONTRACT_ID +where CC.ID is null; + +select * +from CONTRACT +where CODE = 'S23108'; + +select *, locate('-', CODE) +from CONTRACT +# where PARENT_CODE is null or length(PARENT_CODE) = 0 +where locate('-', CODE) = 0 +order by CREATED desc + +limit 100; + +select * +from COMPANY_VENDOR +where VEN_CODE like '%|shykwlyxgs|%'; + +select * +from COMPANY_VENDOR +where COMPANY_ID = 3; + +select * +from COMPANY_CUSTOMER +where CUS_CODE like '%|shykwlyxgs|%'; +select * +from COMPANY_CUSTOMER +where COMPANY_ID = 795; + + + +select * +from CONTRACT +where GUID = '7A839F83-70FF-4F3F-9063-F930B93F1445'; + +select * +from COMPANY +where NAME = '德州六顺电气自动化设备有限公司'; + + +select GUID, COUNT(*) +from CONTRACT +group by GUID +having count(*) > 1; + +select COMPANY_ID, count(*) as total +from COMPANY_VENDOR +group by COMPANY_ID +having total > 1; + +select COMPANY_ID, count(*) as total +from COMPANY_CUSTOMER +group by COMPANY_ID +having total > 1; + +select NAME, count(*) as total +from VENDOR +group by NAME +having total > 1; + + + +select * +from COMPANY_CUSTOMER_FILE CCF + left join COMPANY_CUSTOMER_EVALUATION_FORM_FILE on CCF.ID = COMPANY_CUSTOMER_EVALUATION_FORM_FILE.ID +where CUSTOMER_ID = 1266 + and CCF.TYPE = 'EvaluationForm'; + +select cceff1_0.ID, + cceff1_1.CUSTOMER_ID, + cceff1_1.EDIT_FILE_PATH, + cceff1_1.FILE_PATH, + cceff1_1.SIGN_DATE, + cceff1_1.TYPE, + cceff1_1.VALID, + cceff1_0.CATALOG, + cceff1_0.CREDIT_LEVEL, + cceff1_0.LEVEL, + cceff1_0.score1, + cceff1_0.score2, + cceff1_0.score3, + cceff1_0.score4, + cceff1_0.score5, + cceff1_0.SCORE_TEMPLATE_VER +from COMPANY_CUSTOMER_FILE ccf1_0 + left join (COMPANY_CUSTOMER_EVALUATION_FORM_FILE cceff1_0 join COMPANY_CUSTOMER_FILE cceff1_1 + on cceff1_0.ID = cceff1_1.ID) on ccf1_0.ID = cceff1_0.ID +where ccf1_0.CUSTOMER_ID = 1266 + and ccf1_0.TYPE = 'EvaluationForm'; + + +/** + * 统计合同的State数量 + */ +select STATE, COUNT(*) +from CONTRACT +group by STATE +having count(*) > 1; + +/** + * 统计从2024年合同的GROUP数量 + */ +select GROUP_ID, COUNT(*) +from CONTRACT +where CONTRACT.SETUP_DATE between '2024-01-01' and '2024-12-31' +group by GROUP_ID +having count(*) > 1; + + + +INSERT INTO PRODUCT_DELIVERY_SIGN_METHOD (NAME, CODE) +VALUES ('到货签收', 'A'); +INSERT INTO PRODUCT_DELIVERY_SIGN_METHOD (NAME, CODE) +VALUES ('到货签收+拼装签收', 'P'); + +INSERT INTO PRODUCT_USAGE (NAME, CODE) +VALUES ('高效机房', 'H'); +INSERT INTO PRODUCT_USAGE (NAME, CODE) +VALUES ('能源站', 'E'); +INSERT INTO PRODUCT_USAGE (NAME, CODE) +VALUES ('高效机房+能源站', 'F'); +INSERT INTO PRODUCT_USAGE (NAME, CODE) +VALUES ('其他', 'O'); + +INSERT INTO PROJECT_TYPE (NAME, CODE) +VALUES ('既有建筑', 'R'); +INSERT INTO PROJECT_TYPE (NAME, CODE) +VALUES ('新建项目', 'N'); +INSERT INTO PROJECT_TYPE (NAME, CODE) +VALUES ('家装', 'Z'); + + +INSERT INTO PRODUCT_TYPE (NAME, CODE) +VALUES ('开利设备', 'C'); +INSERT INTO PRODUCT_TYPE (NAME, CODE) +VALUES ('净化设备', 'J'); +INSERT INTO PRODUCT_TYPE (NAME, CODE) +VALUES ('东芝设备', 'T'); +INSERT INTO PRODUCT_TYPE (NAME, CODE) +VALUES ('其他设备', 'Q'); +INSERT INTO PRODUCT_TYPE (NAME, CODE) +VALUES ('多种设备混合', 'X'); +INSERT INTO PRODUCT_TYPE (NAME, CODE) +VALUES ('应急维修', 'M'); +INSERT INTO PRODUCT_TYPE (NAME, CODE) +VALUES ('年度保养', 'A'); +INSERT INTO PRODUCT_TYPE (NAME, CODE) +VALUES ('技术咨询服务', 'F'); + + +INSERT INTO PROJECT_INDUSTRY (NAME, CODE) +VALUES ('汽车行业', 'A'); +INSERT INTO PROJECT_INDUSTRY (NAME, CODE) +VALUES ('电子行业', 'B'); +INSERT INTO PROJECT_INDUSTRY (NAME, CODE) +VALUES ('医药行业', 'C'); +INSERT INTO PROJECT_INDUSTRY (NAME, CODE) +VALUES ('食品行业', 'D'); +INSERT INTO PROJECT_INDUSTRY (NAME, CODE) +VALUES ('化工行业', 'E'); +INSERT INTO PROJECT_INDUSTRY (NAME, CODE) +VALUES ('太阳能光伏', 'F'); +INSERT INTO PROJECT_INDUSTRY (NAME, CODE) +VALUES ('农林行业', 'G'); +INSERT INTO PROJECT_INDUSTRY (NAME, CODE) +VALUES ('数据中心', 'H'); +INSERT INTO PROJECT_INDUSTRY (NAME, CODE) +VALUES ('机械加工', 'I'); +INSERT INTO PROJECT_INDUSTRY (NAME, CODE) +VALUES ('医院', 'J'); +INSERT INTO PROJECT_INDUSTRY (NAME, CODE) +VALUES ('机场高铁', 'K'); +INSERT INTO PROJECT_INDUSTRY (NAME, CODE) +VALUES ('文化体育', 'L'); +INSERT INTO PROJECT_INDUSTRY (NAME, CODE) +VALUES ('商业楼宇', 'M'); +INSERT INTO PROJECT_INDUSTRY (NAME, CODE) +VALUES ('住宅楼宇', 'N'); +INSERT INTO PROJECT_INDUSTRY (NAME, CODE) +VALUES ('其它', 'O'); + + +INSERT INTO PROJECT_SALE_TYPE (NAME, CODE) +VALUES ('设备销售', 'S'); +INSERT INTO PROJECT_SALE_TYPE (NAME, CODE) +VALUES ('工程安装', 'G'); +INSERT INTO PROJECT_SALE_TYPE (NAME, CODE) +VALUES ('服务', 'M'); + + +INSERT INTO PRODUCT_DELIVERY_SIGN_METHOD (NAME, CODE) +VALUES ('按进度结算', 'P'); + +ALTER USER 'ecep'@'10.84.209.229' IDENTIFIED WITH mysql_native_password BY 'ecep.62335656'; + +UPDATE supplier_ms.CONTRACT t +SET t.STANDARD_PAY_WAY = false; + +# \\\\\\\\10.84.209.8\\\\项目信息\\\\相关方信息\\\\% +select *, replace(FILE_PATH, '\\相关方信息\\', '\\B-相关方\\') +from COMPANY_FILE +where FILE_PATH like '\\\\\\\\10.84.209.8\\\\项目信息\\\\相关方信息\\\\%'; + +# +update COMPANY f +set f.PATH = replace(PATH, '\\相关方信息\\', '\\B-相关方\\') +where PATH like '\\\\\\\\10.84.209.8\\\\项目信息\\\\相关方信息\\\\%'; +update COMPANY_FILE f +set f.FILE_PATH = replace(FILE_PATH, '\\相关方信息\\', '\\B-相关方\\') +where FILE_PATH like '\\\\\\\\10.84.209.8\\\\项目信息\\\\相关方信息\\\\%'; + + +delete +from COMPANY_VENDOR_ENTITY +where VEN_CODE is null; + +alter table CONTRACT + add TOTAL_QUANTITY decimal(10, 2) default 0.00 not null; +alter table CONTRACT + add TOTAL_AMOUNT decimal(10, 2) default 0.00 not null; +alter table CONTRACT + add TOTAL_UNTAX_AMOUNT decimal(10, 2) default 0.00 not null; + + +select i1_0.ID, + i1_0.CATALOG_ID, + i1_0.CODE, + i1_0.CREATE_DATE, + i1_0.DESCRIPTION, + i1_0.NAME, + i1_0.PURCHASE_PRICE, + i1_0.PURCHASE_TAX, + i1_0.SALE_PRICE, + i1_0.SALE_TAX, + i1_0.SPEC, + i1_0.UNIT, + i1_0.UPDATE_DATE +from INVENTORY i1_0 + left join INVENTORY_CATALOG c1_0 on c1_0.ID = i1_0.CATALOG_ID +where i1_0.CATALOG_ID is not null and c1_0.NAME = ? + or i1_0.CODE like ? escape '' or i1_0.SPEC like ? escape '' or i1_0.NAME like ? escape '' +order by i1_0.ID desc +limit ?,? \ No newline at end of file diff --git a/docs/temp_u8.sql b/docs/temp_u8.sql new file mode 100644 index 0000000..f60ddfb --- /dev/null +++ b/docs/temp_u8.sql @@ -0,0 +1,136 @@ +/** + + */ + +select * +from Vendor +where cVenCode = '一冷开利'; + +select * +from UFDATA_001_2017.dbo.Customer +where cCusCode = 'GLDKJGFYXGSBJFGS'; + +select * +from CM_List; + +select * +from CM_Contract_Main +where strContractID = 'S24057-F1'; +select * +from CM_Contract_A +where strContractID = 'S24057-F1'; +select * +from CM_Contract_B +where strContractID = 'S24057-F1'; +select * +from CM_Contract_C +where strContractID = 'S24057-F1'; +select * +from CM_Contract +where strContractID = 'S24057-F1'; + + + +select * +from UFDATA_001_2017.dbo.CM_Contract +where GUID = '1B74DDAF-12FA-4676-A236-AFB3245ACB96' + or strContractID = 'S23111ACRM'; +select * +from UFDATA_001_2017.dbo.CM_Contract_B +where GUID = '1B74DDAF-12FA-4676-A236-AFB3245ACB96' + or strContractID = 'S23111ACRM'; + + + +select * +from Person +where cPersonCode = '07'; + + + +/** + * 根据合同ID,统计合同下所有商品信息 + */ +select I.GUID, + sum(isnull(I.dblQuantity, 0)) AS SumdblQuantity, + sum(isnull(I.dblUntaxSum, 0)) AS SumdblUntaxSum, + sum(isnull(I.dblSum, 0)) AS SumdblSum, + sum(isnull(I.PieceNum, 0)) as SumdblPieceNum +from CM_Contract_Item_B I +where I.intEnd = 1 + and I.IntFlag <> 1 and strContractID='S24036ACNJ' +group by I.GUID; + + +select sum( decCount),sum(decNoRateMoney), sum(decRateMoney) + from CM_ExecInterface +where cContractID = 'S24036ACNJ'; + + +select * +from CM_ExecInterface left Join so_somain On so_somain.id=CM_ExecInterface.cOrderID where cContractID = 'S24036ACNJ'; + +/** + order id 1000003696 + */ +/** + 付款计划 + */ + select * from CM_Contract_Pay where strContractID = 'S24036ACNJ'; + + + +select * from UFDATA_001_2017.dbo.Inventory as I left join ComputationUnit as U on I.cComUnitCode=U.cComunitCode where I.cInvStd = 'KH29EZ050'; + + +-- 计算查询的CPU使用率占比 +DECLARE @init_sum_cpu_time int, @utilizedCpuCount int +SELECT @utilizedCpuCount = COUNT( * ) +FROM sys.dm_os_schedulers +WHERE status = 'VISIBLE ONLINE' +SELECT @init_sum_cpu_time = SUM(cpu_time) +FROM sys.dm_exec_requests +WAITFOR DELAY '00:00:05' +SELECT CONVERT(DECIMAL(5,2),((SUM(cpu_time) - @init_sum_cpu_time) / (@utilizedCpuCount * 5000.00)) * 100) AS [CPU FROM Queries AS Percent of Total CPU Capacity] +FROM sys.dm_exec_requests; + +-- 获取当前高CPU活动的查询 +SELECT TOP 10 s.session_id, + r.status, + r.cpu_time, + r.logical_reads, + r.total_elapsed_time / (1000 * 60) 'Elaps M', + SUBSTRING(st.TEXT, (r.statement_start_offset / 2) + 1, + ((CASE r.statement_end_offset + WHEN -1 THEN DATALENGTH(st.TEXT) + ELSE r.statement_end_offset + END - r.statement_start_offset) / 2) + 1) AS statement_text, + s.program_name, + s.last_request_end_time, + s.login_time, + r.open_transaction_count +FROM sys.dm_exec_sessions AS s + JOIN sys.dm_exec_requests AS r ON r.session_id = s.session_id + CROSS APPLY sys.Dm_exec_sql_text(r.sql_handle) AS st +WHERE r.session_id != @@SPID +ORDER BY r.cpu_time DESC; + + +--历史上占用大量 CPU 的查询 +SELECT TOP 10 st.text AS batch_text, + SUBSTRING(st.TEXT, (qs.statement_start_offset / 2) + 1, ((CASE qs.statement_end_offset WHEN - 1 THEN DATALENGTH(st.TEXT) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2) + 1) AS statement_text, + (qs.total_worker_time / 1000) / qs.execution_count AS avg_cpu_time_ms, + (qs.total_elapsed_time / 1000) / qs.execution_count AS avg_elapsed_time_ms, + qs.total_logical_reads / qs.execution_count AS avg_logical_reads, + (qs.total_worker_time / 1000) AS cumulative_cpu_time_all_executions_ms, + (qs.total_elapsed_time / 1000) AS cumulative_elapsed_time_all_executions_ms +FROM sys.dm_exec_query_stats qs + CROSS APPLY sys.dm_exec_sql_text(sql_handle) st +ORDER BY(qs.total_worker_time / qs.execution_count) DESC; + + + +select cPOID,cInvCode, count(*) from IA_Subsidiary left join PO_Pomain on PO_Pomain.cPOID=IA_Subsidiary.cOrderCode where cPOID='0000004297' group by cPOID,cInvCode; + +select GUID,strContractID,strContractName,strContractType,strParentID,strContractKind,strWay,strContractGrp, strContractDesc,strBisectionUnit,strContractOrderDate,strContractStartDate,strContractEndDate,strSetupPerson,strSetupDate,strInurePerson,strInureDate,strVaryPerson,dtVaryDate,strPersonID, dblTotalCurrency,dblExecCurrency,dblTotalQuantity,dblExecQuqantity from CM_Contract_B where GUID ='A6E6ACE0-EA2A-433B-BC07-19BB8293D7A3'; + N'%运输费%' escape '' order by i1_0.ID desc limit 0,100; \ No newline at end of file diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000..c53b327 Binary files /dev/null and b/icon.ico differ diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..d7c358e --- /dev/null +++ b/mvnw @@ -0,0 +1,259 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.2 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..6f779cf --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,149 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.2 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' +$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" +if ($env:MAVEN_USER_HOME) { + $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" +} +$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..805e2f1 --- /dev/null +++ b/pom.xml @@ -0,0 +1,240 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.7 + + + com.ecep.contract + Contract-Manager + 0.0.45-SNAPSHOT + Contract-Manager + Contract-Manager + + + + + + + 1 + 宋其青 + qiqing.song@ecep.com + + + + + + + + + + 21 + 22.0.2 + 5.3.0 + + UTF-8 + UTF-8 + ${java.version} + ${java.version} + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + + + org.springframework.boot + spring-boot-starter-mail + + + + + + com.github.ben-manes.caffeine + caffeine + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + com.zaxxer + HikariCP + + + + + + + + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.data + spring-data-jpa + + + + + + + + + org.openjfx + javafx-controls + ${javafx.version} + + + org.openjfx + javafx-fxml + ${javafx.version} + + + org.openjfx + javafx-web + ${javafx.version} + + + org.controlsfx + controlsfx + 11.2.0 + + + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + ch.qos.logback + logback-access + 1.5.13 + + + org.apache.poi + poi + ${poi.version} + + + org.apache.poi + poi-ooxml + ${poi.version} + + + org.apache.pdfbox + pdfbox + 3.0.3 + + + + + + com.mysql + mysql-connector-j + runtime + + + com.microsoft.sqlserver + mssql-jdbc + runtime + + + + + + + org.openjfx + javafx-maven-plugin + 0.0.8 + + com.ecep.contract.manager.AppV2 + app + app-jlink + app-jlink-image + true + true + 2 + + + + org.apache.maven.plugins + maven-jar-plugin + 3.4.2 + + + + true + lib/ + com.ecep.contract.manager.AppV2 + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 3.1.2 + + + copy-dependencies + package + + copy-dependencies + + + ${project.build.directory}/lib + + + + + + org.codehaus.mojo + versions-maven-plugin + 2.16.2 + + + true + increment-version + install + + set + + + false + true + + true + 3 + + + + + + + + + diff --git a/src/main/java/com/ecep/contract/manager/AppV2.java b/src/main/java/com/ecep/contract/manager/AppV2.java new file mode 100644 index 0000000..7f50025 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/AppV2.java @@ -0,0 +1,33 @@ +package com.ecep.contract.manager; + +import javafx.application.Application; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Created by Administrator on 2017/4/16. + */ +public class AppV2 { + private static final Logger logger = LoggerFactory.getLogger(AppV2.class); + + public static void main(String[] args) { + Application.launch(Desktop.class, args); + done(); + } + + private static void done() { + logger.info("done"); + try { + Desktop.shutdown(); + } catch (Exception e) { + throw new RuntimeException(e); + } + System.out.println("AppV2.done"); + } + + public static final String DEFAULT_DB_HOST = "10.84.209.145"; // "db-server1.ecctrl.com" + public static final String DEFAULT_DB_PORT = "3306"; + public static final String DEFAULT_DB_USERNAME = "supplier_ms"; + public static final String DEFAULT_DB_PASSWORD = "[TPdseO!JKMmlrpf"; + public static final String DEFAULT_DB_DATABASE = "supplier_ms"; +} diff --git a/src/main/java/com/ecep/contract/manager/CurrentEmployee.java b/src/main/java/com/ecep/contract/manager/CurrentEmployee.java new file mode 100644 index 0000000..c28ebdb --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/CurrentEmployee.java @@ -0,0 +1,89 @@ +package com.ecep.contract.manager; + +import com.ecep.contract.manager.ds.other.model.Employee; +import com.ecep.contract.manager.ds.other.model.EmployeeRole; +import com.ecep.contract.manager.ds.other.service.EmployeeService; +import com.ecep.contract.manager.ds.other.vo.EmployeeViewModel; +import javafx.application.Platform; +import javafx.beans.property.SimpleListProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.collections.FXCollections; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Locale; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * 当前登录用户 + */ +public class CurrentEmployee extends EmployeeViewModel { + private static final Logger logger = LoggerFactory.getLogger(CurrentEmployee.class); + /** + * 语言环境 + */ + private SimpleObjectProperty locale = new SimpleObjectProperty<>(Locale.getDefault()); + /** + * 角色 + */ + private SimpleListProperty roles = new SimpleListProperty<>(FXCollections.observableArrayList()); + /** + * 是否系统管理员 + */ + public boolean isSystemAdministrator() { + return roles.stream().anyMatch(EmployeeRole::isSystemAdministrator); + } + /** + * 语言环境属性 + */ + public SimpleObjectProperty localeProperty() { + return locale; + } + /** + * 角色属性 + */ + public SimpleListProperty rolesProperty() { + return roles; + } + /** + * 初始化 + */ + public CompletableFuture initialize() { + CompletableFuture future = new CompletableFuture<>(); + ScheduledExecutorService executorService = Desktop.instance.getExecutorService(); + /** + * 异步初始化当前用户 + * 1. 从数据库中查询当前用户 + * 2. 从数据库中查询当前用户的角色 + * 3. 更新当前用户的信息 + * 4. 更新当前用户的角色 + */ + executorService.submit(() -> { + // issue #1 sss 2020-07-05 + EmployeeService employeeService = SpringApp.getBean(EmployeeService.class); + Employee employee = employeeService.findById(Desktop.instance.getActiveEmployeeId()); + List roles = employeeService.getRolesByEmployeeId(employee.getId()); + Platform.runLater(() -> { + update(employee); + rolesProperty().setAll(roles); + future.complete(null); + }); + }); + /** + * 定时更新用户活动状态 + */ + executorService.scheduleWithFixedDelay(() -> { + try { + SpringApp.getBean(EmployeeService.class).updateActive(Desktop.instance.getSessionId()); + } catch (Exception e) { + if (logger.isErrorEnabled()) { + logger.error("updateActive:{}", e.getMessage(), e); + } + } + }, 10, 10, TimeUnit.SECONDS); + return future; + } +} diff --git a/src/main/java/com/ecep/contract/manager/Desktop.java b/src/main/java/com/ecep/contract/manager/Desktop.java new file mode 100644 index 0000000..0e7d470 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/Desktop.java @@ -0,0 +1,325 @@ +package com.ecep.contract.manager; + +import com.ecep.contract.manager.ds.other.controller.LoginWidowController; +import com.ecep.contract.manager.ui.BaseController; +import com.ecep.contract.manager.ui.MessageHolder; +import com.ecep.contract.manager.ui.task.TaskMonitorCenter; +import com.ecep.contract.manager.util.UITools; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.fxml.FXMLLoader; +import javafx.scene.Node; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.text.Text; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import lombok.Getter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URL; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.*; +import java.util.function.Consumer; +import java.util.logging.Level; + +/** + * JavaFx 应用程序 + * + * @author ecep + * Created by ecep on 2017/05/08. + */ +public class Desktop extends Application { + private static final Logger logger = LoggerFactory.getLogger(Desktop.class); + public static Desktop instance; + + /** + * 在默认浏览器中打开指定的URL。 + *

+ * 该函数使用JavaFX的HostServices类来调用系统默认的浏览器,并打开传入的URL。 + * + * @param url 要在浏览器中打开的URL字符串。该参数不能为空,且应为有效的URL格式。 + */ + public static void showInBrowse(String url) { + instance.getHostServices().showDocument(url); + } + + /** + * 在系统的文件资源管理器中打开指定的文件夹。 + *

+ * 该方法首先尝试使用 java.awt.Desktop API 打开文件夹。如果该 API 不支持, + * 则在 Windows 系统中使用 explorer.exe 打开文件夹。 + * + * @param dir 要打开的文件夹对象。如果为 null 或无效路径,可能会抛出异常。 + * @throws RuntimeException 如果使用 java.awt.Desktop 打开文件夹时发生 IOException, + * 则将其包装为 RuntimeException 抛出。 + */ + public static void showInExplorer(File dir) { + if (java.awt.Desktop.isDesktopSupported()) { + try { + java.awt.Desktop.getDesktop().open(dir); + } catch (IOException e) { + throw new RuntimeException(e); + } + } else { + try { + // 在Windows中使用explorer.exe打开文件夹,路径用双引号括起来 + Process process = Runtime.getRuntime().exec( + new String[] { "explorer.exe", "\"" + dir.getAbsolutePath() + "\"" }, null, new File(".")); + // process.waitFor(); + } catch (IOException e) { + if (logger.isDebugEnabled()) { + logger.debug("Unable open {}", dir.getAbsolutePath(), e); + } + } + } + } + + public static void checkAndShowInExplorer(String path, Consumer consumer) { + if (!StringUtils.hasText(path)) { + consumer.accept("文件/目录为空,无法打开"); + return; + } + File file = new File(path); + if (!file.exists()) { + if (file.isFile()) { + consumer.accept("文件 " + file.getAbsolutePath() + " 不存在,请确认"); + } else { + consumer.accept("目录 " + file.getAbsolutePath() + " 不存在,请确认"); + } + return; + } + + try { + Desktop.showInExplorer(file); + consumer.accept("打开文件/目录 " + path); + } catch (Exception e) { + consumer.accept("打开文件错误:" + e.getMessage()); + } + } + + public static void shutdown() { + if (logger.isDebugEnabled()) { + logger.debug("shutdown"); + } + if (instance != null) { + try { + instance.stop(); + } catch (Throwable e) { + logger.error("shutdown error", e); + } + } + } + + private ScheduledExecutorService scheduledExecutorService = null; + private final TaskMonitorCenter taskMonitorCenter = new TaskMonitorCenter(); + + private final SimpleIntegerProperty sessionId = new SimpleIntegerProperty(0); + @Getter + private final CurrentEmployee activeEmployee = new CurrentEmployee(); + + public void setActiveEmployeeId(int activeEmployeeId) { + activeEmployee.getId().set(activeEmployeeId); + } + + public int getActiveEmployeeId() { + return activeEmployee.getId().get(); + } + + public int getSessionId() { + return sessionId.get(); + } + + public void setSessionId(int sessionId) { + this.sessionId.set(sessionId); + } + + public ScheduledExecutorService getExecutorService() { + if (scheduledExecutorService == null) { + scheduledExecutorService = Executors.newScheduledThreadPool(3); + } + return scheduledExecutorService; + } + + public TaskMonitorCenter getTaskMonitorCenter() { + return taskMonitorCenter; + } + + @Override + public void start(Stage primaryStage) throws Exception { + if (logger.isDebugEnabled()) { + logger.debug("start"); + } + if (instance != null) { + logger.error("Desktop already started"); + } + instance = this; + + URL resource = getClass().getResource("/ui/start_lamp.fxml"); + FXMLLoader loader = new FXMLLoader(resource); + primaryStage.setTitle("CMS"); + primaryStage.initStyle(StageStyle.TRANSPARENT); + + Parent root = loader.load(); + Scene scene = new Scene(root); + scene.getStylesheets().add("/ui/start_lamp.css"); + + primaryStage.setScene(scene); + primaryStage.setOnShown(e -> { + System.out.println("primaryStage#OnShown"); + }); + primaryStage.show(); + System.out.println("Desktop.start -> primaryStage.show()"); + + try { + startSpringApp(primaryStage, root, loader); + } catch (Exception e) { + UITools.showExceptionAndWait("启动失败", e); + } + } + + private void startSpringApp(Stage primaryStage, Parent root, FXMLLoader loader) { + System.out.println("Desktop.startSpringApp"); + // 更新窗口标题 + Node titleNode = root.lookup("#title"); + if (titleNode != null) { + primaryStage.setTitle(((Text) titleNode).getText()); + } + + Node lookup = root.lookup("#logBox"); + if (!(lookup instanceof VBox logBox)) { + throw new RuntimeException("启动界面加载失败, #logger 类型错误"); + } + + ScrollPane logPane = (ScrollPane) root.lookup("#logPane"); + + logBox.getChildren().clear(); + MessageHolder holder = (level, message) -> { + Text text = new Text(message); + if (Level.WARNING == level) { // warning + text.setFill(Color.YELLOW); + } else if (Level.SEVERE == level) {// error + text.setFill(Color.RED); + } else if (Level.FINE == level) { // debug + text.setFill(Color.GRAY); + } else { + text.setFill(Color.WHITE); + } + Platform.runLater(() -> { + logBox.getChildren().add(text); + logPane.layout(); + logPane.setVvalue(1.0); + }); + }; + + holder.info("启动中,请稍后..."); + + CompletableFuture.runAsync(() -> { + try { + // + holder.info("读取配置文件..."); + Properties properties = new Properties(); + File configFile = new File("config.properties"); + if (configFile.exists()) { + holder.debug("读取配置文件 " + configFile.getName() + "..."); + try (FileInputStream input = new FileInputStream(configFile)) { + properties.load(input); + holder.info("配置文件读取成功."); + } catch (IOException e) { + holder.error("读取失败:" + e.getMessage()); + logger.error(e.getMessage(), e); + return; + } + } + + CompletableFuture.runAsync(() -> { + SpringApp.launch(properties, holder); + ConfigurableListableBeanFactory beanFactory = SpringApp.context.getBeanFactory(); + + beanFactory.registerSingleton("scheduledExecutorService", getExecutorService()); + beanFactory.registerSingleton("taskMonitorCenter", taskMonitorCenter); + + }); + + try { + LoginWidowController controller = new LoginWidowController(); + controller.setHolder(holder); + controller.setPrimaryStage(primaryStage); + controller.setProperties(properties); + while (true) { + controller.tryLogin(); + break; + } + if (logger.isDebugEnabled()) { + logger.debug("login in"); + } + } catch (Exception e) { + holder.error("登录失败:" + e.getMessage()); + logger.error(e.getMessage(), e); + } + } catch (Exception e) { + holder.error(e.getMessage()); + logger.error(e.getMessage(), e); + } + }); + System.out.println("Desktop.startSpringApp."); + } + + @Override + public void stop() throws Exception { + if (logger.isDebugEnabled()) { + logger.debug("stop"); + } + CompletableFuture future = BaseController.shutdown(); + future.orTimeout(5, TimeUnit.SECONDS); + future.exceptionally(e -> { + logger.error(e.getMessage(), e); + return null; + }); + future.thenRun(() -> { + SpringApp.shutdown(); + try { + shutdownExecutorService(); + super.stop(); + if (logger.isDebugEnabled()) { + logger.debug("stopped"); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + }); + } + + private void shutdownExecutorService() { + List runnableList = scheduledExecutorService.shutdownNow(); + for (Runnable runnable : runnableList) { + if (logger.isDebugEnabled()) { + logger.debug("shutdown runnable = {}", runnable); + } + if (runnable instanceof FutureTask future) { + if (logger.isDebugEnabled()) { + logger.debug("runnable as future, isCancelled() = {}, isDone() = {}", future.isCancelled(), + future.isDone()); + } + if (future.cancel(true)) { + if (logger.isDebugEnabled()) { + logger.debug("runnable as future canceled"); + } + } + } + } + scheduledExecutorService.close(); + } + +} diff --git a/src/main/java/com/ecep/contract/manager/MyPersistentCookieStore.java b/src/main/java/com/ecep/contract/manager/MyPersistentCookieStore.java new file mode 100644 index 0000000..109ad1f --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/MyPersistentCookieStore.java @@ -0,0 +1,205 @@ +package com.ecep.contract.manager; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import java.io.*; +import java.net.CookieStore; +import java.net.HttpCookie; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; + +public class MyPersistentCookieStore implements CookieStore { + + + private final File file; + private final ObjectMapper objectMapper; + private Map> cookiesMap = new HashMap<>(); + + public MyPersistentCookieStore(File file, ObjectMapper objectMapper) { + this.file = file; + this.objectMapper = objectMapper; + if (file != null && file.exists()) { + loadFromFile2(); + } + } + + private void loadFromFile2() { + try { + ObjectNode root = (ObjectNode) objectMapper.readTree(file); + for (Iterator> it = root.fields(); it.hasNext(); ) { + Map.Entry entry = it.next(); + String key = entry.getKey(); + ArrayNode value = (ArrayNode) entry.getValue(); + + List cookies = new ArrayList<>(); + for (JsonNode node1 : value) { + HttpCookie cookie = new HttpCookie(node1.get("name").asText(), node1.get("value").asText()); + objectMapper.updateValue(node1, cookie); + cookies.add(cookie); + } + URI uri = URI.create(key); + System.out.println(key + " -> " + uri); + cookiesMap.put(uri, cookies); + } + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void saveToFile2() { + try { + objectMapper.writeValue(file, cookiesMap); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void loadFromFile(File file) { + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + while ((line = reader.readLine()) != null) { + // 假设格式为: name=value;domain=domain;path=path;expires=expires; + String[] parts = line.split(";"); + String nameValue = parts[0]; + String[] nameValueParts = nameValue.split("="); + String name = nameValueParts[0]; + String value = nameValueParts.length > 1 ? nameValueParts[1] : ""; + HttpCookie cookie = new HttpCookie(name, value); + for (int i = 1; i < parts.length; i++) { + String[] attribute = parts[i].split("="); + if (attribute[0].equals("domain")) { + cookie.setDomain(attribute[1]); + } else if (attribute[0].equals("path")) { + cookie.setPath(attribute[1]); + } else if (attribute[0].equals("expires")) { + // 解析日期格式并设置过期时间 + // 这里只是示例,实际需要正确解析日期格式 + Date expiresDate = new Date(Long.parseLong(attribute[1])); + cookie.setMaxAge(expiresDate.getTime() - System.currentTimeMillis()); + } + } + URI uri = URI.create(cookie.getDomain()); + List cookies = cookiesMap.getOrDefault(uri, new ArrayList<>()); + cookies.add(cookie); + cookiesMap.put(uri, cookies); + } + } catch (IOException e) { + // 处理文件读取错误 + e.printStackTrace(); + } + } + + public void saveToFile() { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + for (Map.Entry> entry : cookiesMap.entrySet()) { + for (HttpCookie cookie : entry.getValue()) { + String line = cookie.getName() + "=" + cookie.getValue() + ";domain=" + cookie.getDomain() + ";path=" + cookie.getPath(); + if (cookie.getMaxAge() > 0) { + line += ";expires=" + (System.currentTimeMillis() + cookie.getMaxAge()); + } + writer.write(line); + writer.newLine(); + } + } + } catch (IOException e) { + // 处理文件写入错误 + e.printStackTrace(); + } + } + + @Override + public void add(URI uri, HttpCookie cookie) { + if (cookie == null) { + throw new NullPointerException("cookie is null"); + } + + if (cookie.getDomain() != null) { + URI key = getEffectiveURI(cookie); + List cookies = cookiesMap.getOrDefault(key, new ArrayList<>()); + cookies.remove(cookie); + cookies.add(cookie); + cookiesMap.put(key, cookies); + } + + if (uri != null) { + URI key = getEffectiveURI(uri); + List cookies = cookiesMap.getOrDefault(key, new ArrayList<>()); + cookies.remove(cookie); + cookies.add(cookie); + cookiesMap.put(key, cookies); + } + saveToFile2(); + } + + @Override + public List get(URI uri) { + URI effectiveURI = getEffectiveURI(uri); + System.out.println("effectiveURI = " + effectiveURI); + return cookiesMap.getOrDefault(effectiveURI, new ArrayList<>()); + } + + @Override + public List getCookies() { + return cookiesMap.values().stream().flatMap(List::stream).toList(); + } + + @Override + public List getURIs() { + return cookiesMap.keySet().stream().toList(); + } + + @Override + public boolean remove(URI uri, HttpCookie cookie) { + URI key = getEffectiveURI(uri); + List httpCookies = cookiesMap.get(key); + if (httpCookies == null) { + return false; + } + return httpCookies.remove(cookie); + } + + @Override + public boolean removeAll() { + cookiesMap.clear(); + return true; + } + + private URI getEffectiveURI(URI uri) { + URI effectiveURI = null; + try { + effectiveURI = new URI("http", + uri.getHost(), + null, // path component + null, // query component + null // fragment component + ); + } catch (URISyntaxException ignored) { + ignored.printStackTrace(); + effectiveURI = uri; + } + + return effectiveURI; + } + + private URI getEffectiveURI(HttpCookie cookie) { + URI effectiveURI = null; + try { + effectiveURI = new URI("http", + cookie.getDomain(), + null, // path component + null, // query component + null // fragment component + ); + } catch (URISyntaxException ignored) { + + } + + return effectiveURI; + } +} diff --git a/src/main/java/com/ecep/contract/manager/MyPersistentCookieStore2.java b/src/main/java/com/ecep/contract/manager/MyPersistentCookieStore2.java new file mode 100644 index 0000000..aa5d0ee --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/MyPersistentCookieStore2.java @@ -0,0 +1,436 @@ +package com.ecep.contract.manager; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import java.io.*; +import java.net.CookieStore; +import java.net.HttpCookie; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import java.util.concurrent.locks.ReentrantLock; + +public class MyPersistentCookieStore2 implements CookieStore { + + + private final File file; + private final ObjectMapper objectMapper; + + private List cookieJar = null; + + // the cookies are indexed by its domain and associated uri (if present) + // CAUTION: when a cookie removed from main data structure (i.e. cookieJar), + // it won't be cleared in domainIndex & uriIndex. Double-check the + // presence of cookie when retrieve one form index store. + private Map> domainIndex = null; + private Map> uriIndex = null; + + // use ReentrantLock instead of synchronized for scalability + private ReentrantLock lock = null; + + + public MyPersistentCookieStore2(File file, ObjectMapper objectMapper) { + this.file = file; + this.objectMapper = objectMapper; + cookieJar = new ArrayList<>(); + domainIndex = new HashMap<>(); + uriIndex = new HashMap<>(); + + lock = new ReentrantLock(false); + + if (file != null && file.exists()) { + loadFromFile2(); + } + } + + private void loadFromFile2() { + try { + ObjectNode root = (ObjectNode) objectMapper.readTree(file); + + ArrayNode cookieJarNode = (ArrayNode) root.get("cookieJar"); + for (JsonNode node1 : cookieJarNode) { + HttpCookie cookie = new HttpCookie(node1.get("name").asText(), node1.get("value").asText()); + objectMapper.updateValue(node1, cookie); + cookieJar.add(cookie); + } + + + ObjectNode domainIndexNode = (ObjectNode) root.get("domainIndex"); + for (Iterator> it = domainIndexNode.fields(); it.hasNext(); ) { + Map.Entry entry = it.next(); + String key = entry.getKey(); + ArrayNode value = (ArrayNode) entry.getValue(); + + List cookies = new ArrayList<>(); + for (JsonNode node1 : value) { + HttpCookie cookie = new HttpCookie(node1.get("name").asText(), node1.get("value").asText()); + objectMapper.updateValue(node1, cookie); + cookies.add(cookie); + } + domainIndex.put(key, cookies); + } + + ObjectNode uriIndexNode = (ObjectNode) root.get("uriIndex"); + for (Iterator> it = uriIndexNode.fields(); it.hasNext(); ) { + Map.Entry entry = it.next(); + String key = entry.getKey(); + ArrayNode value = (ArrayNode) entry.getValue(); + + List cookies = new ArrayList<>(); + for (JsonNode node1 : value) { + HttpCookie cookie = new HttpCookie(node1.get("name").asText(), node1.get("value").asText()); + objectMapper.updateValue(node1, cookie); + cookies.add(cookie); + } + URI uri = URI.create(key); + System.out.println(key + " -> " + uri); + uriIndex.put(uri, cookies); + } + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void saveToFile2() { + try { + HashMap map = new HashMap<>(); + map.put("cookieJar", cookieJar); + map.put("domainIndex", domainIndex); + map.put("uriIndex", uriIndex); + objectMapper.writeValue(file, map); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + /** + * Add one cookie into cookie store. + */ + public void add(URI uri, HttpCookie cookie) { + // pre-condition : argument can't be null + if (cookie == null) { + throw new NullPointerException("cookie is null"); + } + + + lock.lock(); + try { + // remove the ole cookie if there has had one + cookieJar.remove(cookie); + + // add new cookie if it has a non-zero max-age + if (cookie.getMaxAge() != 0) { + cookieJar.add(cookie); + // and add it to domain index + if (cookie.getDomain() != null) { + addIndex(domainIndex, cookie.getDomain(), cookie); + } + if (uri != null) { + // add it to uri index, too + addIndex(uriIndex, getEffectiveURI(uri), cookie); + } + } + } finally { + lock.unlock(); + } + + saveToFile2(); + } + + + /** + * Get all cookies, which: + * 1) given uri domain-matches with, or, associated with + * given uri when added to the cookie store. + * 3) not expired. + * See RFC 2965 sec. 3.3.4 for more detail. + */ + public List get(URI uri) { + // argument can't be null + if (uri == null) { + throw new NullPointerException("uri is null"); + } + + List cookies = new ArrayList<>(); + boolean secureLink = "https".equalsIgnoreCase(uri.getScheme()); + lock.lock(); + try { + // check domainIndex first + getInternal1(cookies, domainIndex, uri.getHost(), secureLink); + // check uriIndex then + getInternal2(cookies, uriIndex, getEffectiveURI(uri), secureLink); + } finally { + lock.unlock(); + } + + return cookies; + } + + /** + * Get all cookies in cookie store, except those have expired + */ + public List getCookies() { + List rt; + + lock.lock(); + try { + Iterator it = cookieJar.iterator(); + while (it.hasNext()) { + if (it.next().hasExpired()) { + it.remove(); + } + } + } finally { + rt = Collections.unmodifiableList(cookieJar); + lock.unlock(); + } + + return rt; + } + + /** + * Get all URIs, which are associated with at least one cookie + * of this cookie store. + */ + public List getURIs() { + List uris = new ArrayList<>(); + + lock.lock(); + try { + Iterator it = uriIndex.keySet().iterator(); + while (it.hasNext()) { + URI uri = it.next(); + List cookies = uriIndex.get(uri); + if (cookies == null || cookies.size() == 0) { + // no cookies list or an empty list associated with + // this uri entry, delete it + it.remove(); + } + } + } finally { + uris.addAll(uriIndex.keySet()); + lock.unlock(); + } + + return uris; + } + + + /** + * Remove a cookie from store + */ + public boolean remove(URI uri, HttpCookie ck) { + // argument can't be null + if (ck == null) { + throw new NullPointerException("cookie is null"); + } + + boolean modified = false; + lock.lock(); + try { + modified = cookieJar.remove(ck); + } finally { + lock.unlock(); + } + + return modified; + } + + + /** + * Remove all cookies in this cookie store. + */ + public boolean removeAll() { + lock.lock(); + try { + if (cookieJar.isEmpty()) { + return false; + } + cookieJar.clear(); + domainIndex.clear(); + uriIndex.clear(); + } finally { + lock.unlock(); + } + + return true; + } + + + /* ---------------- Private operations -------------- */ + + + /* + * This is almost the same as HttpCookie.domainMatches except for + * one difference: It won't reject cookies when the 'H' part of the + * domain contains a dot ('.'). + * I.E.: RFC 2965 section 3.3.2 says that if host is x.y.domain.com + * and the cookie domain is .domain.com, then it should be rejected. + * However that's not how the real world works. Browsers don't reject and + * some sites, like yahoo.com do actually expect these cookies to be + * passed along. + * And should be used for 'old' style cookies (aka Netscape type of cookies) + */ + private boolean netscapeDomainMatches(String domain, String host) { + if (domain == null || host == null) { + return false; + } + + // if there's no embedded dot in domain and domain is not .local + boolean isLocalDomain = ".local".equalsIgnoreCase(domain); + int embeddedDotInDomain = domain.indexOf('.'); + if (embeddedDotInDomain == 0) { + embeddedDotInDomain = domain.indexOf('.', 1); + } + if (!isLocalDomain && (embeddedDotInDomain == -1 || embeddedDotInDomain == domain.length() - 1)) { + return false; + } + + // if the host name contains no dot and the domain name is .local + int firstDotInHost = host.indexOf('.'); + if (firstDotInHost == -1 && isLocalDomain) { + return true; + } + + int domainLength = domain.length(); + int lengthDiff = host.length() - domainLength; + if (lengthDiff == 0) { + // if the host name and the domain name are just string-compare equal + return host.equalsIgnoreCase(domain); + } else if (lengthDiff > 0) { + // need to check H & D component + String H = host.substring(0, lengthDiff); + String D = host.substring(lengthDiff); + + return (D.equalsIgnoreCase(domain)); + } else if (lengthDiff == -1) { + // if domain is actually .host + return (domain.charAt(0) == '.' && + host.equalsIgnoreCase(domain.substring(1))); + } + + return false; + } + + private void getInternal1(List cookies, Map> cookieIndex, + String host, boolean secureLink) { + // Use a separate list to handle cookies that need to be removed so + // that there is no conflict with iterators. + ArrayList toRemove = new ArrayList<>(); + for (Map.Entry> entry : cookieIndex.entrySet()) { + String domain = entry.getKey(); + List lst = entry.getValue(); + for (HttpCookie c : lst) { + if ((c.getVersion() == 0 && netscapeDomainMatches(domain, host)) || + (c.getVersion() == 1 && HttpCookie.domainMatches(domain, host))) { + if ((cookieJar.indexOf(c) != -1)) { + // the cookie still in main cookie store + if (!c.hasExpired()) { + // don't add twice and make sure it's the proper + // security level + if ((secureLink || !c.getSecure()) && + !cookies.contains(c)) { + cookies.add(c); + } + } else { + toRemove.add(c); + } + } else { + // the cookie has been removed from main store, + // so also remove it from domain indexed store + toRemove.add(c); + } + } + } + // Clear up the cookies that need to be removed + for (HttpCookie c : toRemove) { + lst.remove(c); + cookieJar.remove(c); + + } + toRemove.clear(); + } + } + + // @param cookies [OUT] contains the found cookies + // @param cookieIndex the index + // @param comparator the prediction to decide whether or not + // a cookie in index should be returned + private void getInternal2(List cookies, + Map> cookieIndex, + Comparable comparator, boolean secureLink) { + for (T index : cookieIndex.keySet()) { + if (comparator.compareTo(index) == 0) { + List indexedCookies = cookieIndex.get(index); + // check the list of cookies associated with this domain + if (indexedCookies != null) { + Iterator it = indexedCookies.iterator(); + while (it.hasNext()) { + HttpCookie ck = it.next(); + if (cookieJar.indexOf(ck) != -1) { + // the cookie still in main cookie store + if (!ck.hasExpired()) { + // don't add twice + if ((secureLink || !ck.getSecure()) && + !cookies.contains(ck)) + cookies.add(ck); + } else { + it.remove(); + cookieJar.remove(ck); + } + } else { + // the cookie has been removed from main store, + // so also remove it from domain indexed store + it.remove(); + } + } + } // end of indexedCookies != null + } // end of comparator.compareTo(index) == 0 + } // end of cookieIndex iteration + } + + // add 'cookie' indexed by 'index' into 'indexStore' + private void addIndex(Map> indexStore, + T index, + HttpCookie cookie) { + if (index != null) { + List cookies = indexStore.get(index); + if (cookies != null) { + // there may already have the same cookie, so remove it first + cookies.remove(cookie); + + cookies.add(cookie); + } else { + cookies = new ArrayList<>(); + cookies.add(cookie); + indexStore.put(index, cookies); + } + } + } + + + // + // for cookie purpose, the effective uri should only be http://host + // the path will be taken into account when path-match algorithm applied + // + private URI getEffectiveURI(URI uri) { + URI effectiveURI = null; + try { + effectiveURI = new URI("http", + uri.getHost(), + null, // path component + null, // query component + null // fragment component + ); + } catch (URISyntaxException ignored) { + effectiveURI = uri; + } + + return effectiveURI; + } +} diff --git a/src/main/java/com/ecep/contract/manager/MyProperties.java b/src/main/java/com/ecep/contract/manager/MyProperties.java new file mode 100644 index 0000000..6447617 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/MyProperties.java @@ -0,0 +1,35 @@ +package com.ecep.contract.manager; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; + +@Component +@ConfigurationProperties(prefix = "my") +public class MyProperties { + @Getter + @Setter + private String downloadsPath; + + + /** + * 尝试返回当前用户的下载文件夹 + */ + public File getDownloadDirectory() { + String downloadsPath = getDownloadsPath(); + if (StringUtils.hasText(downloadsPath)) { + return new File(downloadsPath); + } + + // 没有配置下载目录时,尝试使用默认设置 + String home = System.getProperty("user.home"); + Path path = Paths.get(home, "Downloads"); + return path.toFile(); + } +} diff --git a/src/main/java/com/ecep/contract/manager/SpringApp.java b/src/main/java/com/ecep/contract/manager/SpringApp.java new file mode 100644 index 0000000..1c619a5 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/SpringApp.java @@ -0,0 +1,259 @@ +package com.ecep.contract.manager; + +import com.ecep.contract.manager.cloud.CloudRepositoriesConfig; +import com.ecep.contract.manager.ds.DsRepositoriesConfig; +import com.ecep.contract.manager.ui.MessageHolder; +import com.ecep.contract.manager.util.MyDateTimeUtils; +import com.ecep.contract.manager.util.UITools; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.boot.ConfigurableBootstrapContext; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.SpringApplicationHook; +import org.springframework.boot.SpringApplicationRunListener; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup; +import org.springframework.boot.context.metrics.buffering.StartupTimeline; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.caffeine.CaffeineCacheManager; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.env.PropertySource; +import org.springframework.core.metrics.StartupStep; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; + +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Locale; +import java.util.Properties; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; +import java.util.concurrent.ScheduledExecutorService; + +@SpringBootApplication +@EnableScheduling +@EnableAsync +@EnableCaching +public class SpringApp { + private static final Logger logger = LoggerFactory.getLogger(SpringApp.class); + + static SpringApplication application; + static ConfigurableApplicationContext context; + + public static T getBean(Class requiredType) throws BeansException { + return context.getBean(requiredType); + } + + public static void launch(Properties properties, MessageHolder holder) { + application = new SpringApplication(SpringApp.class); + BufferingApplicationStartup startup = new BufferingApplicationStartup(2000); + application.setApplicationStartup(startup); + // + holder.debug("应用程序环境准备中..."); + SpringApplication.withHook(new Hook(holder), () -> { + // 动态地注册或修改这些组件和配置 + application.addBootstrapRegistryInitializer(registry -> { + // + System.out.println("registry = " + registry); + + }); + application.addListeners(event -> { + logger.debug("SpringApp.launch ApplicationListener, event:{}", event); + }); + + application.addInitializers(app -> { + logger.debug("SpringApp.launch ApplicationContextInitializer"); + ConfigurableEnvironment environment = app.getEnvironment(); + logger.debug("environment = {}", environment); + PropertySource dynamicProperties = environment.getPropertySources().get("dynamicProperties"); + if (dynamicProperties != null) { + logger.debug("dynamicProperties = {}", dynamicProperties); + } + environment.getPropertySources().addLast(new PropertiesPropertySource("dynamicProperties", properties)); + // app.getBeanFactory().registerSingleton("dataSource", dataSource()); + logger.debug("app = {}", app); + + if (app instanceof AnnotationConfigApplicationContext ctx) { + ctx.register(DsRepositoriesConfig.class); + ctx.register(CloudRepositoriesConfig.class); + } + }); + + + startup.start(""); + context = application.run(); + logger.debug("SpringApp.launch application.run()."); + Duration between = Duration.between(startup.getBufferedTimeline().getStartTime(), Instant.now()); + holder.info("应用程序环境加载完成... " + between); + }); + CompletableFuture.runAsync(() -> { + // 在这里调用 startup 性能分析 + analyzeStartupPerformance(startup); + }); + } + + /** + * 分析启动性能数据并输出到日志 + */ + private static void analyzeStartupPerformance(BufferingApplicationStartup startup) { + // 获取所有记录的事件 + StartupTimeline timeline = startup.getBufferedTimeline(); + if (timeline == null || timeline.getEvents().isEmpty()) { + logger.warn("StartupTimeline 为空或没有事件!"); + return; + } + logger.info("总共有 {} 个事件", timeline.getEvents().size()); + + // 找出与 Bean 初始化相关的步骤 + timeline.getEvents().stream() + .filter(event -> event.getStartupStep().getName().startsWith("spring.beans.")) + .sorted((a, b) -> Long.compare(b.getDuration().toMillis(), a.getDuration().toMillis())) + .limit(30) + .forEach(event -> { + String name = event.getStartupStep().getName(); + long duration = event.getDuration().toMillis(); + logger.info("Bean 初始化阶段: {} - 耗时: {} ms", name, duration); + + for (StartupStep.Tag tag : event.getStartupStep().getTags()) { + if ("beanName".equals(tag.getKey())) { + logger.info(" └── Bean 名称: {}", tag.getValue()); + } + } + }); + } + + public static String getMessage(String code, Object[] args, Locale locale) { + return context.getMessage(code, args, locale); + } + + public static void shutdown() { + System.out.println("SpringApp.shutdown"); + if (logger.isDebugEnabled()) { + logger.debug("shutdown"); + } + if (context != null) { + if (context.isRunning()) { + context.close(); + } + } + } + + public static boolean isRunning() { + return context != null && context.isRunning(); + } + + static class Hook implements SpringApplicationHook, SpringApplicationRunListener { + MessageHolder holder; + + Hook(MessageHolder holder) { + this.holder = holder; + } + + public void debug(String msg) { + holder.debug(msg); + } + + @Override + public void starting(ConfigurableBootstrapContext bootstrapContext) { + logger.debug("Desktop.starting"); + debug("Spring Application 启动中..."); + } + + @Override + public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) { + logger.debug("Desktop.environmentPrepared"); + debug("初始化 Environment 中,请稍后..."); + } + + @Override + public void contextPrepared(ConfigurableApplicationContext context) { + logger.debug("Desktop.contextPrepared"); + debug("Spring Application Context 预处理中,请稍后..."); + } + + @Override + public void contextLoaded(ConfigurableApplicationContext context) { + logger.debug("Desktop.contextLoaded"); + debug("Spring Application Context 初始化完毕,请稍后..."); + } + + + @Override + public void started(ConfigurableApplicationContext context, Duration timeTaken) { + logger.debug("Desktop.started"); + debug("Spring Application 启动完毕."); + } + + @Override + public void ready(ConfigurableApplicationContext context, Duration timeTaken) { + logger.debug("Desktop.ready"); + debug("Spring Application ready."); + } + + @Override + public void failed(ConfigurableApplicationContext context, Throwable exception) { + logger.error("Desktop.failed", exception); + holder.error("Spring Application 启动失败(" + exception.getMessage() + ")."); + UITools.showExceptionAndWait("启动失败", exception); + } + + @Override + public SpringApplicationRunListener getRunListener(SpringApplication springApplication) { + return this; + } + } + + @EventListener + public void handleClosedEvent(ContextClosedEvent event) { + if (logger.isDebugEnabled()) { + logger.debug("handleClosedEvent={}", event); + } + Desktop.shutdown(); + } + + @Bean + public CacheManager cacheManager() { +// return new ConcurrentMapCacheManager("myCache"); + CaffeineCacheManager cacheManager = new CaffeineCacheManager(); + cacheManager.setAsyncCacheMode(true); + return cacheManager; + } + + @Bean + public ObjectMapper objectMapper() { + ObjectMapper objectMapper = new ObjectMapper(); + JavaTimeModule javaTimeModule = new JavaTimeModule(); + javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE)); + javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(MyDateTimeUtils.DEFAULT_DATETIME_FORMAT_PATTERN))); + javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss"))); + javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE)); + javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(MyDateTimeUtils.DEFAULT_DATETIME_FORMAT_PATTERN))); + javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ISO_LOCAL_TIME)); + objectMapper.registerModule(javaTimeModule); + return objectMapper; + } + + @Bean + public ScheduledExecutorService scheduledExecutorService() { + return Executors.newScheduledThreadPool(3); + } + +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/AbstractCtx.java b/src/main/java/com/ecep/contract/manager/cloud/AbstractCtx.java new file mode 100644 index 0000000..bf24c5f --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/AbstractCtx.java @@ -0,0 +1,173 @@ +package com.ecep.contract.manager.cloud; + +import com.ecep.contract.manager.ds.other.service.SysConfService; +import com.ecep.contract.manager.ui.MessageHolder; +import com.ecep.contract.manager.util.MyStringUtils; +import com.ecep.contract.manager.util.NumberUtils; +import lombok.Getter; +import lombok.Setter; +import org.springframework.util.StringUtils; + +import java.math.BigDecimal; +import java.sql.Timestamp; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; +import java.util.Locale; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import static com.ecep.contract.manager.SpringApp.getBean; + +public class AbstractCtx { + @Setter + SysConfService confService; + + @Setter + @Getter + Locale locale = Locale.getDefault(); + + public SysConfService getConfService() { + if (confService == null) { + confService = getBean(SysConfService.class); + } + return confService; + } + + + public boolean updateText(Supplier getter, Consumer setter, String text, MessageHolder holder, String topic) { + if (!Objects.equals(getter.get(), text)) { + setter.accept(text); + holder.info(topic + "修改为: " + text); + return true; + } + return false; + } + + public boolean updateAppendText(Supplier getter, Consumer setter, String text, MessageHolder holder, String topic) { + if (StringUtils.hasText(text)) { + String str = MyStringUtils.appendIfAbsent(getter.get(), text); + if (!Objects.equals(getter.get(), str)) { + setter.accept(text); + holder.info(topic + "修改为: " + text); + return true; + } + } + return false; + } + + + public boolean updateLocalDate(Supplier getter, Consumer setter, java.sql.Date date, MessageHolder holder, String topic) { + if (date != null) { + return updateLocalDate(getter, setter, date.toLocalDate(), holder, topic); + } + return false; + } + + public boolean updateLocalDate(Supplier getter, Consumer setter, LocalDate date, MessageHolder holder, String topic) { + return updateLocalDate(getter, setter, date, holder, topic, false); + } + + public boolean updateLocalDate(Supplier getter, Consumer setter, LocalDate date, MessageHolder holder, String topic, boolean allowNull) { + if (date == null && !allowNull) { + return false; + } + if (!Objects.equals(getter.get(), date)) { + setter.accept(date); + holder.info(topic + "更新为 " + date); + return true; + } + return false; + } + + public boolean updateLocalDate(Supplier getter, Consumer setter, String strDate, MessageHolder holder, String topic) { + LocalDate date = null; + if (StringUtils.hasText(strDate)) { + try { + date = LocalDate.parse(strDate); + } catch (DateTimeParseException e) { + holder.warn("无法解析的日期:" + strDate); + } + } + return updateLocalDate(getter, setter, date, holder, topic); + } + + public boolean updateLocalDate(Supplier getter, Consumer setter, Timestamp timestamp, MessageHolder holder, String topic) { + LocalDate date = null; + + if (timestamp != null) { + try { + date = timestamp.toLocalDateTime().toLocalDate(); + } catch (DateTimeParseException e) { + holder.warn("解析日期" + timestamp + " 异常:" + e.getMessage()); + } + } + return updateLocalDate(getter, setter, date, holder, topic); + } + + public boolean updateLocalDateTime(Supplier getter, Consumer setter, Timestamp timestamp, MessageHolder holder, String topic) { + LocalDateTime dateTime = null; + + if (timestamp != null) { + try { + // fixed nanos + timestamp.setNanos(0); + dateTime = timestamp.toLocalDateTime(); + } catch (DateTimeParseException e) { + holder.warn("解析日期" + timestamp + " 异常:" + e.getMessage()); + } + } + + if (!Objects.equals(getter.get(), dateTime)) { + setter.accept(dateTime); + holder.info(topic + "修改为: " + dateTime); + return true; + } + return false; + } + + public boolean updateInstant(Supplier getter, Consumer setter, Instant instant, MessageHolder holder, String topic) { + if (!Objects.equals(getter.get(), instant)) { + setter.accept(instant); + holder.info(topic + "修改为: " + instant); + return true; + } + return false; + } + + + public boolean updateNumber(Supplier getter, Consumer setter, BigDecimal value, MessageHolder holder, String topic) { + double val = value.doubleValue(); + return updateNumber(getter, setter, val, holder, topic); + } + + public boolean updateNumber(Supplier getter, Consumer setter, double value, MessageHolder holder, String topic) { + if (getter.get() == null || !NumberUtils.equals(getter.get(), value)) { + setter.accept(value); + holder.info(topic + "修改为: " + value); + return true; + } + return false; + } + + public boolean updateNumber(Supplier getter, Consumer setter, float value, MessageHolder holder, String topic) { + if (getter.get() == null || !NumberUtils.equals(getter.get(), value)) { + setter.accept(value); + holder.info(topic + "修改为: " + value); + return true; + } + return false; + } + + + public boolean updateNumber(Supplier getter, Consumer setter, Integer value, MessageHolder holder, String topic) { + if (getter.get() == null || !NumberUtils.equals(getter.get(), value)) { + setter.accept(value); + holder.info(topic + "修改为: " + value); + return true; + } + return false; + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/CloudBaseInfo.java b/src/main/java/com/ecep/contract/manager/cloud/CloudBaseInfo.java new file mode 100644 index 0000000..9c67b9b --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/CloudBaseInfo.java @@ -0,0 +1,73 @@ +package com.ecep.contract.manager.cloud; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.other.model.IdentityEntity; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.proxy.HibernateProxy; + +import java.time.Instant; +import java.util.Objects; + +/** + * 记录同步来源 + */ +@Getter +@Setter +// @Entity +// @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) +@ToString +@MappedSuperclass +public abstract class CloudBaseInfo implements IdentityEntity { + /** + * 主键 + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID", nullable = false) + private Integer id; + + /** + * 平台编号 + */ + @Column(name = "CLOUD_ID") + private String cloudId; + + /** + * 本地更新时间戳,控制更新频率和重复更新 + */ + @Column(name = "LATEST_UPDATE") + private Instant latestUpdate; + + /** + * 关联的公司 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "COMPANY_ID") + @ToString.Exclude + private Company company; + + @Version + @ColumnDefault("0") + @Column(name = "VERSION", nullable = false) + private int version; + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + if (thisEffectiveClass != oEffectiveClass) return false; + CloudBaseInfo cloudInfo = (CloudBaseInfo) o; + return getId() != null && Objects.equals(getId(), cloudInfo.getId()); + } + + @Override + public final int hashCode() { + return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); + } +} \ No newline at end of file diff --git a/src/main/java/com/ecep/contract/manager/cloud/CloudInfo.java b/src/main/java/com/ecep/contract/manager/cloud/CloudInfo.java new file mode 100644 index 0000000..4a1c92c --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/CloudInfo.java @@ -0,0 +1,78 @@ +package com.ecep.contract.manager.cloud; + +import com.ecep.contract.manager.ds.company.model.Company; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.proxy.HibernateProxy; + +import java.time.Instant; +import java.util.Objects; + +/** + * 记录同步来源 + */ +@Getter +@Setter +@Entity +//@org.springframework.data.relational.core.mapping.Table(name = "CLOUD_INFO") +@Table(name = "CLOUD_INFO", schema = "supplier_ms") +@ToString +public class CloudInfo { + /** + * 主键 + */ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID", nullable = false) + private Integer id; + + /** + * 记录源类型 + */ + @Column(name = "CLOUD_TYPE", length = 50) + private CloudType type; + + /** + * + */ + @Column(name = "CLOUD_ID") + private String cloudId; + + /** + * 本地更新时间戳,控制更新频率和重复更新 + */ + @Column(name = "LATEST_UPDATE") + private Instant latestUpdate; + + /** + * 关联的公司 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "COMPANY_ID") + @ToString.Exclude + private Company company; + + @Version + @ColumnDefault("0") + @Column(name = "VERSION", nullable = false) + private int version; + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + if (thisEffectiveClass != oEffectiveClass) return false; + CloudInfo cloudInfo = (CloudInfo) o; + return getId() != null && Objects.equals(getId(), cloudInfo.getId()); + } + + @Override + public final int hashCode() { + return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); + } +} \ No newline at end of file diff --git a/src/main/java/com/ecep/contract/manager/cloud/CloudInfoRepository.java b/src/main/java/com/ecep/contract/manager/cloud/CloudInfoRepository.java new file mode 100644 index 0000000..01b30f8 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/CloudInfoRepository.java @@ -0,0 +1,15 @@ +package com.ecep.contract.manager.cloud; + +import org.springframework.context.annotation.Lazy; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; + +@Lazy +@Repository +public interface CloudInfoRepository + // curd + extends CrudRepository, PagingAndSortingRepository { + + +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/CloudInfoViewModel.java b/src/main/java/com/ecep/contract/manager/cloud/CloudInfoViewModel.java new file mode 100644 index 0000000..d9f9337 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/CloudInfoViewModel.java @@ -0,0 +1,79 @@ +package com.ecep.contract.manager.cloud; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.other.vo.IdentityViewModel; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.*; +import java.util.Objects; + +/** + * 云信息 + */ +@EqualsAndHashCode(callSuper = false) +@Data +public class CloudInfoViewModel extends IdentityViewModel { + + /** + * 云端Id + */ + private SimpleStringProperty cloudId = new SimpleStringProperty(); + /** + * 公司 + */ + private SimpleObjectProperty company = new SimpleObjectProperty<>(); + /** + * 最后更新日期 + */ + private SimpleObjectProperty latest = new SimpleObjectProperty<>(); + /** + * Version + */ + private SimpleIntegerProperty version = new SimpleIntegerProperty(); + + @Override + protected void updateFrom(V info) { + super.updateFrom(info); + cloudId.set(info.getCloudId()); + company.set(info.getCompany()); + + if (info.getLatestUpdate() != null) { + ZoneId zone = ZoneId.systemDefault(); + ZonedDateTime zonedDateTime = info.getLatestUpdate().atZone(zone); + LocalDateTime localDateTime = zonedDateTime.toLocalDateTime(); + latest.set(localDateTime); + } else { + latest.set(null); + } + version.set(info.getVersion()); + } + + @Override + public boolean copyTo(V info) { + boolean changed = super.copyTo(info); + + if (!Objects.equals(cloudId.get(), info.getCloudId())) { + info.setCloudId(cloudId.get()); + changed = true; + } + + Instant latestUpdate = null; + LocalDateTime latestUpdateDateTime = latest.get(); + if (latestUpdateDateTime != null) { + latestUpdate = latestUpdateDateTime.toInstant(ZoneOffset.ofHours(8)); + } + if (!Objects.equals(latestUpdate, info.getLatestUpdate())) { + info.setLatestUpdate(latestUpdate); + changed = true; + } + return changed; + } + + +} + + diff --git a/src/main/java/com/ecep/contract/manager/cloud/CloudRepositoriesConfig.java b/src/main/java/com/ecep/contract/manager/cloud/CloudRepositoriesConfig.java new file mode 100644 index 0000000..a8f01a1 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/CloudRepositoriesConfig.java @@ -0,0 +1,45 @@ +package com.ecep.contract.manager.cloud; + +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.data.repository.config.BootstrapMode; + +@Configuration +@EnableJpaRepositories(bootstrapMode = BootstrapMode.LAZY) +public class CloudRepositoriesConfig { +// @Bean +// @Role(BeanDefinition.ROLE_INFRASTRUCTURE) +// public BeanDefinition cloudInfoRepositoryDefinition() { +// BeanDefinition definition = new org.springframework.beans.factory.support.GenericBeanDefinition(); +// definition.setBeanClassName("com.ecep.contract.manager.cloud.CloudInfoRepository"); +// definition.setLazyInit(true); +// return definition; +// } +// +// @Bean +// @Role(BeanDefinition.ROLE_INFRASTRUCTURE) +// public BeanDefinition cloudRkRepositoryDefinition() { +// BeanDefinition definition = new org.springframework.beans.factory.support.GenericBeanDefinition(); +// definition.setBeanClassName("com.ecep.contract.manager.cloud.u8.CloudRkRepository"); +// definition.setLazyInit(true); +// return definition; +// } +// +// @Bean +// @Role(BeanDefinition.ROLE_INFRASTRUCTURE) +// public BeanDefinition cloudTycRepositoryDefinition() { +// BeanDefinition definition = new org.springframework.beans.factory.support.GenericBeanDefinition(); +// definition.setBeanClassName("com.ecep.contract.manager.cloud.u8.CloudTycRepository"); +// definition.setLazyInit(true); +// return definition; +// } +// +// @Bean +// @Role(BeanDefinition.ROLE_INFRASTRUCTURE) +// public BeanDefinition cloudYuRepositoryDefinition() { +// BeanDefinition definition = new org.springframework.beans.factory.support.GenericBeanDefinition(); +// definition.setBeanClassName("com.ecep.contract.manager.cloud.u8.CloudYuRepository"); +// definition.setLazyInit(true); +// return definition; +// } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/CloudType.java b/src/main/java/com/ecep/contract/manager/cloud/CloudType.java new file mode 100644 index 0000000..135d2d3 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/CloudType.java @@ -0,0 +1,23 @@ +package com.ecep.contract.manager.cloud; + +/** + * 记录源类型 + */ +public enum CloudType { + /** + * 上海电气集团相关方平台 + */ + RK, + /** + * 天眼查 + */ + TYC, + /** + * 用友U8 + */ + U8, + /** + * 老版本APP + */ + OLD; +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/old/CompanyContactUtils.java b/src/main/java/com/ecep/contract/manager/cloud/old/CompanyContactUtils.java new file mode 100644 index 0000000..4262f78 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/old/CompanyContactUtils.java @@ -0,0 +1,66 @@ +package com.ecep.contract.manager.cloud.old; + +import com.ecep.contract.manager.ds.company.model.CompanyContact; +import com.ecep.contract.manager.ds.company.model.CompanyContract; + +import java.util.Map; +import java.util.Objects; + +/** + * + */ +public class CompanyContactUtils { + /** + * 使用 map 应用到 contact, 如有更新, 则返回 true + */ + public static boolean applyContactByMap(CompanyContact contact, Map map) { + boolean modified = false; + String name = (String) map.get("NAME"); + String phone = (String) map.get("PHONE"); + String email = (String) map.get("EMAIL"); + String address = (String) map.get("ADDRESS"); + if (name != null) { + // 更新备注 + if (!Objects.equals(contact.getName(), name)) { + contact.setName(name); + modified = true; + } + } + if (phone != null) { + // 更新备注 + if (!Objects.equals(contact.getPhone(), phone)) { + contact.setPhone(phone); + modified = true; + } + } + if (email != null) { + // 更新备注 + if (!Objects.equals(contact.getEmail(), email)) { + contact.setEmail(email); + modified = true; + } + } + if (address != null) { + // 更新备注 + if (!Objects.equals(contact.getAddress(), address)) { + contact.setAddress(address); + modified = true; + } + } + return modified; + } + + /** + * 要和{@link CompanyContract#getContactKey(CompanyContact)}一致 + */ + public static String getContactKey(Map map) { + if (map == null) { + return null; + } + String name = (String) map.get("NAME"); + String phone = (String) map.get("PHONE"); + String email = (String) map.get("EMAIL"); + return name + phone + email; + } + +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/old/CompanyCustomerFileUtils.java b/src/main/java/com/ecep/contract/manager/cloud/old/CompanyCustomerFileUtils.java new file mode 100644 index 0000000..c85975d --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/old/CompanyCustomerFileUtils.java @@ -0,0 +1,53 @@ +package com.ecep.contract.manager.cloud.old; + +import com.ecep.contract.manager.ds.customer.model.CompanyCustomerFile; + +import java.time.LocalDate; +import java.util.Map; +import java.util.Objects; + +public class CompanyCustomerFileUtils { + public static boolean applyCustomerFileByMap(CompanyCustomerFile customerFile, Map m) { + boolean modified = false; + + String filePath = (String) m.get("FILE_PATH"); + String editFilePath = (String) m.get("EDIT_FILE_PATH"); + java.sql.Date signDate = (java.sql.Date) m.get("SIGN_DATE"); + Boolean valid = (Boolean) m.get("VALID"); + + if (filePath != null) { + // file path + if (!Objects.equals(customerFile.getFilePath(), filePath)) { + customerFile.setFilePath(filePath); + modified = true; + } + } + if (editFilePath != null) { + // edit file path + if (!Objects.equals(customerFile.getEditFilePath(), editFilePath)) { + customerFile.setEditFilePath(editFilePath); + modified = true; + } + } + if (signDate != null) { + // date + LocalDate localDate = signDate.toLocalDate(); + if (!Objects.equals(customerFile.getSignDate(), localDate)) { + customerFile.setSignDate(localDate); + modified = true; + } + } + if (valid != null) { + // valid + if (!Objects.equals(customerFile.isValid(), valid)) { + customerFile.setValid(valid); + modified = true; + } + } + return modified; + } + + public static String getCustomerFileKey(Map m) { + return m.get("FILE_PATH").toString(); + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/old/CompanyVendorFileUtils.java b/src/main/java/com/ecep/contract/manager/cloud/old/CompanyVendorFileUtils.java new file mode 100644 index 0000000..4fcb21e --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/old/CompanyVendorFileUtils.java @@ -0,0 +1,53 @@ +package com.ecep.contract.manager.cloud.old; + +import com.ecep.contract.manager.ds.vendor.model.CompanyVendorFile; + +import java.time.LocalDate; +import java.util.Map; +import java.util.Objects; + +public class CompanyVendorFileUtils { + public static boolean applyVendorFileByMap(CompanyVendorFile vendorFile, Map m) { + boolean modified = false; + + String filePath = (String) m.get("FILE_PATH"); + String editFilePath = (String) m.get("EDIT_FILE_PATH"); + java.sql.Date signDate = (java.sql.Date) m.get("SIGN_DATE"); + Boolean valid = (Boolean) m.get("VALID"); + + if (filePath != null) { + // file path + if (!Objects.equals(vendorFile.getFilePath(), filePath)) { + vendorFile.setFilePath(filePath); + modified = true; + } + } + if (editFilePath != null) { + // edit file path + if (!Objects.equals(vendorFile.getEditFilePath(), editFilePath)) { + vendorFile.setEditFilePath(editFilePath); + modified = true; + } + } + if (signDate != null) { + // date + LocalDate localDate = signDate.toLocalDate(); + if (!Objects.equals(vendorFile.getSignDate(), localDate)) { + vendorFile.setSignDate(localDate); + modified = true; + } + } + if (valid != null) { + // valid + if (!Objects.equals(vendorFile.isValid(), valid)) { + vendorFile.setValid(valid); + modified = true; + } + } + return modified; + } + + public static String getVendorFileKey(Map m) { + return m.get("FILE_PATH").toString(); + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/old/OldVersionService.java b/src/main/java/com/ecep/contract/manager/cloud/old/OldVersionService.java new file mode 100644 index 0000000..85d459b --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/old/OldVersionService.java @@ -0,0 +1,1126 @@ +package com.ecep.contract.manager.cloud.old; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.cloud.rk.CloudRk; +import com.ecep.contract.manager.cloud.rk.CloudRkService; +import com.ecep.contract.manager.cloud.tyc.CloudTyc; +import com.ecep.contract.manager.cloud.tyc.CloudTycService; +import com.ecep.contract.manager.ds.company.CompanyFileType; +import com.ecep.contract.manager.ds.company.model.*; +import com.ecep.contract.manager.ds.company.repository.CompanyContactRepository; +import com.ecep.contract.manager.ds.company.repository.CompanyOldNameRepository; +import com.ecep.contract.manager.ds.contract.ContractFileType; +import com.ecep.contract.manager.ds.other.repository.SysConfRepository; +import com.ecep.contract.manager.ds.company.repository.CompanyFileRepository; +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.contract.model.ContractFile; +import com.ecep.contract.manager.ds.contract.repository.ContractFileRepository; +import com.ecep.contract.manager.ds.contract.repository.ContractRepository; +import com.ecep.contract.manager.ds.customer.model.CompanyCustomer; +import com.ecep.contract.manager.ds.customer.model.CompanyCustomerFile; +import com.ecep.contract.manager.ds.customer.repository.CompanyCustomerFileRepository; +import com.ecep.contract.manager.ds.customer.repository.CompanyCustomerRepository; +import com.ecep.contract.manager.ds.vendor.VendorType; +import com.ecep.contract.manager.ds.vendor.model.CompanyVendor; +import com.ecep.contract.manager.ds.vendor.model.CompanyVendorFile; +import com.ecep.contract.manager.ds.vendor.repository.CompanyVendorRepository; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.vendor.service.CompanyVendorFileService; +import jakarta.transaction.Transactional; +import javafx.application.Platform; +import javafx.concurrent.Task; +import org.controlsfx.control.TaskProgressView; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.jdbc.core.ColumnMapRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.beans.PropertyDescriptor; +import java.io.File; +import java.time.Instant; +import java.time.LocalDate; +import java.util.*; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Stream; + +/** + * 从老板APP的数据库中导入数据 + *

+ * 数据结构不同 + */ +@Lazy +@Service +public class OldVersionService { + private static final Logger logger = LoggerFactory.getLogger(OldVersionService.class); + @Autowired + private ContractFileRepository contractFileRepository; + + + public enum Types { + VENDOR, CUSTOMER + } + + @Autowired + private JdbcTemplate jdbcTemplate; + @Autowired + private SysConfRepository confRepository; + @Lazy + @Autowired + private CompanyService companyService; + @Autowired + private CloudRkService cloudRkService; + @Autowired + private CloudTycService cloudTycService; + @Autowired + private ScheduledExecutorService scheduledExecutorService; + @Autowired + private CompanyContactRepository companyContactRepository; + @Autowired + private CompanyCustomerRepository companyCustomerRepository; + @Autowired + private CompanyVendorRepository companyVendorRepository; + @Autowired + private CompanyOldNameRepository companyOldNameRepository; + @Autowired + private ContractRepository contractRepository; + @Autowired + private CompanyFileRepository companyFileRepository; + + public Long countOfVendor() { + return jdbcTemplate.queryForObject("select COUNT(*) from VENDOR;", Long.class); + } + + public Stream> queryAllVendorForStream() { + return jdbcTemplate.queryForStream( + "select ID,NAME,UNISCID,ENT_STATUS,ENT_TYPE,PATH_EXIST,PATH,TYPE," + + "DISTRICT,INDUSTRY,TELEPHONE,REG_ADDR,ADDRESS,REGISTERED_CAPITAL,REGISTERED_CAPITAL_CURRENCY," + + "LEGAL_REPRESENTATIVE,CLOUD_ID,TYC_ID,TYC_SCORE,OPERATION_PERIOD_BEGIN,OPERATION_PERIOD_END,MEMO," + + "SETUP_DATE,CREATED from VENDOR;", + new ColumnMapRowMapper() + ); + } + + public Long countOfVendorContract() { + return jdbcTemplate.queryForObject("select COUNT(*) from VENDOR_CONTRACT;", Long.class); + } + + public Stream> queryAllVendorContractForStream() { + return jdbcTemplate.queryForStream("select ID,NO,DIRECTORY from VENDOR_CONTRACT;", new ColumnMapRowMapper()); + } + + public List> queryVendorContractFileByContractId(int contractId) { + return jdbcTemplate.queryForList("select ID, CONTRACT_ID, TYPE, FILE, APPLY_DATE, MEMO from VENDOR_CONTRACT_APPROVAL where CONTRACT_ID=?;", contractId); + } + + public Stream> queryAllVendorContactForStream() { + return jdbcTemplate.queryForStream("select NAME,PHONE,EMAIL,ADDRESS,VENDOR_ID as GID from VENDOR_CONTACT;", new ColumnMapRowMapper()); + } + + public Stream> queryAllVendorOldNameForStream() { + return jdbcTemplate.queryForStream("select NAME,PATH,VENDOR_ID as GID,MEMO from VENDOR_OLD_NAME;", new ColumnMapRowMapper()); + } + + public List> queryVendorEvidenceByVendorId(int vendorId) { + return jdbcTemplate.queryForList("select ID, TYPE, FILE_PATH, APPLY_DATE, EXPIRING_DATE from VENDOR_EVIDENCE where VENDOR_ID=?;", vendorId); + } + + public List> queryVendorEvalFormByVendorId(int vendorId) { + return jdbcTemplate.queryForList("select ID, FILE_PATH, EDIT_FILE_PATH, SIGN_DATE,VALID from VENDOR_EVAL_FORM where VENDOR_ID=?;", vendorId); + } + + @Transactional + public int newCompanyVendor(String companyName) { + Integer maxId = jdbcTemplate.queryForObject("select max(ID) from VENDOR;", Integer.class); + if (maxId != null) { + int nextId = maxId + 1; + if (jdbcTemplate.update("INSERT INTO VENDOR (ID, NAME)values(?, ?);", nextId, companyName) > 0) { + return nextId; + } + } + return -1; + } + + public Long countOfCustomer() { + return jdbcTemplate.queryForObject("select COUNT(*) from CUSTOMER;", Long.class); + } + + public Stream> queryAllCustomerForStream() { + return jdbcTemplate.queryForStream( + "select ID,NAME,UNISCID,ENT_STATUS,ENT_TYPE,PATH_EXIST,PATH," + + "DISTRICT,INDUSTRY,TELEPHONE,REG_ADDR,ADDRESS,REGISTERED_CAPITAL," + + "LEGAL_REPRESENTATIVE,CLOUD_ID,TYC_ID,TYC_SCORE,OPERATION_PERIOD_BEGIN,OPERATION_PERIOD_END,MEMO," + + "SETUP_DATE,CREATED from CUSTOMER;", + new ColumnMapRowMapper() + ); + } + + public Long countOfCustomerContract() { + return jdbcTemplate.queryForObject("select COUNT(*) from CUSTOMER_CONTRACT;", Long.class); + } + + public Stream> queryAllCustomerContractForStream() { + return jdbcTemplate.queryForStream("select ID,NO,DIRECTORY from CUSTOMER_CONTRACT;", new ColumnMapRowMapper()); + } + + public List> queryCustomerContractFileByContractId(int contractId) { + return jdbcTemplate.queryForList("select ID, CONTRACT_ID, TYPE, FILE, APPLY_DATE, MEMO from CUSTOMER_CONTRACT_APPROVAL where CONTRACT_ID=?;", contractId); + } + + public Stream> queryAllCustomerContactForStream() { + return jdbcTemplate.queryForStream("select NAME,PHONE,EMAIL,ADDRESS,CUSTOMER_ID as GID from CUSTOMER_CONTACT;", new ColumnMapRowMapper()); + } + + public Stream> queryAllCustomerOldNameForStream() { + return jdbcTemplate.queryForStream("select NAME,PATH,CUSTOMER_ID as GID,MEMO from CUSTOMER_OLD_NAME;", new ColumnMapRowMapper()); + } + + public List> queryCustomerEvidenceByCustomerId(int customerId) { + return jdbcTemplate.queryForList("select ID, TYPE, FILE_PATH, APPLY_DATE, EXPIRING_DATE from CUSTOMER_EVIDENCE where CUSTOMER_ID=?;", customerId); + } + + public List> queryCustomerEvalFormByCustomerId(int customerId) { + return jdbcTemplate.queryForList("select ID, FILE_PATH, EDIT_FILE_PATH, SIGN_DATE, VALID from CUSTOMER_EVAL_FORM where CUSTOMER_ID=?;", customerId); + } + + @Transactional + public int newCompanyCustomer(String companyName) { + Integer maxId = jdbcTemplate.queryForObject("select max(ID) from CUSTOMER;", Integer.class); + if (maxId != null) { + int nextId = maxId + 1; + if (jdbcTemplate.update("INSERT INTO CUSTOMER (ID, NAME)values(?, ?);", nextId, companyName) > 0) { + return nextId; + } + } + return -1; + } + + /** + * 生成定时同步任务 + * + * @param taskProgressView 任务视图 + */ + public void scheduledTasks(TaskProgressView> taskProgressView) { + // 15分钟运行一次合同合同任务 + scheduledExecutorService.scheduleAtFixedRate(() -> { + if (logger.isDebugEnabled()) { + logger.debug("createSyncTask"); + } + + boolean autoSyncEnable = confRepository.get("cloud.old.auto_sync.enable", false); + if (!autoSyncEnable) { + return; + } + + Task vendorTask = new OldVersionSyncVendorTask(); + Task customerTask = new OldVersionSyncCustomerTask(); + + scheduledExecutorService.schedule(() -> { + vendorTask.run(); + customerTask.run(); + }, 5, TimeUnit.SECONDS); + Platform.runLater(() -> { + taskProgressView.getTasks().addAll(vendorTask, customerTask); + }); + }, 3, TimeUnit.DAYS.toSeconds(1), TimeUnit.SECONDS); + } + + /** + * 更新公司信息 + * + * @param updater 要更新的公司对象 + * @param map 数据map + */ + public boolean applyCompany(Company updater, Map map) { + boolean changed = false; + + String[][] fieldMapping = new String[][]{ + {"entStatus", "ENT_STATUS"}, + {"entType", "ENT_TYPE"}, + {"district", "DISTRICT"}, + {"industry", "INDUSTRY"}, + {"telephone", "TELEPHONE"}, + {"regAddr", "REG_ADDR"}, + {"address", "ADDRESS"}, + {"registeredCapital", "REGISTERED_CAPITAL"}, + {"registeredCapitalCurrency", "REGISTERED_CAPITAL_CURRENCY"}, + {"legalRepresentative", "LEGAL_REPRESENTATIVE"}, + {"memo", "MEMO"} + }; + + java.sql.Date created = (java.sql.Date) map.get("CREATED"); + java.sql.Date operationPeriodBegin = (java.sql.Date) map.get("OPERATION_PERIOD_BEGIN"); + java.sql.Date operationPeriodEnd = (java.sql.Date) map.get("OPERATION_PERIOD_END"); + java.sql.Date setupDate = (java.sql.Date) map.get("SETUP_DATE"); + + // PropertyDescriptor[] propertyDescriptors = BeanUtils.getPropertyDescriptors(Company.class); + + for (String[] fieldMap : fieldMapping) { + if (updateCompanyProperty(updater, fieldMap[0], map.get(fieldMap[1]))) { + changed = true; + } + } + + if (setupDate != null) { + if (updateCompanyProperty(updater, "setupDate", setupDate.toLocalDate())) { + changed = true; + } + } + if (operationPeriodBegin != null) { + if (updateCompanyProperty(updater, "operationPeriodBegin", operationPeriodBegin.toLocalDate())) { + changed = true; + } + } + if (operationPeriodEnd != null) { + if (updateCompanyProperty(updater, "operationPeriodEnd", operationPeriodEnd.toLocalDate())) { + changed = true; + } + } + + if (created != null) { + if (updateCompanyProperty(updater, "created", created.toLocalDate())) { + changed = true; + } + } + return changed; + } + + private boolean updateCompanyProperty(Company updater, String field, Object newValue) { + if (newValue == null) { + return false; + } + PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(Company.class, field); + if (descriptor == null) { + logger.error("Company 的字段 {} 不存在,请确认.", field); + return false; + } + try { + Object oldValue = descriptor.getReadMethod().invoke(updater); + if (!Objects.equals(oldValue, newValue)) { + descriptor.getWriteMethod().invoke(updater, newValue); + return true; + } + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + + private void updateProperty(String input, Supplier getter, Consumer setter) { + if (input != null) { + if (!input.equals(getter.get())) { + setter.accept(input); + } + } + } + + /** + * 应用联系人数据 + *

+ * {@link #queryAllVendorContactForStream()} {@link #queryAllCustomerContactForStream()} + */ + public void applyContacts(Company updater, List> contacts, File basePath) { + if (contacts.isEmpty()) { + return; + } + // 检索出公司下属的所有联系人 + List myContacts = companyContactRepository.findAllByCompany(updater); + // 1. 先做数据库排重 + HashMap contactMap = new HashMap<>(); + for (CompanyContact contact : myContacts) { + String key = CompanyContract.getContactKey(contact); + if (contactMap.containsKey(key)) { + // 有重复,删除,按List顺序,只保留第一个 + companyContactRepository.delete(contact); + if (logger.isInfoEnabled()) { + logger.info("Duplicate CompanyContact, Delete {}", contact); + } + } else { + // 名称记录到 contactMap 中 + contactMap.put(key, contact); + } + } + + // 2. 使用导入数据更新 数据中的 已存在的记录 + for (Map contact : contacts) { + String key = CompanyContactUtils.getContactKey(contact); + CompanyContact oldName1 = contactMap.get(key); + if (oldName1 == null) { + continue; + } + + if (CompanyContactUtils.applyContactByMap(oldName1, contact)) { + CompanyContact saved = companyContactRepository.save(oldName1); + contactMap.put(key, saved); + } + } + + // 3. 移除导入的曾用名中删除数据库中已经存在的 + contacts.removeIf(m -> contactMap.containsKey(CompanyContactUtils.getContactKey(m))); + + // 4. 剩下的保存 + for (Map m : contacts) { + String name = (String) m.get("NAME"); + if (!StringUtils.hasText(name)) { + continue; + } + try { + CompanyContact contact = new CompanyContact(); + CompanyContactUtils.applyContactByMap(contact, m); + contact.setCompany(updater); + companyContactRepository.save(contact); + if (logger.isInfoEnabled()) { + logger.info("Create New CompanyContact {}", contact); + } + } catch (Exception e) { + throw new RuntimeException("Unable Save " + m, e); + } + } + } + + public void applyOldNames(Company updater, List> oldNames, File basePath) { + if (oldNames.isEmpty()) { + return; + } + // fixed 移除和更新公司同名的曾用名 + oldNames.removeIf(m -> { + String name = (String) m.get("NAME"); + if (name == null) { + return true; + } + return name.equals(updater.getName()); + }); + + // 检索出公司下属的所有曾用名 + List myOldNames = companyOldNameRepository.findAllByCompanyId(updater.getId()); + // 1. 先做数据库排重 + HashMap oldNameMap = new HashMap<>(); + for (CompanyOldName oldName : myOldNames) { + String key = oldName.getName(); + if (oldNameMap.containsKey(key)) { + // 有重复,删除,按List顺序,只保留第一个 + companyOldNameRepository.delete(oldName); + if (logger.isInfoEnabled()) { + logger.info("Duplicate CompanyOldName, Delete {}", oldName); + } + } else { + // 名称记录到 oldNameMap 中 + oldNameMap.put(key, oldName); + } + } + + // fixed 移除和更新公司同名的曾用名 + CompanyOldName mainName = oldNameMap.get(updater.getName()); + if (mainName != null) { + companyOldNameRepository.delete(mainName); + if (logger.isInfoEnabled()) { + logger.info("Same Name as Company Name, Delete {}", mainName); + } + } + + + // 2. 使用导入数据更新 数据中的 已存在的记录 + for (Map oldName : oldNames) { + String key = (String) oldName.getOrDefault("NAME", null); + CompanyOldName oldName1 = oldNameMap.get(key); + if (oldName1 == null) { + continue; + } + boolean modified = false; + String oldNamePath = (String) oldName.get("PATH"); + String oldNameMemo = (String) oldName.get("MEMO"); + if (oldNamePath != null) { + // 更新路径 + File dest = new File(basePath, oldNamePath); + String absolutePath = dest.getAbsolutePath(); + if (!Objects.equals(oldName1.getPath(), absolutePath)) { + oldName1.setPath(absolutePath); + modified = true; + } + } + if (oldNameMemo != null) { + // 更新备注 + if (!Objects.equals(oldName1.getMemo(), oldNameMemo)) { + oldName1.setMemo(oldNameMemo); + modified = true; + } + } + if (modified) { + CompanyOldName saved = companyOldNameRepository.save(oldName1); + oldNameMap.put(key, saved); + } + } + + // 3. 移除导入的曾用名中删除数据库中已经存在的 + oldNames.removeIf(m -> oldNameMap.containsKey((String) m.get("NAME"))); + + // 4. 剩下的保存 + for (Map m : oldNames) { + String name = (String) m.get("NAME"); + String memo = (String) m.get("MEMO"); + String path = (String) m.get("PATH"); + if (!StringUtils.hasText(name)) { + continue; + } + + try { + CompanyOldName oldName = new CompanyOldName(); + oldName.setCompanyId(updater.getId()); + oldName.setName(name); + oldName.setMemo(memo); + oldName.setAmbiguity(false); + if (StringUtils.hasText(path)) { + File dest = new File(basePath, path); + String absolutePath = dest.getAbsolutePath(); + oldName.setPath(absolutePath); + } + companyOldNameRepository.save(oldName); + if (logger.isInfoEnabled()) { + logger.info("Create New CompanyOldName {}", oldName); + } + } catch (Exception e) { + throw new RuntimeException("Unable Save " + m, e); + } + } + } + + /** + * 应用天眼查和相关方平台的Id + */ + private void applyCloudRkAndTyc(Company updater, Map map) { + String cloudRkId = (String) map.getOrDefault("CLOUD_ID", null); + String cloudTycId = (String) map.getOrDefault("TYC_ID", null); + Integer tycScore = (Integer) map.getOrDefault("TYC_SCORE", null); + + // fixed when new company before save will throw exception + if (cloudRkId != null) { + CloudRk cloudRk = cloudRkService.getOrCreateCloudRk(updater); + if (!Objects.equals(cloudRk.getCloudId(), cloudRkId)) { + cloudRk.setCloudId(cloudRkId); + cloudRkService.save(cloudRk); + } + } + if (cloudTycId != null) { + boolean tycModified = false; + CloudTyc cloudTyc = cloudTycService.getOrCreateCloudTyc(updater); + if (!Objects.equals(cloudTyc.getCloudId(), cloudTycId)) { + cloudTyc.setCloudId(cloudTycId); + tycModified = true; + } + if (!Objects.equals(cloudTyc.getScore(), tycScore)) { + cloudTyc.setScore(tycScore); + tycModified = true; + } + if (tycModified) { + cloudTycService.save(cloudTyc); + } + } + } + + public void applyVendorOrCustomer(Company updater, Types type, Map map, File basePath) { + Integer id = (Integer) map.get("ID"); + String path = (String) map.get("PATH"); + String absolutePath = null; + if (path != null) { + File dest = new File(basePath, path); + absolutePath = dest.getAbsolutePath(); + } + if (type == Types.VENDOR) { + String vendorType = (String) map.get("TYPE"); + boolean changed = false; + CompanyVendor companyVendor = companyVendorRepository.findByCompany(updater).orElseGet(() -> { + CompanyVendor vendor = new CompanyVendor(); + vendor.setId(id); + vendor.setCompany(updater); + vendor.setCreated(Instant.now()); + return vendor; + }); + + if (StringUtils.hasText(vendorType)) { + VendorType t = VendorType.valueOf(vendorType); + if (!Objects.equals(t, companyVendor.getType())) { + companyVendor.setType(t); + changed = true; + } + } + + if (companyVendor.getCompany() == null) { + companyVendor.setCompany(updater); + changed = true; + } + + if (!Objects.equals(absolutePath, companyVendor.getPath())) { + companyVendor.setPath(absolutePath); + changed = true; + } + + if (changed) { + companyVendorRepository.save(companyVendor); + } + + CompanyVendorFileService fileService = SpringApp.getBean(CompanyVendorFileService.class); + + // 1. 先做数据库排重 + List files = fileService.findAllByVendor(companyVendor); + HashMap fileMap = new HashMap<>(); + for (CompanyVendorFile vendorFile : files) { + String key = vendorFile.getFilePath(); + if (fileMap.containsKey(key)) { + // 有重复,删除,按List顺序,只保留第一个 + fileService.delete(vendorFile); + if (logger.isInfoEnabled()) { + logger.info("Duplicate CompanyVendorFile, Delete {}", vendorFile); + } + } else { + // 名称记录到 fileMap 中 + fileMap.put(key, vendorFile); + } + } + + // 2. 使用导入数据更新 数据中的 已存在的记录 + List> rs = queryVendorEvalFormByVendorId(id); + for (Map m : rs) { + String key = CompanyVendorFileUtils.getVendorFileKey(m); + CompanyVendorFile vendorFile = fileMap.get(key); + if (vendorFile == null) { + continue; + } + + if (CompanyVendorFileUtils.applyVendorFileByMap(vendorFile, m)) { + CompanyVendorFile saved = fileService.save(vendorFile); + fileMap.put(key, saved); + } + } + + // 3. 移除导入的数据库中已经存在的 + rs.removeIf(m -> fileMap.containsKey(CompanyVendorFileUtils.getVendorFileKey(m))); + + // 4. 剩下的保存 + for (Map m : rs) { + String key = CompanyVendorFileUtils.getVendorFileKey(m); + if (!StringUtils.hasText(key)) { + continue; + } + try { + CompanyVendorFile vendorFile = new CompanyVendorFile(); + CompanyVendorFileUtils.applyVendorFileByMap(vendorFile, m); + vendorFile.setVendor(companyVendor); + fileService.save(vendorFile); + if (logger.isInfoEnabled()) { + logger.info("Create New CompanyVendorFile {}", vendorFile); + } + } catch (Exception e) { + throw new RuntimeException("Unable Save " + m, e); + } + } + + } else if (type == Types.CUSTOMER) { + boolean changed = false; + CompanyCustomer companyCustomer = companyCustomerRepository.findByCompany(updater).orElseGet(() -> { + CompanyCustomer customer = new CompanyCustomer(); + customer.setId(id); + customer.setCompany(updater); + customer.setCreated(Instant.now()); + return customer; + }); + if (companyCustomer.getCompany() == null) { + companyCustomer.setCompany(updater); + changed = true; + } + if (!Objects.equals(absolutePath, companyCustomer.getPath())) { + companyCustomer.setPath(absolutePath); + changed = true; + } + if (changed) { + companyCustomerRepository.save(companyCustomer); + } + + CompanyCustomerFileRepository fileRepository = SpringApp.getBean(CompanyCustomerFileRepository.class); + + // 1. 先做数据库排重 + List files = fileRepository.findAllByCustomer(companyCustomer); + HashMap fileMap = new HashMap<>(); + for (CompanyCustomerFile customerFile : files) { + String key = customerFile.getFilePath(); + if (fileMap.containsKey(key)) { + // 有重复,删除,按List顺序,只保留第一个 + fileRepository.delete(customerFile); + if (logger.isInfoEnabled()) { + logger.info("Duplicate CompanyCustomerFile, Delete {}", customerFile); + } + } else { + // 名称记录到 fileMap 中 + fileMap.put(key, customerFile); + } + } + + // 2. 使用导入数据更新 数据中的 已存在的记录 + List> rs = queryCustomerEvalFormByCustomerId(id); + for (Map m : rs) { + String key = CompanyCustomerFileUtils.getCustomerFileKey(m); + CompanyCustomerFile vendorFile = fileMap.get(key); + if (vendorFile == null) { + continue; + } + + if (CompanyCustomerFileUtils.applyCustomerFileByMap(vendorFile, m)) { + CompanyCustomerFile saved = fileRepository.save(vendorFile); + fileMap.put(key, saved); + } + } + + // 3. 移除导入的数据库中已经存在的 + rs.removeIf(m -> fileMap.containsKey(CompanyCustomerFileUtils.getCustomerFileKey(m))); + + // 4. 剩下的保存 + for (Map m : rs) { + String key = CompanyVendorFileUtils.getVendorFileKey(m); + if (!StringUtils.hasText(key)) { + continue; + } + try { + CompanyCustomerFile customerFile = new CompanyCustomerFile(); + CompanyCustomerFileUtils.applyCustomerFileByMap(customerFile, m); + customerFile.setCustomer(companyCustomer); + fileRepository.save(customerFile); + if (logger.isInfoEnabled()) { + logger.info("Create New CompanyCustomerFile {}", customerFile); + } + } catch (Exception e) { + throw new RuntimeException("Unable Save " + m, e); + } + } + } + } + + public Company syncVendor( + Map map, File basePath, + Map>> oldNameGroupedMap, + Map>> contactGroupedMap + ) { + String uniscid = (String) map.get("UNISCID"); + if (StringUtils.hasText(uniscid)) { + return syncVendorOrCustomerByUniscid(map, Types.VENDOR, basePath, oldNameGroupedMap, contactGroupedMap); + } else { + return syncVendorOrCustomerByName(map, Types.VENDOR, basePath, oldNameGroupedMap, contactGroupedMap); + } + } + + public Company syncCustomer( + Map map, File basePath, + Map>> oldNameGroupedMap, + Map>> contactGroupedMap + ) { + String uniscid = (String) map.get("UNISCID"); + if (StringUtils.hasText(uniscid)) { + return syncVendorOrCustomerByUniscid(map, Types.CUSTOMER, basePath, oldNameGroupedMap, contactGroupedMap); + } else { + return syncVendorOrCustomerByName(map, Types.CUSTOMER, basePath, oldNameGroupedMap, contactGroupedMap); + } + } + + private Company syncVendorOrCustomerByName( + Map map, Types type, File basePath, + Map>> oldNameGroupedMap, + Map>> contactGroupedMap + ) { + Integer id = (Integer) map.get("ID"); + String name = (String) map.get("NAME"); + if (name.equals("散户")) { + return null; + } + Company updater = companyService.findAndRemoveDuplicateCompanyByNameOrAbbName(name, null); + if (updater == null) { + if (logger.isDebugEnabled()) { + logger.debug("No match Name {} , map = {}", name, map); + } + return null; + } + boolean updaterModified = false; + + List> oldNameList; + { + List> m = oldNameGroupedMap.get(id); + if (m != null) { + oldNameList = new ArrayList<>(m); + } else { + oldNameList = new ArrayList<>(); + } + } + + // fixed 当名称不一致时 + if (!updater.getName().equals(name)) { + // 把当前名称存入 曾用名 中 + Map m = new HashMap<>(); + m.put("NAME", name); + oldNameList.add(m); + } + + if (applyCompany(updater, map)) { + updaterModified = true; + + } + applyCloudRkAndTyc(updater, map); + + applyVendorOrCustomer(updater, type, map, basePath); + applyOldNames(updater, oldNameList, basePath); + + List> contacts; + { + List> m = contactGroupedMap.get(id); + if (m != null) { + contacts = new ArrayList<>(m); + } else { + contacts = new ArrayList<>(); + } + } + applyContacts(updater, contacts, basePath); + if (syncEvidence(updater, type, id)) { + updaterModified = true; + } + if (updaterModified) { + updater = companyService.save(updater); + } + return updater; + } + + private boolean syncEvidence(Company updater, Types type, Integer id) { + List> importRs = type == Types.VENDOR ? queryVendorEvidenceByVendorId(id) : queryCustomerEvidenceByCustomerId(id); + if (importRs.isEmpty()) { + return false; + } + boolean updaterChanged = companyService.makePathAbsent(updater); + String path = updater.getPath(); + if (!StringUtils.hasText(path)) { + if (logger.isInfoEnabled()) { + logger.info("Company home not exist {}", updater); + } + return false; + } + File companyPath = new File(path); + + // 检索出公司下属的所有文件 + List existRs = companyFileRepository.findByCompany(updater); + // 1. 先做数据库排重 + HashMap existMap = new HashMap<>(); + for (CompanyFile companyFile : existRs) { + String key = companyFile.getFilePath(); + if (existMap.containsKey(key)) { + // 有重复,删除,按List顺序,只保留第一个 + companyFileRepository.delete(companyFile); + if (logger.isInfoEnabled()) { + logger.info("Duplicate CompanyFile, Delete {}", companyFile); + } + } else { + // 名称记录到 existMap 中 + existMap.put(key, companyFile); + } + } + + // 2. 使用导入数据更新 数据中的 已存在的记录 + for (Map rs : importRs) { + // ID, TYPE, FILE_PATH, APPLY_DATE, EXPIRING_DATE + String key = (String) rs.get("FILE_PATH"); + CompanyFile oldName1 = existMap.get(key); + if (oldName1 == null) { + continue; + } + boolean modified = false; + + String rsType = (String) rs.get("TYPE"); + java.sql.Date rsApplyDate = (java.sql.Date) rs.get("APPLY_DATE"); + java.sql.Date rsExpiringDate = (java.sql.Date) rs.get("EXPIRING_DATE"); + if (rsType != null) { + if (oldName1.getType() == null || !Objects.equals(oldName1.getType().name(), rsType)) { + oldName1.setType(CompanyFileType.valueOf(rsType)); + modified = true; + } + } + if (rsApplyDate != null) { + LocalDate localDate = rsApplyDate.toLocalDate(); + if (!Objects.equals(oldName1.getApplyDate(), localDate)) { + oldName1.setApplyDate(localDate); + modified = true; + } + } + if (rsExpiringDate != null) { + LocalDate localDate = rsExpiringDate.toLocalDate(); + if (!Objects.equals(oldName1.getExpiringDate(), localDate)) { + oldName1.setExpiringDate(localDate); + modified = true; + } + } + + //TODO 移动文件,删除记录 + + + if (modified) { + CompanyFile saved = companyFileRepository.save(oldName1); + existMap.put(key, saved); + } + } + + // 3. 移除导入的曾用名中删除数据库中已经存在的 + importRs.removeIf(m -> existMap.containsKey((String) m.get("FILE_PATH"))); + + // 4. 剩下的保存 + for (Map m : importRs) { + String rsFilePath = (String) m.get("FILE_PATH"); + String rsType = (String) m.get("TYPE"); + java.sql.Date rsApplyDate = (java.sql.Date) m.get("APPLY_DATE"); + java.sql.Date rsExpiringDate = (java.sql.Date) m.get("EXPIRING_DATE"); + + if (!StringUtils.hasText(rsFilePath)) { + continue; + } + + // 如果文件已经不在时,跳过 + File file = new File(rsFilePath); + if (!file.exists()) { + continue; + } + + File dest = new File(companyPath, file.getName()); + if (!file.renameTo(dest)) { + continue; + } + + try { + CompanyFile companyFile = new CompanyFile(); + companyFile.setCompany(updater); + if (StringUtils.hasText(rsType)) { + companyFile.setType(CompanyFileType.valueOf(rsType)); + } + companyFile.setFilePath(dest.getAbsolutePath()); + if (rsApplyDate != null) { + companyFile.setApplyDate(rsApplyDate.toLocalDate()); + } + if (rsExpiringDate != null) { + companyFile.setExpiringDate(rsExpiringDate.toLocalDate()); + } + companyFileRepository.save(companyFile); + if (logger.isInfoEnabled()) { + logger.info("Create New CompanyFile {}", companyFile); + } + } catch (Exception e) { + throw new RuntimeException("Unable Save " + m, e); + } + } + return updaterChanged; + } + + private Company syncVendorOrCustomerByUniscid( + Map map, Types type, File basePath, + Map>> oldNameGroupedMap, + Map>> contactGroupedMap + ) { + Integer id = (Integer) map.get("ID"); + String uniscid = (String) map.get("UNISCID"); + String name = (String) map.get("NAME"); + + Company updater = companyService.findAndRemoveDuplicateCompanyByUniscid(uniscid); + boolean updaterModified = false; + if (updater == null) { + // 没有记录 + if (logger.isDebugEnabled()) { + logger.debug("Create New Company by uniscid={}", uniscid); + } + updater = new Company(); + updater.setName(name); + updater.setUniscid(uniscid); + updaterModified = true; + } + + List> oldNameList; + { + List> m = oldNameGroupedMap.get(id); + if (m != null) { + oldNameList = new ArrayList<>(m); + } else { + oldNameList = new ArrayList<>(); + } + } + + // fixed 当名称不一致时 + if (!updater.getName().equals(name)) { + // 把当前名称存入 曾用名 中 + Map m = new HashMap<>(); + m.put("NAME", name); + oldNameList.add(m); + } + + if (applyCompany(updater, map)) { + updaterModified = true; + } + if (updaterModified) { + updater = companyService.save(updater); + } + applyCloudRkAndTyc(updater, map); + + applyVendorOrCustomer(updater, type, map, basePath); + applyOldNames(updater, oldNameList, basePath); + + List> contacts; + { + List> m = contactGroupedMap.get(id); + if (m != null) { + contacts = new ArrayList<>(m); + } else { + contacts = new ArrayList<>(); + } + } + applyContacts(updater, contacts, basePath); + if (syncEvidence(updater, type, id)) { + updater = companyService.save(updater); + } + return updater; + } + + public Contract syncCustomerContract(Map map) { + Integer id = (Integer) map.get("ID"); + Contract contract = syncContract(map); + syncContractFile(Types.CUSTOMER, contract, id); + return contract; + } + + public Contract syncVendorContract(Map map) { + Integer id = (Integer) map.get("ID"); + Contract contract = syncContract(map); + syncContractFile(Types.VENDOR, contract, id); + return contract; + } + + private void syncContractFile(Types type, Contract contract, Integer id) { + List> importRs = type == Types.VENDOR ? + queryVendorContractFileByContractId(id) : queryCustomerContractFileByContractId(id); + if (importRs.isEmpty()) { + return; + } + + + // 检索出公司下属的所有文件 + List existRs = contractFileRepository.findByContract(contract); + // 1. 先做数据库排重 + HashMap existMap = new HashMap<>(); + for (ContractFile contractFile : existRs) { + String key = contractFile.getFileName(); + if (existMap.containsKey(key)) { + // 有重复,删除,按List顺序,只保留第一个 + contractFileRepository.delete(contractFile); + if (logger.isInfoEnabled()) { + logger.info("Duplicate ContractFile, Delete {}", contractFile); + } + } else { + // 名称记录到 existMap 中 + existMap.put(key, contractFile); + } + } + + // 2. 使用导入数据更新 数据中的 已存在的记录 + for (Map rs : importRs) { + // ID, CONTRACT_ID, TYPE, FILE, APPLY_DATE, MEMO + String key = (String) rs.get("FILE"); + ContractFile oldName1 = existMap.get(key); + if (oldName1 == null) { + continue; + } + boolean modified = false; + + String rsType = (String) rs.get("TYPE"); + java.sql.Date rsApplyDate = (java.sql.Date) rs.get("APPLY_DATE"); + String memo = (String) rs.get("MEMO"); + if (rsType != null) { + if (oldName1.getType() == null || !Objects.equals(oldName1.getType().name(), rsType)) { + oldName1.setType(ContractFileType.valueOf(rsType)); + modified = true; + } + } + if (rsApplyDate != null) { + LocalDate localDate = rsApplyDate.toLocalDate(); + if (!Objects.equals(oldName1.getApplyDate(), localDate)) { + oldName1.setApplyDate(localDate); + modified = true; + } + } + if (memo != null) { + if (!Objects.equals(oldName1.getDescription(), memo)) { + oldName1.setDescription(memo); + modified = true; + } + } + + if (modified) { + ContractFile saved = contractFileRepository.save(oldName1); + existMap.put(key, saved); + } + } + + // 3. 移除导入的曾用名中删除数据库中已经存在的 + importRs.removeIf(m -> existMap.containsKey((String) m.get("FILE"))); + + // 4. 剩下的保存 + for (Map m : importRs) { + String rsFile = (String) m.get("FILE"); + String rsType = (String) m.get("TYPE"); + java.sql.Date rsApplyDate = (java.sql.Date) m.get("APPLY_DATE"); + String memo = (String) m.get("MEMO"); + try { + ContractFile contractFile = new ContractFile(); + contractFile.setContract(contract); + if (StringUtils.hasText(rsType)) { + contractFile.setType(ContractFileType.valueOf(rsType)); + } + if (StringUtils.hasText(rsFile)) { + contractFile.setFileName(rsFile); + } + if (rsApplyDate != null) { + contractFile.setApplyDate(rsApplyDate.toLocalDate()); + } + if (memo != null) { + contractFile.setDescription(memo); + } + contractFileRepository.save(contractFile); + if (logger.isInfoEnabled()) { + logger.info("Create New ContractFile {}", contractFile); + } + } catch (Exception e) { + throw new RuntimeException("Unable Save " + m, e); + } + } + } + + + public Contract syncContract(Map map) { + String no = String.valueOf(map.get("NO")); + String path = String.valueOf(map.get("DIRECTORY")); + if (!StringUtils.hasText(path)) { + return null; + } + AtomicReference reference = new AtomicReference<>(); + contractRepository.findByCode(no).ifPresentOrElse(contract -> { + if (!Objects.equals(contract.getPath(), path)) { + contract.setPath(path); + Contract saved = contractRepository.save(contract); + reference.set(saved); + } else { + reference.set(contract); + } + }, () -> { + if (logger.isWarnEnabled()) { + logger.warn("No match Contract for {}", no); + } + }); + + + return reference.get(); + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/old/OldVersionSyncCustomerTask.java b/src/main/java/com/ecep/contract/manager/cloud/old/OldVersionSyncCustomerTask.java new file mode 100644 index 0000000..7bda0c8 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/old/OldVersionSyncCustomerTask.java @@ -0,0 +1,125 @@ +package com.ecep.contract.manager.cloud.old; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.contract.model.Contract; +import javafx.concurrent.Task; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * + */ +public class OldVersionSyncCustomerTask extends Task { + private static final Logger logger = LoggerFactory.getLogger(OldVersionSyncCustomerTask.class); + + private final OldVersionService service = SpringApp.getBean(OldVersionService.class); + private final CompanyService companyService = SpringApp.getBean(CompanyService.class); + + private Map>> oldNameGroupedMap; + private Map>> contactGroupedMap; + private File basePath; + private String titlePrefix = ""; + + public OldVersionSyncCustomerTask() { + updateTitle("老版本-同步客户数据"); + } + + @Override + protected void updateTitle(String title) { + super.updateTitle(titlePrefix + title); + } + + @Override + protected Object call() throws Exception { + updateTitle("老版本-同步客户数据"); + basePath = companyService.getCustomerBasePath(); + List runnable = Arrays.asList(this::loadOldNames, this::loadContacts, this::syncCustomers, this::syncContracts); + + for (int i = 0; i < runnable.size(); i++) { + titlePrefix = "老版本-同步客户数据-" + (i + 1) + "/" + runnable.size() + "-"; + try { + runnable.get(i).run(); + } catch (Exception e) { + logger.error("运行至 {} 时,发生错误中断", titlePrefix, e); + break; + } + } + return null; + } + + + private void loadOldNames() { + try (Stream> stream = service.queryAllCustomerOldNameForStream()) { + updateTitle("载入曾用名"); + oldNameGroupedMap = stream.takeWhile(v -> !isCancelled()).parallel() + .collect(Collectors.groupingBy(m -> m.get("GID"))); + if (logger.isDebugEnabled()) { + logger.debug("import Customer oldNames = {} from old version app", oldNameGroupedMap.size()); + } + updateProgress(0.1, 1); + } + } + + private void loadContacts() { + try (Stream> stream = service.queryAllCustomerContactForStream()) { + updateTitle("载入联系人"); + contactGroupedMap = stream.takeWhile(v -> !isCancelled()).parallel() + .collect(Collectors.groupingBy(m -> m.get("GID"))); + logger.debug("import Customer contacts = {} from old version app", contactGroupedMap.size()); + updateProgress(0.2, 1); + } + } + + private void syncCustomers() { + try (Stream> stream = service.queryAllCustomerForStream()) { + updateTitle("客户信息"); + long size = service.countOfCustomer(); + AtomicInteger counter = new AtomicInteger(0); + stream.takeWhile(v -> !isCancelled()).forEach(map -> { + if (isCancelled()) { + updateMessage("Cancelled"); + return; + } + Company updated = service.syncCustomer(map, basePath, oldNameGroupedMap, contactGroupedMap); + if (updated != null) { + updateMessage(counter.get() + " / " + size + ">" + updated.getName()); + } else { + updateMessage(counter.get() + " / " + size + ">" + map.get("NAME")); + } + updateProgress(counter.incrementAndGet(), size); + }); + } + } + + private void syncContracts() { + try (Stream> stream = service.queryAllCustomerContractForStream()) { + updateTitle("合同信息"); + long size = service.countOfCustomerContract(); + AtomicInteger counter = new AtomicInteger(0); + stream.takeWhile(v -> !isCancelled()).forEach(map -> { + if (isCancelled()) { + updateMessage("Cancelled"); + return; + } + Contract updated = service.syncCustomerContract(map); + if (updated != null) { + updateMessage(counter.get() + " / " + size + ">" + updated.getName()); + } else { + updateMessage(counter.get() + " / " + size + ">" + map.get("NO")); + } + updateProgress(counter.incrementAndGet(), size); + }); + } + } + +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/old/OldVersionSyncVendorTask.java b/src/main/java/com/ecep/contract/manager/cloud/old/OldVersionSyncVendorTask.java new file mode 100644 index 0000000..14346f4 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/old/OldVersionSyncVendorTask.java @@ -0,0 +1,119 @@ +package com.ecep.contract.manager.cloud.old; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.contract.model.Contract; +import javafx.concurrent.Task; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class OldVersionSyncVendorTask extends Task { + private static final Logger logger = LoggerFactory.getLogger(OldVersionSyncVendorTask.class); + + private final OldVersionService service = SpringApp.getBean(OldVersionService.class); + private final CompanyService companyService = SpringApp.getBean(CompanyService.class); + private Map>> oldNameGroupedMap; + private Map>> contactGroupedMap; + private File basePath; + private String titlePrefix = ""; + + public OldVersionSyncVendorTask() { + updateTitle("老版本-同步供应商数据"); + } + + @Override + protected void updateTitle(String title) { + super.updateTitle(titlePrefix + title); + } + + + @Override + protected Object call() throws Exception { + basePath = companyService.getVendorBasePath(); + + List runnable = Arrays.asList(this::loadOldNames, this::loadContacts, this::syncVendors, this::syncContracts); + for (int i = 0; i < runnable.size(); i++) { + titlePrefix = "老版本-同步供应商数据-" + (i + 1) + "/" + runnable.size() + "-"; + try { + runnable.get(i).run(); + } catch (Exception e) { + logger.error("运行至 {} 时,发生错误中断", titlePrefix, e); + break; + } + } + return null; + } + + private void loadOldNames() { + try (Stream> stream = service.queryAllVendorOldNameForStream()) { + updateTitle("载入曾用名"); + oldNameGroupedMap = stream.takeWhile(v -> !isCancelled()).parallel() + .collect(Collectors.groupingBy(m -> m.get("GID"))); + if (logger.isDebugEnabled()) { + logger.debug("import vendor oldNames = {} from old version app", oldNameGroupedMap.size()); + } + updateProgress(0.1, 1); + } + } + + private void loadContacts() { + try (Stream> stream = service.queryAllVendorContactForStream()) { + updateTitle("载入联系人"); + contactGroupedMap = stream.takeWhile(v -> !isCancelled()).parallel() + .collect(Collectors.groupingBy(m -> m.get("GID"))); + logger.debug("import vendor contacts = {} from old version app", contactGroupedMap.size()); + updateProgress(0.2, 1); + } + } + + private void syncVendors() { + try (Stream> stream = service.queryAllVendorForStream()) { + updateTitle("客户信息"); + long size = service.countOfVendor(); + AtomicInteger counter = new AtomicInteger(0); + stream.takeWhile(v -> !isCancelled()).forEach(map -> { + if (isCancelled()) { + updateMessage("Cancelled"); + return; + } + Company updated = service.syncVendor(map, basePath, oldNameGroupedMap, contactGroupedMap); + if (updated != null) { + updateMessage(counter.get() + " / " + size + ">" + updated.getName()); + } else { + updateMessage(counter.get() + " / " + size + ">" + map.get("NAME")); + } + updateProgress(counter.incrementAndGet(), size); + }); + } + } + + private void syncContracts() { + try (Stream> stream = service.queryAllVendorContractForStream()) { + updateTitle("合同信息"); + long size = service.countOfVendorContract(); + AtomicInteger counter = new AtomicInteger(0); + stream.takeWhile(v -> !isCancelled()).forEach(map -> { + if (isCancelled()) { + updateMessage("Cancelled"); + return; + } + Contract updated = service.syncVendorContract(map); + if (updated != null) { + updateMessage(counter.get() + " / " + size + ">" + updated.getName()); + } else { + updateMessage(counter.get() + " / " + size + ">" + map.get("NO")); + } + updateProgress(counter.incrementAndGet(), size); + }); + } + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/rk/BlackListUpdateContext.java b/src/main/java/com/ecep/contract/manager/cloud/rk/BlackListUpdateContext.java new file mode 100644 index 0000000..feb8d12 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/rk/BlackListUpdateContext.java @@ -0,0 +1,42 @@ +package com.ecep.contract.manager.cloud.rk; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Getter; +import lombok.Setter; + +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.URI; + +public class BlackListUpdateContext { + + + @Setter + @Getter + private ObjectMapper objectMapper; + private Proxy.Type proxyType; + @Getter + private Proxy socksProxy; + @Setter + @Getter + private String url; + @Getter + private long elapse = 1440; + + + public void setProxy(String proxy) { + if (proxy != null) { + URI proxyUri = URI.create(proxy); + proxyType = Proxy.Type.valueOf(proxyUri.getScheme().toUpperCase()); + socksProxy = new Proxy( + proxyType, + new InetSocketAddress(proxyUri.getHost(), proxyUri.getPort()) + ); + } + } + + public void setElapse(String value) { + elapse = Long.parseLong(value); + } + +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRk.java b/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRk.java new file mode 100644 index 0000000..f9cab9b --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRk.java @@ -0,0 +1,84 @@ +package com.ecep.contract.manager.cloud.rk; + +import com.ecep.contract.manager.cloud.CloudBaseInfo; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.ColumnDefault; + +import java.time.Instant; + +@Getter +@Setter +@Entity +@Table(name = "CLOUD_RK", schema = "supplier_ms") +public class CloudRk extends CloudBaseInfo { + /** + * 是否自动更新 + */ + @Column(name = "AUTO_UPDATE", nullable = false, columnDefinition = "boolean default true") + private boolean autoUpdate = false; + + /** + * 更新间隔天数 + */ + @Column(name = "UPDATE_DAYS", nullable = false, columnDefinition = "int default 30") + private int updateDays; + + /** + * 客户信用评级 + */ + @ColumnDefault("''") + @Column(name = "CUSTOMER_GRADE", nullable = false) + private String customerGrade; + /** + * 客户信用总分 + */ + @ColumnDefault("0") + @Column(name = "CUSTOMER_SCORE", nullable = false) + private Integer customerScore; + + @Column(name = "CUSTOMER_DESCRIPTION") + private String customerDescription; + + /** + * 供应商信用评级 + */ + @ColumnDefault("''") + @Column(name = "VENDOR_GRADE", nullable = false) + private String vendorGrade; + /** + * 供应商信用总分 + */ + @ColumnDefault("0") + @Column(name = "VENDOR_SCORE", nullable = false) + private Integer vendorScore; + + @Column(name = "VENDOR_DESCRIPTION") + private String vendorDescription; + + /** + * 相关方资信评价等级 + */ + @ColumnDefault("''") + @Column(name = "CREDIT_RANK", nullable = false) + private String rank; + + @Column(name = "CREDIT_RANK_DESCRIPTION") + private String rankDescription; + + + @Column(name = "CLOUD_LATEST") + private Instant cloudLatest; + + @Column(name = "BLACK_LIST_UPDATED") + private Instant cloudBlackListUpdated; + + @Column(name = "ENT_UPDATED") + private Instant cloudEntUpdate; + + @Column(name = "DESCRIPTION") + private String description; +} \ No newline at end of file diff --git a/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRkDataRepairTask.java b/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRkDataRepairTask.java new file mode 100644 index 0000000..11d6a5a --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRkDataRepairTask.java @@ -0,0 +1,55 @@ +package com.ecep.contract.manager.cloud.rk; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ui.MessageHolder; +import com.ecep.contract.manager.ui.Tasker; +import lombok.Setter; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; + +/** + * 数据修复任务 + */ +public class CloudRkDataRepairTask extends Tasker { + @Setter + private int batchSize = 50; + + @Override + protected Object execute(MessageHolder holder) { + CompanyService companyService = getBean(CompanyService.class); + CloudRkService cloudRkService = getBean(CloudRkService.class); + + Pageable pageRequest = PageRequest.ofSize(batchSize); + while (!isCancelled()) { + Page page = companyService.findAll(null, pageRequest); + int index = page.getNumber() * page.getSize(); + + int i = 1; + for (Company company : page) { + if (isCancelled()) { + break; + } + + String prefix = (index + i) + "/" + page.getTotalElements() + ", " + company.getName() + "> "; + + cloudRkService.getOrCreateCloudRk(company); + holder.info(prefix + " 完成"); + + updateProgress(index + i, page.getTotalElements()); + i++; + } + if (!page.hasNext()) { + break; + } + pageRequest = page.nextPageable(); + + } + if (isCancelled()) { + updateMessage("Cancelled"); + } + + return null; + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRkInfoViewModel.java b/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRkInfoViewModel.java new file mode 100644 index 0000000..779f94d --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRkInfoViewModel.java @@ -0,0 +1,172 @@ +package com.ecep.contract.manager.cloud.rk; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Objects; + +import com.ecep.contract.manager.cloud.CloudInfoViewModel; +import com.ecep.contract.manager.ui.util.MyDateTimePropertyUtils; + +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class CloudRkInfoViewModel extends CloudInfoViewModel { + private SimpleBooleanProperty autoUpdate = new SimpleBooleanProperty(); + + private SimpleStringProperty rank = new SimpleStringProperty(); + private SimpleStringProperty rankDescription = new SimpleStringProperty(); + + + private SimpleStringProperty customerGrade = new SimpleStringProperty(); + private SimpleIntegerProperty customerScore = new SimpleIntegerProperty(); + private SimpleStringProperty customerDescription = new SimpleStringProperty(); + + private SimpleStringProperty vendorGrade = new SimpleStringProperty(); + private SimpleIntegerProperty vendorScore = new SimpleIntegerProperty(); + private SimpleStringProperty vendorDescription = new SimpleStringProperty(); + + private SimpleObjectProperty cloudLatest = new SimpleObjectProperty<>(); + private SimpleObjectProperty cloudBlackListUpdated = new SimpleObjectProperty<>(); + private SimpleObjectProperty cloudEntUpdate = new SimpleObjectProperty<>(); + + private SimpleStringProperty description = new SimpleStringProperty(); + + @Override + protected void updateFrom(CloudRk info) { + super.updateFrom(info); + update_((CloudRk) info); + } + + private void update_(CloudRk info) { + autoUpdate.set(info.isAutoUpdate()); + rank.set(info.getRank()); + rankDescription.set(info.getRankDescription()); + + customerGrade.set(info.getCustomerGrade()); + customerScore.set(info.getCustomerScore()); + customerDescription.set(info.getCustomerDescription()); + + vendorGrade.set(info.getVendorGrade()); + vendorScore.set(info.getVendorScore()); + vendorDescription.set(info.getVendorDescription()); + + description.set(info.getDescription()); + + if (info.getCloudLatest() != null) { + ZoneId zone = ZoneId.systemDefault(); + ZonedDateTime zonedDateTime = info.getCloudLatest().atZone(zone); + LocalDateTime localDateTime = zonedDateTime.toLocalDateTime(); + cloudLatest.set(localDateTime); + } else { + cloudLatest.set(null); + } + + if (info.getCloudBlackListUpdated() != null) { + ZoneId zone = ZoneId.systemDefault(); + ZonedDateTime zonedDateTime = info.getCloudBlackListUpdated().atZone(zone); + LocalDateTime localDateTime = zonedDateTime.toLocalDateTime(); + cloudBlackListUpdated.set(localDateTime); + } else { + cloudBlackListUpdated.set(null); + } + + if (info.getCloudEntUpdate() != null) { + ZoneId zone = ZoneId.systemDefault(); + ZonedDateTime zonedDateTime = info.getCloudEntUpdate().atZone(zone); + LocalDateTime localDateTime = zonedDateTime.toLocalDateTime(); + cloudEntUpdate.set(localDateTime); + } else { + cloudEntUpdate.set(null); + } + } + + @Override + public boolean copyTo(CloudRk info) { + boolean modified = super.copyTo(info); + if (copyTo_((CloudRk) info)) { + modified = true; + } + return modified; + } + + private boolean copyTo_(CloudRk info) { + boolean modified = false; + if (info.isAutoUpdate() != autoUpdate.get()) { + info.setAutoUpdate(autoUpdate.get()); + modified = true; + } + + if (!Objects.equals(info.getRank(), rank.get())) { + info.setRank(rank.get()); + modified = true; + } + if (!Objects.equals(info.getRankDescription(), rankDescription.get())) { + info.setRankDescription(rankDescription.get()); + modified = true; + } + if (!Objects.equals(info.getCustomerGrade(), customerGrade.get())) { + info.setCustomerGrade(customerGrade.get()); + modified = true; + } + if (!Objects.equals(info.getCustomerScore(), customerScore.get())) { + info.setCustomerScore(customerScore.get()); + modified = true; + } + if (!Objects.equals(info.getCustomerDescription(), customerDescription.get())) { + info.setCustomerDescription(customerDescription.get()); + modified = true; + } + if (!Objects.equals(info.getVendorGrade(), vendorGrade.get())) { + info.setVendorGrade(vendorGrade.get()); + modified = true; + } + if (!Objects.equals(info.getVendorScore(), vendorScore.get())) { + info.setVendorScore(vendorScore.get()); + modified = true; + } + if (!Objects.equals(info.getVendorDescription(), vendorDescription.get())) { + info.setVendorDescription(vendorDescription.get()); + modified = true; + } + + Instant latest = MyDateTimePropertyUtils.localDateTimeToInstant(cloudLatest); + if (!Objects.equals(info.getCloudLatest(), latest)) { + info.setCloudLatest(latest); + modified = true; + } + + Instant blackListUpdated = MyDateTimePropertyUtils.localDateTimeToInstant(cloudBlackListUpdated); + if (!Objects.equals(info.getCloudBlackListUpdated(), blackListUpdated)) { + info.setCloudBlackListUpdated(blackListUpdated); + modified = true; + } + + if (!Objects.equals(info.getDescription(), description.get())) { + info.setDescription(description.get()); + modified = true; + } + + Instant entUpdated = MyDateTimePropertyUtils.localDateTimeToInstant(cloudEntUpdate); + if (!Objects.equals(info.getCloudEntUpdate(), entUpdated)) { + info.setCloudEntUpdate(entUpdated); + modified = true; + } + return modified; + } + + public static CloudRkInfoViewModel from(CloudRk info) { + CloudRkInfoViewModel viewModel = new CloudRkInfoViewModel(); + viewModel.updateFrom(info); + return viewModel; + } + + +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRkManagerSkin.java b/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRkManagerSkin.java new file mode 100644 index 0000000..8b3c304 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRkManagerSkin.java @@ -0,0 +1,111 @@ +package com.ecep.contract.manager.cloud.rk; + +import org.hibernate.Hibernate; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.company.controller.CompanyTableCell; +import com.ecep.contract.manager.ds.company.controller.CompanyWindowController; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ui.AbstEntityManagerSkin; +import com.ecep.contract.manager.ui.util.LocalDateTimeTableCell; + +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TableColumn; +import javafx.scene.control.cell.CheckBoxTableCell; +import lombok.Setter; + +public class CloudRkManagerSkin + extends AbstEntityManagerSkin { + @Setter + private CompanyService companyService; + + public CloudRkManagerSkin(CloudRkManagerWindowController controller) { + super(controller); + } + + CloudRkService getCloudRkService() { + return controller.getViewModelService(); + } + + CompanyService getCompanyService() { + if (companyService == null) { + companyService = SpringApp.getBean(CompanyService.class); + } + return companyService; + } + + @Override + public void initializeTable() { + controller.idColumn.setCellValueFactory(param -> param.getValue().getId()); + + controller.companyColumn.setCellValueFactory(param -> param.getValue().getCompany()); + controller.companyColumn.setCellFactory(param -> new CompanyTableCell<>(getCompanyService())); + + controller.cloudIdColumn.setCellValueFactory(param -> param.getValue().getCloudId()); + + controller.latestUpdateColumn.setCellValueFactory(param -> param.getValue().getLatest()); + controller.latestUpdateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); + + controller.cloudLatestColumn.setCellValueFactory(param -> param.getValue().getCloudLatest()); + controller.cloudLatestColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); + + controller.cloudBlackListUpdatedColumn.setCellValueFactory(param -> param.getValue().getCloudBlackListUpdated()); + controller.cloudBlackListUpdatedColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); + + controller.cloudEntUpdateColumn.setCellValueFactory(param -> param.getValue().getCloudEntUpdate()); + controller.cloudEntUpdateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); + + controller.autoUpdateColumn.setCellValueFactory(param -> param.getValue().getAutoUpdate()); + controller.autoUpdateColumn.setCellFactory(CheckBoxTableCell.forTableColumn(controller.autoUpdateColumn)); + + controller.descriptionColumn.setCellValueFactory(param -> param.getValue().getDescription()); + } + + private void onAutoUpdateColumnEditCommit(TableColumn.CellEditEvent event) { + CloudRkInfoViewModel row = event.getRowValue(); + row.getAutoUpdate().set(event.getNewValue()); + saveRowData(row); + } + + + @Override + protected void createContextMenu(ContextMenu contextMenu) { + MenuItem item2 = new MenuItem("刷新"); + item2.setOnAction(this::onTableRefreshAction); + + MenuItem item3 = new MenuItem("清空备注"); + item3.setOnAction(this::onTableClearDescriptionAction); + + contextMenu.getItems().addAll(item2, item3); + } + + /** + * 请空选择行的注释 + * + * @param event event + */ + public void onTableClearDescriptionAction(ActionEvent event) { + ObservableList selectedItems = getTableView().getSelectionModel().getSelectedItems(); + if (selectedItems.isEmpty()) { + return; + } + CloudRkService service = getCloudRkService(); + for (CloudRkInfoViewModel selectedItem : selectedItems) { + selectedItem.getDescription().set(""); + selectedItem.saveInFxApplicationThread(service); + } + } + + @Override + protected void onTableRowDoubleClickedAction(CloudRkInfoViewModel item) { + Company company = item.getCompany().get(); + if (!Hibernate.isInitialized(item)) { + company = getCompanyService().findById(company.getId()); + } + CompanyWindowController.show(company, getTableView().getScene().getWindow()); + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRkManagerWindowController.java b/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRkManagerWindowController.java new file mode 100644 index 0000000..75728af --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRkManagerWindowController.java @@ -0,0 +1,160 @@ +package com.ecep.contract.manager.cloud.rk; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.cloud.CloudBaseInfo; +import com.ecep.contract.manager.cloud.CloudInfo; +import com.ecep.contract.manager.cloud.tyc.CloudTyc; +import com.ecep.contract.manager.cloud.tyc.CloudTycService; +import com.ecep.contract.manager.cloud.u8.CloudYu; +import com.ecep.contract.manager.cloud.u8.YongYouU8Service; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ui.AbstManagerWindowController; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.Message; +import com.ecep.contract.manager.ui.ViewModelService; +import com.ecep.contract.manager.util.UITools; +import javafx.concurrent.Task; +import javafx.event.ActionEvent; +import javafx.scene.control.TableColumn; +import javafx.stage.WindowEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Consumer; + +@Lazy +@Scope("prototype") +@Component +@FxmlPath("/ui/cloud/rk_manager.fxml") +public class CloudRkManagerWindowController + extends AbstManagerWindowController { + private static final Logger logger = LoggerFactory.getLogger(CloudRkManagerWindowController.class); + + public static void show() { + show(CloudRkManagerWindowController.class, null); + } + + @Autowired + private CloudRkService cloudRkService; + + public TableColumn idColumn; + public TableColumn latestUpdateColumn; + + public TableColumn companyColumn; + public TableColumn cloudIdColumn; + public TableColumn cloudLatestColumn; + public TableColumn cloudBlackListUpdatedColumn; + public TableColumn cloudEntUpdateColumn; + public TableColumn autoUpdateColumn; + public TableColumn descriptionColumn; + + + @Override + protected CloudRkManagerSkin createDefaultSkin() { + return new CloudRkManagerSkin(this); + } + + @Override + public void onShown(WindowEvent windowEvent) { + super.onShown(windowEvent); + getTitle().set("数据源:集团相关方"); + } + + boolean copyTo(CloudInfo v, CloudBaseInfo cloudRk) { + boolean modified = false; + if (!Objects.equals(cloudRk.getLatestUpdate(), v.getLatestUpdate())) { + cloudRk.setLatestUpdate(v.getLatestUpdate()); + modified = true; + } + + if (!Objects.equals(cloudRk.getCloudId(), v.getCloudId())) { + cloudRk.setCloudId(v.getCloudId()); + modified = true; + } + + if (!Objects.equals(cloudRk.getCompany(), v.getCompany())) { + cloudRk.setCompany(v.getCompany()); + modified = true; + } + return modified; + } + + /** + * 数据迁移,从 CloudInfo 迁移到 CloudRk + */ + public void onDateTransferAction(ActionEvent event) { + + CompletableFuture.runAsync(() -> { + com.ecep.contract.manager.cloud.CloudInfoRepository cloudInfoRepository = SpringApp.getBean(com.ecep.contract.manager.cloud.CloudInfoRepository.class); + CloudTycService tycService = SpringApp.getBean(CloudTycService.class); + YongYouU8Service u8Service = SpringApp.getBean(YongYouU8Service.class); + + cloudInfoRepository.findAll().forEach(v -> { + try { + CloudRk cloudRk = cloudRkService.getOrCreateCloudRk(v); + if (copyTo(v, cloudRk)) { + cloudRkService.save(cloudRk); + } + + CloudTyc cloudTyc = tycService.getOrCreateCloudTyc(v); + if (copyTo(v, cloudTyc)) { + tycService.save(cloudTyc); + } + + CloudYu cloudYu = u8Service.getOrCreateCloudYu(v); + if (copyTo(v, cloudYu)) { + u8Service.save(cloudYu); + } + } catch (Exception e) { + e.printStackTrace(); + } + }); + + }); + } + + + private void initializeTask(Task task, String prefix, Consumer 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) { + CloudRkDataRepairTask task = new CloudRkDataRepairTask(); + task.titleProperty().get(); + UITools.showTaskDialogAndWait("修复数据", task, null); + } + + public void onSyncAction(ActionEvent event) { + CloudRkSyncTask task = new CloudRkSyncTask(); + UITools.showTaskDialogAndWait("同步数据", task, consumer -> { + initializeTask(task, "同步数据", msg -> consumer.accept(Message.info(msg))); + }); + } + + @Override + public CloudRkService getViewModelService() { + return cloudRkService; + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRkRepository.java b/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRkRepository.java new file mode 100644 index 0000000..9ab6ce8 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRkRepository.java @@ -0,0 +1,57 @@ +package com.ecep.contract.manager.cloud.rk; + +import com.ecep.contract.manager.ds.company.model.Company; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +@Repository +public interface CloudRkRepository + // curd + extends CrudRepository, PagingAndSortingRepository, + // JPA interfaces + JpaRepository, JpaSpecificationExecutor { + + Stream findByCloudLatestAfter(Instant timestamp); + + Stream findByCloudEntUpdateAfter(Instant timestamp); + + /** + * 按公司查找 Cloud RK + * + * @param company 公司对象 + * @return Cloud RK + */ + Optional findByCompany(Company company); + + /** + * 按公司查找 Cloud RK + * + * @param companyId 公司对象编号 + * @return Cloud RK + */ + Optional findByCompanyId(int companyId); + + List findAllByCompanyId(int companyId); + + long countByLatestUpdateBefore(Instant instant); + + long countByAutoUpdateIsTrueAndLatestUpdateBefore(Instant instant); + + List findTop100ByLatestUpdateBeforeOrderByLatestUpdateDesc(Instant instant); + + List findTop100ByAutoUpdateIsTrueAndLatestUpdateBeforeOrderByLatestUpdateDesc(Instant instant); + + @Modifying + @Transactional + int deleteAllByCompany(Company company); +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRkService.java b/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRkService.java new file mode 100644 index 0000000..4f76cda --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRkService.java @@ -0,0 +1,417 @@ +package com.ecep.contract.manager.cloud.rk; + + +import com.ecep.contract.manager.cloud.CloudInfo; +import com.ecep.contract.manager.ds.company.BlackReasonType; +import com.ecep.contract.manager.ds.company.CompanyFileUtils; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyBlackReason; +import com.ecep.contract.manager.ds.company.repository.CompanyBlackReasonRepository; +import com.ecep.contract.manager.ds.company.repository.CompanyOldNameRepository; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.other.service.SysConfService; +import com.ecep.contract.manager.ui.ViewModelService; +import com.ecep.contract.manager.util.HttpJsonUtils; +import com.ecep.contract.manager.util.MyStringUtils; +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import jakarta.persistence.criteria.Path; +import javafx.application.Platform; +import javafx.concurrent.Task; +import lombok.Data; +import org.controlsfx.control.TaskProgressView; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.Caching; +import org.springframework.context.annotation.Lazy; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +@Lazy +@Service +@CacheConfig(cacheNames = "cloud-rk") +public class CloudRkService implements ViewModelService { + private static final Logger logger = LoggerFactory.getLogger(CloudRkService.class); + + public static final String NAME = "集团相关方平台"; + public static final String VENDOR_NAME = "元素征信"; + public final static String ENTERPRISE_CREDIT_REPORT = "企业征信报告"; + + public static final String KEY_PROXY = "cloud.rk.proxy"; + public static final String KEY_SYNC_ELAPSE = "cloud.rk.sync.elapse"; + public static final long DEFAULT_SYNC_ELAPSE = 36000L; + public static final String KEY_VENDOR_REPORT_URL = "cloud.rk.vendor.report.url"; + public static final String KEY_CUSTOMER_REPORT_URL = "cloud.rk.customer.report.url"; + public static final String KEY_ENT_SCORE_URL = "cloud.rk.ent_score.url"; + public static final String KEY_ENT_REPORT_URL = "cloud.rk.ent_report.url"; + public static final String KEY_ENT_FUZZY_URL = "cloud.rk.ent_fuzzy.url"; + public static final String KEY_BLACK_LIST_URL = "cloud.rk.black_list.url"; + public static final String KEY_BLACK_LIST_ELAPSE = "cloud.rk.black_list.elapse"; + @Autowired + private ObjectMapper objectMapper; + + /** + * 同步超时, 单位 毫秒 + * + * @return 毫秒 + */ + public long getSyncElapse() { + String string = confService.getString(KEY_SYNC_ELAPSE); + if (!StringUtils.hasText(string)) { + return DEFAULT_SYNC_ELAPSE; + } + return MyStringUtils.toLong(string, DEFAULT_SYNC_ELAPSE); + } + + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class EntInfo { + @JsonAlias("entid") + private String id; + @JsonAlias("entname") + private String name; + private boolean nowName; + } + + @Autowired + private SysConfService confService; + @Autowired + private CloudRkRepository cloudRKRepository; + @Lazy + @Autowired + private CompanyService companyService; + @Autowired + private CompanyOldNameRepository companyOldNameRepository; + @Autowired + private CompanyBlackReasonRepository companyBlackReasonRepository; + @Autowired + private ScheduledExecutorService scheduledExecutorService; + + @Cacheable(key = "#p0") + public CloudRk findById(Integer id) { + return cloudRKRepository.findById(id).orElse(null); + } + + public Page findAll(Specification spec, Pageable pageable) { + return cloudRKRepository.findAll(spec, pageable); + } + + @Override + public Specification getSpecification(String searchText) { + if (!StringUtils.hasText(searchText)) { + return null; + } + return (root, query, builder) -> { + Path company = root.get("company"); + return builder.or( + builder.like(company.get("name"), "%" + searchText + "%"), + builder.like(company.get("shortName"), "%" + searchText + "%"), + builder.like(root.get("cloudId"), "%" + searchText + "%"), + builder.like(root.get("description"), "%" + searchText + "%") + ); + }; + } + + /** + * 更新黑名单列表 + */ + public void updateBlackList( + Company company, CloudRk cloudRk, BlackListUpdateContext context + ) throws IOException { + List companyNames = new ArrayList<>(); + companyNames.add(company.getName()); + // fixed 平台API使用企业名称,可能记录的是曾用名 + companyOldNameRepository.findAllByCompanyId(company.getId()).forEach(oldName -> { + // 歧义的曾用名不采用 + if (oldName.getAmbiguity()) { + return; + } + companyNames.add(oldName.getName()); + }); + + List reasonList = new ArrayList<>(); + List dbReasons = companyBlackReasonRepository.findAllByCompany(company); + + companyNames.forEach(name -> { + String url = context.getUrl() + URLEncoder.encode(name, StandardCharsets.UTF_8); + try { + HttpJsonUtils.get(url, json -> { + if (!json.has("success") || !json.get("success").asBoolean()) { + System.out.println("json = " + json.toPrettyString()); + return; + } + + if (json.has("data")) { + JsonNode data = json.get("data"); + try { + if (data.has("blackReason")) { + for (JsonNode reason : data.get("blackReason")) { + toCompanyBlackReasonList(company, BlackReasonType.BLACK, reason, dbReasons, reasonList, context.getObjectMapper()); + } + } + if (data.has("greyReason")) { + for (JsonNode reason : data.get("greyReason")) { + toCompanyBlackReasonList(company, BlackReasonType.GRAY, reason, dbReasons, reasonList, context.getObjectMapper()); + } + } + } catch (Exception ex) { + logger.error("{} {},json = {}", company.getName(), ex.getMessage(), json, ex); + throw new RuntimeException(json.toString(), ex); + } + } + + // 保存JSON数据到公司目录 + String companyPath = company.getPath(); + if (StringUtils.hasText(companyPath)) { + File dir = new File(companyPath); + if (dir.exists()) { + File file = new File(dir, CompanyFileUtils.FILE_BLACK_LIST_JSON); + try { + objectMapper.writeValue(file, json); + } catch (IOException e) { + logger.warn("Unable Save BlackList to {}, company:{}", file, company.getName(), e); + } + } + } + }, context.getObjectMapper(), context.getSocksProxy()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + + if (!reasonList.isEmpty()) { + companyBlackReasonRepository.saveAll(reasonList); + } + cloudRk.setCloudBlackListUpdated(Instant.now()); + } + + private void toCompanyBlackReasonList( + Company company, BlackReasonType type, + JsonNode reason, List dbReasons, + List reasonList, ObjectMapper objectMapper + ) throws JsonMappingException { + ObjectNode object = (ObjectNode) reason; + String key = "rk-" + object.remove("id").asText(); + CompanyBlackReason cbr = dbReasons.stream().filter(r -> r.getKey().equals(key)).findAny().orElseGet(CompanyBlackReason::new); + objectMapper.updateValue(cbr, reason); + cbr.setCompany(company); + cbr.setType(type); + cbr.setKey(key); + reasonList.add(cbr); + } + + public CompletableFuture createBlackListUpdateContext() { + return CompletableFuture.supplyAsync(() -> { + BlackListUpdateContext context = new BlackListUpdateContext(); + context.setObjectMapper(objectMapper); + context.setProxy(confService.getString(KEY_PROXY)); + context.setUrl(confService.getString(KEY_BLACK_LIST_URL)); + // context.setElapse(confService.getLong(KEY_BLACK_LIST_ELAPSE)); + return context; + }); + } + + /** + * @return true + */ + public boolean checkBlackListUpdateElapse( + Company company, CloudRk cloudRk, BlackListUpdateContext context + ) { + Instant start = cloudRk.getCloudBlackListUpdated(); + if (start == null) { + return true; + } + Instant elapse = start.plusSeconds(context.getElapse()); + return elapse.isBefore(Instant.now()); + } + + public CloudRk getOrCreateCloudRk(CloudInfo info) { + Optional optional = cloudRKRepository.findById(info.getId()); + return optional.orElseGet(() -> getOrCreateCloudRk(info.getCompany())); + } + + /** + * 返回或者创建 Cloud RK + * + * @param company 公司对象 + * @return Cloud RK + */ + public CloudRk getOrCreateCloudRk(Company company) { + Integer companyId = company.getId(); + List list = cloudRKRepository.findAllByCompanyId(companyId); + if (list.isEmpty()) { + CloudRk rk = new CloudRk(); + rk.setCompany(company); + rk.setCustomerGrade(""); + rk.setCustomerScore(-1); + rk.setVendorGrade(""); + rk.setVendorScore(-1); + rk.setRank(""); + return cloudRKRepository.save(rk); + } + if (list.size() == 1) { + return list.getFirst(); + } + + // 查询有 CloudId 的记录 + List hasCouldIdList = list.stream().filter(v -> StringUtils.hasText(v.getCloudId())).collect(Collectors.toList()); + // 没有匹配到一条时 + if (hasCouldIdList.isEmpty()) { + // 保留第一条,其他删除 + CloudRk rk = list.removeFirst(); + cloudRKRepository.deleteAll(list); + return rk; + } + + // 只有匹配到一条有 CloudId 的记录 + if (hasCouldIdList.size() == 1) { + // 保留匹配的记录,其他删除 + CloudRk rk = hasCouldIdList.removeFirst(); + list.remove(rk); + cloudRKRepository.deleteAll(list); + return rk; + } + + // 查询有 CloudLatest 的记录 + List hasLatestList = hasCouldIdList.stream().filter(v -> v.getCloudLatest() != null).collect(Collectors.toList()); + // 没有匹配到一条时 + if (hasLatestList.isEmpty()) { + // 保留第一条,其他删除 + CloudRk rk = hasCouldIdList.removeFirst(); + list.remove(rk); + cloudRKRepository.deleteAll(list); + return rk; + } + + // 只有匹配到一条有 CloudId 的记录 + if (hasLatestList.size() == 1) { + // 保留匹配的记录,其他删除 + CloudRk rk = hasLatestList.removeFirst(); + list.remove(rk); + cloudRKRepository.deleteAll(list); + return rk; + } + + return hasLatestList.getFirst(); + } + + /** + * 保存 Cloud Rk + * + * @param cloudRk Cloud Rk 对象 + * @return 更新的 Cloud Rk + */ + @CacheEvict(key = "#p0.id") + public CloudRk save(CloudRk cloudRk) { + return cloudRKRepository.save(cloudRk); + } + + @CacheEvict(key = "#p0.id") + @Override + public void delete(CloudRk entity) { + cloudRKRepository.delete(entity); + } + + + /** + * 返回 在 {@link #getSyncElapse()} 毫秒前,更新的 + * + * @return 记录条数 + */ + public long countNeedUpdate() { + Instant now = Instant.now(); + long elapse = getSyncElapse(); + Instant instant = now.minusSeconds(elapse); + return cloudRKRepository.countByAutoUpdateIsTrueAndLatestUpdateBefore(instant); + } + + /** + * 返回距离上次更新超过 SysConfig:cloud.rk.black_list.elapse 秒的公司 + */ +// @Transactional + public List findNeedUpdate() { + Instant now = Instant.now(); + long elapse = getSyncElapse(); + Instant instant = now.minusSeconds(elapse); + return cloudRKRepository.findTop100ByAutoUpdateIsTrueAndLatestUpdateBeforeOrderByLatestUpdateDesc(instant); + } + + /** + * 生成定时同步任务 + * + * @param taskProgressView 任务视图 + */ + public void scheduledTasks(TaskProgressView> taskProgressView) { + AtomicReference reference = new AtomicReference<>(); + Runnable runnable = () -> { + CloudRkSyncTask task = new CloudRkSyncTask(); + Platform.runLater(() -> taskProgressView.getTasks().add(task)); + // 延迟10秒 + task.delay(10, CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS, scheduledExecutorService)) + .thenRun(() -> { + try { + task.run(); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + // 30 分钟后再次运行 + scheduledExecutorService.schedule(reference.get(), 30, TimeUnit.MINUTES); + } + ); + }; + reference.set(runnable); + // 第一次延时启动 + scheduledExecutorService.schedule(runnable, 1, TimeUnit.MINUTES); + } + + @CacheEvict + public void deleteByCompany(Company company) { + int deleted = cloudRKRepository.deleteAllByCompany(company); + if (deleted > 0) { + if (logger.isInfoEnabled()) { + logger.info("Delete {} records by company:#{}", deleted, company.getId()); + } + } + } + + // TODO 这个可以无法更新缓存 + @Caching( + evict = { + @CacheEvict(key = "#p0.id"), + @CacheEvict(key = "#p1.id"), + } + ) + public void resetTo(Company from, Company to) { + List list = cloudRKRepository.findAllByCompanyId(from.getId()); + for (CloudRk item : list) { + item.setCompany(to); + } + cloudRKRepository.saveAll(list); + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRkSyncTask.java b/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRkSyncTask.java new file mode 100644 index 0000000..f69d6f2 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/rk/CloudRkSyncTask.java @@ -0,0 +1,143 @@ +package com.ecep.contract.manager.cloud.rk; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.cloud.rk.ctx.CloudRkCtx; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ui.MessageHolder; +import com.ecep.contract.manager.ui.Tasker; +import org.hibernate.Hibernate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; + +import java.time.Instant; +import java.time.LocalDate; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +/** + * 集团相关方平台同步任务 + */ +public class CloudRkSyncTask extends Tasker { + private static final Logger logger = LoggerFactory.getLogger(CloudRkSyncTask.class); + + private ContractService contractService; + + @Override + protected Object execute(MessageHolder holder) throws Exception { + updateTitle("集团相关方平台"); + + CloudRkCtx cloudRkCtx = null; + CloudRkService service = null; + try { + cloudRkCtx = new CloudRkCtx(); + service = SpringApp.getBean(CloudRkService.class); + cloudRkCtx.setCloudRkService(service); + } catch (BeansException e) { + holder.error("没有找到 " + CloudRkService.NAME + " 服务"); + return null; + } + + long total = service.countNeedUpdate(); + if (total == 0) { + holder.info("没有需要更新"); + return null; + } + + AtomicInteger counter = new AtomicInteger(0); + holder.info("统计需要更新的 " + total + " 条"); + + try { + // 每次获取100条记录 + while (!isCancelled()) { + List needUpdate = service.findNeedUpdate(); + if (needUpdate.isEmpty()) { + holder.info("处理完成"); + break; + } + for (CloudRk cloudRk : needUpdate) { + if (isCancelled()) { + break; + } + MessageHolder subHolder = holder.sub(counter.get() + " / " + total + ">"); + // fixed lazy + Company company = cloudRk.getCompany(); + if (company == null) { + subHolder.error("数据不完整,没有关联公司"); + break; + } + if (!Hibernate.isInitialized(company)) { + company = getCompanyService().findById(company.getId()); + cloudRk.setCompany(company); + } + if (cloudRk.isAutoUpdate()) { + LocalDate date = LocalDate.now().minusYears(3); + long count = getContractService().findAllByCompany(company).stream() + .filter(c -> c.getSetupDate() != null) + .filter(c -> c.getSetupDate().isAfter(date)) + .count(); + if (count == 0) { + holder.info("公司:" + company.getName() + " 没有3年以上的合同, 取消自动更新"); + cloudRk.setAutoUpdate(false); + } + } + try { + cloudRk.setDescription(""); + if (cloudRkCtx.syncCompany(company, cloudRk, subHolder)) { + + } + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + cloudRk.setLatestUpdate(Instant.now()); + service.save(cloudRk); + } + updateProgress(counter.incrementAndGet(), total); + } + } + + if (isCancelled()) { + updateMessage("Cancelled"); + } + } catch (Exception e) { + logger.error("运行至 {}/{} 时,发生错误中断", counter.get(), total, e); + } + updateProgress(1, 1); + return null; + } + + + public CompletableFuture delay(int second, Executor executor) { + CompletableFuture future = new CompletableFuture<>(); + + long until = System.currentTimeMillis() + second * 1000L; + String title = getTitle(); + AtomicReference reference = new AtomicReference<>(); + Runnable runnable = () -> { + long let = until - System.currentTimeMillis(); + if (let < 0) { + System.out.println("complete @" + Thread.currentThread().getName()); + future.complete(null); + } else { + // 再次调度 + executor.execute(reference.get()); + updateTitle(title + " 延时 " + ((int) let / 1000) + "秒"); + } + }; + reference.set(runnable); + // 第一次调度 + executor.execute(runnable); + return future; + } + + ContractService getContractService() { + if (contractService == null) { + contractService = getBean(ContractService.class); + } + return contractService; + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/rk/EntReportParser.java b/src/main/java/com/ecep/contract/manager/cloud/rk/EntReportParser.java new file mode 100644 index 0000000..0caca69 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/rk/EntReportParser.java @@ -0,0 +1,271 @@ +package com.ecep.contract.manager.cloud.rk; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.company.CompanyFileUtils; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyContact; +import com.ecep.contract.manager.ds.company.model.CompanyOldName; +import com.ecep.contract.manager.ds.company.repository.CompanyContactRepository; +import com.ecep.contract.manager.ds.company.repository.CompanyOldNameRepository; +import com.ecep.contract.manager.ds.company.service.CompanyOldNameService; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Data; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanUtils; +import org.springframework.util.StringUtils; + +import java.beans.PropertyDescriptor; +import java.io.File; +import java.io.IOException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +@Data +public class EntReportParser { + private static final Logger logger = LoggerFactory.getLogger(EntReportParser.class); + private ObjectMapper objectMapper; + private Company company; + private CloudRk cloudRk; + private boolean modified = false; + + + public void parse(JsonNode json) { + if (!json.has("B1001")) { + // 没有数据 + throw new RuntimeException("B1001 can't be null, json:" + json); + } + JsonNode b1001 = json.get("B1001"); + if (!b1001.has("count") || b1001.get("count").asInt() < 1 || !b1001.has("data")) { + // 没有数据 + return; + } + JsonNode data = b1001.get("data"); + + updateCompanyProperty("entType", data.get("enttype")); + updateCompanyProperty("entStatus", data.get("entstatus")); + updateCompanyProperty("setupDate", data.get("esdate")); + + updateCompanyUniscid(data); + updateCompanyNameHistory(data); + + updateCompanyProperty("regAddr", data.get("dom")); + updateCompanyProperty("registeredCapital", data.get("regcap")); + updateCompanyProperty("registeredCapitalCurrency", data.get("regcapcur")); + updateCompanyProperty("legalRepresentative", data.get("frname")); + updateCompanyProperty("district", data.get("regorgprovince")); + updateCompanyProperty("telephone", data.get("tel")); + updateCompanyProperty("address", data.get("oploc")); + updateCompanyProperty("operationPeriodBegin", data.get("opfrom")); + updateCompanyProperty("operationPeriodEnd", data.get("opto"), "-"); + updateCompanyProperty("industry", data.get("nicfulltitle")); + + // + updateCloudRkEntUpdateDate(data); + + // 更新法人联系人 + updateLegalRepresentativeContact(data); + + // 保存JSON数据到公司目录 + saveJsonToFile(json); + + } + + + private void updateLegalRepresentativeContact(JsonNode data) { + String legalRepresentative = company.getLegalRepresentative(); + if (!StringUtils.hasText(legalRepresentative)) { + return; + } + CompanyContact contact = null; + boolean modified = false; + CompanyContactRepository contactRepository = SpringApp.getBean(CompanyContactRepository.class); + List contactList = contactRepository.findAllByCompanyAndName(company, legalRepresentative); + if (contactList == null) { + // db error + return; + } + if (contactList.isEmpty()) { + //没有,创建法人联系人 + contact = new CompanyContact(); + contact.setCompany(company); + contact.setName(legalRepresentative); + contact.setPosition("法定代表人"); + contact.setCreated(LocalDate.now()); + modified = true; + } else { + Optional any = contactList.stream().filter(c -> "法定代表人".equals(c.getPosition())).findAny(); + if (any.isEmpty()) { + any = contactList.stream().findAny(); + } + contact = any.get(); +// if (contact.getPostion() == null || !contact.setPostion().contains("法定代表人")) { +// contact.setMemo("法定代表人"); +// modified = true; +// } + } + + if (!StringUtils.hasText(contact.getEmail())) { + String text = data.get("email").asText(); + contact.setEmail(text); + modified = true; + } + + if (!StringUtils.hasText(contact.getAddress())) { + String text = company.getAddress(); + contact.setAddress(text); + modified = true; + } + + if (!StringUtils.hasText(contact.getPhone())) { + String text = company.getTelephone(); + contact.setPhone(text); + modified = true; + } + + if (!StringUtils.hasText(contact.getPosition())) { + contact.setPosition("法定代表人"); + modified = true; + } + + if (modified) { + contactRepository.save(contact); + } + + } + + private void updateCompanyNameHistory(JsonNode data) { + JsonNode node = data.get("nameHistory"); + if (node == null) { + return; + } + // 历史曾用名 + String nameHistory = node.asText(); + if (!StringUtils.hasText(nameHistory)) { + return; + } + List historyNames = new ArrayList<>(); + for (String str : nameHistory.split(",")) { + String trimmed = str.trim(); + if (StringUtils.hasText(trimmed)) { + historyNames.add(trimmed); + } + } + CompanyOldNameService service = SpringApp.getBean(CompanyOldNameService.class); + List oldNames = service.findAllByCompany(company); + for (CompanyOldName oldName : oldNames) { + historyNames.remove(oldName.getName()); + } + for (String historyName : historyNames) { + CompanyOldName oldName = new CompanyOldName(); + oldName.setName(historyName); + oldName.setCompanyId(company.getId()); + oldName.setMemo("从相关方平台导入"); + oldName.setAmbiguity(false); + service.save(oldName); + } + } + + private void updateCompanyUniscid(JsonNode data) { + JsonNode node = data.get("uniscid"); + if (node == null) { + return; + } + + String uid = node.asText(); + if (StringUtils.hasText(uid)) { + if (!uid.equals(company.getUniscid())) { + if (logger.isInfoEnabled()) { + logger.info("更新 {} 的 UNISCID {} -> {}", company.getName(), company.getUniscid(), uid); + } + company.setUniscid(uid); + modified = true; + } + } else { + //fixed 当平台返回的 社会统一信用代码为空时,如果原来已经有的,则不做更新 + if (StringUtils.hasText(company.getUniscid())) { + if (logger.isInfoEnabled()) { + logger.info("来自平台的 UNISCID 为空,但本地{}已经记录{},不做更改", company.getName(), company.getUniscid()); + } + } else { + company.setUniscid(""); + modified = true; + } + } + // + } + + /** + * 更新 平台的数据更新时间戳 + */ + private void updateCloudRkEntUpdateDate(JsonNode data) { + JsonNode node = data.get("updated"); + if (node == null) { + return; + } + LocalDateTime updated = objectMapper.convertValue(node, LocalDateTime.class); + cloudRk.setCloudEntUpdate(updated.toInstant(ZoneOffset.ofHours(8))); + } + + private void updateCompanyProperty(String field, JsonNode node, String... excludeValues) { + if (node == null || node.isNull()) { + return; + } + String text = node.asText(); + for (String excludeValue : excludeValues) { + if (Objects.equals(text, excludeValue)) { + return; + } + } + updateCompanyProperty(field, node); + } + + /** + * 更新属性通用方法, 数据的类型转换由 objectMapper 提供 + * + * @param field 类属性名称 + * @param node 数据来源 json node + */ + private void updateCompanyProperty(String field, JsonNode node) { + if (node == null || node.isNull()) { + return; + } + PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(Company.class, field); + if (descriptor == null) { + logger.error("Company 的字段 {} 不存在,请确认.", field); + return; + } + try { + Object oldValue = descriptor.getReadMethod().invoke(company); + Object newValue = objectMapper.convertValue(node, descriptor.getReadMethod().getReturnType()); + if (!Objects.equals(oldValue, newValue)) { + descriptor.getWriteMethod().invoke(company, newValue); + modified = true; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + + private void saveJsonToFile(JsonNode json) { + String companyPath = company.getPath(); + if (StringUtils.hasText(companyPath)) { + File dir = new File(companyPath); + if (dir.exists()) { + File file = new File(dir, CompanyFileUtils.FILE_B1001_JSON); + try { + objectMapper.writeValue(file, json); + } catch (IOException e) { + logger.warn("Unable Save BlackList to {}, company:{}", file, company.getName(), e); + } + } + } + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/rk/ctx/CloudRkCtx.java b/src/main/java/com/ecep/contract/manager/cloud/rk/ctx/CloudRkCtx.java new file mode 100644 index 0000000..6f7ac87 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/rk/ctx/CloudRkCtx.java @@ -0,0 +1,823 @@ +package com.ecep.contract.manager.cloud.rk.ctx; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.cloud.AbstractCtx; +import com.ecep.contract.manager.cloud.rk.CloudRk; +import com.ecep.contract.manager.cloud.rk.CloudRkService; +import com.ecep.contract.manager.ds.company.BlackReasonType; +import com.ecep.contract.manager.ds.company.CompanyFileUtils; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyBlackReason; +import com.ecep.contract.manager.ds.company.model.CompanyContact; +import com.ecep.contract.manager.ds.company.model.CompanyOldName; +import com.ecep.contract.manager.ds.company.service.CompanyBlackReasonService; +import com.ecep.contract.manager.ds.company.service.CompanyContactService; +import com.ecep.contract.manager.ds.company.service.CompanyOldNameService; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ui.MessageHolder; +import com.ecep.contract.manager.util.HttpJsonUtils; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Setter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +import javax.net.ssl.SSLException; +import java.io.File; +import java.io.IOException; +import java.net.*; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import static com.ecep.contract.manager.SpringApp.getBean; + +public class CloudRkCtx extends AbstractCtx { + private static final Logger logger = LoggerFactory.getLogger(CloudRkCtx.class); + @Setter + private CloudRkService cloudRkService; + @Setter + private CompanyService companyService; + @Setter + private CompanyBlackReasonService companyBlackReasonService; + @Setter + private ObjectMapper objectMapper; + private Proxy socksProxy; + + public CloudRkService getCloudRkService() { + if (cloudRkService == null) { + cloudRkService = getBean(CloudRkService.class); + } + return cloudRkService; + } + + public CompanyService getCompanyService() { + if (companyService == null) { + companyService = getBean(CompanyService.class); + } + return companyService; + } + + CompanyBlackReasonService getCompanyBlackReasonService() { + if (companyBlackReasonService == null) { + companyBlackReasonService = getBean(CompanyBlackReasonService.class); + } + return companyBlackReasonService; + } + + ObjectMapper getObjectMapper() { + if (objectMapper == null) { + objectMapper = getBean(ObjectMapper.class); + } + return objectMapper; + } + + Proxy getSocksProxy() { + if (socksProxy == null) { + String proxy = getConfService().getString(CloudRkService.KEY_PROXY); + URI proxyUri = URI.create(proxy); + Proxy.Type proxyType = Proxy.Type.valueOf(proxyUri.getScheme().toUpperCase()); + socksProxy = new Proxy( + proxyType, + new InetSocketAddress(proxyUri.getHost(), proxyUri.getPort()) + ); + } + return socksProxy; + } + + public void post(String url, Consumer> data, Consumer consumer) throws IOException { + HttpJsonUtils.post(url, data, consumer, getObjectMapper(), getSocksProxy()); + } + + + public boolean syncCompany(Company company, CloudRk cloudRk, MessageHolder holder) { + if (!StringUtils.hasText(cloudRk.getCloudId())) { + holder.warn("未定义平台编号, 尝试从平台上自动获取"); + // 当未定义平台编号时,尝试自动获取 + if (!queryCloudIdAndSelectOne(company, cloudRk, holder)) { + // 自动获取到平台编号失败,立即返回 + return false; + } + } + + if (!StringUtils.hasText(cloudRk.getCloudId()) || cloudRk.getCloudId().equals("-")) { + // 平台编号为 空 或 - 时,跳过同步 + holder.debug("平台编号为 空 或 - 时,跳过同步"); + return false; + } + + boolean updated = false; + try { + if (updateEnterpriseInfo(company, cloudRk, holder)) { + company = getCompanyService().save(company); + updated = true; + } + + if (updateBlackList(company, cloudRk, holder)) { + updated = true; + } + + if (updateEnterpriseCredit(company, cloudRk, holder)) { + // cloudRk modified + updated = true; + } + if (updateCustomerScore(company, cloudRk, holder)) { + // cloudRk modified + updated = true; + } + if (updateVendorScore(company, cloudRk, holder)) { + // cloudRk modified + updated = true; + } + } catch (Exception e) { + // 异常 + logger.error("使用评分接口更新企业资信评价等级时发生错误", e); + cloudRk.setDescription("评分接口错误:" + e.getMessage()); + } + return updated; + } + + + /** + * 更新黑名单列表 + */ + public boolean updateBlackList( + Company company, CloudRk cloudRk, MessageHolder holder + ) throws IOException { + Instant start = cloudRk.getCloudBlackListUpdated(); + if (start != null) { + long elapse = getConfService().getLong(CloudRkService.KEY_BLACK_LIST_ELAPSE); + if (elapse > 0) { + Instant next = start.plusSeconds(elapse); + if (next.isAfter(Instant.now())) { + holder.debug("更新时间未到, 上次更新时间 = " + start); + return false; + } + } + } + + String api = getConfService().getString(CloudRkService.KEY_BLACK_LIST_URL); + List companyNames = getCompanyService().getAllNames(company); + + + List reasonList = new ArrayList<>(); + List dbReasons = getCompanyBlackReasonService().findAllByCompany(company); + for (String name : companyNames) { + String url = api + URLEncoder.encode(name, StandardCharsets.UTF_8); + try { + HttpJsonUtils.get(url, json -> { + applyBlackReason(json, company, cloudRk, reasonList, dbReasons, holder); + saveJsonToFile(company, json, "black-" + name + ".json", holder); + }, getObjectMapper(), getSocksProxy()); + } catch (IOException e) { + catchException(e, holder); + } + } + + if (reasonList.isEmpty()) { + cloudRk.setCloudBlackListUpdated(Instant.now()); + return false; + + } + for (CompanyBlackReason companyBlackReason : reasonList) { + getCompanyBlackReasonService().save(companyBlackReason); + } + cloudRk.setCloudBlackListUpdated(Instant.now()); + return true; + } + + private boolean applyBlackReason( + JsonNode json, Company company, CloudRk cloudRk, + List reasonList, List dbReasons, + MessageHolder holder + ) { + if (isUnSuccess(json, holder)) { + return false; + } + + if (!json.has("data")) { + holder.error("数据异常,返回的json中没有 data 字段"); + return false; + } + JsonNode data = json.get("data"); + + if (data.has("blackReason")) { + for (JsonNode reason : data.get("blackReason")) { + try { + toCompanyBlackReasonList(company, BlackReasonType.BLACK, reason, dbReasons, reasonList); + } catch (JsonMappingException e) { + holder.error("blackReason " + e.getMessage() + ", " + reason); + } + } + } + + if (data.has("greyReason")) { + for (JsonNode reason : data.get("greyReason")) { + try { + toCompanyBlackReasonList(company, BlackReasonType.GRAY, reason, dbReasons, reasonList); + } catch (JsonMappingException e) { + holder.error("greyReason " + e.getMessage() + ", " + reason); + } + } + } + return true; + } + + private void toCompanyBlackReasonList( + Company company, BlackReasonType type, + JsonNode reason, List dbReasons, + List reasonList + ) throws JsonMappingException { + ObjectNode object = (ObjectNode) reason; + String key = "rk-" + object.remove("id").asText(); + CompanyBlackReason cbr = dbReasons.stream().filter(r -> r.getKey().equals(key)).findAny().orElseGet(CompanyBlackReason::new); + getObjectMapper().updateValue(cbr, reason); + cbr.setCompany(company); + cbr.setType(type); + cbr.setKey(key); + reasonList.add(cbr); + } + + + /** + * 更新评分 + */ + public boolean updateEnterpriseCredit( + Company company, CloudRk cloudRk, MessageHolder holder + ) throws IOException { + String api = getConfService().getString(CloudRkService.KEY_ENT_SCORE_URL); + AtomicBoolean modified = new AtomicBoolean(false); + try { + post(api, data -> { + data.put("entname", company.getName()); + // data.put("entid", cloudInfo.getCloudId()); + data.put("get", true); + }, json -> { + modified.set(applyEnterpriseCredit(json, cloudRk, holder)); + saveJsonToFile(company, json, "credit.json", holder); + }); + } catch (IOException e) { + catchException(e, holder); + } + return modified.get(); + } + + private boolean applyEnterpriseCredit(JsonNode json, CloudRk cloudRk, MessageHolder holder) { + if (isUnSuccess(json, holder)) { + return false; + } + if (isUnHasField(json, "data", holder)) { + return false; + } + JsonNode data = json.get("data"); + boolean modified = false; + String level = ""; + String description = ""; + if (data.isNull()) { + level = "-"; + } else { + level = data.get("level").asText(); + description = data.get("levelDescription").asText(); + } + + if (updateText(cloudRk::getRank, cloudRk::setRank, level, holder, "企业资信评价等级")) { + modified = true; + } + if (updateText(cloudRk::getRankDescription, cloudRk::setRankDescription, description, holder, "企业资信评价等级说明")) { + modified = true; + } + return modified; + } + + private boolean isUnHasField(JsonNode json, String field, MessageHolder holder) { + if (!json.has("data")) { + holder.error("数据异常,返回的json中没有 data 字段"); + return true; + } + return false; + } + + private boolean isUnSuccess(JsonNode json, MessageHolder holder) { + if (isUnHasField(json, "success", holder)) { + return true; + } + if (!json.get("success").asBoolean()) { + holder.error("数据异常,返回 success = false, " + json); + return true; + } + return false; + } + + /** + * 客户信用 + */ + public boolean updateCustomerScore( + Company company, CloudRk cloudRk, MessageHolder holder + ) throws IOException { + String url = getConfService().getString(CloudRkService.KEY_CUSTOMER_REPORT_URL); + AtomicBoolean modified = new AtomicBoolean(false); + try { + post(url, data -> { + // data.put("entName", company.getName()); + data.put("entId", cloudRk.getCloudId()); + data.put("get", true); + }, json -> { + modified.set(applyCustomerScore(json, company, cloudRk, holder)); + saveJsonToFile(company, json, "customer-score.json", holder); + }); + } catch (IOException e) { + catchException(e, holder); + } + return modified.get(); + } + + private boolean applyCustomerScore(JsonNode json, Company company, CloudRk cloudRk, MessageHolder holder) { + if (isUnSuccess(json, holder)) { + return false; + } + + boolean modified = false; + String grade = ""; + int score = 0; + String description = ""; + if (isUnHasField(json, "data", holder)) { + grade = "-"; + score = -1; + } else { + JsonNode data = json.get("data"); + if (data.isNull()) { + grade = "无"; + score = -1; + } else { + grade = data.get("grade").asText(); + score = data.get("totalScore").asInt(); + description = data.get("description").asText(); + } + } + + if (updateText(cloudRk::getCustomerGrade, cloudRk::setCustomerGrade, grade, holder, "客户信用评级")) { + modified = true; + } + if (updateNumber(cloudRk::getCustomerScore, cloudRk::setCustomerScore, score, holder, "客户信用总分")) { + modified = true; + } + if (updateText(cloudRk::getCustomerDescription, cloudRk::setCustomerDescription, description, holder, "客户信用评级说明")) { + modified = true; + } + return modified; + } + + + public boolean updateVendorScore( + Company company, CloudRk cloudRk, MessageHolder holder + ) throws IOException { + String url = getConfService().getString(CloudRkService.KEY_VENDOR_REPORT_URL); + AtomicBoolean modified = new AtomicBoolean(false); + try { + post(url, data -> { + // data.put("entName", company.getName()); + data.put("entId", cloudRk.getCloudId()); + data.put("get", true); + }, json -> { + modified.set(applyVendorScore(json, cloudRk, holder)); + saveJsonToFile(company, json, "vendor-score.json", holder); + }); + } catch (IOException e) { + catchException(e, holder); + } + return modified.get(); + } + + private boolean applyVendorScore(JsonNode json, CloudRk cloudRk, MessageHolder holder) { + if (isUnSuccess(json, holder)) { + return false; + } + boolean modified = false; + String grade = ""; + int score = 0; + String description = ""; + if (isUnHasField(json, "data", holder)) { + grade = "-"; + score = -1; + } else { + JsonNode data = json.get("data"); + if (data.isNull()) { + grade = "无"; + score = -1; + } else { + grade = data.get("scoreLevel").asText(); + score = data.get("score").asInt(); + description = data.get("scoreDes").asText(); + } + } + + if (updateText(cloudRk::getVendorGrade, cloudRk::setVendorGrade, grade, holder, "供应商信用得分")) { + modified = true; + } + if (updateNumber(cloudRk::getVendorScore, cloudRk::setVendorScore, score, holder, "供应商信用总分")) { + modified = true; + } + if (updateText(cloudRk::getVendorDescription, cloudRk::setVendorDescription, description, holder, "供应商信用评级说明")) { + modified = true; + } + return modified; + } + + /** + * 自动获取到平台编号 + * + * @param company 公司对象 + * @param cloudRk Cloud Rk + * @return true 更新了 cloudId,否则false + */ + private boolean queryCloudIdAndSelectOne( + Company company, CloudRk cloudRk, MessageHolder holder + ) { + try { + List entInfos = queryEnterpriseWithFuzzy(company, cloudRk, holder); + // 返回的查询结果为空时 + if (entInfos.isEmpty()) { + // 设置为 -, 不在重复查找 + cloudRk.setCloudId("-"); + holder.warn("在平台中没有匹配到 " + company.getName()); + return false; + } + + // 在返回的结果中,找到与公司名字一致的一个 + Optional optional = entInfos.stream().filter(n -> n.getName().equals(company.getName())).findAny(); + if (optional.isPresent()) { + cloudRk.setCloudId(optional.get().getId()); + return true; + } + + // + holder.error("在平台中查询到多个匹配 (" + entInfos.stream().map(CloudRkService.EntInfo::getName).collect(Collectors.joining(", ")) + "),请手工同步选择匹配"); + return false; + + } catch (Exception e) { + // 异常 + holder.error("查询接口获取企业平台编号发生错误 = " + e.getMessage()); + if (logger.isErrorEnabled()) { + logger.error("使用模糊查询接口获取 {} 企业平台编号发生错误", company.getName(), e); + } + return false; + } + } + + + /** + * 使用模糊查询接口查询相关企业信息 + */ + public List queryEnterpriseWithFuzzy( + Company company, CloudRk cloudRk, MessageHolder holder + ) throws IOException { + String url = getConfService().getString(CloudRkService.KEY_ENT_FUZZY_URL); + List results = new ArrayList<>(); + ObjectMapper objectMapper = getObjectMapper(); + try { + HttpJsonUtils.post(url, data -> { + data.put("theKey", company.getName()); + data.put("get", true); + }, json -> { + applyEnterpriseQuery(json, company, cloudRk, results, holder); + saveJsonToFile(company, json, "fuzzy.json", holder); + }, objectMapper, getSocksProxy()); + } catch (IOException ex) { + catchException(ex, holder); + } + return results; + } + + private boolean applyEnterpriseQuery(JsonNode json, Company company, CloudRk cloudRk, List results, MessageHolder holder) { + if (!json.has("data")) { + // 没有数据 + holder.error("数据异常,返回的json中没有 data 字段"); + return false; + } + JsonNode dataNode = json.get("data"); + if (!dataNode.isArray()) { + holder.error("数据异常,返回的json中 data 字段不是数组"); + return false; + } + ObjectMapper objectMapper = getObjectMapper(); + + for (JsonNode node : dataNode) { + try { + CloudRkService.EntInfo entInfo = new CloudRkService.EntInfo(); + objectMapper.updateValue(entInfo, node); + if (node.has("isNowName")) { + String s = node.get("isNowName").asText(); + entInfo.setNowName(s.equals("1") || s.equals("true")); + } + results.add(entInfo); + } catch (Exception e) { + holder.error("更新企业信息失败:" + e.getMessage() + ", json=" + node.toString()); + return false; + } + } + return true; + } + + /** + * 更新企业工商注册信息 + */ + public boolean updateEnterpriseInfo( + Company company, CloudRk cloudRk, MessageHolder holder + ) throws IOException { + String api = getConfService().getString(CloudRkService.KEY_ENT_REPORT_URL); + Proxy socksProxy = getSocksProxy(); + holder.debug("更新企业工商注册信息: " + company.getName() + " @ " + api + ", proxy=" + socksProxy); + AtomicBoolean modified = new AtomicBoolean(false); + try { + post(api, data -> { + data.put("entName", company.getName()); + data.put("entid", cloudRk.getCloudId()); + data.put("get", true); + data.put("method", "data"); + data.put("nodetype", "B1001"); + }, jsonNode -> { + modified.set(applyEnterpriseInfo(jsonNode, company, cloudRk, holder)); + saveJsonToFile(company, jsonNode, CompanyFileUtils.FILE_B1001_JSON, holder); + }); + } catch (IOException e) { + catchException(e, holder); + } + return modified.get(); + } + + private void catchException(IOException e, MessageHolder holder) throws IOException { + if (e instanceof SSLException) { + holder.error("网络错误:" + e.getMessage()); + // 网络错误时,抛出异常,中断后续网络请求 + throw e; + } + if (e instanceof SocketException) { + holder.error("网络错误:" + e.getMessage()); + // 网络错误时,抛出异常,中断后续网络请求 + throw e; + } + holder.error(e.getMessage()); + } + + private boolean applyEnterpriseInfo(JsonNode json, Company company, CloudRk cloudRk, MessageHolder holder) { + if (!json.has("B1001")) { + holder.error("数据异常,返回的json中没有 B1001 字段"); + return false; + } + JsonNode b1001 = json.get("B1001"); + if (!b1001.has("count") || b1001.get("count").asInt() < 1 || !b1001.has("data")) { + holder.error("数据异常,B1001 字段没有数据"); + return false; + } + boolean modified = false; + JsonNode data = b1001.get("data"); + if (updateText(company::getEntType, company::setEntType, data, "enttype", holder, "企业类型")) { + modified = true; + } + if (updateText(company::getEntStatus, company::setEntStatus, data, "entstatus", holder, "企业状态")) { + modified = true; + } + if (updateLocalDate(company::getSetupDate, company::setSetupDate, data, "esdate", holder, "成立日期")) { + modified = true; + } + if (updateText(company::getUniscid, company::setUniscid, data, "uniscid", holder, "企业状态")) { + modified = true; + } + if (updateText(company::getRegAddr, company::setRegAddr, data, "dom", holder, "注册地址")) { + modified = true; + } + if (updateText(company::getRegisteredCapital, company::setRegisteredCapital, data, "regcap", holder, "注册资金")) { + modified = true; + } + if (updateText(company::getRegisteredCapitalCurrency, company::setRegisteredCapitalCurrency, data, "regcapcur", holder, "资本金币种")) { + modified = true; + } + if (updateText(company::getLegalRepresentative, company::setLegalRepresentative, data, "frname", holder, "法人")) { + modified = true; + } + if (updateText(company::getDistrict, company::setDistrict, data, "regorgprovince", holder, "注册区域")) { + modified = true; + } + if (updateText(company::getTelephone, company::setTelephone, data, "tel", holder, "注册电话")) { + modified = true; + } + if (updateText(company::getAddress, company::setAddress, data, "oploc", holder, "通讯地址")) { + modified = true; + } + if (updateOperationPeriodBegin(company, data, holder)) { + modified = true; + } + if (updateOperationPeriodEnd(company, data, holder)) { + modified = true; + } + if (updateText(company::getIndustry, company::setIndustry, data, "nicfulltitle", holder, "行业")) { + modified = true; + } + + updateCompanyNameHistory(company, data, holder.sub("曾用名")); + updateLegalRepresentativeContact(company, data, holder.sub("法人联系方式")); + updateInstant(cloudRk::getCloudEntUpdate, cloudRk::setCloudEntUpdate, data, "updated", holder, "更新时间", false); + return modified; + } + + + /** + * 更新法人联系人联系方式 + */ + private void updateLegalRepresentativeContact(Company company, JsonNode data, MessageHolder holder) { + String legalRepresentative = company.getLegalRepresentative(); + if (!StringUtils.hasText(legalRepresentative)) { + return; + } + + + CompanyContactService contactService = SpringApp.getBean(CompanyContactService.class); + List contactList = contactService.findAllByCompanyAndName(company, legalRepresentative); + if (contactList == null) { + // db error + return; + } + CompanyContact contact = null; + boolean modified = false; + if (contactList.isEmpty()) { + //没有,创建法人联系人 + contact = new CompanyContact(); + contact.setCompany(company); + contact.setName(legalRepresentative); + contact.setPosition("法定代表人"); + contact.setCreated(LocalDate.now()); + modified = true; + } else { + // 先尝试查找法人 + Optional any = contactList.stream().filter(c -> "法定代表人".equals(c.getPosition())).findAny(); + // 如果没有找到,列表中第一个联系人 + if (any.isEmpty()) { + any = contactList.stream().findFirst(); + } + contact = any.get(); + if (updateText(contact::getPosition, contact::setPosition, "法定代表人", holder, "职位")) { + modified = true; + } + } + + if (!StringUtils.hasText(contact.getEmail())) { + if (updateText(contact::getEmail, contact::setEmail, data, "email", holder, "邮箱")) { + modified = true; + } + } + + if (!StringUtils.hasText(contact.getAddress())) { + if (updateText(contact::getAddress, contact::setAddress, data, "oploc", holder, "地址")) { + modified = true; + } + } + + if (!StringUtils.hasText(contact.getPhone())) { + if (updateText(contact::getPhone, contact::setPhone, data, "tel", holder, "电话")) { + modified = true; + } + } + + if (modified) { + contactService.save(contact); + } + + } + + private boolean updateOperationPeriodBegin(Company company, JsonNode data, MessageHolder holder) { + return updateLocalDate(company::getOperationPeriodBegin, company::setOperationPeriodBegin, data, "opfrom", holder, "营业期限起始日期", true); + } + + private boolean updateOperationPeriodEnd(Company company, JsonNode data, MessageHolder holder) { + JsonNode node = data.get("opto"); + if (node == null) { + return false; + } + String text = node.asText(); + if (StringUtils.hasText(text)) { + if (text.equals("-")) { + return updateLocalDate(company::getOperationPeriodEnd, company::setOperationPeriodEnd, (LocalDate) null, holder, "营业期限截至日期", true); + } + return updateLocalDate(company::getOperationPeriodEnd, company::setOperationPeriodEnd, data, "opto", holder, "营业期限截至日期", true); + } + return false; + } + + + private void updateCompanyNameHistory(Company company, JsonNode data, MessageHolder holder) { + JsonNode node = data.get("nameHistory"); + if (node == null) { + return; + } + // 历史曾用名 + String nameHistory = node.asText(); + if (!StringUtils.hasText(nameHistory)) { + return; + } + List historyNames = new ArrayList<>(); + for (String str : nameHistory.split(",")) { + String trimmed = str.trim(); + if (StringUtils.hasText(trimmed)) { + historyNames.add(trimmed); + } + } + CompanyOldNameService service = SpringApp.getBean(CompanyOldNameService.class); + List oldNames = service.findAllByCompany(company); + for (CompanyOldName oldName : oldNames) { + // 已经存在的移除 + historyNames.remove(oldName.getName()); + } + for (String historyName : historyNames) { + CompanyOldName oldName = service.createNew(company, historyName, false); + oldName.setMemo("从相关方平台导入"); + service.save(oldName); + } + } + + private boolean updateLocalDate(Supplier getter, Consumer setter, JsonNode data, String field, MessageHolder holder, String topic, boolean allowNull) { + JsonNode node = data.get(field); + if (node == null || node.isNull()) { + return false; + } + LocalDate localDate = getObjectMapper().convertValue(node, LocalDate.class); + if (localDate == null && !allowNull) { + return false; + } + return updateLocalDate(getter, setter, localDate, holder, topic, allowNull); + } + + private boolean updateLocalDate(Supplier getter, Consumer setter, JsonNode data, String field, MessageHolder holder, String topic) { + return updateLocalDate(getter, setter, data, field, holder, topic, false); + } + + private void updateInstant(Supplier getter, Consumer setter, JsonNode data, String field, MessageHolder holder, String topic, boolean allowNull) { + JsonNode node = data.get("updated"); + if (node == null) { + return; + } + LocalDateTime updated = getObjectMapper().convertValue(node, LocalDateTime.class); + if (updated == null) { + if (!allowNull) { + return; + } + updateInstant(getter, setter, null, holder, topic); + return; + } + Instant instant = updated.toInstant(ZoneOffset.ofHours(8)); + updateInstant(getter, setter, instant, holder, topic); + } + + private boolean updateText(Supplier getter, Consumer setter, JsonNode data, String field, MessageHolder holder, String topic) { + JsonNode node = data.get(field); + if (node == null || node.isNull()) { + return false; + } + String text = node.asText(); + if (!StringUtils.hasText(text)) { + return false; + } + return updateText(getter, setter, text, holder, topic); + } + + private void saveJsonToFile(Company company, JsonNode json, String fileName, MessageHolder holder) { + String companyPath = company.getPath(); + if (!StringUtils.hasText(companyPath)) { + return; + } + + File dir = new File(companyPath); + if (!dir.exists()) { + return; + } + + File file = new File(dir, fileName); + try { + getObjectMapper().writeValue(file, json); + holder.debug("保存文件 " + file.getName()); + } catch (IOException e) { + holder.error("保存文件 " + file.getName() + " 发生错误:" + e.getMessage()); + logger.error("Save {}", file.getAbsolutePath(), e); + } + } + + public CloudRk getOrCreateCloudRk(Company company) { + return getCloudRkService().getOrCreateCloudRk(company); + } + + public CloudRk save(CloudRk cloudRk) { + return getCloudRkService().save(cloudRk); + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/tyc/CloudTyc.java b/src/main/java/com/ecep/contract/manager/cloud/tyc/CloudTyc.java new file mode 100644 index 0000000..6ddddf7 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/tyc/CloudTyc.java @@ -0,0 +1,28 @@ +package com.ecep.contract.manager.cloud.tyc; + +import com.ecep.contract.manager.cloud.CloudBaseInfo; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.ColumnDefault; + +import java.time.Instant; + +@Getter +@Setter +@Entity +@Table(name = "CLOUD_TYC", schema = "supplier_ms") +public class CloudTyc extends CloudBaseInfo { + + @ColumnDefault("0") + @Column(name = "SCORE", nullable = false) + private Integer score; + + @Column(name = "CLOUD_LATEST") + private Instant cloudLatest; + + + +} \ No newline at end of file diff --git a/src/main/java/com/ecep/contract/manager/cloud/tyc/CloudTycInfoViewModel.java b/src/main/java/com/ecep/contract/manager/cloud/tyc/CloudTycInfoViewModel.java new file mode 100644 index 0000000..b9aa6a2 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/tyc/CloudTycInfoViewModel.java @@ -0,0 +1,77 @@ +package com.ecep.contract.manager.cloud.tyc; + +import com.ecep.contract.manager.cloud.CloudInfoViewModel; +import com.ecep.contract.manager.ui.util.MyDateTimePropertyUtils; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Objects; + +@Data +@EqualsAndHashCode(callSuper = false) +public class CloudTycInfoViewModel extends CloudInfoViewModel { + + private SimpleIntegerProperty score = new SimpleIntegerProperty(); + private SimpleObjectProperty cloudLatest = new SimpleObjectProperty<>(); + + @Override + protected void updateFrom(CloudTyc info) { + super.updateFrom(info); + update_((CloudTyc) info); + } + + private void update_(CloudTyc info) { + score.set(info.getScore()); + + if (info.getCloudLatest() != null) { + ZoneId zone = ZoneId.systemDefault(); + ZonedDateTime zonedDateTime = info.getCloudLatest().atZone(zone); + LocalDateTime localDateTime = zonedDateTime.toLocalDateTime(); + cloudLatest.set(localDateTime); + } else { + cloudLatest.set(null); + } + } + + @Override + public boolean copyTo(CloudTyc info) { + boolean modified = super.copyTo(info); + if (copyTo_((CloudTyc) info)) { + modified = true; + } + return modified; + } + + private boolean copyTo_(CloudTyc info) { + boolean modified = super.copyTo(info); + if (!Objects.equals(info.getId(), getId().get())) { + info.setId(getId().get()); + modified = true; + } + + if (!Objects.equals(info.getScore(), getScore().get())) { + info.setScore(getScore().get()); + modified = true; + } + + Instant latest = MyDateTimePropertyUtils.localDateTimeToInstant(cloudLatest); + if (!Objects.equals(info.getCloudLatest(), latest)) { + info.setCloudLatest(latest); + modified = true; + } + + return modified; + } + + public static CloudTycInfoViewModel from(CloudTyc cc) { + CloudTycInfoViewModel model = new CloudTycInfoViewModel(); + model.update(cc); + return model; + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/tyc/CloudTycManagerSkin.java b/src/main/java/com/ecep/contract/manager/cloud/tyc/CloudTycManagerSkin.java new file mode 100644 index 0000000..ea53cf4 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/tyc/CloudTycManagerSkin.java @@ -0,0 +1,134 @@ +package com.ecep.contract.manager.cloud.tyc; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.company.controller.CompanyTableCell; +import com.ecep.contract.manager.ds.company.controller.CompanyWindowController; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ui.AbstEntityManagerSkin; +import com.ecep.contract.manager.ui.ManagerSkin; +import com.ecep.contract.manager.ui.util.LocalDateTimeTableCell; +import jakarta.persistence.criteria.Path; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import lombok.Setter; +import org.hibernate.Hibernate; +import org.springframework.data.domain.Page; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.util.StringUtils; + +import java.util.List; + +public class CloudTycManagerSkin + extends AbstEntityManagerSkin + implements ManagerSkin { + @Setter + private CloudTycService cloudTycService; + + @Setter + private CompanyService companyService; + + public CloudTycManagerSkin(CloudTycManagerWindowController controller) { + super(controller); + } + + CloudTycService getCloudTycService() { + if (cloudTycService == null) { + cloudTycService = getBean(CloudTycService.class); + } + return cloudTycService; + } + + CompanyService getCompanyService() { + if (companyService == null) { + companyService = SpringApp.getBean(CompanyService.class); + } + return companyService; + } + + @Override + protected List loadTableData() { + String searchText = controller.searchKeyField.getText(); + Specification spec = null; + if (StringUtils.hasText(searchText)) { + + Specification companySpec = (root, query, builder) -> { + Path company = root.get("company"); + return builder.or( + builder.like(company.get("name"), "%" + searchText + "%"), + builder.like(company.get("shortName"), "%" + searchText + "%") + ); + }; + + Specification cloudIdSpec = (root, query, builder) -> { + return builder.like(root.get("cloudId"), "%" + searchText + "%"); + }; + + spec = Specification.anyOf(companySpec, cloudIdSpec); + } + + Page page = getCloudTycService().findAll(spec, getPageable()); + updateFooter(page); + return page.map(CloudTycInfoViewModel::from).toList(); + } + + @Override + public void initializeTable() { + controller.idColumn.setCellValueFactory(param -> param.getValue().getId()); + controller.companyColumn.setCellValueFactory(param -> param.getValue().getCompany()); + controller.companyColumn.setCellFactory(param -> new CompanyTableCell<>(getCompanyService())); + + controller.cloudIdColumn.setCellValueFactory(param -> param.getValue().getCloudId()); + + controller.latestUpdateColumn.setCellValueFactory(param -> param.getValue().getLatest()); + controller.latestUpdateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); + + controller.cloudLatestColumn.setCellValueFactory(param -> param.getValue().getCloudLatest()); + controller.cloudLatestColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); + + controller.scoreColumn.setCellValueFactory(param -> param.getValue().getScore()); + + } + + @Override + protected void createContextMenu(ContextMenu contextMenu) { + MenuItem item2 = new MenuItem("刷新"); + item2.setOnAction(this::onTableRefreshAction); + + MenuItem item3 = new MenuItem("清空备注"); + item3.setOnAction(this::onTableClearDescriptionAction); + + contextMenu.getItems().addAll(item2, item3); + } + + /** + * 请空选择行的注释 + * + * @param event event + */ + public void onTableClearDescriptionAction(ActionEvent event) { + ObservableList selectedItems = getTableView().getSelectionModel().getSelectedItems(); + if (selectedItems.isEmpty()) { + return; + } + for (CloudTycInfoViewModel selectedItem : selectedItems) { + CloudTyc cloudTyc = getCloudTycService().findById(selectedItem.getId().get()); + // selectedItem.getDescription().set(""); + if (selectedItem.copyTo(cloudTyc)) { + CloudTyc saved = getCloudTycService().save(cloudTyc); + selectedItem.update(saved); + } + } + } + + @Override + protected void onTableRowDoubleClickedAction(CloudTycInfoViewModel item) { + Company company = item.getCompany().get(); + if (!Hibernate.isInitialized(item)) { + company = getCompanyService().findById(company.getId()); + } + CompanyWindowController.show(company, getTableView().getScene().getWindow()); + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/tyc/CloudTycManagerWindowController.java b/src/main/java/com/ecep/contract/manager/cloud/tyc/CloudTycManagerWindowController.java new file mode 100644 index 0000000..042f8fa --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/tyc/CloudTycManagerWindowController.java @@ -0,0 +1,126 @@ +package com.ecep.contract.manager.cloud.tyc; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.cloud.CloudBaseInfo; +import com.ecep.contract.manager.cloud.CloudInfo; +import com.ecep.contract.manager.ds.company.controller.CompanyWindowController; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ui.AbstManagerWindowController; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.ViewModelService; +import com.ecep.contract.manager.ui.util.DelayOnceExecutor; +import com.ecep.contract.manager.ui.util.LocalDateTimeTableCell; +import com.ecep.contract.manager.util.TableViewUtils; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.scene.control.*; +import javafx.stage.Stage; +import javafx.stage.WindowEvent; +import org.hibernate.Hibernate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Scope; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import static com.ecep.contract.manager.util.TableViewUtils.getTableViewVisibleRows; + +/** + * 天眼查信息管理窗口控制器 + */ +@Lazy +@Scope("prototype") +@Component +@FxmlPath("/ui/cloud/tyc_manager.fxml") +public class CloudTycManagerWindowController + extends AbstManagerWindowController { + + public static void show() { + show(CloudTycManagerWindowController.class, null); + } + + @Autowired + private CloudTycService cloudTycService; + + @Autowired + private CompanyService companyService; + + + public TableColumn idColumn; + public TableColumn latestUpdateColumn; + + public TableColumn companyColumn; + public TableColumn cloudIdColumn; + public TableColumn cloudLatestColumn; + public TableColumn scoreColumn; + + public TableColumn descriptionColumn; + + + + @Override + public CloudTycService getViewModelService() { + return cloudTycService; + } + @Override + protected CloudTycManagerSkin createDefaultSkin() { + return new CloudTycManagerSkin(this); + } + + @Override + public void show(Stage stage) { + super.show(stage);getTitle().set("数据源:天眼查"); + } + + /** + * 数据迁移,从 CloudInfo 迁移到 CloudRk + */ + public void onDateTransferAction(ActionEvent event) { + CompletableFuture.runAsync(() -> { + com.ecep.contract.manager.cloud.CloudInfoRepository cloudInfoRepository = SpringApp.getBean(com.ecep.contract.manager.cloud.CloudInfoRepository.class); + cloudInfoRepository.findAll().forEach(v -> { + try { + CloudTyc cloudTyc = cloudTycService.getOrCreateCloudTyc(v); + if (copyTo(v, cloudTyc)) { + cloudTycService.save(cloudTyc); + } + } catch (Exception e) { + e.printStackTrace(); + } + }); + + }); + + } + + boolean copyTo(CloudInfo v, CloudBaseInfo cloudRk) { + boolean modified = false; + if (!Objects.equals(cloudRk.getLatestUpdate(), v.getLatestUpdate())) { + cloudRk.setLatestUpdate(v.getLatestUpdate()); + modified = true; + } + + if (!Objects.equals(cloudRk.getCloudId(), v.getCloudId())) { + cloudRk.setCloudId(v.getCloudId()); + modified = true; + } + + if (!Objects.equals(cloudRk.getCompany(), v.getCompany())) { + cloudRk.setCompany(v.getCompany()); + modified = true; + } + return modified; + } + +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/tyc/CloudTycRepository.java b/src/main/java/com/ecep/contract/manager/cloud/tyc/CloudTycRepository.java new file mode 100644 index 0000000..76e9d39 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/tyc/CloudTycRepository.java @@ -0,0 +1,31 @@ +package com.ecep.contract.manager.cloud.tyc; + +import com.ecep.contract.manager.ds.company.model.Company; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface CloudTycRepository + // curd + extends CrudRepository, PagingAndSortingRepository, + // JPA interfaces + JpaRepository, JpaSpecificationExecutor { + + List findAllByCompanyId(Integer companyId); + + Optional findByCompanyId(Integer companyId); + + @Modifying + @Transactional + int deleteAllByCompany(Company company); + + +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/tyc/CloudTycService.java b/src/main/java/com/ecep/contract/manager/cloud/tyc/CloudTycService.java new file mode 100644 index 0000000..787b694 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/tyc/CloudTycService.java @@ -0,0 +1,210 @@ +package com.ecep.contract.manager.cloud.tyc; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.cloud.CloudInfo; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.other.repository.SysConfRepository; +import com.ecep.contract.manager.ui.MessageHolder; +import com.ecep.contract.manager.ui.ViewModelService; +import com.ecep.contract.manager.util.MyStringUtils; +import com.ecep.contract.manager.util.UITools; +import javafx.application.Platform; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Lazy +@Service +public class CloudTycService implements ViewModelService { + public static final String NAME = "天眼查"; + + public final static String TYC_ENTERPRISE_ANALYSIS_REPORT = "企业分析报告"; + public final static String TYC_ENTERPRISE_BASIC_REPORT = "基础版企业信用报告"; + public final static String TYC_ENTERPRISE_MAJOR_REPORT = "专业版企业信用报告"; + public final static String TYC_ENTERPRISE_CREDIT_REPORT = "企业信用信息公示报告"; + public static final String URL_COMPANY = "https://www.tianyancha.com/company/%s"; + public static final String URL_COMPANY_SEARCH = "https://www.tianyancha.com/search?key=%s"; + + private static final Logger logger = LoggerFactory.getLogger(CloudTycService.class); + + /** + * 天眼查报告,文件名中必须包含 天眼查 字样 + * + * @param fileName 文件名 + * @return 是否是天眼查报告 + */ + public static boolean isTycReport(String fileName) { + // 文件名中包含 天眼查 字样 + return fileName.contains(CloudTycService.NAME); + } + + @Autowired + private CloudTycRepository cloudTycRepository; + + + public CloudTyc getOrCreateCloudTyc(CloudInfo info) { + Optional optional = cloudTycRepository.findById(info.getId()); + return optional.orElseGet(() -> getOrCreateCloudTyc(info.getCompany())); + } + + public CloudTyc getOrCreateCloudTyc(Company company) { + Integer companyId = company.getId(); + List list = cloudTycRepository.findAllByCompanyId(companyId); + if (list.isEmpty()) { + CloudTyc tyc = new CloudTyc(); + tyc.setCompany(company); + tyc.setScore(-1); + tyc.setCloudLatest(null); + return cloudTycRepository.save(tyc); + } + if (list.size() == 1) { + return list.getFirst(); + } + + // 查询有 CloudId 的记录 + List hasCouldIdList = list.stream() + .filter(v -> { + return StringUtils.hasText(v.getCloudId()) + && MyStringUtils.isAllDigit(v.getCloudId()); + }) + .collect(Collectors.toList()); + + // 没有匹配到一条时 + if (hasCouldIdList.isEmpty()) { + // 保留第一条,其他删除 + CloudTyc rk = list.removeFirst(); + cloudTycRepository.deleteAll(list); + return rk; + } + + // 只有匹配到一条有 CloudId 的记录 + if (hasCouldIdList.size() == 1) { + // 保留匹配的记录,其他删除 + CloudTyc rk = hasCouldIdList.removeFirst(); + list.remove(rk); + cloudTycRepository.deleteAll(list); + return rk; + } + + + // 查询有 Score 的记录 + List hasLatestList = hasCouldIdList.stream().filter(v -> { + return v.getScore() != null && v.getScore() > 0; + }).collect(Collectors.toList()); + // 没有匹配到一条时 + if (hasLatestList.isEmpty()) { + // 保留第一条,其他删除 + CloudTyc rk = hasCouldIdList.removeFirst(); + list.remove(rk); + cloudTycRepository.deleteAll(list); + return rk; + } + + // 只有匹配到一条有 CloudId 的记录 + if (hasLatestList.size() == 1) { + // 保留匹配的记录,其他删除 + CloudTyc rk = hasLatestList.removeFirst(); + list.remove(rk); + cloudTycRepository.deleteAll(list); + return rk; + } + + return hasLatestList.getFirst(); + + } + + public CloudTyc save(CloudTyc cloudTyc) { + return cloudTycRepository.save(cloudTyc); + } + + @Override + public void delete(CloudTyc entity) { + cloudTycRepository.delete(entity); + } + + public void save(CloudTycInfoViewModel viewModel) { + int infoId = viewModel.getId().get(); + CloudTyc cloudTyc = cloudTycRepository.findById(infoId).orElse(null); + if (cloudTyc == null) { + return; + } + if (viewModel.copyTo(cloudTyc)) { + cloudTyc.setLatestUpdate(Instant.now()); + CloudTyc saved = cloudTycRepository.save(cloudTyc); + Platform.runLater(() -> viewModel.update(saved)); + } + } + + public void showInBrowse(String cloudId) { + String baseUrl = SpringApp.getBean(SysConfRepository.class).get("cloud.tyc.company"); + if (!StringUtils.hasText(baseUrl)) { + UITools.showAlertAndWait("系统参数 cloud.tyc.company 未配置"); + return; + } + + String url = baseUrl + cloudId; + + com.ecep.contract.manager.Desktop.showInBrowse(url); + + // UITools.showExceptionAndWait("使用浏览器打开网址出现异常", e); + + } + + + public void deleteByCompany(Company company) { + int deleted = cloudTycRepository.deleteAllByCompany(company); + if (deleted > 0) { + if (logger.isInfoEnabled()) { + logger.info("Delete {} records by company:#{}", deleted, company.getId()); + } + } + } + + public void resetTo(Company from, Company to) { + List list = cloudTycRepository.findAllByCompanyId(from.getId()); + for (CloudTyc item : list) { + item.setCompany(to); + } + cloudTycRepository.saveAll(list); + } + + public CloudTyc findById(Integer id) { + return cloudTycRepository.findById(id).orElse(null); + } + + public Page findAll(Specification spec, PageRequest pageable) { + return cloudTycRepository.findAll(spec, pageable); + } + + public Page findAll(Specification spec, Pageable pageable) { + return cloudTycRepository.findAll(spec, pageable); + } + + + @Override + public Specification getSpecification(String searchText) { + if (!StringUtils.hasText(searchText)) { + return null; + } + return (root, query, builder) -> { + return builder.like(root.get("cloudId"), "%" + searchText + "%"); + }; + } + + // TODO + public void syncCompany(Company company, MessageHolder holder) { + holder.warn("TODO 未实现"); + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/CloudYu.java b/src/main/java/com/ecep/contract/manager/cloud/u8/CloudYu.java new file mode 100644 index 0000000..048f211 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/CloudYu.java @@ -0,0 +1,42 @@ +package com.ecep.contract.manager.cloud.u8; + +import com.ecep.contract.manager.cloud.CloudBaseInfo; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.ColumnDefault; + +import java.time.Instant; +import java.time.LocalDate; + +@Getter +@Setter +@Entity +@Table(name = "CLOUD_YU", schema = "supplier_ms") +@ToString +public class CloudYu extends CloudBaseInfo { + @ColumnDefault("''") + @Column(name = "EX_MSG") + private String exceptionMessage; + + /** + * 供应商信息更新日期 + */ + @Column(name = "VEN_UP_DATE") + private LocalDate vendorUpdateDate; + + /** + * 客户信息更新日期 + */ + @Column(name = "CUS_UP_DATE") + private LocalDate customerUpdateDate; + + /** + * 数据更新日期 + */ + @Column(name = "CLOUD_LATEST") + private Instant cloudLatest; +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/CloudYuInfoViewModel.java b/src/main/java/com/ecep/contract/manager/cloud/u8/CloudYuInfoViewModel.java new file mode 100644 index 0000000..ebed61e --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/CloudYuInfoViewModel.java @@ -0,0 +1,86 @@ +package com.ecep.contract.manager.cloud.u8; + +import com.ecep.contract.manager.cloud.CloudInfoViewModel; +import com.ecep.contract.manager.ui.util.MyDateTimePropertyUtils; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.*; +import java.util.Objects; + +@Data +@EqualsAndHashCode(callSuper = false) +public class CloudYuInfoViewModel extends CloudInfoViewModel { + private SimpleStringProperty vendorCode = new SimpleStringProperty(); + private SimpleStringProperty vendorClassCode = new SimpleStringProperty(); + private SimpleObjectProperty vendorDevelopDate = new SimpleObjectProperty<>(); + + private SimpleStringProperty customerCode = new SimpleStringProperty(); + private SimpleStringProperty customerClassCode = new SimpleStringProperty(); + private SimpleObjectProperty customerDevelopDate = new SimpleObjectProperty<>(); + + private SimpleObjectProperty cloudLatest = new SimpleObjectProperty<>(); + + @Override + protected void updateFrom(CloudYu info) { + super.updateFrom(info); + update_((CloudYu) info); + } + + private void update_(CloudYu info) { + vendorCode.set(info.getExceptionMessage()); + vendorDevelopDate.set(info.getVendorUpdateDate()); + customerDevelopDate.set(info.getCustomerUpdateDate()); + if (info.getCloudLatest() != null) { + ZoneId zone = ZoneId.systemDefault(); + ZonedDateTime zonedDateTime = info.getCloudLatest().atZone(zone); + LocalDateTime localDateTime = zonedDateTime.toLocalDateTime(); + cloudLatest.set(localDateTime); + } else { + cloudLatest.set(null); + } + } + + @Override + public boolean copyTo(CloudYu info) { + boolean modified = super.copyTo(info); + if (copyTo_((CloudYu) info)) { + modified = true; + } + return modified; + } + + private boolean copyTo_(CloudYu info) { + boolean modified = false; + if (!Objects.equals(info.getExceptionMessage(), vendorCode.get())) { + info.setExceptionMessage(vendorCode.get()); + modified = true; + } + if (!Objects.equals(info.getVendorUpdateDate(), vendorDevelopDate.get())) { + info.setVendorUpdateDate(vendorDevelopDate.get()); + modified = true; + } + if (!Objects.equals(info.getCustomerUpdateDate(), customerDevelopDate.get())) { + info.setCustomerUpdateDate(customerDevelopDate.get()); + modified = true; + } + + Instant latest = MyDateTimePropertyUtils.localDateTimeToInstant(cloudLatest); + if (!Objects.equals(info.getCloudLatest(), latest)) { + info.setCloudLatest(latest); + modified = true; + } + + return modified; + } + + public static CloudYuInfoViewModel from(CloudYu cc) { + CloudYuInfoViewModel model = new CloudYuInfoViewModel(); + model.update(cc); + return model; + } + + +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/CloudYuRepository.java b/src/main/java/com/ecep/contract/manager/cloud/u8/CloudYuRepository.java new file mode 100644 index 0000000..d6ac591 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/CloudYuRepository.java @@ -0,0 +1,31 @@ +package com.ecep.contract.manager.cloud.u8; + +import com.ecep.contract.manager.ds.company.model.Company; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface CloudYuRepository + // curd + extends CrudRepository, PagingAndSortingRepository, + // JPA interfaces + JpaRepository, JpaSpecificationExecutor { + + List findAllByCompanyId(Integer companyId); + + Optional findByCompanyId(Integer companyId); + + @Modifying + @Transactional + int deleteAllByCompany(Company company); + + +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/ContractGroupSyncTask.java b/src/main/java/com/ecep/contract/manager/cloud/u8/ContractGroupSyncTask.java new file mode 100644 index 0000000..b43ed04 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/ContractGroupSyncTask.java @@ -0,0 +1,102 @@ +package com.ecep.contract.manager.cloud.u8; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.contract.model.ContractGroup; +import com.ecep.contract.manager.ds.contract.service.ContractGroupService; + +import javafx.concurrent.Task; +import lombok.Setter; + +/** + * 同步合同分组 + */ +public class ContractGroupSyncTask extends Task { + private static final Logger logger = LoggerFactory.getLogger(ContractGroupSyncTask.class); + @Setter + private ContractGroupService contractGroupService; + + public ContractGroupSyncTask() { + updateTitle("用友U8系统-同步合同分组信息"); + } + + ContractGroupService getContractGroupService() { + if (contractGroupService == null) { + contractGroupService = SpringApp.getBean(ContractGroupService.class); + } + return contractGroupService; + } + + @Override + protected Object call() throws Exception { + YongYouU8Repository repository = null; + try { + repository = SpringApp.getBean(YongYouU8Repository.class); + } catch (BeansException e) { + logger.error("can't get bean of YongYouU8Repository", e); + return null; + } + + AtomicInteger counter = new AtomicInteger(0); + updateMessage("读取 U8 系统 CM_Group 数据表..."); + List> list = repository.queryAllContractGroup(); + int size = list.size(); + updateMessage("总共读取 CM_Group 数据 " + size + " 条"); + + for (Map map : list) { + if (isCancelled()) { + updateMessage("Cancelled"); + return null; + } + sync(map, msg -> { + updateMessage(counter.get() + "/" + size + ">" + msg); + }); + // 更新进度 + updateProgress(counter.incrementAndGet(), size); + } + + return null; + } + + private void sync(Map map, Consumer consumer) { + boolean modified = false; + + String groupCode = (String) map.get("cGroupID"); + String groupName = (String) map.get("cGroupName"); + String groupTitle = (String) map.get("cRemark"); + + ContractGroupService service = getContractGroupService(); + ContractGroup contractGroup = service.findByCode(groupCode); + if (contractGroup == null) { + contractGroup = service.newContractGroup(); + consumer.accept("新建合同分组:" + groupCode); + modified = true; + } + + if (!Objects.equals(contractGroup.getCode(), groupCode)) { + contractGroup.setCode(groupCode); + modified = true; + } + if (!Objects.equals(contractGroup.getName(), groupName)) { + contractGroup.setName(groupName); + modified = true; + } + if (!Objects.equals(contractGroup.getTitle(), groupTitle)) { + contractGroup.setTitle(groupTitle); + modified = true; + } + + if (modified) { + service.save(contractGroup); + consumer.accept("更新" + contractGroup.getName() + " 信息"); + } + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/ContractKindSyncTask.java b/src/main/java/com/ecep/contract/manager/cloud/u8/ContractKindSyncTask.java new file mode 100644 index 0000000..f3b6b4e --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/ContractKindSyncTask.java @@ -0,0 +1,102 @@ +package com.ecep.contract.manager.cloud.u8; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.contract.model.ContractKind; +import com.ecep.contract.manager.ds.contract.service.ContractKindService; +import javafx.concurrent.Task; +import lombok.Setter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +import org.springframework.beans.BeansException; + +/** + * 同步合同分类 + */ +public class ContractKindSyncTask extends Task { + private static final Logger logger = LoggerFactory.getLogger(ContractKindSyncTask.class); + @Setter + private ContractKindService contractKindService; + + public ContractKindSyncTask() { + updateTitle("用友U8系统-同步合同分类信息"); + } + + ContractKindService getContractKindService() { + if (contractKindService == null) { + contractKindService = SpringApp.getBean(ContractKindService.class); + } + return contractKindService; + } + + @Override + protected Object call() throws Exception { + YongYouU8Repository repository = null; + try { + repository = SpringApp.getBean(YongYouU8Repository.class); + } catch (BeansException e) { + logger.error("can't get bean of YongYouU8Repository", e); + return null; + } + + AtomicInteger counter = new AtomicInteger(0); + updateMessage("读取 U8 系统 CM_Kind 数据表..."); + List> list = repository.queryAllContractKind(); + int size = list.size(); + updateMessage("总共读取 CM_Kind 数据 " + size + " 条"); + + for (Map map : list) { + if (isCancelled()) { + updateMessage("Cancelled"); + return null; + } + sync(map, msg -> { + updateMessage(counter.get() + "/" + size + ">" + msg); + }); + // 更新进度 + updateProgress(counter.incrementAndGet(), size); + } + + return null; + } + + private void sync(Map map, Consumer consumer) { + boolean modified = false; + + String typeCode = (String) map.get("KindID"); + String typeName = (String) map.get("KindName"); + String typeTitle = (String) map.get("Description"); + + ContractKindService kindService = getContractKindService(); + ContractKind contractKind = kindService.findByCode(typeCode); + if (contractKind == null) { + contractKind = new ContractKind(); + consumer.accept("新建合同分类:" + typeCode); + modified = true; + } + + if (!Objects.equals(contractKind.getCode(), typeCode)) { + contractKind.setCode(typeCode); + modified = true; + } + if (!Objects.equals(contractKind.getName(), typeName)) { + contractKind.setName(typeName); + modified = true; + } + if (!Objects.equals(contractKind.getTitle(), typeTitle)) { + contractKind.setTitle(typeTitle); + modified = true; + } + + if (modified) { + kindService.save(contractKind); + consumer.accept("更新" + contractKind.getName() + " 信息"); + } + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/ContractSyncAllTask.java b/src/main/java/com/ecep/contract/manager/cloud/u8/ContractSyncAllTask.java new file mode 100644 index 0000000..5fc396d --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/ContractSyncAllTask.java @@ -0,0 +1,49 @@ +package com.ecep.contract.manager.cloud.u8; + +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ds.contract.tasker.AbstContractRepairTasker; +import com.ecep.contract.manager.ui.MessageHolder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +/** + * 用友U8系统-同步全量合同 + */ +public class ContractSyncAllTask extends AbstContractRepairTasker { + private static final Logger logger = LoggerFactory.getLogger(ContractSyncAllTask.class); + + + @Override + protected void repair(MessageHolder holder) { + updateTitle("用友U8系统-同步全量合同"); + YongYouU8Repository repository = getBean(YongYouU8Repository.class); + + long total = repository.countAllContracts(); + AtomicInteger counter = new AtomicInteger(0); + + try (Stream> stream = repository.queryAllContractForStream()) { + stream.forEach(rs -> { + if (isCancelled()) { + holder.debug("Cancelled"); + return; + } + MessageHolder subHolder = holder.sub(counter.get() + " / " + total + "> "); + try { + repairFromCMList(rs, subHolder); + } catch (Exception e) { + updateMessage(e.getMessage()); + logger.error("data = {}", rs, e); + + } + updateProgress(counter.incrementAndGet(), total); + }); + } + } + +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/ContractSyncContext.java b/src/main/java/com/ecep/contract/manager/cloud/u8/ContractSyncContext.java new file mode 100644 index 0000000..a2f19ff --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/ContractSyncContext.java @@ -0,0 +1,92 @@ +package com.ecep.contract.manager.cloud.u8; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.contract.model.ContractGroup; +import com.ecep.contract.manager.ds.contract.model.ContractKind; +import com.ecep.contract.manager.ds.contract.model.ContractType; +import com.ecep.contract.manager.ds.contract.service.ContractGroupService; +import com.ecep.contract.manager.ds.contract.service.ContractKindService; +import com.ecep.contract.manager.ds.contract.service.ContractTypeService; +import com.ecep.contract.manager.ds.other.model.Employee; +import com.ecep.contract.manager.ds.other.service.EmployeeService; +import lombok.Getter; +import lombok.Setter; +import org.springframework.beans.BeansException; + +import java.util.function.Consumer; + +public class ContractSyncContext { + T getBean(Class requiredType) throws BeansException { + return SpringApp.getBean(requiredType); + } + + private ContractTypeService contractTypeService; + private ContractGroupService contractGroupService; + private ContractKindService contractKindService; + private EmployeeService employeeService; + + @Getter + @Setter + Consumer consumer; + + public ContractSyncContext() { + } + + private ContractTypeService getContractTypeService() { + if (contractTypeService == null) { + contractTypeService = getBean(ContractTypeService.class); + } + return contractTypeService; + } + + private ContractGroupService getContractGroupService() { + if (contractGroupService == null) { + contractGroupService = getBean(ContractGroupService.class); + } + return contractGroupService; + } + + private ContractKindService getContractKindService() { + if (contractKindService == null) { + contractKindService = getBean(ContractKindService.class); + } + return contractKindService; + } + + private EmployeeService getEmployeeService() { + if (employeeService == null) { + employeeService = getBean(EmployeeService.class); + } + return employeeService; + } + + public ContractType getTypeByCode(String typeCode) { + return getContractTypeService().findByCode(typeCode); + } + + public ContractGroup getGroupByCode(String groupCode) { + return getContractGroupService().findByCode(groupCode); + } + + public ContractKind getKindByName(String kindName) { + return getContractKindService().findByName(kindName); + } + + + + public void updateMessage(String message) { + Consumer consumer = getConsumer(); + if (consumer != null) { + consumer.accept(message); + } + } + public Employee findEmployeeByCode(String personCode) { + return getEmployeeService().findByCode(personCode); + } + public Employee findEmployeeByName(String personName) { + if (personName == null) { + return null; + } + return getEmployeeService().findByName(personName); + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/ContractSyncTask.java b/src/main/java/com/ecep/contract/manager/cloud/u8/ContractSyncTask.java new file mode 100644 index 0000000..530f6af --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/ContractSyncTask.java @@ -0,0 +1,136 @@ +package com.ecep.contract.manager.cloud.u8; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ds.contract.tasker.AbstContractRepairTasker; +import com.ecep.contract.manager.ui.MessageHolder; +import lombok.Setter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; + +/** + * 合同同步任务 + */ +public class ContractSyncTask extends AbstContractRepairTasker { + private static final Logger logger = LoggerFactory.getLogger(ContractSyncTask.class); + @Setter + private boolean useLatestId = true; + private YongYouU8Repository repository; + + public ContractSyncTask() { + updateTitle("用友U8系统-同步合同"); + } + + private void syncByLatestId(MessageHolder holder) { + int latestId = getConfService().getInt(ContractService.CONTRACT_LATEST_ID); + updateTitle("用友U8系统-同步合同,从 " + latestId + " 开始"); + + Long total = repository.countAllContracts(latestId); + updateTitle("用友U8系统-同步合同,从 " + latestId + " 开始,合计 " + total + " 条"); + if (logger.isDebugEnabled()) { + logger.debug("sync latest Id from {}, have {} record.", latestId, total); + } + + AtomicInteger counter = new AtomicInteger(0); + AtomicReference reference = new AtomicReference<>(latestId); + try (Stream> stream = repository.queryAllContractForStream(latestId)) { + stream.forEach(rs -> { + if (isCancelled()) { + updateMessage("Cancelled"); + return; + } + MessageHolder subHolder = holder.sub(counter.get() + " / " + total + "> "); + try { + Integer ID = (Integer) rs.get("ID"); + repairFromCMList(rs, subHolder); + if (ID != null) { + Integer latest = reference.get(); + if (latest == null || ID.compareTo(latest) > 0) { + reference.set(ID); + } + } + + } catch (Exception e) { + logger.error("Contract sync failure {}, data = {}", e.getMessage(), rs, e); + updateMessage(e.getMessage()); + throw e; + } + updateProgress(counter.incrementAndGet(), total); + }); + } + getConfService().set(ContractService.CONTRACT_LATEST_ID, String.valueOf(reference.get())); + } + + private void syncByLatestDate(MessageHolder holder) { + String strDateTime = getConfService().getString(ContractService.CONTRACT_LATEST_DATE); + LocalDateTime latestDateTime = null; + if (StringUtils.hasText(strDateTime)) { + try { + latestDateTime = LocalDateTime.parse(strDateTime); + } catch (Exception ignored) { + } + } + if (latestDateTime == null) { + latestDateTime = LocalDateTime.of(2025, 1, 1, 0, 0, 0); + } + updateTitle("用友U8系统-同步合同,从 " + latestDateTime + " 开始"); + + Long total = repository.countAllContracts(latestDateTime); + updateTitle("用友U8系统-同步合同,从 " + latestDateTime + " 开始,合计 " + total + " 条"); + if (logger.isDebugEnabled()) { + logger.debug("from latest date {}, have {} record.", latestDateTime, total); + } + + AtomicInteger counter = new AtomicInteger(0); + AtomicReference reference = new AtomicReference<>(latestDateTime); + + try (Stream> stream = repository.queryAllContractForStream(latestDateTime)) { + stream.forEach(rs -> { + if (isCancelled()) { + updateMessage("Cancelled"); + return; + } + + MessageHolder subHolder = holder.sub(counter.get() + " / " + total + "> "); + try { + Timestamp dtDate = (Timestamp) rs.get("dtDate"); + + repairFromCMList(rs, subHolder); + + if (dtDate != null) { + LocalDateTime latest = reference.get(); + LocalDateTime localDate = dtDate.toLocalDateTime(); + if (latest == null || localDate.isAfter(latest)) { + reference.set(localDate); + } + } + } catch (Exception e) { + logger.error("Contract sync failure {}, data = {}", e.getMessage(), rs, e); + updateMessage(e.getMessage()); + throw e; + } + updateProgress(counter.incrementAndGet(), total); + }); + } + getConfService().set(ContractService.CONTRACT_LATEST_DATE, String.valueOf(reference.get())); + } + + @Override + protected void repair(MessageHolder holder) { + updateTitle("用友U8系统-同步合同"); + repository = SpringApp.getBean(YongYouU8Repository.class); + if (useLatestId) { + syncByLatestId(holder); + } else { + syncByLatestDate(holder); + } + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/ContractTypeSyncTask.java b/src/main/java/com/ecep/contract/manager/cloud/u8/ContractTypeSyncTask.java new file mode 100644 index 0000000..ab1c6e9 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/ContractTypeSyncTask.java @@ -0,0 +1,112 @@ +package com.ecep.contract.manager.cloud.u8; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.contract.model.ContractType; +import com.ecep.contract.manager.ds.contract.service.ContractTypeService; +import javafx.concurrent.Task; +import lombok.Setter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +import org.springframework.beans.BeansException; + +/** + * 同步合同类型 + */ +public class ContractTypeSyncTask extends Task { + private static final Logger logger = LoggerFactory.getLogger(ContractTypeSyncTask.class); + @Setter + private ContractTypeService contractTypeService; + + public ContractTypeSyncTask() { + updateTitle("用友U8系统-同步合同类型信息"); + } + + ContractTypeService getContractTypeService() { + if (contractTypeService == null) { + contractTypeService = SpringApp.getBean(ContractTypeService.class); + } + return contractTypeService; + } + + @Override + protected Object call() throws Exception { + YongYouU8Repository repository = null; + try { + repository = SpringApp.getBean(YongYouU8Repository.class); + } catch (BeansException e) { + logger.error("can't get bean of YongYouU8Repository", e); + return null; + } + + AtomicInteger counter = new AtomicInteger(0); + updateMessage("读取 U8 系统 CM_Type,CM_TypeClass 数据表..."); + List> list = repository.queryAllContractType(); + int size = list.size(); + updateMessage("总共读取 CM_Type,CM_TypeClass 数据 " + size + " 条"); + + for (Map map : list) { + if (isCancelled()) { + updateMessage("Cancelled"); + return null; + } + sync(map, msg -> { + updateMessage(counter.get() + "/" + size + ">" + msg); + }); + // 更新进度 + updateProgress(counter.incrementAndGet(), size); + } + + return null; + } + + private void sync(Map map, Consumer consumer) { + boolean modified = false; + + String typeCode = (String) map.get("cTypeCode"); + String typeName = (String) map.get("cTypeName"); + String typeTitle = (String) map.get("cCharacter"); + String typeCatalog = (String) map.get("cClassName"); + String typeDirection = (String) map.get("cDirection"); + + ContractTypeService typeService = getContractTypeService(); + ContractType contractType = typeService.findByCode(typeCode); + if (contractType == null) { + contractType = new ContractType(); + consumer.accept("新建合同类型:" + typeCode); + modified = true; + } + + if (!Objects.equals(contractType.getCode(), typeCode)) { + contractType.setCode(typeCode); + modified = true; + } + if (!Objects.equals(contractType.getName(), typeName)) { + contractType.setName(typeName); + modified = true; + } + if (!Objects.equals(contractType.getTitle(), typeTitle)) { + contractType.setTitle(typeTitle); + modified = true; + } + if (!Objects.equals(contractType.getCatalog(), typeCatalog)) { + contractType.setTitle(typeTitle); + modified = true; + } + if (!Objects.equals(contractType.getDirection(), typeDirection)) { + contractType.setTitle(typeTitle); + modified = true; + } + + if (modified) { + typeService.save(contractType); + consumer.accept("更新" + contractType.getName() + " 信息"); + } + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/CustomerClassSyncTask.java b/src/main/java/com/ecep/contract/manager/cloud/u8/CustomerClassSyncTask.java new file mode 100644 index 0000000..375974c --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/CustomerClassSyncTask.java @@ -0,0 +1,70 @@ +package com.ecep.contract.manager.cloud.u8; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.customer.model.CustomerCatalog; +import com.ecep.contract.manager.ds.customer.service.CompanyCustomerService; +import javafx.concurrent.Task; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 同步客户分类 + */ +public class CustomerClassSyncTask extends Task { + + public CustomerClassSyncTask() { + updateTitle("用友U8系统-同步客户分类信息"); + } + + @Override + protected Object call() throws Exception { + YongYouU8Service service = SpringApp.getBean(YongYouU8Service.class); + CompanyCustomerService customerService = SpringApp.getBean(CompanyCustomerService.class); + + AtomicInteger counter = new AtomicInteger(0); + updateMessage("读取 U8 系统 CustomerClass 数据表..."); + List> list = service.queryAllCustomerClass(); + int size = list.size(); + updateMessage("总共读取 CustomerClass 数据 " + size + " 条"); + + for (Map map : list) { + if (isCancelled()) { + updateMessage("Cancelled"); + return null; + } + + boolean modified = false; + + String code = (String) map.get("cCCCode"); + String name = (String) map.get("cCCName"); + + CustomerCatalog customerCatalog = customerService.findCatalogByCode(code); + if (customerCatalog == null) { + customerCatalog = new CustomerCatalog(); + updateMessage("新建客户分类:" + code); + modified = true; + } + + if (!Objects.equals(customerCatalog.getCode(), code)) { + customerCatalog.setCode(code); + modified = true; + } + if (!Objects.equals(customerCatalog.getName(), name)) { + customerCatalog.setName(name); + modified = true; + } + + if (modified) { + customerService.save(customerCatalog); + updateMessage("更新" + customerCatalog.getName() + " 信息"); + } + // 更新进度 + updateProgress(counter.incrementAndGet(), size); + } + + return null; + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/CustomerSyncTask.java b/src/main/java/com/ecep/contract/manager/cloud/u8/CustomerSyncTask.java new file mode 100644 index 0000000..6f9bc07 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/CustomerSyncTask.java @@ -0,0 +1,144 @@ +package com.ecep.contract.manager.cloud.u8; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.cloud.u8.ctx.CompanyCtx; +import com.ecep.contract.manager.cloud.u8.ctx.CustomerCtx; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.contract.tasker.AbstContractRepairTasker; +import com.ecep.contract.manager.ds.customer.model.CompanyCustomer; +import com.ecep.contract.manager.ds.customer.model.CompanyCustomerEntity; +import com.ecep.contract.manager.ds.customer.service.CompanyCustomerService; +import com.ecep.contract.manager.ui.MessageHolder; +import lombok.Setter; +import org.hibernate.Hibernate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; + +import java.time.Instant; +import java.time.LocalDate; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +/** + * 同步客户任务 + */ +public class CustomerSyncTask extends AbstContractRepairTasker { + private static final Logger logger = LoggerFactory.getLogger(CustomerSyncTask.class); + private YongYouU8Repository repository; + private final CustomerCtx customerCtx = new CustomerCtx(); + @Setter + private YongYouU8Service yongYouU8Service; + + public CustomerSyncTask() { + updateTitle("用友U8系统-同步客户"); + } + + + private CompanyCustomerService getCompanyCustomerService() { + return customerCtx.getCompanyCustomerService(); + } + + @Override + protected void repair(MessageHolder holder) { + try { + yongYouU8Service = getBean(YongYouU8Service.class); + } catch (BeansException e) { + holder.warn("未启用 " + YongYouU8Service.NAME + " 服务"); + return; + } + repository = SpringApp.getBean(YongYouU8Repository.class); + yongYouU8Service.initialize(customerCtx); + + AtomicInteger counter = new AtomicInteger(0); + Long total = repository.countAllCustomers(); + updateTitle("用友U8系统-同步客户,合计 " + total + " 条"); + try (Stream> stream = repository.queryAllCustomerForStream()) { + stream.forEach(rs -> { + if (isCancelled()) { + updateMessage("Cancelled"); + return; + } + try { + String cusCode = (String) rs.get("cCusCode"); + MessageHolder subHolder = holder.sub(counter.get() + " / " + total + "> " + cusCode + " "); + boolean modified = false; + CompanyCustomerEntity entity = customerCtx.findOrCreateByCode(rs, cusCode, subHolder); + Map map = repository.findCustomerByCusCode(entity.getCode()); + if (map == null) { + subHolder.error("客户项不存在:" + cusCode); + return; + } + if (customerCtx.applyEntityDetail(entity, map, subHolder)) { + modified = true; + } + if (modified) { + entity = customerCtx.getCompanyCustomerEntityService().save(entity); + } + updateCompanyNameAndAbbName(entity, subHolder); + updateCloudYu(entity); + + } catch (Exception e) { + logger.error("Customer sync failure {}, data = {}", e.getMessage(), rs, e); + updateMessage(e.getMessage()); + throw e; + } + updateProgress(counter.incrementAndGet(), total); + }); + } + } + + private void updateCloudYu(CompanyCustomerEntity entity) { + CompanyCustomer customer = entity.getCustomer(); + if (customer == null) { + return; + } + if (!Hibernate.isInitialized(customer)) { + customer = getCompanyCustomerService().findById(customer.getId()); + } + Company company = customer.getCompany(); + if (company == null) { + return; + } + CloudYu cloudYu = yongYouU8Service.getOrCreateCloudYu(company); + if (cloudYu == null) { + return; + } + cloudYu.setCustomerUpdateDate(LocalDate.now()); + cloudYu.setCloudLatest(Instant.now()); + cloudYu.setExceptionMessage(""); + yongYouU8Service.save(cloudYu); + + } + + private void updateCompanyNameAndAbbName(CompanyCustomerEntity entity, MessageHolder holder) { + CompanyService companyService = customerCtx.getCompanyService(); + CompanyCustomer customer = entity.getCustomer(); + if (customer == null) { + return; + } + if (!Hibernate.isInitialized(customer)) { + customer = getCompanyCustomerService().findById(customer.getId()); + } + Company company = customer.getCompany(); + if (company == null) { + return; + } + if (!Hibernate.isInitialized(company)) { + company = companyService.findById(company.getId()); + } + boolean modified = false; + CompanyCtx companyCtx = customerCtx.getCompanyCtx(); + if (companyCtx.updateCompanyNameIfAbsent(company, entity.getName(), holder)) { + modified = true; + } + if (companyCtx.updateCompanyAbbNameIfAbsent(company, entity.getAbbName(), holder)) { + modified = true; + } + if (modified) { + companyService.save(company); + } + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/EmployeesSyncTask.java b/src/main/java/com/ecep/contract/manager/cloud/u8/EmployeesSyncTask.java new file mode 100644 index 0000000..4a80507 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/EmployeesSyncTask.java @@ -0,0 +1,155 @@ +package com.ecep.contract.manager.cloud.u8; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.other.model.Department; +import com.ecep.contract.manager.ds.other.model.Employee; +import com.ecep.contract.manager.ds.other.service.DepartmentService; +import com.ecep.contract.manager.ui.MessageHolder; +import com.ecep.contract.manager.ui.Tasker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 用友U8系统-同步员工信息 + */ +public class EmployeesSyncTask extends Tasker { + private static final Logger logger = LoggerFactory.getLogger(EmployeesSyncTask.class); + private final AtomicInteger counter = new AtomicInteger(0); + + DepartmentService departmentService; + + public EmployeesSyncTask() { + updateTitle("用友U8系统-同步员工信息"); + } + + DepartmentService getDepartmentService() { + if (departmentService == null) { + departmentService = SpringApp.getBean(DepartmentService.class); + } + return departmentService; + } + + @Override + protected Object execute(MessageHolder holder) throws Exception { + YongYouU8Service service = SpringApp.getBean(YongYouU8Service.class); + + holder.debug("读取 U8 系统 Person 数据表..."); + List> list = service.queryAllPerson(); + int size = list.size(); + holder.debug("总共读取 Person 数据 " + size + " 条"); + + for (Map rs : list) { + if (isCancelled()) { + holder.debug("Cancelled"); + return null; + } + + MessageHolder sub = holder.sub(counter.get() + "/" + size + ">"); + sync(rs, sub); + + // 更新进度 + updateProgress(counter.incrementAndGet(), size); + } + return null; + } + + private void sync(Map rs, MessageHolder holder) { + String personCode = (String) rs.get("cPersonCode"); + String personName = (String) rs.get("cPersonName"); + String departmentCode = (String) rs.get("cDepCode"); + String personEmail = (String) rs.get("cPersonEmail"); + String personPhone = (String) rs.get("cPersonPhone"); + java.sql.Timestamp personValidDate = (java.sql.Timestamp) rs.get("dPValidDate"); + java.sql.Timestamp personInValidDate = (java.sql.Timestamp) rs.get("dPInValidDate"); + boolean modified = false; + + + Employee employee = getEmployeeService().findByCode(personCode); + // 按员工代码未匹配时,尝试使用名字去匹配 + if (employee == null) { + employee = getEmployeeService().findByName(personName); + if (employee == null) { + employee = new Employee(); + employee.setCode(personCode); + employee.setName(personName); + employee.setActive(false); + employee.setCreated(LocalDate.now()); + holder.info("创建员工:" + personCode + ", 姓名:" + personName + "."); + +// consumer.accept("员工编号:" + personCode + ", 姓名:" + personName + ", 本地未创建"); +// return; + } + employee.setCode(personCode); + modified = true; + } + + MessageHolder subHolder = holder.sub(personName + ": "); + + + if (!StringUtils.hasText(employee.getName())) { + employee.setName(personName); + modified = true; + } + + if (StringUtils.hasText(departmentCode)) { + Department departmentByCode = getDepartmentService().findByCode(departmentCode); + if (departmentByCode == null) { + subHolder.warn("部门代码:" + departmentCode + "未匹配到部门"); + } else { + if (!Objects.equals(employee.getDepartment(), departmentByCode)) { + employee.setDepartment(departmentByCode); + subHolder.info("更新部门:" + departmentByCode.getName()); + modified = true; + } + } + } + + if (StringUtils.hasText(personPhone)) { + if (!Objects.equals(personPhone, employee.getPhone())) { + employee.setPhone(personPhone); + subHolder.info("更新电话:" + personPhone); + modified = true; + } + } + + if (StringUtils.hasText(personEmail)) { + if (!Objects.equals(personEmail, employee.getEmail())) { + employee.setEmail(personEmail); + subHolder.info("更新Email:" + personEmail); + modified = true; + } + } + + if (personValidDate != null) { + LocalDate localDate = personValidDate.toLocalDateTime().toLocalDate(); + if (!Objects.equals(localDate, employee.getEntryDate())) { + employee.setEntryDate(localDate); + subHolder.info("更新入职日期:" + localDate); + modified = true; + } + } + + if (personInValidDate != null) { + LocalDate localDate = personInValidDate.toLocalDateTime().toLocalDate(); + if (!Objects.equals(localDate, employee.getLeaveDate())) { + employee.setLeaveDate(localDate); + subHolder.info("更新离职日期:" + localDate); + modified = true; + } + } + + if (modified) { + getEmployeeService().save(employee); + subHolder.info("更新保存"); + } else { + subHolder.debug("无更新"); + } + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/U8DataSourceFactory.java b/src/main/java/com/ecep/contract/manager/cloud/u8/U8DataSourceFactory.java new file mode 100644 index 0000000..a320585 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/U8DataSourceFactory.java @@ -0,0 +1,61 @@ +package com.ecep.contract.manager.cloud.u8; + + +import com.ecep.contract.manager.ds.other.service.SysConfService; +import com.zaxxer.hikari.HikariDataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import javax.sql.DataSource; + +@Lazy +@Component +@ConditionalOnBean(YongYouU8Service.class) +public class U8DataSourceFactory { + @Lazy + @Autowired + private SysConfService service; + + public U8DataSourceFactory() { + System.out.println("U8DataSourceFactory.U8DataSourceFactory"); + } + + @Lazy + @Bean + // @ConfigurationProperties(prefix = "u8.datasource") + public DataSource MSSQL_Server() { + System.out.println("U8DataSourceFactory.MSSQL_Server"); + + String host = service.get("u8.db.server.ip", "192.168.1.1"); + String database = service.get("u8.db.database", "UF_DATA"); + String username = service.get("u8.db.server.name", "sa"); + String password = service.get("u8.db.server.password", ""); + boolean encrypt = service.get("u8.db.server.encrypt", false); + boolean trustServerCertificate = service.get("u8.db.server.trustServerCertificate", true); + + String url = "jdbc:sqlserver://" + + host + + ";databaseName=" + + database + + ";encrypt=" + + encrypt + + ";trustServerCertificate=" + + trustServerCertificate +// + ";sslProtocol=TLSv1" + ; + + HikariDataSource source = DataSourceBuilder.create() + .type(HikariDataSource.class) + .url(url) + .username(username) + .password(password) + .driverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver") + .build(); + source.setMinimumIdle(0); + return source; + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/VendorClassSyncTask.java b/src/main/java/com/ecep/contract/manager/cloud/u8/VendorClassSyncTask.java new file mode 100644 index 0000000..300c33e --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/VendorClassSyncTask.java @@ -0,0 +1,70 @@ +package com.ecep.contract.manager.cloud.u8; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.vendor.model.VendorCatalog; +import com.ecep.contract.manager.ds.vendor.repository.VendorClassRepository; +import javafx.concurrent.Task; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 同步供应商分类 + */ +public class VendorClassSyncTask extends Task { + + public VendorClassSyncTask() { + updateTitle("用友U8系统-同步供应商分类信息"); + } + + @Override + protected Object call() throws Exception { + YongYouU8Service service = SpringApp.getBean(YongYouU8Service.class); + VendorClassRepository groupRepository = SpringApp.getBean(VendorClassRepository.class); + + AtomicInteger counter = new AtomicInteger(0); + updateMessage("读取 U8 系统 VendorClass 数据表..."); + List> list = service.queryAllVendorClass(); + int size = list.size(); + updateMessage("总共读取 VendorClass 数据 " + size + " 条"); + + for (Map map : list) { + if (isCancelled()) { + updateMessage("Cancelled"); + return null; + } + + boolean modified = false; + + String code = (String) map.get("cVCCode"); + String name = (String) map.get("cVCName"); + + VendorCatalog vendorCatalog = groupRepository.findByCode(code).orElse(null); + if (vendorCatalog == null) { + vendorCatalog = new VendorCatalog(); + updateMessage("新建供应商分类:" + code); + modified = true; + } + + if (!Objects.equals(vendorCatalog.getCode(), code)) { + vendorCatalog.setCode(code); + modified = true; + } + if (!Objects.equals(vendorCatalog.getName(), name)) { + vendorCatalog.setName(name); + modified = true; + } + + if (modified) { + groupRepository.save(vendorCatalog); + updateMessage("更新" + vendorCatalog.getName() + " 信息"); + } + // 更新进度 + updateProgress(counter.incrementAndGet(), size); + } + + return null; + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/VendorSyncTask.java b/src/main/java/com/ecep/contract/manager/cloud/u8/VendorSyncTask.java new file mode 100644 index 0000000..544bc51 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/VendorSyncTask.java @@ -0,0 +1,157 @@ +package com.ecep.contract.manager.cloud.u8; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.cloud.u8.ctx.CompanyCtx; +import com.ecep.contract.manager.cloud.u8.ctx.VendorCtx; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.contract.tasker.AbstContractRepairTasker; +import com.ecep.contract.manager.ds.vendor.model.CompanyVendor; +import com.ecep.contract.manager.ds.vendor.model.CompanyVendorEntity; +import com.ecep.contract.manager.ds.vendor.service.CompanyVendorEntityService; +import com.ecep.contract.manager.ds.vendor.service.CompanyVendorService; +import com.ecep.contract.manager.ui.MessageHolder; +import lombok.Setter; +import org.hibernate.Hibernate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; + +import java.time.Instant; +import java.time.LocalDate; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +/** + * 供应商同步任务 + */ +public class VendorSyncTask extends AbstContractRepairTasker { + private static final Logger logger = LoggerFactory.getLogger(VendorSyncTask.class); + private final VendorCtx vendorCtx = new VendorCtx(); + private CompanyVendorService companyVendorService; + private CompanyVendorEntityService vendorEntityService; + private YongYouU8Repository repository; + @Setter + private YongYouU8Service yongYouU8Service; + + public VendorSyncTask() { + updateTitle("用友U8系统-同步供应商"); + } + + private CompanyVendorService getCompanyVendorService() { + if (companyVendorService == null) { + companyVendorService = SpringApp.getBean(CompanyVendorService.class); + } + return companyVendorService; + } + + private CompanyVendorEntityService getVendorEntityService() { + if (vendorEntityService == null) { + vendorEntityService = SpringApp.getBean(CompanyVendorEntityService.class); + } + return vendorEntityService; + } + + @Override + protected void repair(MessageHolder holder) { + try { + yongYouU8Service = getBean(YongYouU8Service.class); + } catch (BeansException e) { + holder.warn("未启用 " + YongYouU8Service.NAME + " 服务"); + return; + } + repository = SpringApp.getBean(YongYouU8Repository.class); + yongYouU8Service.initialize(vendorCtx); + + AtomicInteger counter = new AtomicInteger(0); + Long total = repository.countAllVendors(); + updateTitle("用友U8系统-同步供应商,合计 " + total + " 条"); + + try (Stream> stream = repository.queryAllVendorForStream()) { + stream.forEach(rs -> { + if (isCancelled()) { + updateMessage("Cancelled"); + return; + } + try { + String venCode = (String) rs.get("cVenCode"); + MessageHolder subHolder = holder.sub(counter.get() + " / " + total + "> " + venCode + " "); + boolean modified = false; + CompanyVendorEntity entity = vendorCtx.findOrCreateByCode(venCode, subHolder); + Map map = repository.findVendorByVendCode(entity.getCode()); + if (map == null) { + subHolder.error("供应商项不存在:" + venCode); + return; + } + if (vendorCtx.applyEntityDetail(entity, map, subHolder)) { + modified = true; + } + if (modified) { + entity = getVendorEntityService().save(entity); + } + updateCompanyNameAndAbbName(entity, subHolder); + updateCloudYu(entity); + + } catch (Exception e) { + logger.error("Vendor sync failure {}, data = {}", e.getMessage(), rs, e); + updateMessage(e.getMessage()); + throw e; + } + updateProgress(counter.incrementAndGet(), total); + }); + } + } + + private void updateCloudYu(CompanyVendorEntity entity) { + CompanyVendor vendor = entity.getVendor(); + if (vendor == null) { + return; + } + if (!Hibernate.isInitialized(vendor)) { + vendor = getCompanyVendorService().findById(vendor.getId()); + } + Company company = vendor.getCompany(); + if (company == null) { + return; + } + CloudYu cloudYu = yongYouU8Service.getOrCreateCloudYu(company); + if (cloudYu == null) { + return; + } + cloudYu.setVendorUpdateDate(LocalDate.now()); + cloudYu.setCloudLatest(Instant.now()); + cloudYu.setExceptionMessage(""); + yongYouU8Service.save(cloudYu); + } + + private void updateCompanyNameAndAbbName(CompanyVendorEntity entity, MessageHolder holder) { + CompanyService companyService = vendorCtx.getCompanyService(); + CompanyVendor vendor = entity.getVendor(); + if (vendor == null) { + return; + } + if (!Hibernate.isInitialized(vendor)) { + vendor = getCompanyVendorService().findById(vendor.getId()); + } + Company company = vendor.getCompany(); + if (company == null) { + return; + } + if (!Hibernate.isInitialized(company)) { + company = companyService.findById(company.getId()); + } + boolean modified = false; + CompanyCtx companyCtx = vendorCtx.getCompanyCtx(); + if (companyCtx.updateCompanyNameIfAbsent(company, entity.getName(), holder)) { + modified = true; + } + if (companyCtx.updateCompanyAbbNameIfAbsent(company, entity.getAbbName(), holder)) { + modified = true; + } + if (modified) { + companyService.save(company); + } + } + +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/YongYouU8ManagerSkin.java b/src/main/java/com/ecep/contract/manager/cloud/u8/YongYouU8ManagerSkin.java new file mode 100644 index 0000000..5efd80c --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/YongYouU8ManagerSkin.java @@ -0,0 +1,135 @@ +package com.ecep.contract.manager.cloud.u8; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.company.controller.CompanyTableCell; +import com.ecep.contract.manager.ds.company.controller.CompanyWindowController; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ui.AbstEntityManagerSkin; +import com.ecep.contract.manager.ui.ManagerSkin; +import com.ecep.contract.manager.ui.util.LocalDateTimeTableCell; +import jakarta.persistence.criteria.Path; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import lombok.Setter; +import org.hibernate.Hibernate; +import org.springframework.data.domain.Page; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.util.StringUtils; + +import java.util.List; + +public class YongYouU8ManagerSkin + extends AbstEntityManagerSkin + implements ManagerSkin { + @Setter + private YongYouU8Service u8Service; + @Setter + private CompanyService companyService; + + public YongYouU8ManagerSkin(YongYouU8ManagerWindowController controller) { + super(controller); + } + + YongYouU8Service getU8Service() { + if (u8Service == null) { + u8Service = SpringApp.getBean(YongYouU8Service.class); + } + return u8Service; + } + + CompanyService getCompanyService() { + if (companyService == null) { + companyService = SpringApp.getBean(CompanyService.class); + } + return companyService; + } + + @Override + protected List loadTableData() { + String searchText = controller.searchKeyField.getText(); + Specification spec = null; + if (StringUtils.hasText(searchText)) { + + Specification companySpec = (root, query, builder) -> { + Path company = root.get("company"); + return builder.or( + builder.like(company.get("name"), "%" + searchText + "%"), + builder.like(company.get("shortName"), "%" + searchText + "%") + ); + }; + + Specification cloudIdSpec = (root, query, builder) -> { + return builder.like(root.get("cloudId"), "%" + searchText + "%"); + }; + + Specification exceptionSpec = (root, query, builder) -> { + return builder.like(root.get("exceptionMessage"), "%" + searchText + "%"); + }; + spec = Specification.anyOf(companySpec, cloudIdSpec, exceptionSpec); + } + + Page page = getU8Service().findAll(spec, getPageable()); + updateFooter(page); + return page.map(CloudYuInfoViewModel::from).toList(); + } + + @Override + public void initializeTable() { + controller.idColumn.setCellValueFactory(param -> param.getValue().getId()); + controller.companyColumn.setCellValueFactory(param -> param.getValue().getCompany()); + controller.companyColumn.setCellFactory(param -> new CompanyTableCell<>(getCompanyService())); + + controller.cloudIdColumn.setCellValueFactory(param -> param.getValue().getCloudId()); + + controller.latestUpdateColumn.setCellValueFactory(param -> param.getValue().getLatest()); + controller.latestUpdateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); + + controller.cloudLatestColumn.setCellValueFactory(param -> param.getValue().getCloudLatest()); + controller.cloudLatestColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); + + controller.descriptionColumn.setCellValueFactory(param -> param.getValue().getVendorCode()); + } + + @Override + protected void createContextMenu(ContextMenu contextMenu) { + MenuItem item2 = new MenuItem("刷新"); + item2.setOnAction(this::onTableRefreshAction); + + MenuItem item3 = new MenuItem("清空备注"); + item3.setOnAction(this::onTableClearDescriptionAction); + + contextMenu.getItems().addAll(item2, item3); + } + + /** + * 请空选择行的注释 + * + * @param event event + */ + public void onTableClearDescriptionAction(ActionEvent event) { + ObservableList selectedItems = getTableView().getSelectionModel().getSelectedItems(); + if (selectedItems.isEmpty()) { + return; + } + for (CloudYuInfoViewModel selectedItem : selectedItems) { + CloudYu cloudYu = getU8Service().findById(selectedItem.getId().get()); + selectedItem.getCustomerCode().set(""); + if (selectedItem.copyTo(cloudYu)) { + CloudYu saved = getU8Service().save(cloudYu); + selectedItem.update(saved); + } + } + } + + @Override + protected void onTableRowDoubleClickedAction(CloudYuInfoViewModel item) { + Company company = item.getCompany().get(); + if (!Hibernate.isInitialized(item)) { + company = getCompanyService().findById(company.getId()); + } + CompanyWindowController.show(company, getTableView().getScene().getWindow()); + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/YongYouU8ManagerWindowController.java b/src/main/java/com/ecep/contract/manager/cloud/u8/YongYouU8ManagerWindowController.java new file mode 100644 index 0000000..75dddf8 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/YongYouU8ManagerWindowController.java @@ -0,0 +1,191 @@ +package com.ecep.contract.manager.cloud.u8; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.cloud.CloudBaseInfo; +import com.ecep.contract.manager.cloud.CloudInfo; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ui.AbstManagerWindowController; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.Message; +import com.ecep.contract.manager.ui.ViewModelService; +import com.ecep.contract.manager.util.UITools; +import javafx.concurrent.Task; +import javafx.event.ActionEvent; +import javafx.scene.control.TableColumn; +import javafx.stage.Stage; +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.time.LocalDateTime; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledExecutorService; +import java.util.function.Consumer; + +@Lazy +@Scope("prototype") +@Component +@FxmlPath("/ui/cloud/u8_manager.fxml") +public class YongYouU8ManagerWindowController + extends AbstManagerWindowController { + private static final Logger logger = LoggerFactory.getLogger(YongYouU8ManagerWindowController.class); + + public static void show() { + show(YongYouU8ManagerWindowController.class, null); + } + + @Autowired + private ScheduledExecutorService scheduledExecutorService; + + public TableColumn idColumn; + public TableColumn latestUpdateColumn; + public TableColumn companyColumn; + public TableColumn cloudIdColumn; + public TableColumn cloudLatestColumn; + public TableColumn descriptionColumn; + + @Override + public YongYouU8Service getViewModelService() { + return getSkin().getU8Service(); + } + + @Override + protected YongYouU8ManagerSkin createDefaultSkin() { + return new YongYouU8ManagerSkin(this); + } + + + @Override + public void show(Stage stage) { + super.show(stage); + getTitle().set("数据源:用友U8"); + } + + /** + * 数据迁移,从 CloudInfo 迁移到 CloudRk + */ + public void onDateTransferAction(ActionEvent event) { + + CompletableFuture.runAsync(() -> { + com.ecep.contract.manager.cloud.CloudInfoRepository cloudInfoRepository = SpringApp.getBean(com.ecep.contract.manager.cloud.CloudInfoRepository.class); + YongYouU8Service u8Service = SpringApp.getBean(YongYouU8Service.class); + + cloudInfoRepository.findAll().forEach(v -> { + try { + CloudYu cloudYu = u8Service.getOrCreateCloudYu(v); + if (copyTo(v, cloudYu)) { + u8Service.save(cloudYu); + } + } catch (Exception e) { + setStatus(e.getMessage()); + e.printStackTrace(); + } + }); + + }); + } + + boolean copyTo(CloudInfo v, CloudBaseInfo cloudRk) { + boolean modified = false; + if (!Objects.equals(cloudRk.getLatestUpdate(), v.getLatestUpdate())) { + cloudRk.setLatestUpdate(v.getLatestUpdate()); + modified = true; + } + + if (!Objects.equals(cloudRk.getCloudId(), v.getCloudId())) { + cloudRk.setCloudId(v.getCloudId()); + modified = true; + } + + if (!Objects.equals(cloudRk.getCompany(), v.getCompany())) { + cloudRk.setCompany(v.getCompany()); + modified = true; + } + return modified; + } + + + private > void initializeTask(T task, String prefix, Consumer consumer) { + task.setOnScheduled(e -> { + consumer.accept("正在从用友U8同步" + 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); + }); + scheduledExecutorService.submit(task); + consumer.accept("同步任务已创建..."); + } + + public void onPersonSyncAction(ActionEvent event) { + EmployeesSyncTask task = new EmployeesSyncTask(); + UITools.showTaskDialogAndWait("员工数据同步", task, null); + } + + public void onContractSyncAction(ActionEvent event) { + ContractSyncTask task = new ContractSyncTask(); + UITools.showTaskDialogAndWait("合同数据增量同步", task, null); + } + + public void onContractAllSyncAction(ActionEvent event) { + ContractSyncAllTask task = new ContractSyncAllTask(); + UITools.showTaskDialogAndWait("合同数据全量同步", task, null); + } + + public void onVendorSyncAction(ActionEvent event) { + VendorSyncTask task = new VendorSyncTask(); + UITools.showTaskDialogAndWait("供应商数据同步", task, null); + } + + public void onCustomerSyncAction(ActionEvent event) { + CustomerSyncTask task = new CustomerSyncTask(); + UITools.showTaskDialogAndWait("客户数据同步", task, null); + } + + public void onContractGroupSyncAction(ActionEvent event) { + Task task = new ContractGroupSyncTask(); + UITools.showTaskDialogAndWait("合同分组数据同步", task, consumer -> { + initializeTask(task, "合同分组数据", msg -> consumer.accept(Message.info(msg))); + }); + } + + public void onContractTypeSyncAction(ActionEvent event) { + Task task = new ContractTypeSyncTask(); + UITools.showTaskDialogAndWait("合同分类数据同步", task, consumer -> { + initializeTask(task, "合同分类数据", msg -> consumer.accept(Message.info(msg))); + }); + } + + public void onContractKindSyncAction(ActionEvent event) { + Task task = new ContractKindSyncTask(); + UITools.showTaskDialogAndWait("合同类型数据同步", task, consumer -> { + initializeTask(task, "合同类型数据", msg -> consumer.accept(Message.info(msg))); + }); + } + + public void onVendorClassSyncAction(ActionEvent event) { + Task task = new VendorClassSyncTask(); + UITools.showTaskDialogAndWait("供应商分类数据同步", task, consumer -> { + initializeTask(task, "供应商分类数据", msg -> consumer.accept(Message.info(msg))); + }); + } + + public void onCustomerClassSyncAction(ActionEvent event) { + Task task = new CustomerClassSyncTask(); + UITools.showTaskDialogAndWait("客户分类数据同步", task, consumer -> { + initializeTask(task, "客户分类数据", msg -> consumer.accept(Message.info(msg))); + }); + } + + +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/YongYouU8Repository.java b/src/main/java/com/ecep/contract/manager/cloud/u8/YongYouU8Repository.java new file mode 100644 index 0000000..51a3c68 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/YongYouU8Repository.java @@ -0,0 +1,377 @@ +package com.ecep.contract.manager.cloud.u8; + +import com.ecep.contract.manager.ds.contract.ContractPayWay; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.context.annotation.Lazy; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.ColumnMapRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +import javax.sql.DataSource; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +@Lazy +@Repository +@ConditionalOnBean(YongYouU8Service.class) +public class YongYouU8Repository { + private JdbcTemplate jdbcTemplate; + @Lazy + @Autowired + @Qualifier("MSSQL_Server") + private DataSource dataSource; + + private JdbcTemplate getJdbcTemplate() { + if (jdbcTemplate == null) { + jdbcTemplate = new JdbcTemplate(dataSource); + } + return jdbcTemplate; + } + + + /** + * 返回 U8 系统中 供应商总数 + * + * @return 供应商总数 + */ + public Long countAllVendors() { + return getJdbcTemplate().queryForObject("select count(*) from Vendor", Long.class); + } + + /** + * 以流式返回所有供应商数据 + * + * @return Stream 数据流 + */ + public Stream> queryAllVendorForStream() { + return getJdbcTemplate().queryForStream( + "select cVenCode,cVenName,cVenAbbName," + + "cVCCode,cVenAddress,cast(dVenDevDate as DATE) as venDevDate," + + "cVenBank, cVenAccount, cCreatePerson,cModifyPerson,dModifyDate " + + "from Vendor", + new ColumnMapRowMapper() + ); + } + + + /** + * 以 U8 供应商 的代码查询 供应商 + * + * @param vendCode U8 供应商 的代码,vendCode 唯一 + * @return 字段和值的Map集合, 没找到匹配的记录时,返回null + */ + public Map findVendorByVendCode(String vendCode) { + try { + return getJdbcTemplate().queryForObject("select cVenCode,cVenName,cVenAbbName," + + "cVCCode,cVenAddress," + + "cast(dVenDevDate as DATE) as devDate,dModifyDate," + + "cVenBank,cVenAccount,cCreatePerson, cModifyPerson " + + "from Vendor where cVenCode=?", new ColumnMapRowMapper(), vendCode); + } catch (EmptyResultDataAccessException e) { + return null; + } catch (Exception e) { + throw new RuntimeException(vendCode, e); + } + } + + /** + * 返回 U8 系统中 客户总数 + * + * @return 客户总数 + */ + public Long countAllCustomers() { + return getJdbcTemplate().queryForObject("select count(*) from Customer", Long.class); + } + + /** + * 以流式返回所有客户数据 + * + * @return Stream 数据流 + */ + public Stream> queryAllCustomerForStream() { + return getJdbcTemplate().queryForStream( + "select cCusCode,cCusName,cCusAbbName," + + "cCCCode,cCusAddress,cast(dCusDevDate as DATE) as cusDevDate," + + "cCusBank,cCusAccount, cCreatePerson,cModifyPerson,dModifyDate " + + "from Customer", + new ColumnMapRowMapper() + ); + } + + /** + * 以 U8 客户 的代码查询 客户 + * + * @param cusCode U8 客户 的代码,cusCode 唯一 + * @return 字段和值的Map集合 + */ + public Map findCustomerByCusCode(String cusCode) { + return getJdbcTemplate().queryForObject("select cCusCode,cCusName,cCusAbbName," + + "cCCCode,cCusAddress," + + "cast(dCusDevDate as DATE) as devDate,dModifyDate," + + "cCusBank,cCusAccount,cCreatePerson,cModifyPerson " + + "from Customer where cCusCode=?", new ColumnMapRowMapper(), cusCode); + } + + /** + * 返回 U8 系统中 合同总数 + * 在U8中合同在多个地方存在,CM_List, CM_Contract_A,CM_Contract_B,CM_Contract_C + * + * @return 合同总数 + */ + public Long countAllContracts(int latestId) { + return getJdbcTemplate().queryForObject("select count(*) from CM_List where ID>?", Long.class, latestId); + } + + public Long countAllContracts(LocalDateTime dateTime) { + return getJdbcTemplate().queryForObject("select count(*) from CM_List where dtDate>?", Long.class, dateTime); + } + + public Long countAllContracts() { + return getJdbcTemplate().queryForObject("select count(*) from CM_List", Long.class); + } + + public Long countAllContractB() { + return getJdbcTemplate().queryForObject("select count(*) from CM_Contract_B", Long.class); + } + + public Long countAllContractB(LocalDate latestDate) { + return getJdbcTemplate().queryForObject( + "select count(*) " + + "from CM_Contract_B " + + "where strSetupDate>=? or (dtVaryDate is not null and dtVaryDate>=?)" + , Long.class, latestDate, latestDate); + } + + public Stream> queryAllContractForStream() { + return getJdbcTemplate().queryForStream( + "select * from CM_List", new ColumnMapRowMapper() + ); + } + + public Stream> queryAllContractForStream(int latestId) { + return getJdbcTemplate().queryForStream( + "select * from CM_List where ID > ?", + new ColumnMapRowMapper(), + latestId + ); + } + + public Stream> queryAllContractForStream(LocalDateTime dateTime) { + return getJdbcTemplate().queryForStream( + "select * from CM_List where dtDate > ?", + new ColumnMapRowMapper(), + dateTime + ); + } + + public Stream> queryAllContractBForStream() { + return getJdbcTemplate().queryForStream( + "select GUID,strContractID,strContractName,strContractType,strParentID,strContractKind,strWay," + + "strContractGrp,strContractDesc,strBisectionUnit," + + // 时间 + "strContractOrderDate,strContractStartDate,strContractEndDate," + + // 创建人和时间 + "strSetupPerson,strSetupDate," + + // 生效人和时间 + "strInurePerson,strInureDate," + + // 修改人和时间 + "strVaryPerson,dtVaryDate," + + // 业务员 + "strPersonID, " + + "dblTotalCurrency,dblExecCurrency,dblTotalQuantity,dblExecQuqantity " + + "from CM_Contract_B ", + new ColumnMapRowMapper() + ); + } + + public Stream> queryAllContractBForStream(LocalDate latestDate) { + return getJdbcTemplate().queryForStream( + "select GUID,strContractID,strContractName,strContractType,strParentID,strContractKind,strWay," + + "strContractGrp,strContractDesc,strBisectionUnit," + + // 时间 + "strContractOrderDate,strContractStartDate,strContractEndDate," + + // 创建人和时间 + "strSetupPerson,strSetupDate," + + // 生效人和时间 + "strInurePerson,strInureDate," + + // 修改人和时间 + "strVaryPerson,dtVaryDate," + + // 业务员 + "strPersonID, " + + "dblTotalCurrency,dblExecCurrency,dblTotalQuantity,dblExecQuqantity " + + "from CM_Contract_B where strSetupDate>=? or (dtVaryDate is not null and dtVaryDate>=?)", + new ColumnMapRowMapper(), latestDate, latestDate + ); + } + + /** + * 从 CM_Contract_B 表中,检索 strBisectionUnit 是 unit 的记录 + * + * @param unit 单位编码 + * @param payWay 付款方向,付 或 收 + */ + public Stream> queryAllContractByUnitForStream(String unit, ContractPayWay payWay) { + return getJdbcTemplate().queryForStream( + "select GUID,strContractID,strContractName,strContractType,strParentID,strContractKind,strWay," + + "strContractGrp, strContractDesc,strBisectionUnit," + + // 时间 + "strContractOrderDate,strContractStartDate,strContractEndDate," + + // 创建人和时间 + "strSetupPerson,strSetupDate," + + // 生效人和时间 + "strInurePerson,strInureDate," + + // 修改人和时间 + "strVaryPerson,dtVaryDate," + + // 业务员 + "strPersonID, " + + "dblTotalCurrency,dblExecCurrency,dblTotalQuantity,dblExecQuqantity " + + "from CM_Contract_B where strBisectionUnit=? and strWay=?", + new ColumnMapRowMapper(), unit, payWay.getText() + ); + } + + public Map queryContractDetail(String guid) { + return getJdbcTemplate().queryForObject("select GUID,strContractID,strContractName,strContractType,strParentID,strContractKind,strWay," + + "strContractGrp, strContractDesc,strBisectionUnit," + + // 时间 + "strContractOrderDate,strContractStartDate,strContractEndDate," + + // 创建人和时间 + "strSetupPerson,strSetupDate," + + // 生效人和时间 + "strInurePerson,strInureDate," + + // 修改人和时间 + "strVaryPerson,dtVaryDate," + + // 业务员 + "strPersonID, " + + "dblTotalCurrency,dblExecCurrency,dblTotalQuantity,dblExecQuqantity " + + " from CM_Contract_B where GUID = ?", new ColumnMapRowMapper(), guid); + } + + /** + * 合计合同的执行数量、金额、 不含税金额 + * + * @param code 合同号 + * @return Map + */ + public Map sumContractExec(String code) { + return getJdbcTemplate().queryForObject("select sum(decCount) as execQuantity, sum(decRateMoney) as execAmount, sum(decNoRateMoney) as execUnTaxAmount " + + "from CM_ExecInterface " + + "where cContractID = ?;", new ColumnMapRowMapper(), code); + } + + public List> findAllContractExecByContractCode(String code) { + return getJdbcTemplate().queryForList("select * from CM_ExecInterface where cContractID=?", code); + } + + public List> findAllContractItemByContractCode(String code) { + return getJdbcTemplate().queryForList("select * from CM_Contract_Item_B where strContractID=?", code); + } + + public List> findAllContractPayPlanByContractCode(String code) { + return getJdbcTemplate().queryForList("select * from CM_Contract_Pay where strContractID=?", code); + } + + public Map querySalesOrderDetail(String code) { + return getJdbcTemplate().queryForObject("select * " + + " from SO_SOMain where cSOCode = ?", new ColumnMapRowMapper(), code); + } + + public List> findAllSalesOrderItemByContractCode(String code) { + return getJdbcTemplate().queryForList("select * from SO_SODetails where cContractID=?", code); + } + + public Map queryPurchaseOrderDetail(Integer code) { + return getJdbcTemplate().queryForObject("select * " + + " from PO_Pomain where POID = ?", new ColumnMapRowMapper(), code); + } + + + /** + * 采购合同项 + * + * @param code 采购合同号 + * @return List> + */ + public List> findAllPurchaseOrderItemByContractCode(String code) { + return getJdbcTemplate().queryForList("select * from PO_Podetails where ContractCode=?", code); + } + + /** + * 查询存货档案数据 + * + * @param code 存货编码 + * @return Map + */ + public Map queryInventoryDetail(String code) { + return getJdbcTemplate().queryForObject("select I.*, U.cComUnitName from Inventory as I left join ComputationUnit as U on I.cComUnitCode=U.cComunitCode where cInvCode = ?", new ColumnMapRowMapper(), code); + } + + public List> queryAllPerson() { + return getJdbcTemplate().queryForList("select * from Person;"); + } + + public List> queryAllContractGroup() { + return getJdbcTemplate().queryForList("select cGroupID, cGroupName,cRemark from CM_Group;"); + } + + public List> queryAllContractType() { + return getJdbcTemplate().queryForList( + "select cTypeCode, cTypeName, CM_TypeClass.cClassName, cCharacter, cDirection " + + "from CM_Type left join CM_TypeClass on CM_Type.cClassCode = CM_TypeClass.cClassCode;"); + } + + public List> queryAllContractKind() { + return getJdbcTemplate().queryForList("select KindID,KindName,Description from CM_Kind;"); + } + + public List> queryAllVendorClass() { + return getJdbcTemplate().queryForList("select cVCCode, cVCName, iVCGrade from VendorClass;"); + } + + public List> queryAllCustomerClass() { + return getJdbcTemplate().queryForList("select cCCCode, cCCName, iCCGrade from CustomerClass;"); + } + + + public List> findAllPurchaseBillVoucherByVendorCode(String code) { + return getJdbcTemplate().queryForList("select * from PurBillVouch where cVenCode=?", code); + } + + public Map findPurchaseBillVoucherById(Integer pbvId) { + return getJdbcTemplate().queryForObject("select * from PurBillVouch where PBVID = ?", new ColumnMapRowMapper(), pbvId); + } + + public List> findAllPurchaseBillVoucherItemByPbvId(int pbvId) { + return getJdbcTemplate().queryForList("select * from PurBillVouchs where PBVID=?", pbvId); + } + + /** + * 通过合同编号查询采购 + * + * @param contractCode 合同编号 + */ + public List> findAllPurchaseBillVoucherItemByContractCode(String contractCode) { + return getJdbcTemplate().queryForList("select * from PurBillVouchs where ContractCode=?", contractCode); + } + + public List> findAllSalesBillVoucherByCustomerCode(String code) { + return getJdbcTemplate().queryForList("select * from SaleBillVouch where cCusCode=?", code); + } + + public List> findAllSalesBillVoucherBySalesOrderCode(String code) { + return getJdbcTemplate().queryForList("select * from SaleBillVouch where cSOCode=?", code); + } + + public List> findAllSalesBillVoucherItemBySBVID(int sbvid) { + return getJdbcTemplate().queryForList("select * from SaleBillVouchs where SBVID=?", sbvid); + } + + + +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/YongYouU8Service.java b/src/main/java/com/ecep/contract/manager/cloud/u8/YongYouU8Service.java new file mode 100644 index 0000000..e8a1f46 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/YongYouU8Service.java @@ -0,0 +1,233 @@ +package com.ecep.contract.manager.cloud.u8; + +import com.ecep.contract.manager.Desktop; +import com.ecep.contract.manager.cloud.CloudInfo; +import com.ecep.contract.manager.cloud.u8.ctx.AbstractYongYouU8Ctx; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.customer.service.CompanyCustomerService; +import com.ecep.contract.manager.ds.other.service.EmployeeService; +import com.ecep.contract.manager.ds.vendor.service.CompanyVendorService; +import com.ecep.contract.manager.ui.ViewModelService; +import com.ecep.contract.manager.ui.task.MonitoredTask; + +import javafx.application.Platform; +import javafx.concurrent.Task; +import org.controlsfx.control.TaskProgressView; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Lazy; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + +@Lazy +@Service +@ConditionalOnProperty(name = "cloud.u8.enabled", havingValue = "true") +public class YongYouU8Service implements ViewModelService { + private static final Logger logger = LoggerFactory.getLogger(YongYouU8Service.class); + public static final String NAME = "用友U8"; + + @Lazy + @Autowired + private YongYouU8Repository repository; + @Lazy + @Autowired + private CompanyService companyService; + @Lazy + @Autowired + private CompanyVendorService companyVendorService; + @Lazy + @Autowired + private CloudYuRepository cloudYuRepository; + @Lazy + @Autowired + private EmployeeService employeeService; + @Lazy + @Autowired + private CompanyCustomerService companyCustomerService; + + public YongYouU8Service() { + + } + + public CloudYu findById(Integer id) { + return cloudYuRepository.findById(id).orElse(null); + } + + /** + * 创建 + * + * @param info 供应商信息 + * @return 供应商对象 + */ + public CloudYu getOrCreateCloudYu(CloudInfo info) { + Optional optional = cloudYuRepository.findById(info.getId()); + return optional.orElseGet(() -> getOrCreateCloudYu(info.getCompany())); + } + + /** + * 创建或获取供应商云信息 + * + * @param company 公司 + * @return 供应商云信息 + */ + public CloudYu getOrCreateCloudYu(Company company) { + Integer companyId = company.getId(); + List list = cloudYuRepository.findAllByCompanyId(companyId); + if (list.isEmpty()) { + CloudYu cloudYu = new CloudYu(); + cloudYu.setCompany(company); + cloudYu.setExceptionMessage(""); + cloudYu.setVendorUpdateDate(null); + cloudYu.setCustomerUpdateDate(null); + cloudYu.setCloudLatest(null); + return cloudYuRepository.save(cloudYu); + } + + // 只有匹配到一条有 CloudId 的记录 + if (list.size() == 1) { + // 保留匹配的记录,其他删除 + CloudYu rk = list.removeFirst(); + list.remove(rk); + cloudYuRepository.deleteAll(list); + return rk; + } + + return list.getFirst(); + } + + /** + * 生成定时同步任务 + * + * @param taskProgressView 任务视图 + */ + public void scheduledTasks(TaskProgressView> taskProgressView) { + ScheduledExecutorService executorService = Desktop.instance.getExecutorService(); + executorService.scheduleAtFixedRate(() -> { + ContractSyncTask task = new ContractSyncTask(); + MonitoredTask registerTask = Desktop.instance.getTaskMonitorCenter().registerTask(task); + executorService.schedule(registerTask, 5, TimeUnit.SECONDS); + }, 3, TimeUnit.MINUTES.toSeconds(15), TimeUnit.SECONDS); + + executorService.scheduleAtFixedRate(() -> { + // 1小时运行一次同步供应商任务 + VendorSyncTask vendorTask = new VendorSyncTask(); + MonitoredTask registerVendorTask = Desktop.instance.getTaskMonitorCenter().registerTask(vendorTask); + executorService.schedule(registerVendorTask, 60, TimeUnit.SECONDS); + + // 1小时运行一次同步客户任务 + CustomerSyncTask customerTask = new CustomerSyncTask(); + MonitoredTask registerCustomerTask = Desktop.instance.getTaskMonitorCenter() + .registerTask(customerTask); + executorService.schedule(registerCustomerTask, 60, TimeUnit.SECONDS); + + }, 3, TimeUnit.HOURS.toSeconds(1), TimeUnit.SECONDS); + + } + + /** + * 保存 Cloud Yu + * + * @param cloudYu Cloud Yu 对象 + * @return 更新的 Cloud Yu + */ + public CloudYu save(CloudYu cloudYu) { + return cloudYuRepository.save(cloudYu); + } + + @Override + public void delete(CloudYu entity) { + cloudYuRepository.delete(entity); + } + + public void deleteByCompany(Company company) { + int deleted = cloudYuRepository.deleteAllByCompany(company); + if (deleted > 0) { + if (logger.isInfoEnabled()) { + logger.info("Delete {} records by company:#{}", deleted, company.getId()); + } + } + } + + public void resetTo(Company from, Company to) { + List list = cloudYuRepository.findAllByCompanyId(from.getId()); + for (CloudYu item : list) { + item.setCompany(to); + } + cloudYuRepository.saveAll(list); + } + + public Page findAll(Specification spec, Pageable pageable) { + return cloudYuRepository.findAll(spec, pageable); + } + + @Override + public Specification getSpecification(String searchText) { + if (!StringUtils.hasText(searchText)) { + return null; + } + return (root, query, builder) -> { + return builder.like(root.get("cloudId"), "%" + searchText + "%"); + }; + } + + public List> queryAllContractKind() { + return repository.queryAllContractKind(); + } + + public List> queryAllVendorClass() { + return repository.queryAllVendorClass(); + } + + public List> queryAllCustomerClass() { + return repository.queryAllCustomerClass(); + } + + public List> queryAllContractType() { + return repository.queryAllContractType(); + } + + public List> queryAllContractGroup() { + return repository.queryAllContractGroup(); + } + + public Long countAllCustomers() { + return repository.countAllCustomers(); + } + + public Stream> queryAllCustomerForStream() { + return repository.queryAllCustomerForStream(); + } + + public List> queryAllPerson() { + return repository.queryAllPerson(); + } + + public Map findVendorByVendCode(String unit) { + return repository.findVendorByVendCode(unit); + } + + public Map findCustomerByCusCode(String unit) { + return repository.findCustomerByCusCode(unit); + } + + public void initialize(AbstractYongYouU8Ctx ctx) { + ctx.setRepository(repository); + ctx.setCompanyService(companyService); + ctx.setEmployeeService(employeeService); + ctx.setCompanyVendorService(companyVendorService); + ctx.setCompanyCustomerService(companyCustomerService); + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/AbstractYongYouU8Ctx.java b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/AbstractYongYouU8Ctx.java new file mode 100644 index 0000000..5c392d4 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/AbstractYongYouU8Ctx.java @@ -0,0 +1,216 @@ +package com.ecep.contract.manager.cloud.u8.ctx; + +import com.ecep.contract.manager.cloud.AbstractCtx; +import com.ecep.contract.manager.cloud.u8.YongYouU8Repository; +import com.ecep.contract.manager.cloud.u8.YongYouU8Service; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.customer.model.CompanyCustomer; +import com.ecep.contract.manager.ds.customer.model.CompanyCustomerEntity; +import com.ecep.contract.manager.ds.customer.service.CompanyCustomerEntityService; +import com.ecep.contract.manager.ds.customer.service.CompanyCustomerService; +import com.ecep.contract.manager.ds.other.model.Employee; +import com.ecep.contract.manager.ds.other.service.EmployeeService; +import com.ecep.contract.manager.ds.vendor.model.CompanyVendor; +import com.ecep.contract.manager.ds.vendor.model.CompanyVendorEntity; +import com.ecep.contract.manager.ds.vendor.service.CompanyVendorEntityService; +import com.ecep.contract.manager.ds.vendor.service.CompanyVendorService; +import com.ecep.contract.manager.ui.MessageHolder; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.Hibernate; +import org.springframework.beans.BeansException; +import org.springframework.util.StringUtils; + +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import static com.ecep.contract.manager.SpringApp.getBean; + +public class AbstractYongYouU8Ctx extends AbstractCtx { + @Getter + @Setter + YongYouU8Repository repository; + @Setter + CompanyService companyService; + @Setter + EmployeeService employeeService; + @Setter + CompanyCustomerService companyCustomerService; + @Setter + CompanyCustomerEntityService companyCustomerEntityService; + @Setter + CompanyVendorService companyVendorService; + @Setter + CompanyVendorEntityService companyVendorEntityService; + + + public void from(AbstractYongYouU8Ctx parent) { + repository = parent.repository; + companyService = parent.companyService; + employeeService = parent.employeeService; + companyCustomerService = parent.companyCustomerService; + companyVendorService = parent.companyVendorService; + } + + public void initializeRepository(MessageHolder holder) { + if (repository != null) { + return; + } + try { + repository = getBean(YongYouU8Repository.class); + } catch (BeansException e) { + holder.warn("未启用 " + YongYouU8Service.NAME + " 服务"); + } + } + + + public CompanyService getCompanyService() { + if (companyService == null) { + companyService = getBean(CompanyService.class); + } + return companyService; + } + + EmployeeService getEmployeeService() { + if (employeeService == null) { + employeeService = getBean(EmployeeService.class); + } + return employeeService; + } + + public CompanyCustomerService getCompanyCustomerService() { + if (companyCustomerService == null) { + companyCustomerService = getBean(CompanyCustomerService.class); + } + return companyCustomerService; + } + + public CompanyCustomerEntityService getCompanyCustomerEntityService() { + if (companyCustomerEntityService == null) { + companyCustomerEntityService = getBean(CompanyCustomerEntityService.class); + } + return companyCustomerEntityService; + } + + public CompanyVendorService getCompanyVendorService() { + if (companyVendorService == null) { + companyVendorService = getBean(CompanyVendorService.class); + } + return companyVendorService; + } + + public CompanyVendorEntityService getCompanyVendorEntityService() { + if (companyVendorEntityService == null) { + companyVendorEntityService = getBean(CompanyVendorEntityService.class); + } + return companyVendorEntityService; + } + + boolean updateEmployeeByCode(Supplier getter, Consumer setter, String code, MessageHolder holder, String topic) { + if (StringUtils.hasText(code)) { + Employee employee = getEmployeeService().findByCode(code); + if (employee != null) { + if (!Objects.equals(getter.get(), employee)) { + setter.accept(employee); + holder.info(topic + "更新为 " + employee.getName()); + return true; + } + } + } + return false; + } + + + boolean updateEmployeeByName(Supplier getter, Consumer setter, String name, MessageHolder holder, String topic) { + if (StringUtils.hasText(name)) { + Employee employee = getEmployeeService().findByName(name); + if (employee != null) { + if (!Objects.equals(getter.get(), employee)) { + setter.accept(employee); + holder.info(topic + "更新为 " + employee.getName()); + return true; + } + } + } + return false; + } + + boolean updateCompanyByCustomerCode(Supplier getter, Consumer setter, String customerCode, MessageHolder holder, String topic) { + Company company = null; + if (StringUtils.hasText(customerCode)) { + CompanyCustomerEntityService customerEntityService = getCompanyCustomerEntityService(); + CompanyCustomerService customerService = getCompanyCustomerService(); + CompanyCustomerEntity entity = customerEntityService.findByCustomerCode(customerCode); + if (entity == null) { + holder.warn("无效" + topic + ":" + customerCode); + } else { + CompanyCustomer customer = entity.getCustomer(); + if (customer == null) { + holder.warn("无效" + topic + ":" + customerCode); + } else { + if (!Hibernate.isInitialized(customer)) { + customer = customerService.findById(customer.getId()); + } + company = customer.getCompany(); + } + } + } + if (company == null) { + setter.accept(null); + holder.warn("无效" + topic + ":" + customerCode); + return true; + } else { + if (!Objects.equals(getter.get(), company)) { + setter.accept(company); + if (!Hibernate.isInitialized(company)) { + company = getCompanyService().findById(company.getId()); + } + holder.info(topic + "修改为: " + company.getName()); + return true; + } + } + return false; + } + + + boolean updateCompanyByVendorCode(Supplier getter, Consumer setter, String vendorCode, MessageHolder holder, String topic) { + Company company = null; + if (StringUtils.hasText(vendorCode)) { + CompanyVendorEntityService vendorEntityService = getCompanyVendorEntityService(); + CompanyVendorService customerService = getCompanyVendorService(); + CompanyVendorEntity entity = vendorEntityService.findByCode(vendorCode); + if (entity == null) { + holder.warn("无效" + topic + ":" + vendorCode); + } else { + CompanyVendor customer = entity.getVendor(); + if (customer == null) { + holder.warn("无效" + topic + ":" + vendorCode); + } else { + if (!Hibernate.isInitialized(customer)) { + customer = customerService.findById(customer.getId()); + } + company = customer.getCompany(); + } + } + } + if (company == null) { + setter.accept(null); + holder.warn("无效" + topic + ":" + vendorCode); + return true; + } else { + if (!Objects.equals(getter.get(), company)) { + setter.accept(company); + if (!Hibernate.isInitialized(company)) { + company = getCompanyService().findById(company.getId()); + } + holder.info(topic + "修改为: " + company.getName()); + return true; + } + } + return false; + } + + +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/CompanyBankAccountCtx.java b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/CompanyBankAccountCtx.java new file mode 100644 index 0000000..6eee14e --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/CompanyBankAccountCtx.java @@ -0,0 +1,24 @@ +package com.ecep.contract.manager.cloud.u8.ctx; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.service.CompanyBankAccountService; +import com.ecep.contract.manager.ui.MessageHolder; +import lombok.Setter; + +import static com.ecep.contract.manager.SpringApp.getBean; + +public class CompanyBankAccountCtx extends AbstractYongYouU8Ctx { + @Setter + private CompanyBankAccountService companyBankAccountService; + + CompanyBankAccountService getCompanyBankAccountService() { + if (companyBankAccountService == null) { + companyBankAccountService = getBean(CompanyBankAccountService.class); + } + return companyBankAccountService; + } + + public void updateBankAccount(Company company, String bank, String bankAccount, MessageHolder holder) { + getCompanyBankAccountService().updateBankAccount(company, bank, bankAccount, holder); + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/CompanyCtx.java b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/CompanyCtx.java new file mode 100644 index 0000000..9acf2e2 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/CompanyCtx.java @@ -0,0 +1,128 @@ +package com.ecep.contract.manager.cloud.u8.ctx; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.cloud.u8.YongYouU8Service; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyOldName; +import com.ecep.contract.manager.ds.company.service.CompanyOldNameService; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.other.service.SysConfService; +import com.ecep.contract.manager.ui.MessageHolder; +import lombok.Setter; +import org.springframework.util.StringUtils; + +import java.time.LocalDate; +import java.util.List; +import java.util.Objects; + +import static com.ecep.contract.manager.SpringApp.getBean; + +public class CompanyCtx extends AbstractYongYouU8Ctx { + public static final String AUTO_CREATE_COMPANY_AFTER = "cloud.u8.auto-create-company-after"; + + @Setter + private CompanyOldNameService companyOldNameService; + + CompanyOldNameService getCompanyOldNameService() { + if (companyOldNameService == null) { + companyOldNameService = getBean(CompanyOldNameService.class); + } + return companyOldNameService; + } + + public boolean updateCompanyNameIfAbsent(Company company, String name, MessageHolder holder) { + if (!StringUtils.hasText(name)) { + return false; + } + if (StringUtils.hasText(company.getName())) { + if (Objects.equals(company.getName(), name)) { + return false; + } + appendOldNameIfAbsent(company, name, false, holder); + return false; + } else { + return updateText(company::getName, company::setName, name, holder, "公司名称"); + } + } + + + public boolean updateCompanyAbbNameIfAbsent(Company company, String abbName, MessageHolder holder) { + if (!StringUtils.hasText(abbName)) { + return false; + } + // 简称与公司名相同,跳过 + if (Objects.equals(company.getName(), abbName)) { + return false; + } + if (StringUtils.hasText(company.getShortName())) { + if (Objects.equals(company.getShortName(), abbName)) { + return false; + } + appendOldNameIfAbsent(company, abbName, true, holder); + return false; + } else { + return updateText(company::getShortName, company::setShortName, abbName, holder, "公司简称"); + } + } + + private void appendOldNameIfAbsent(Company company, String name, boolean ambiguity, MessageHolder holder) { + List oldNames = getCompanyOldNameService().findAllByCompanyAndName(company, name); + if (oldNames.isEmpty()) { + CompanyOldName companyOldName = getCompanyOldNameService().createNew(company, name, ambiguity); + companyOldName.setMemo("来自" + YongYouU8Service.NAME); + getCompanyOldNameService().save(companyOldName); + holder.info("新增曾用名:" + name); + } + } + + public boolean syncCompany(Company company, MessageHolder holder) { + return false; + } + + public Company findOrCreateByNameOrAbbName(String name, String abbName, LocalDate developDate, MessageHolder holder) { + Company company = getCompanyService().findAndRemoveDuplicateCompanyByNameOrAbbName(name, abbName); + if (company != null) { + return company; + } + + // 判断是否是个人 + holder.warn("企业库中找不到" + name + " 或 " + abbName + " 的企业"); + // 推测个人用户,归入散户 + if (name.length() < 4 && !name.contains("公司")) { + company = getCompanyService().findByName("散户"); + if (company == null) { + return null; + } + holder.info(name + " 个人用户归集到散户"); + return company; + } + + // 尝试创建公司 + company = autoCreateNewCompanyAfter(developDate, name, abbName, holder); + if (company != null) { + holder.info("自动创建公司:" + company.getName()); + } + return null; + } + + private Company autoCreateNewCompanyAfter(LocalDate devDate, String name, String abbName, MessageHolder holder) { + if (devDate == null) { + holder.warn("开发时间未知,不创建公司"); + return null; + } + CompanyService companyService = getCompanyService(); + String autoCreateAfter = SpringApp.getBean(SysConfService.class).getString(AUTO_CREATE_COMPANY_AFTER); + // 当配置存在,且开发时间小于指定时间,不创建 + if (StringUtils.hasText(autoCreateAfter)) { + LocalDate miniDate = LocalDate.parse(autoCreateAfter); + if (devDate.isBefore(miniDate)) { + // 创建时间小于指定时间,不创建 + holder.warn(name + " 的开发时间 " + devDate + " 是 " + miniDate + " 之前的, 按规定不创建, 如有需要请手动创建"); + return null; + } + } + Company com = companyService.createNewCompany(name); + com.setShortName(abbName); + return companyService.save(com); + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/ContractCtx.java b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/ContractCtx.java new file mode 100644 index 0000000..ed5d033 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/ContractCtx.java @@ -0,0 +1,1340 @@ +package com.ecep.contract.manager.cloud.u8.ctx; + +import com.ecep.contract.manager.cloud.u8.YongYouU8Service; +import com.ecep.contract.manager.ds.company.CompanyFileUtils; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.contract.ContractFileType; +import com.ecep.contract.manager.ds.contract.ContractPayWay; +import com.ecep.contract.manager.ds.contract.model.*; +import com.ecep.contract.manager.ds.contract.service.ContractFileService; +import com.ecep.contract.manager.ds.contract.service.ContractItemService; +import com.ecep.contract.manager.ds.contract.service.ContractPayPlanService; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ds.customer.model.CompanyCustomer; +import com.ecep.contract.manager.ds.customer.model.CompanyCustomerEntity; +import com.ecep.contract.manager.ds.customer.service.CompanyCustomerEntityService; +import com.ecep.contract.manager.ds.customer.service.CompanyCustomerService; +import com.ecep.contract.manager.ds.other.model.Employee; +import com.ecep.contract.manager.ds.project.ProjectCtx; +import com.ecep.contract.manager.ds.project.model.Project; +import com.ecep.contract.manager.ds.project.model.ProjectSaleType; +import com.ecep.contract.manager.ds.project.service.SaleTypeService; +import com.ecep.contract.manager.ds.vendor.model.CompanyVendor; +import com.ecep.contract.manager.ds.vendor.model.CompanyVendorEntity; +import com.ecep.contract.manager.ds.vendor.service.CompanyVendorEntityService; +import com.ecep.contract.manager.ds.vendor.service.CompanyVendorService; +import com.ecep.contract.manager.ui.MessageHolder; +import com.ecep.contract.manager.util.MyDateTimeUtils; +import com.ecep.contract.manager.util.NumberUtils; +import com.ecep.contract.manager.util.TaxRateUtils; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.Hibernate; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.text.NumberFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static com.ecep.contract.manager.SpringApp.getBean; + +public class ContractCtx extends AbstractYongYouU8Ctx { + + @Setter + private ContractService contractService; + @Setter + private ContractItemService contractItemService; + @Setter + private ContractFileService contractFileService; + @Setter + private ContractPayPlanService contractPayPlanService; + @Setter + private SaleTypeService saleTypeService; + @Setter + private VendorCtx vendorCtx; + @Setter + private CustomerCtx customerCtx; + @Setter + private CompanyCtx companyCtx; + @Setter + private InventoryCtx inventoryCtx; + @Setter + private ProjectCtx projectCtx; + + @Getter + @Setter + private int vendorEntityUpdateDelayDays = 1; + @Getter + @Setter + private int customerEntityUpdateDelayDays = 1; + + public ContractService getContractService() { + if (contractService == null) { + contractService = getBean(ContractService.class); + } + return contractService; + } + + ContractItemService getContractItemService() { + if (contractItemService == null) { + contractItemService = getBean(ContractItemService.class); + } + return contractItemService; + } + + ContractPayPlanService getContractPayPlanService() { + if (contractPayPlanService == null) { + contractPayPlanService = getBean(ContractPayPlanService.class); + } + return contractPayPlanService; + } + + ContractFileService getContractFileService() { + if (contractFileService == null) { + contractFileService = getBean(ContractFileService.class); + } + return contractFileService; + } + + SaleTypeService getSaleTypeService() { + if (saleTypeService == null) { + saleTypeService = getBean(SaleTypeService.class); + } + return saleTypeService; + } + + VendorCtx getVendorCtx() { + if (vendorCtx == null) { + vendorCtx = new VendorCtx(); + vendorCtx.from(this); + } + return vendorCtx; + } + + CompanyCtx getCompanyCtx() { + if (companyCtx == null) { + companyCtx = new CompanyCtx(); + companyCtx.from(this); + } + return companyCtx; + } + + CustomerCtx getCustomerCtx() { + if (customerCtx == null) { + customerCtx = new CustomerCtx(); + customerCtx.from(this); + } + return customerCtx; + } + + InventoryCtx getInventoryCtx() { + if (inventoryCtx == null) { + inventoryCtx = new InventoryCtx(); + inventoryCtx.from(this); + } + return inventoryCtx; + } + + ProjectCtx getProjectCtx() { + if (projectCtx == null) { + projectCtx = new ProjectCtx(); + } + return projectCtx; + } + + public void syncContract(Company company, MessageHolder holder) { + List list = getContractService().findAllByCompany(company); + for (int i = 0; i < list.size(); i++) { + Contract contract = list.get(i); + syncContract(contract, holder); + } + } + + public void syncContract(Contract contract, MessageHolder holder) { + boolean modified = updateContractDetailByGuid(contract, contract.getGuid(), holder); + if (updateContractExec(contract, holder)) { + modified = true; + } + + if (modified) { + contract = save(contract); + } + + // fixed contract 对象需要更新保存了,syncContractItems 里可能会有新建的item,有引用,保存时会异常 + if (syncContractItems(contract, holder)) { + modified = true; + contract = save(contract); + } + } + + public boolean updateContractDetailByGuid(Contract contract, String guid, MessageHolder holder) { + if (repository == null) { + return false; + } + Map data; + try { + data = repository.queryContractDetail(guid); + } catch (Exception e) { + throw new RuntimeException("查询合同详情失败, guid=" + guid, e); + } + return applyContractDetail(contract, data, holder); + } + + /** + * 应用合同数据 + */ + public boolean applyContractDetail(Contract contract, Map rs, MessageHolder holder) { + if (rs == null || rs.isEmpty()) { + holder.warn("CM_Contract_B 中未检索到合同数据"); + return false; + } + + boolean modified = false; + String guid = (String) rs.get("GUID"); + String code = (String) rs.get("strContractID"); + String name = (String) rs.get("strContractName"); + String parentCode = (String) rs.get("strParentID"); + Double totalCurrency = (Double) rs.get("dblTotalCurrency"); + + String description = (String) rs.get("strContractDesc"); + + String typeCode = (String) rs.get("strContractType"); + String groupCode = (String) rs.get("strContractGrp"); + String kindName = (String) rs.get("strContractKind"); + String payWay = (String) rs.get("strWay"); + String unit = (String) rs.get("strBisectionUnit"); + + if (updateText(contract::getGuid, contract::setGuid, guid, holder, "GUID")) { + modified = true; + } + if (updateText(contract::getCode, contract::setCode, code, holder, "合同编号")) { + modified = true; + } + if (updateText(contract::getName, contract::setName, name, holder, "合同名称")) { + modified = true; + } + if (StringUtils.hasText(parentCode)) { + if (updateText(contract::getParentCode, contract::setParentCode, parentCode, holder, "父合同号")) { + modified = true; + } + } + if (updatePayWay(contract, payWay, holder)) { + modified = true; + } + if (updateTypeByCode(contract, typeCode, holder)) { + modified = true; + } + if (updateGroupByCode(contract, groupCode, holder)) { + modified = true; + } + if (updateKindByCode(contract, kindName, holder)) { + modified = true; + } + if (updateEmployeeByCode(contract, (String) rs.get("strPersonID"), holder)) { + modified = true; + } + if (updateLocalDate(contract::getOrderDate, contract::setOrderDate, (String) rs.get("strContractOrderDate"), + holder, "下单日期")) { + modified = true; + } + if (updateLocalDate(contract::getStartDate, contract::setStartDate, (String) rs.get("strContractStartDate"), + holder, "开始日期")) { + modified = true; + } + if (updateLocalDate(contract::getEndDate, contract::setEndDate, (String) rs.get("strContractEndDate"), holder, + "结束日期")) { + modified = true; + } + if (updateLocalDate(contract::getSetupDate, contract::setSetupDate, (String) rs.get("strSetupDate"), holder, + "提交日期")) { + modified = true; + } + if (updateLocalDate(contract::getInureDate, contract::setInureDate, (String) rs.get("strInureDate"), holder, + "生效日期")) { + modified = true; + } + if (updateLocalDate(contract::getVaryDate, contract::setVaryDate, (String) rs.get("dtVaryDate"), holder, + "变更日期")) { + modified = true; + } + if (updateEmployeeByName(contract::getSetupPerson, contract::setSetupPerson, (String) rs.get("strSetupPerson"), + holder, "提交人")) { + modified = true; + } + if (updateEmployeeByName(contract::getInurePerson, contract::setInurePerson, (String) rs.get("strInurePerson"), + holder, "生效人")) { + modified = true; + } + if (updateEmployeeByName(contract::getVaryPerson, contract::setVaryPerson, (String) rs.get("strVaryPerson"), + holder, "修改人")) { + modified = true; + } + if (totalCurrency != null) { + if (contract.getAmount() == null) { + contract.setAmount(0d); + modified = true; + } else { + if (!NumberUtils.equals(totalCurrency, contract.getAmount())) { + holder.warn("注意合同金额和合计总金额不一致:" + totalCurrency); + } + } + } + if (updateAppendText(contract::getDescription, contract::setDescription, description, holder, "备注")) { + modified = true; + } + + switch (contract.getPayWay()) { + case PAY -> { + if (updateCompanyByVendorUnitCode(contract, unit, holder)) { + modified = true; + } + } + case RECEIVE -> { + if (updateCompanyByCustomerUnitCode(contract, unit, holder)) { + modified = true; + } + } + default -> { + holder.warn("未知合同付款方向:" + payWay); + } + } + + if (contract.getType() != null && contract.getPayWay() != null) { + ContractType type = contract.getType(); + if (!Hibernate.isInitialized(type)) { + type = getContractService().findTypeById(type.getId()); + contract.setType(type); + } + if (!type.getDirection().equals(contract.getPayWay().getText())) { + holder.warn("合同类型:" + type.getName() + "的付款方向与付款方式不一致:" + contract.getPayWay().getText()); + } + } + + if (contract.getKind() != null && contract.getType() != null) { + ContractKind kind = contract.getKind(); + if (!Hibernate.isInitialized(kind)) { + kind = getContractService().findKindById(kind.getId()); + contract.setKind(kind); + } + ContractType type = contract.getType(); + if (!Hibernate.isInitialized(type)) { + type = getContractService().findTypeById(type.getId()); + contract.setType(type); + } + if (!kind.getName().equals(type.getTitle())) { + holder.warn("合同分类的名称:" + kind.getName() + "与合同类型的标题:" + type.getName() + "不一致"); + } + } + return modified; + } + + /** + * 更新合同 + * + * @param contract 更新的合同对象 + * @param unit 合同对应的单位代码 + * @param holder 消息处理对象 + */ + private boolean updateCompanyByVendorUnitCode(Contract contract, String unit, MessageHolder holder) { + Company company = contract.getCompany(); + boolean modified = false; + boolean vendorModified = false; + CompanyVendorService vendorService = getCompanyVendorService(); + CompanyVendorEntityService vendorEntityService = getCompanyVendorEntityService(); + CompanyVendorEntity entity = vendorEntityService.findByCode(unit); + if (entity == null) { + entity = new CompanyVendorEntity(); + entity.setCode(unit); + } + entity = updateVendorEntityDetailByCode(entity, unit, holder); + + CompanyVendor vendor = entity.getVendor(); + if (vendor == null) { + if (company != null) { + vendor = vendorService.findByCompany(company); + } + } else { + if (!Hibernate.isInitialized(vendor)) { + vendor = vendorService.findById(vendor.getId()); + } + if (vendor.getCompany() == null) { + vendor.setCompany(company); + vendorModified = true; + } else { + if (company == null) { + company = vendor.getCompany(); + } else if (!Objects.equals(company, vendor.getCompany())) { + holder.error( + "供应商的企业和合同的企业不一致, 供应商的企业:" + vendor.getCompany().getId() + ",合同的企业:" + company.getId()); + } + } + } + if (vendor == null) { + vendor = new CompanyVendor(); + vendor.setCompany(company); + vendorModified = true; + } + if (vendor.getDevelopDate() == null + || (entity.getDevelopDate() != null && vendor.getDevelopDate().isAfter(entity.getDevelopDate()))) { + vendor.setDevelopDate(entity.getDevelopDate()); + vendorModified = true; + holder.info("供应商的开发日期修改为:" + entity.getDevelopDate()); + } + if (vendorModified) { + vendor = vendorService.save(vendor); + } + + // 确定 company + if (company == null) { + company = getCompanyCtx().findOrCreateByNameOrAbbName(entity.getName(), entity.getAbbName(), + entity.getDevelopDate(), holder); + } + + if (updateCompany(contract, company, holder)) { + modified = true; + } + + return modified; + } + + private boolean updateCompany(Contract contract, Company company, MessageHolder holder) { + if (company == null) { + return false; + } + if (Objects.equals(contract.getCompany(), company)) { + return false; + } + if (!Hibernate.isInitialized(company)) { + company = getCompanyService().findById(company.getId()); + } + contract.setCompany(company); + holder.info("关联至 " + company.getName()); + return true; + } + + private CompanyVendorEntity updateVendorEntityDetailByCode(CompanyVendorEntity entity, String unitCode, + MessageHolder holder) { + if (vendorEntityUpdateDelayDays > 0) { + LocalDateTime today = LocalDateTime.now(); + if (entity.getFetchedTime() != null) { + LocalDateTime plussed = entity.getFetchedTime().plusDays(vendorEntityUpdateDelayDays); + if (!plussed.isBefore(today)) { + holder.info("供应商表项 #" + entity.getId() + " 的更新时间:" + entity.getFetchedTime() + ",未超过" + + vendorEntityUpdateDelayDays + "天,将不再更新"); + return entity; + } + } + entity = getVendorCtx().updateVendorEntityDetailByCode(entity, unitCode, holder); + entity.setFetchedTime(today); + return getVendorCtx().save(entity); + } + return getVendorCtx().updateVendorEntityDetailByCode(entity, unitCode, holder); + } + + private CompanyCustomerEntity updateCustomerEntityDetailByCode(CompanyCustomerEntity entity, String unitCode, + MessageHolder holder) { + if (customerEntityUpdateDelayDays > 0) { + LocalDateTime today = LocalDateTime.now(); + if (entity.getFetchedTime() != null) { + LocalDateTime plussed = entity.getFetchedTime().plusDays(customerEntityUpdateDelayDays); + if (!plussed.isBefore(today)) { + holder.info("客户表项 #" + entity.getId() + " 的更新时间:" + entity.getFetchedTime() + ",未超过" + + customerEntityUpdateDelayDays + "天,将不再更新"); + return entity; + } + } + entity = getCustomerCtx().updateCustomerEntityDetailByCode(entity, unitCode, holder); + entity.setFetchedTime(today); + return getCustomerCtx().save(entity); + } + return getCustomerCtx().updateCustomerEntityDetailByCode(entity, unitCode, holder); + } + + private boolean updateCompanyByCustomerUnitCode(Contract contract, String unit, MessageHolder holder) { + Company company = contract.getCompany(); + boolean modified = false; + boolean customerModified = false; + CompanyCustomerEntityService customerEntityService = getCompanyCustomerEntityService(); + CompanyCustomerService customerService = getCompanyCustomerService(); + CompanyCustomerEntity entity = customerEntityService.findByCustomerCode(unit); + if (entity == null) { + entity = new CompanyCustomerEntity(); + entity.setCode(unit); + } + entity = updateCustomerEntityDetailByCode(entity, unit, holder); + + CompanyCustomer customer = entity.getCustomer(); + if (customer == null) { + if (company != null) { + customer = customerService.findByCompany(company); + } + } else { + if (!Hibernate.isInitialized(customer)) { + customer = customerService.findById(customer.getId()); + } + if (customer.getCompany() == null) { + customer.setCompany(company); + customerModified = true; + } else { + if (company == null) { + company = customer.getCompany(); + } else if (!Objects.equals(company, customer.getCompany())) { + holder.error( + "客户的企业和合同的企业不一致, 客户的企业:" + customer.getCompany().getId() + ",合同的企业:" + company.getId()); + } + } + } + if (customer == null) { + customer = new CompanyCustomer(); + customer.setCompany(company); + customerModified = true; + } + if (customer.getDevelopDate() == null + || (entity.getDevelopDate() != null && customer.getDevelopDate().isAfter(entity.getDevelopDate()))) { + customer.setDevelopDate(entity.getDevelopDate()); + customerModified = true; + holder.info("客户的开发日期修改为:" + entity.getDevelopDate()); + } + if (customerModified) { + customer = customerService.save(customer); + } + // 确定 company + if (company == null) { + company = getCompanyCtx().findOrCreateByNameOrAbbName(entity.getName(), entity.getAbbName(), + entity.getDevelopDate(), holder); + } + + if (updateCompany(contract, company, holder)) { + modified = true; + } + + return modified; + } + + private boolean updatePayWay(Contract contract, String way, MessageHolder holder) { + if (!StringUtils.hasText(way)) { + holder.warn("无付款方向"); + return false; + } + ContractPayWay payWay = ContractPayWay.valueOfText(way); + if (payWay == null) { + holder.warn("付款方向无法识别:" + way); + return false; + } + if (!Objects.equals(contract.getPayWay(), payWay)) { + contract.setPayWay(payWay); + holder.info("付款方向修改为: " + way); + return true; + } + return false; + } + + private boolean updateTypeByCode(Contract contract, String typeCode, MessageHolder holder) { + ContractType type = getContractService().findTypeByCode(typeCode); + if (!Objects.equals(contract.getType(), type)) { + contract.setType(type); + holder.info("合同类型修改为: " + type.getName()); + return true; + } + return false; + } + + private boolean updateGroupByCode(Contract contract, String groupCode, MessageHolder holder) { + ContractGroup group = getContractService().findGroupByCode(groupCode); + if (!Objects.equals(contract.getGroup(), group)) { + contract.setGroup(group); + holder.info("合同分组修改为: " + group.getName()); + return true; + } + return false; + } + + private boolean updateKindByCode(Contract contract, String kindName, MessageHolder holder) { + ContractKind kind = getContractService().findKindByName(kindName); + if (!Objects.equals(contract.getKind(), kind)) { + contract.setKind(kind); + holder.info("合同分类修改为: " + kind.getName()); + return true; + } + return false; + } + + private boolean updateEmployeeByCode(Contract contract, String personId, MessageHolder holder) { + boolean updated = updateEmployeeByCode(contract::getEmployee, contract::setEmployee, personId, holder, "业务员"); + Employee employee = contract.getEmployee(); + if (employee == null) { + holder.warn("* 合同业务员:" + personId + " 未导入"); + } else { + if (!Hibernate.isInitialized(employee)) { + employee = getEmployeeService().findById(employee.getId()); + } + if (!employee.isActive()) { + holder.warn("业务员 " + employee.getName() + " 已经不可用,其离职日期:" + employee.getLeaveDate()); + } + } + return updated; + } + + public Contract findContractByCode(String contractCode) { + return getContractService().findByCode(contractCode); + } + + public boolean syncByVendorEntity(CompanyVendor companyVendor, CompanyVendorEntity entity, MessageHolder holder) { + String code = entity.getCode(); + holder.debug("同步供应商相关项 " + code + "," + entity.getName() + " 的合同"); + if (!StringUtils.hasText(code)) { + holder.warn("系统编号 " + code + " 为空,跳过"); + return false; + } + AtomicBoolean updated = new AtomicBoolean(false); + + try (Stream> stream = repository.queryAllContractByUnitForStream(entity.getCode(), + ContractPayWay.PAY)) { + stream.forEach(rs -> { + if (updateContract(rs, holder)) { + updated.set(true); + } + }); + } + return updated.get(); + } + + /** + * 同步客户数据 + * 1. 同步客户信息 + * 2. 同步客户的合同 + * 客户数据要通过 CompanyCustomerEntity 表关联来从用友数据库中读取 + */ + public boolean syncByCustomerEntity(CompanyCustomer companyCustomer, CompanyCustomerEntity entity, + MessageHolder holder) { + String code = entity.getCode(); + holder.debug("同步客户相关项 " + code + "," + entity.getName() + " 的合同"); + if (!StringUtils.hasText(code)) { + holder.warn("系统编号 " + code + " 为空,跳过"); + return false; + } + AtomicBoolean updated = new AtomicBoolean(false); + try (Stream> stream = repository.queryAllContractByUnitForStream(entity.getCode(), + ContractPayWay.RECEIVE)) { + stream.forEach(rs -> { + if (updateContract(rs, holder)) { + updated.set(true); + } + }); + } + return updated.get(); + } + + public boolean updateContract(String contractID, String guid, MessageHolder holder) { + Contract contract = findOrCreatByGuidOrContractId(guid, contractID, holder); + boolean updated = false; + boolean modified = applyContractDetail(contract, repository.queryContractDetail(guid), holder); + if (modified) { + contract = getContractService().save(contract); + updated = true; + } + return false; + } + + public Contract findOrCreatByGuidOrContractId(String guid, String contractId, MessageHolder holder) { + ContractService service = getContractService(); + UUID.fromString(guid); + Contract contract = service.findByGuid(guid); + if (contract == null) { + holder.debug("根据GUID未查找到合同, GUID: " + guid); + contract = service.findByCode(contractId); + if (contract == null) { + holder.debug("根据合同号未查找到合同, 合同号: " + contractId); + + contract = service.createNew(); + contract.setGuid(guid); + contract.setCode(contractId); + contract = service.save(contract); + holder.info("新建合同:" + contractId+", GUID: "+guid); + } + } + return contract; + } + + private boolean updateContract(Map rs, MessageHolder holder) { + String contractID = (String) rs.get("strContractID"); + String guid = (String) rs.get("GUID"); + boolean updated = false; + Contract contract = findOrCreatByGuidOrContractId(guid, contractID, holder); + boolean modified = applyContractDetail(contract, rs, holder); + if (modified) { + contract = getContractService().save(contract); + updated = true; + } + return updated; + } + + public boolean updateParentContract(Contract contract, MessageHolder holder) { + // 如果已经由父合同编号,跳过 + if (StringUtils.hasText(contract.getParentCode())) { + return false; + } + // 判断合同号是否是子合同 + if (!getContractService().isSubContract(contract)) { + return false; + } + + // 采购合同 + if (contract.getPayWay() != ContractPayWay.PAY) { + return false; + } + + String parentCode = getContractService().getParentCode(contract); + holder.info("尝试查找主合同:" + parentCode); + List list = getContractService().findByCodeStartsWith(ContractPayWay.RECEIVE, parentCode); + if (list.isEmpty()) { + holder.warn("没有找到以" + parentCode + "开头的销售合同,匹配主合同失败!"); + return false; + } + Optional any = list.stream().filter(c -> !c.getCode().contains("-")).findAny(); + if (any.isEmpty()) { + holder.warn("找到的合同号都不符合要求:" + list.stream().map(Contract::getCode).collect(Collectors.joining(",")) + "!"); + return false; + } + Contract parent = any.get(); + holder.info("找到主合同:" + parent.getCode()); + return updateText(contract::getCode, contract::setParentCode, parent.getCode(), holder, "主合同"); + } + + public boolean updateContractExec(Contract contract, MessageHolder holder) { + if (repository == null) { + return false; + } + Map rs = repository.sumContractExec(contract.getCode()); + if (rs == null) { + return false; + } + boolean modified = false; + double execQuantity = (double) Objects.requireNonNullElse(rs.get("execQuantity"), 0d); + double execAmount = (double) Objects.requireNonNullElse(rs.get("execAmount"), 0d); + double execUnTaxAmount = (double) Objects.requireNonNullElse(rs.get("execUnTaxAmount"), 0d); + + if (updateNumber(contract::getExecQuantity, contract::setExecQuantity, execQuantity, holder, "执行数")) { + modified = true; + } + if (updateNumber(contract::getExecAmount, contract::setExecAmount, execAmount, holder, "执行金额")) { + modified = true; + } + if (updateNumber(contract::getExecUnTaxAmount, contract::setExecUnTaxAmount, execUnTaxAmount, holder, + "执行不含税金额")) { + modified = true; + } + return modified; + } + + /** + * 同步合同项目信息 + * + * @param contract 合同 + * @param holder 消息 + */ + public boolean syncContractItems(Contract contract, MessageHolder holder) { + if (repository == null) { + return false; + } + holder.debug("- 同步合同条目 -"); + ContractItemService itemService = getContractItemService(); + boolean changed = false; + + List items = itemService.findAllByContract(contract); + holder.debug("查找到 " + items.size() + " 条合同条目记录在数据库中"); + + Map itemMap = items.stream() + .collect(Collectors.toMap(ContractItem::getRefId, item -> item)); + List> ds = repository.findAllContractItemByContractCode(contract.getCode()); + holder.debug("查找到 " + ds.size() + " 条合同条目记录在 " + YongYouU8Service.NAME); + + List updates = new ArrayList<>(); + for (Map map : ds) { + // holder.debug("条目:" + map.toString()); + int refId = (Integer) map.get("ID"); + ContractItem contractItem = itemMap.remove(refId); + boolean modified = false; + if (contractItem == null) { + contractItem = new ContractItem(); + contractItem.setContract(contract); + contractItem.setRefId(refId); + modified = true; + + holder.info("新增合同条目"); + } + MessageHolder subHolder = holder.sub("--| #" + refId + " - "); + if (applyContractItemDetail(contractItem, map, subHolder)) { + modified = true; + } + if (modified) { + contractItem.setUpdateDate(LocalDateTime.now()); + contractItem = itemService.save(contractItem); + changed = true; + } + updates.add(contractItem); + } + if (!itemMap.isEmpty()) { + for (ContractItem item : itemMap.values()) { + itemService.delete(item); + } + changed = true; + } + + if (sumItems(contract, updates, holder)) { + changed = true; + } + + return changed; + } + + private boolean applyContractItemDetail(ContractItem item, Map map, MessageHolder holder) { + Contract contract = item.getContract(); + if (contract == null) { + holder.error("条目没有合同"); + return false; + } + + String code = (String) map.get("strCode"); + String spec = (String) map.get("cInvStd"); + String title = (String) map.get("strName"); + double amount = (double) map.get("dblQuantity"); + String unit = (String) map.get("strMeasureUnit"); + double taxRate = (double) map.get("dblTaxRatio"); + double taxPrice = (double) map.get("dblPrice"); + double exclusiveTaxPrice = (double) map.get("dblUntaxPrice"); + + String inventoryCatalogCode = (String) map.get("cInvCCode"); + String inventoryCode = (String) map.get("strInvoiceID"); + + String startDate = (String) map.get("dtStartDate"); + String endDate = (String) map.get("dtEndDate"); + + String memo = (String) map.get("strMemo"); + + if (!Hibernate.isInitialized(contract)) { + contract = getContractService().findById(contract.getId()); + item.setContract(contract); + } + boolean modified = false; + + holder.debug("条目:" + title + " x " + amount); + if (taxRate >= TaxRateUtils.TAX_RATE_GENERAL_OLDEST + && TaxRateUtils.isAfterTaxRateChangeTo13(contract.getSetupDate())) { + holder.debug("税率 " + taxRate + "% 调整为 13%"); + taxRate = TaxRateUtils.TAX_RATE_GENERAL; + double exclusiveTax = TaxRateUtils.toExclusiveTax(taxPrice, taxRate); + holder.debug("不含税单价 " + exclusiveTaxPrice + " 调整为 " + exclusiveTax); + exclusiveTaxPrice = exclusiveTax; + } + if (updateText(item::getTitle, item::setTitle, title, holder, "名称")) { + modified = true; + } + if (updateText(item::getSpecification, item::setSpecification, spec, holder, "规格")) { + modified = true; + } + if (updateText(item::getItemCode, item::setItemCode, code, holder, "代码")) { + modified = true; + } + if (updateText(item::getUnit, item::setUnit, unit, holder, "单位")) { + modified = true; + } + if (updateNumber(item::getExclusiveTaxPrice, item::setExclusiveTaxPrice, exclusiveTaxPrice, holder, "不含税单价")) { + modified = true; + } + if (updateNumber(item::getTaxPrice, item::setTaxPrice, taxPrice, holder, "含税单价")) { + modified = true; + } + if (updateNumber(item::getTaxRate, item::setTaxRate, taxRate, holder, "税率")) { + modified = true; + } + if (updateNumber(item::getQuantity, item::setQuantity, amount, holder, "数量")) { + modified = true; + } + if (getInventoryCtx().syncInventoryDetailByCode(item::getInventory, item::setInventory, inventoryCode, + holder.sub("存货 #" + inventoryCode + " - "), "存货")) { + modified = true; + } + + { + LocalDate date = null; + + if (StringUtils.hasText(startDate)) { + try { + date = LocalDate.parse(startDate); + } catch (DateTimeParseException e) { + holder.warn("解析日期" + startDate + " 异常:" + e.getMessage()); + } + } + if (!Objects.equals(item.getStartDate(), date)) { + item.setStartDate(date); + holder.info("开始日期修改为: " + date); + modified = true; + } + } + + { + LocalDate date = null; + if (StringUtils.hasText(endDate)) { + try { + date = LocalDate.parse(endDate); + } catch (DateTimeParseException e) { + holder.warn("解析日期" + startDate + " 异常:" + e.getMessage()); + } + } + if (!Objects.equals(item.getEndDate(), date)) { + item.setEndDate(date); + holder.info("结束日期修改为: " + date); + modified = true; + } + } + + if (!Objects.equals(item.getRemark(), memo)) { + item.setRemark(memo); + modified = true; + } + + return modified; + } + + private boolean sumItems(Contract contract, Collection items, MessageHolder holder) { + if (items == null || items.isEmpty()) { + return false; + } + boolean modified = false; + + double totalQuantity = items.stream().mapToDouble(ContractItem::getQuantity).sum(); + double totalAmount = items.stream().mapToDouble(item -> item.getTaxPrice() * item.getQuantity()).sum(); + double totalUnTaxAmount = items.stream().mapToDouble(item -> item.getExclusiveTaxPrice() * item.getQuantity()) + .sum(); + + if (updateNumber(contract::getTotalQuantity, contract::setTotalQuantity, totalQuantity, holder, "总数量")) { + modified = true; + } + if (updateNumber(contract::getTotalAmount, contract::setTotalAmount, totalAmount, holder, "总金额")) { + modified = true; + } + if (updateNumber(contract::getTotalUnTaxAmount, contract::setTotalUnTaxAmount, totalUnTaxAmount, holder, + "总不含税金额")) { + modified = true; + } + return modified; + } + + public boolean updateContractPath(Contract contract, MessageHolder holder) { + // 如果合同路径存在 + if (CompanyFileUtils.exists(contract.getPath())) { + File dir = new File(contract.getPath()); + if (dir.exists()) { + return false; + } + } + // 尝试创建合同路径 + holder.debug("合同目录不存在,尝试创建"); + + if (contract.getPayWay() == ContractPayWay.PAY) { + // 采购合同 + String parentCode = contract.getParentCode(); + if (!StringUtils.hasText(parentCode)) { + holder.warn("未关联主合同编号,采购合同目录创建在销售合同目录中,需先关联销售合同!"); + return false; + } + Contract parent = getContractService().findByCode(parentCode); + if (parent == null) { + holder.warn("关联主合同编号 " + parentCode + " 异常无法找到对应的合同,请检查主合同编号是否正确,或稍后再试!"); + return false; + } + if (!StringUtils.hasText(parent.getPath())) { + holder.warn("关联主合同编号 " + parent.getCode() + " 未创建目录,请先创建主合同目录,稍后再试!"); + return false; + } + File contractPath = new File(parent.getPath()); + if (!contractPath.exists()) { + holder.debug("父合同 " + parentCode + " 目录不存在 " + parent.getPath()); + return false; + } + String contractDirName = contract.getCode(); + File path = new File(contractPath, contractDirName); + if (!path.exists()) { + if (!path.mkdir()) { + holder.warn("创建目录失败 = " + path.getAbsolutePath()); + return false; + } + } + contract.setPath(path.getAbsolutePath()); + holder.info("目录变更为:" + contract.getPath()); + return true; + } else if (contract.getPayWay() == ContractPayWay.RECEIVE) { + // 销售合同 + File contractPath = null; + Project project = contract.getProject(); + if (project != null) { + project = getProjectCtx().initialize(project); + ProjectSaleType saleType = project.getSaleType(); + if (saleType != null) { + if (!Hibernate.isInitialized(saleType)) { + saleType = getSaleTypeService().findById(saleType.getId()); + } + File dir = new File(saleType.getPath()); + if (saleType.isStoreByYear()) { + dir = new File(dir, "20" + project.getCodeYear()); + if (!dir.exists()) { + if (dir.mkdir()) { + holder.info("新建目录 " + dir.getAbsolutePath()); + } + } + } + contractPath = dir; + } + } + + // 从关联项目上未取得合同目录,尝试根据合同编码来确定合同目录 + if (contractPath == null) { + ContractCatalog catalog = getContractService().findContractCatalogByContract(contract); + if (catalog != null) { + contractPath = getContractService().getContractCatalogPath(catalog, contract); + } + } + + // 没有定位到合同目录 + if (contractPath == null) { + return false; + } + + String contractDirName = contract.getCode() + "-" + CompanyFileUtils.escapeFileName(contract.getName()); + File path = new File(contractPath, contractDirName); + if (!path.exists()) { + if (!path.mkdir()) { + // + holder.warn("创建目录失败 = " + path.getAbsolutePath()); + return false; + } + } + contract.setPath(path.getAbsolutePath()); + holder.info("目录变更为:" + contract.getPath()); + return true; + } + return false; + } + + public boolean updateContractAmount(Contract contract, MessageHolder holder) { + if (contract.getAmount() == null || contract.getAmount() == 0.0d) { + if (contract.getTotalAmount() > 0) { + contract.setAmount(contract.getTotalAmount()); + holder.info("合同金额设置为:" + contract.getAmount()); + return true; + } + } + return false; + } + + public boolean updateContractProject(Contract contract, Project project, MessageHolder holder) { + if (project == null) { + return false; + } + if (Objects.equals(contract.getProject(), project)) { + return false; + } + project = getProjectCtx().initialize(project); + holder.info("合同所属项目更新为:" + project.getName()); + contract.setProject(project); + return true; + } + + /** + * 根据销售合同的客户,更新项目的客户 + * + * @param contract + * @param holder + * @return + */ + public boolean updateContractProject(Contract contract, Contract parent, MessageHolder holder) { + Project project = contract.getProject(); + if (project == null && parent != null) { + // 尝试从父合同中获取项目对象 + project = parent.getProject(); + } + + if (project == null) { + return false; + } + + boolean projectModified = false; + boolean contractModified = false; + + ProjectCtx ctx = getProjectCtx(); + + project = ctx.initialize(project); + if (project.getAmount() == null || project.getAmount() == 0) { + if (ctx.updateNumber(project::getAmount, project::setAmount, contract.getAmount().intValue(), holder, + "项目金额")) { + projectModified = true; + } + } + + // 检查项目的客户 + Company customer = project.getCustomer(); + if (customer == null) { + // 查询合同关联的客户(主合同上的是客户) + if (parent == null) { // 主合同时,parent 为 null + if (contract.getPayWay() == ContractPayWay.RECEIVE) { + customer = contract.getCompany(); + } + } else { + customer = parent.getCompany(); + if (customer == null) { + holder.warn("合同:" + parent.getCode() + " 没有关联的客户"); + } + } + if (customer != null) { + if (ctx.updateCustomer(project, customer, holder)) { + projectModified = true; + } + } + } + + if (projectModified) { + project = ctx.save(project); + contract.setProject(project); + } + + if (updateContractProject(contract, project, holder)) { + // 更新项目客户 + contractModified = true; + } + + return contractModified; + } + + public boolean syncContractFiles(Contract contract, MessageHolder holder) { + String contractPath = contract.getPath(); + if (!StringUtils.hasText(contractPath)) { + return false; + } + File dir = new File(contractPath); + if (!dir.exists()) { + holder.warn("合同目录不存在:" + contractPath); + return false; + } + ContractFileService fileService = getContractFileService(); + List dbFiles = fileService.findAllByContract(contract); + List retrieveFiles = new ArrayList<>(); + boolean modfied = false; + + Map map = new HashMap<>(); + // 排除掉数据库中重复的 + for (ContractFile dbFile : dbFiles) { + String fileName = dbFile.getFileName(); + // 没有文件信息,无效记录,删除 + if (!StringUtils.hasText(fileName)) { + fileService.delete(dbFile); + modfied = true; + continue; + } + + // 目录不存在,删除 + File file = new File(dir, fileName); + if (!file.exists()) { + fileService.delete(dbFile); + modfied = true; + continue; + } + + // 删除文件名重复的 + ContractFile old = map.put(fileName, dbFile); + if (old != null) { + fileService.delete(old); + modfied = true; + } + } + + // 遍历合同目录下的文件,如果未创建,创建 + File[] files = dir.listFiles(); + if (files != null) { + for (File file : files) { + // 只处理文件 + if (!file.isFile()) { + continue; + } + String fileName = file.getName(); + if (map.containsKey(fileName)) { + continue; + } + // 未记录的文件,存储到 retrieveFiles + ContractFile contractFile = new ContractFile(); + contractFile.setContract(contract); + contractFile.setType(ContractFileType.General); + contractFile.setFileName(file.getName()); + syncContractFile(contractFile, file, holder); + retrieveFiles.add(contractFile); + } + } + + if (retrieveFiles.isEmpty()) { + return modfied; + } + + // update db + for (ContractFile retrieveFile : retrieveFiles) { + getContractFileService().save(retrieveFile); + } + holder.info("导入 " + retrieveFiles.size() + " 个文件"); + return true; + } + + /** + * 把文件同步保存到合同 + * + * @param contractFile + * @param file + * @param holder + */ + public void syncContractFile(ContractFile contractFile, File file, MessageHolder holder) { + ContractPayWay payWay = contractFile.getContract().getPayWay(); + + String fileName = file.getName(); + + updateText(contractFile::getFileName, contractFile::setFileName, file.getName(), holder, "文件名"); + if (contractFile.getApplyDate() == null) { + updateLocalDate(contractFile::getApplyDate, contractFile::setApplyDate, + MyDateTimeUtils.pickLocalDate(file.getName()), holder, "生效日期"); + } + + List matched = getContractFileService() + .findAllFileTypes(Locale.getDefault().toLanguageTag()).values().stream() + .filter(local -> { + if (payWay == ContractPayWay.PAY) { + return local.getType().isSupportVendor(); + } else if (payWay == ContractPayWay.RECEIVE) { + return local.getType().isSupportCustomer(); + } + return false; + }).filter(local -> { + String suggestFileName = local.getSuggestFileName(); + if (StringUtils.hasText(suggestFileName)) { + if (suggestFileName.contains("{}")) { + // TODO 匹配 {} + } + if (fileName.contains(suggestFileName)) { + return true; + } + } + return fileName.contains(local.getValue()); + }).toList(); + + // 没有匹配,不做处理 + if (matched.isEmpty()) { + return; + } + + if (matched.size() == 1) { + contractFile.setType(matched.getFirst().getType()); + return; + } + contractFile.setType(matched.stream().max((o1, o2) -> maxLength(o1) - maxLength(o2)).get().getType()); + } + + private int maxLength(ContractFileTypeLocal o1) { + String suggestFileName = o1.getSuggestFileName(); + String value = o1.getValue(); + if (!StringUtils.hasText(suggestFileName)) { + return value.length(); + } + return Math.max(suggestFileName.length(), value.length()); + } + + public Contract save(Contract contract) { + return getContractService().save(contract); + } + + public boolean syncContractPayPlan(Contract contract, MessageHolder holder) { + if (repository == null) { + return false; + } + holder.debug("- 同步合同付款计划 -"); + ContractPayPlanService payPlanService = getContractPayPlanService(); + boolean changed = false; + + List plans = payPlanService.findAllByContract(contract); + holder.debug("查找到 " + plans.size() + " 条合同付款计划记录在数据库中"); + + Map planMap = plans.stream() + .collect(Collectors.toMap(ContractPayPlan::getRefId, item -> item)); + List> ds = repository.findAllContractPayPlanByContractCode(contract.getCode()); + holder.debug("查找到 " + ds.size() + " 条合同付款计划记录在 " + YongYouU8Service.NAME); + + List updates = new ArrayList<>(); + for (Map map : ds) { + // holder.debug("条目:" + map.toString()); + int refId = (Integer) map.get("ID"); + ContractPayPlan plan = planMap.remove(refId); + boolean modified = false; + if (plan == null) { + plan = new ContractPayPlan(); + plan.setContract(contract); + plan.setRefId(refId); + modified = true; + + holder.info("新增合同付款计划"); + } + MessageHolder subHolder = holder.sub("--| #" + refId + " - "); + if (applyContractPayPlanDetail(plan, map, subHolder)) { + modified = true; + } + if (modified) { + plan.setUpdateDate(LocalDateTime.now()); + plan = payPlanService.save(plan); + changed = true; + } + updates.add(plan); + } + if (!planMap.isEmpty()) { + for (ContractPayPlan plan : planMap.values()) { + payPlanService.delete(plan); + } + changed = true; + } + + double totalRatio = updates.stream().mapToDouble(ContractPayPlan::getPayRatio).sum(); + if (!NumberUtils.equals(totalRatio, 100.0)) { + holder.error("合同付款计划合计比例 != 100"); + } + double totalCurrency = updates.stream().mapToDouble(ContractPayPlan::getPayCurrency).sum(); + if (!NumberUtils.equals(totalCurrency, contract.getAmount())) { + NumberFormat numberInstance = NumberFormat.getNumberInstance(getLocale()); + numberInstance.setMaximumFractionDigits(2); + numberInstance.setMinimumFractionDigits(2); + holder.error("合同付款计划合计金额(" + numberInstance.format(totalCurrency) + ")和合同金额(" + + numberInstance.format(contract.getAmount()) + ")不一致!"); + } + return changed; + } + + private boolean applyContractPayPlanDetail(ContractPayPlan plan, Map map, MessageHolder holder) { + boolean modified = false; + String payDate = (String) map.get("dtPayDate"); + double payRatio = (double) map.get("dblPayRatio"); + double payCurrency = (double) map.get("dblPayCurrency"); + String payTerm = (String) map.get("strExp"); + + if (updateLocalDate(plan::getPayDate, plan::setPayDate, payDate, holder, "付款日期")) { + modified = true; + } + if (updateNumber(plan::getPayRatio, plan::setPayRatio, (float) payRatio, holder, "付款比例")) { + modified = true; + } + if (updateNumber(plan::getPayCurrency, plan::setPayCurrency, payCurrency, holder, "付款金额")) { + modified = true; + } + if (updateText(plan::getPayTerm, plan::setPayTerm, payTerm, holder, "付款条款")) { + modified = true; + } + return modified; + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/CustomerCtx.java b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/CustomerCtx.java new file mode 100644 index 0000000..563eacc --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/CustomerCtx.java @@ -0,0 +1,301 @@ +package com.ecep.contract.manager.cloud.u8.ctx; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.cloud.old.OldVersionService; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.customer.model.CompanyCustomer; +import com.ecep.contract.manager.ds.customer.model.CompanyCustomerEntity; +import com.ecep.contract.manager.ds.customer.model.CustomerCatalog; +import com.ecep.contract.manager.ds.customer.service.CompanyCustomerEntityService; +import com.ecep.contract.manager.ui.MessageHolder; +import lombok.Setter; +import org.hibernate.Hibernate; +import org.springframework.util.StringUtils; + +import java.time.Instant; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class CustomerCtx extends AbstractYongYouU8Ctx { + private static final String AUTO_CREATE_CUSTOMER_AFTER = "cloud.u8.auto-create-customer-after"; + @Setter + private CompanyCtx companyCtx; + @Setter + private ContractCtx contractCtx; + @Setter + private CompanyBankAccountCtx companyBankAccountCtx; + @Setter + private CompanyCustomerEntityService customerEntityService; + + public CompanyCtx getCompanyCtx() { + if (companyCtx == null) { + companyCtx = new CompanyCtx(); + companyCtx.from(this); + } + return companyCtx; + } + + ContractCtx getContractCtx() { + if (contractCtx == null) { + contractCtx = new ContractCtx(); + contractCtx.from(this); + } + return contractCtx; + } + + CompanyBankAccountCtx getCompanyBankAccountCtx() { + if (companyBankAccountCtx == null) { + companyBankAccountCtx = new CompanyBankAccountCtx(); + companyBankAccountCtx.from(this); + } + return companyBankAccountCtx; + } + + CompanyCustomerEntityService getCustomerEntityService() { + if (customerEntityService == null) { + customerEntityService = SpringApp.getBean(CompanyCustomerEntityService.class); + } + return customerEntityService; + } + + /** + * 更新客户相关项 + * + * @param item + * @param unitCode + * @param holder + */ + public CompanyCustomerEntity updateCustomerEntityDetailByCode(CompanyCustomerEntity item, String unitCode, MessageHolder holder) { + if (applyEntityDetail(item, repository.findCustomerByCusCode(unitCode), holder)) { + item = save(item); + } + return item; + } + + public boolean applyEntityDetail(CompanyCustomerEntity item, Map map, MessageHolder holder) { + + String name = (String) map.get("cCusName"); + String abbName = (String) map.get("cCusAbbName"); + String cusCode = (String) map.get("cCusCode"); + String classCode = (String) map.get("cCCCode"); + + String createPerson = (String) map.get("cCreatePerson"); + String modifyPerson = (String) map.get("cModifyPerson"); + java.sql.Date devDate = (java.sql.Date) map.get("devDate"); + java.sql.Timestamp modifyDate = (java.sql.Timestamp) map.get("dModifyDate"); + + String bank = (String) map.get("cCusBank"); + String bankAccount = (String) map.get("cCusAccount"); + + + boolean modified = false; + if (updateText(item::getName, item::setName, name, holder, "名称")) { + modified = true; + } + if (updateText(item::getAbbName, item::setAbbName, abbName, holder, "简称")) { + modified = true; + } + if (updateText(item::getCode, item::setCode, cusCode, holder, "客户编号")) { + modified = true; + } + + if (updateCustomerCatalog(item::getCatalog, item::setCatalog, classCode, holder, "分类")) { + modified = true; + } + if (updateEmployeeByName(item::getCreator, item::setCreator, createPerson, holder, "创建人")) { + modified = true; + } + if (updateEmployeeByName(item::getModifier, item::setModifier, modifyPerson, holder, "修改人")) { + modified = true; + } + if (updateLocalDate(item::getDevelopDate, item::setDevelopDate, devDate, holder, "发展日期")) { + modified = true; + } + if (updateLocalDate(item::getModifyDate, item::setModifyDate, modifyDate, holder, "修改日期")) { + modified = true; + } + + if (modified) { + LocalDate today = LocalDate.now(); + if (item.getUpdatedDate() == null || item.getUpdatedDate().isBefore(today)) { + item.setUpdatedDate(today); + holder.info("更新日期更新为 " + today); + } + } + + CompanyCustomer customer = item.getCustomer(); + if (customer == null) { + // 如果没有关联客户,则根据供应商名称或别名查找公司 + Company company = findOrCreateCompanyByCustomerEntity(item, holder); + if (company != null) { + // 找到客户 + + customer = getCompanyCustomerService().findByCompany(company); + if (customer == null) { + customer = createCustomerByCustomerEntity(item, holder); + if (customer != null) { + customer.setCompany(company); + customer = getCompanyCustomerService().save(customer); + } + } + + if (customer != null) { + item.setCustomer(customer); + modified = true; + } + } + + } + if (customer != null) { + if (!Hibernate.isInitialized(customer)) { + customer = getCompanyCustomerService().findById(customer.getId()); + } + Company company = customer.getCompany(); + if (company != null) { + getCompanyBankAccountCtx().updateBankAccount(company, bank, bankAccount, holder); + } + } + return modified; + } + + private boolean updateCustomerCatalog(Supplier getter, Consumer setter, String catalogCode, MessageHolder holder, String topic) { + CustomerCatalog catalog = null; + if (StringUtils.hasText(catalogCode)) { + catalog = getCompanyCustomerService().findCatalogByCode(catalogCode); + } + if (catalog == null) { + setter.accept(null); + holder.warn("无效" + topic + ":" + catalogCode); + return true; + } else { + if (!Objects.equals(getter.get(), catalog)) { + if (!Hibernate.isInitialized(catalog)) { + catalog = getCompanyCustomerService().findCatalogByCode(catalogCode); + } + setter.accept(catalog); + holder.info(topic + "修改为: " + catalog.getName()); + return true; + } + } + return false; + } + + public boolean syncCustomer(Company company, MessageHolder holder) { + CompanyCustomer companyCustomer = getCompanyCustomerService().findByCompany(company); + if (companyCustomer == null) { + holder.error("客户未创建, 如需要请手动创建."); + return false; + } + // 检索相关项 + List entities = getCustomerEntityService().findAllByCustomer(companyCustomer); + if (entities.isEmpty()) { + holder.error("客户未关联任何相关项"); + return false; + } + + boolean updated = false; + boolean companyModified = false; + + // 更新相关项 + for (CompanyCustomerEntity entity : entities) { + if (!StringUtils.hasText(entity.getCode())) { + holder.warn("相关项:" + entity.getCode() + " 无效,跳过"); + continue; + } + if (applyEntityDetail(entity, repository.findCustomerByCusCode(entity.getCode()), holder)) { + entity = getCustomerEntityService().save(entity); + } + + if (getCompanyCtx().updateCompanyNameIfAbsent(company, entity.getName(), holder)) { + companyModified = true; + } + if (getCompanyCtx().updateCompanyAbbNameIfAbsent(company, entity.getAbbName(), holder)) { + companyModified = true; + } + } + + if (companyModified) { + company = getCompanyService().save(company); + updated = true; + } + + // 更新客户的开发日期 + if (updateCustomerDevelopDate(companyCustomer, entities, holder)) { + companyCustomer = getCompanyCustomerService().save(companyCustomer); + updated = true; + } + + for (CompanyCustomerEntity entity : entities) { + if (getContractCtx().syncByCustomerEntity(companyCustomer, entity, holder)) { + updated = true; + } + } + return updated; + } + + private boolean updateCustomerDevelopDate(CompanyCustomer companyCustomer, List entities, MessageHolder holder) { + LocalDate developDate = null; + for (CompanyCustomerEntity entity : entities) { + // 取最早的开发日期 + if (developDate == null || entity.getDevelopDate().isBefore(developDate)) { + developDate = entity.getDevelopDate(); + } + } + return updateLocalDate(companyCustomer::getDevelopDate, companyCustomer::setDevelopDate, developDate, holder, "开发日期"); + } + + public CompanyCustomerEntity findOrCreateByCode(Map rs, String cusCode, MessageHolder subHolder) { + CompanyCustomerEntityService customerEntityService = getCompanyCustomerEntityService(); + CompanyCustomerEntity entity = customerEntityService.findByCustomerCode(cusCode); + if (entity == null) { + entity = new CompanyCustomerEntity(); + entity.setCode(cusCode); + entity = customerEntityService.save(entity); + subHolder.info("创建客户相关项:" + cusCode); + } + return entity; + } + + private CompanyCustomer createCustomerByCustomerEntity(CompanyCustomerEntity entity, MessageHolder holder) { + LocalDate developDate = entity.getDevelopDate(); + if (developDate == null) { + holder.warn(entity.getName() + " 没有设置开发日期,跳过"); + return null; + } + // 创建发展日期从2023年1月1日后的 + LocalDate start = LocalDate.of(2023, 1, 1); + String autoCreateAfter = getConfService().getString(AUTO_CREATE_CUSTOMER_AFTER); + if (StringUtils.hasText(autoCreateAfter)) { + start = LocalDate.parse(autoCreateAfter); + } + if (developDate.isBefore(start)) { + // start 之前的不自动创建 + holder.warn(entity.getName() + " 的发展日期 " + developDate + " 是 " + start + " 之前的, 按规定不自动创建客户, 如有需要请手动创建"); + return null; + } + + CompanyCustomer companyCustomer = new CompanyCustomer(); + int nextId = SpringApp.getBean(OldVersionService.class).newCompanyCustomer(entity.getName()); + companyCustomer.setId(nextId); + companyCustomer.setDevelopDate(developDate); + holder.info("新客户:" + entity.getName() + "分配编号:" + nextId); + companyCustomer.setCreated(Instant.now()); + return companyCustomer; + } + + private Company findOrCreateCompanyByCustomerEntity(CompanyCustomerEntity entity, MessageHolder holder) { + String name = entity.getName(); + String abbName = entity.getAbbName(); + return getCompanyCtx().findOrCreateByNameOrAbbName(name, abbName, entity.getDevelopDate(), holder); + } + + public CompanyCustomerEntity save(CompanyCustomerEntity entity) { + return getCustomerEntityService().save(entity); + } + + +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/InventoryCtx.java b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/InventoryCtx.java new file mode 100644 index 0000000..f6f4a75 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/InventoryCtx.java @@ -0,0 +1,227 @@ +package com.ecep.contract.manager.cloud.u8.ctx; + +import com.ecep.contract.manager.cloud.u8.YongYouU8Service; +import com.ecep.contract.manager.ds.other.model.Inventory; +import com.ecep.contract.manager.ds.other.model.InventoryCatalog; +import com.ecep.contract.manager.ds.other.model.Price; +import com.ecep.contract.manager.ds.other.service.InventoryCatalogService; +import com.ecep.contract.manager.ds.other.service.InventoryService; +import com.ecep.contract.manager.ui.MessageHolder; +import lombok.Setter; +import org.hibernate.Hibernate; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.util.StringUtils; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import static com.ecep.contract.manager.SpringApp.getBean; + +public class InventoryCtx extends AbstractYongYouU8Ctx { + @Setter + private InventoryService inventoryService; + @Setter + private InventoryCatalogService inventoryCatalogService; + + public InventoryService getInventoryService() { + if (inventoryService == null) { + inventoryService = getBean(InventoryService.class); + } + return inventoryService; + } + + public InventoryCatalogService getInventoryCatalogService() { + if (inventoryCatalogService == null) { + inventoryCatalogService = getBean(InventoryCatalogService.class); + } + return inventoryCatalogService; + } + + private boolean applyInventoryDetail(Inventory item, Map map, MessageHolder holder) { + String catalogCode = (String) map.get("cInvCCode"); + String name = (String) map.get("cInvName"); + String spec = (String) map.get("cInvStd"); + String unitCode = (String) map.get("cComUnitCode"); + String unitName = (String) map.get("cComUnitName"); + String createPerson = (String) map.get("cCreatePerson"); + String modifyPerson = (String) map.get("cModifyPerson"); + Timestamp createDate = (Timestamp) map.get("dSDate"); + Timestamp modifyDate = (Timestamp) map.get("dModifyDate"); + + boolean modified = false; + + Double outTaxRate = (Double) map.get("iTaxRate"); + Double inTaxRate = (Double) map.get("iImpTaxRate"); + // 参考成本 + Double baseCostPrice = (Double) map.get("iInvSPrice"); + // 最新成本 + Double latestCostPrice = (Double) map.get("iInvNCost"); + // 计划价/售价 + Double plannedPrice = (Double) map.get("iInvRCost"); + // 最低售价 + Double lowestSalePrice = (Double) map.get("iInvLSCost"); + // 参考售价 + Double suggestedSalePrice = (Double) map.get("iInvSCost"); + + + if (!item.isNameLock()) { + if (updateText(item::getName, item::setName, name, holder, "名称")) { + modified = true; + } + } + + if (!item.isSpecificationLock()) { + if (updateText(item::getSpecification, item::setSpecification, spec, holder, "规格")) { + modified = true; + } + } + + if (updateText(item::getUnit, item::setUnit, unitName, holder, "单位")) { + modified = true; + } + + if (updatePrice(item.getPurchasePrice(), inTaxRate, latestCostPrice, holder, "采购")) { + modified = true; + } + if (updatePrice(item.getSalePrice(), outTaxRate, suggestedSalePrice, holder, "销售")) { + modified = true; + } + + if (updateInventoryCatalog(item::getCatalog, item::setCatalog, catalogCode, holder, "类别")) { + modified = true; + } + if (updateEmployeeByName(item::getCreator, item::setCreator, createPerson, holder, "创建人")) { + modified = true; + } + if (updateLocalDate(item::getCreateTime, item::setCreateTime, createDate, holder, "创建时间")) { + modified = true; + } + if (updateEmployeeByName(item::getUpdater, item::setUpdater, modifyPerson, holder, "修改人")) { + modified = true; + } + if (updateLocalDateTime(item::getUpdateDate, item::setUpdateDate, modifyDate, holder, "修改时间")) { + modified = true; + } + + return modified; + } + + private boolean updatePrice(Price price, Double taxRate, Double preTaxPrice, MessageHolder holder, String title) { + boolean modified = false; + if (taxRate != null) { + if (updateNumber(price::getTaxRate, price::setTaxRate, taxRate.floatValue(), holder, title + "税率")) { + modified = true; + } + } + if (preTaxPrice != null) { + if (updateNumber(price::getPreTaxPrice, price::setPreTaxPrice, preTaxPrice, holder, title + "未税单价")) { + modified = true; + } + } + return modified; + } + + + public boolean syncInventoryDetailByCode(Inventory inventory, String inventoryCode, MessageHolder holder) { + if (repository == null) { + return false; + } + Map map = null; + try { + map = repository.queryInventoryDetail(inventoryCode); + } catch (NoSuchBeanDefinitionException e) { + holder.warn("未启用 " + YongYouU8Service.NAME + " 服务"); + return false; + } catch (EmptyResultDataAccessException e) { + holder.error("无此存货:" + inventoryCode); + return false; + } + if (applyInventoryDetail(inventory, map, holder)) { + inventory.setUpdateDate(LocalDateTime.now()); + return true; + } + return false; + } + + /** + * 依据存货分类编号更新存货分类 + * + * @param getter 获取的函数 + * @param setter 设置的函数 + * @param catalogCode 存货分类编号 + * @param holder 消息处理对象 + * @param topic 主题 + * @return 是否更新 + */ + private boolean updateInventoryCatalog(Supplier getter, Consumer setter, String catalogCode, MessageHolder holder, String topic) { + InventoryCatalog catalog = null; + if (StringUtils.hasText(catalogCode)) { + catalog = getInventoryCatalogService().findByCode(catalogCode); + } + if (catalog == null) { + setter.accept(null); + holder.warn("无效" + topic + ":" + catalogCode); + return true; + } else { + if (!Objects.equals(getter.get(), catalog)) { + if (!Hibernate.isInitialized(catalog)) { + catalog = getInventoryCatalogService().findByCode(catalogCode); + } + setter.accept(catalog); + holder.info(topic + "修改为: " + catalog.getName()); + return true; + } + } + return false; + } + + /** + * 依据存货编号更新存货 + * + * @param getter 获取的函数 + * @param setter 设置的函数 + * @param inventoryCode 存货编号 + * @param holder 消息持有者 + * @param topic 主题 + * @return 是否更新 + */ + public boolean syncInventoryDetailByCode(Supplier getter, Consumer setter, String inventoryCode, MessageHolder holder, String topic) { + if (!StringUtils.hasText(inventoryCode)) { + return false; + } + Inventory inventory = null; + InventoryService service = getInventoryService(); + if (StringUtils.hasText(inventoryCode)) { + inventory = service.findByCode(inventoryCode); + if (inventory == null) { + inventory = service.createNewInstance(); + inventory.setCode(inventoryCode); + if (syncInventoryDetailByCode(inventory, inventoryCode, holder)) { + inventory = getInventoryService().save(inventory); + } else { + return false; + } + } + } + if (inventory == null) { + setter.accept(null); + holder.warn("无效" + topic + ":" + inventoryCode); + return true; + } else { + if (!Objects.equals(getter.get(), inventory)) { + if (!Hibernate.isInitialized(inventory)) { + inventory = service.findByCode(inventoryCode); + } + setter.accept(inventory); + holder.info(topic + "修改为: " + inventory.getName()); + return true; + } + } + return false; + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/InvoiceCtx.java b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/InvoiceCtx.java new file mode 100644 index 0000000..ec5b2b3 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/InvoiceCtx.java @@ -0,0 +1,50 @@ +package com.ecep.contract.manager.cloud.u8.ctx; + +import com.ecep.contract.manager.ds.company.model.Invoice; +import com.ecep.contract.manager.ds.company.service.InvoiceService; +import com.ecep.contract.manager.ui.MessageHolder; +import lombok.Setter; +import org.springframework.util.StringUtils; + +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import static com.ecep.contract.manager.SpringApp.getBean; + +public class InvoiceCtx extends AbstractYongYouU8Ctx { + @Setter + private InvoiceService invoiceService; + + InvoiceService getInvoiceService() { + if (invoiceService == null) { + invoiceService = getBean(InvoiceService.class); + } + return invoiceService; + } + + public boolean updateInvoiceByNumber(Supplier getter, Consumer setter, String invoiceNumber, MessageHolder holder, String topic) { + // TODO 从U8系统中获取数据 + Invoice invoice = null; + if (StringUtils.hasText(invoiceNumber)) { + invoice = getInvoiceService().findByCode(invoiceNumber); + if (invoice == null) { + invoice = new Invoice(); + invoice.setCode(invoiceNumber); + invoice = getInvoiceService().save(invoice); + } + } + if (invoice == null) { + setter.accept(null); + holder.warn("无效" + topic + ":" + invoiceNumber); + return true; + } else { + if (!Objects.equals(getter.get(), invoice)) { + setter.accept(invoice); + holder.info(topic + "修改为: " + invoice.getCode()); + return true; + } + } + return false; + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/PurchaseBillVoucherCtx.java b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/PurchaseBillVoucherCtx.java new file mode 100644 index 0000000..384d7e6 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/PurchaseBillVoucherCtx.java @@ -0,0 +1,419 @@ +package com.ecep.contract.manager.cloud.u8.ctx; + +import com.ecep.contract.manager.cloud.u8.YongYouU8Service; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.Invoice; +import com.ecep.contract.manager.ds.contract.model.*; +import com.ecep.contract.manager.ds.contract.service.PurchaseBillVoucherItemService; +import com.ecep.contract.manager.ds.contract.service.PurchaseBillVoucherService; +import com.ecep.contract.manager.ds.contract.service.PurchaseOrderItemService; +import com.ecep.contract.manager.ds.contract.service.PurchaseOrdersService; +import com.ecep.contract.manager.ds.other.model.Inventory; +import com.ecep.contract.manager.ds.vendor.model.CompanyVendor; +import com.ecep.contract.manager.ds.vendor.model.CompanyVendorEntity; +import com.ecep.contract.manager.ui.MessageHolder; +import lombok.Setter; +import org.springframework.data.domain.Sort; + +import java.math.BigDecimal; +import java.sql.Timestamp; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import static com.ecep.contract.manager.SpringApp.getBean; + +public class PurchaseBillVoucherCtx extends AbstractYongYouU8Ctx { + @Setter + private PurchaseOrdersService purchaseOrdersService; + @Setter + private PurchaseOrderItemService purchaseOrderItemService; + @Setter + private PurchaseBillVoucherService purchaseBillVoucherService; + @Setter + private PurchaseBillVoucherItemService purchaseBillVoucherItemService; + + + InventoryCtx inventoryCtx; + InvoiceCtx invoiceCtx; + ContractCtx contractCtx; + + PurchaseOrdersService getPurchaseOrdersService() { + if (purchaseOrdersService == null) { + purchaseOrdersService = getBean(PurchaseOrdersService.class); + } + return purchaseOrdersService; + } + + PurchaseBillVoucherService getPurchaseBillVoucherService() { + if (purchaseBillVoucherService == null) { + purchaseBillVoucherService = getBean(PurchaseBillVoucherService.class); + } + return purchaseBillVoucherService; + } + + private PurchaseOrderItemService getPurchaseOrderItemService() { + if (purchaseOrderItemService == null) { + purchaseOrderItemService = getBean(PurchaseOrderItemService.class); + } + return purchaseOrderItemService; + } + + PurchaseBillVoucherItemService getPurchaseBillVoucherItemService() { + if (purchaseBillVoucherItemService == null) { + purchaseBillVoucherItemService = getBean(PurchaseBillVoucherItemService.class); + } + return purchaseBillVoucherItemService; + } + + InventoryCtx getInventoryCtx() { + if (inventoryCtx == null) { + inventoryCtx = new InventoryCtx(); + inventoryCtx.from(this); + } + return inventoryCtx; + } + + InvoiceCtx getInvoiceCtx() { + if (invoiceCtx == null) { + invoiceCtx = new InvoiceCtx(); + invoiceCtx.from(this); + } + return invoiceCtx; + } + + ContractCtx getContractCtx() { + if (contractCtx == null) { + contractCtx = new ContractCtx(); + contractCtx.from(this); + } + return contractCtx; + } + + public void syncByCompany(Company company, MessageHolder holder) { + PurchaseBillVoucherService voucherService = getPurchaseBillVoucherService(); + PurchaseBillVoucherItemService voucherItemService = getPurchaseBillVoucherItemService(); + List vouchers = voucherService.findAll((root, q, cb) -> { + return cb.equal(root.get("company"), company); + }, Sort.unsorted()); + holder.debug("查找到 " + vouchers.size() + " 条专用发票记录在数据库中"); + Map voucherMap = vouchers.stream().collect(Collectors.toMap(PurchaseBillVoucher::getRefId, item -> item)); + + CompanyVendor vendor = getCompanyVendorService().findByCompany(company); + if (vendor != null) { + List entities = getCompanyVendorEntityService().findAllByVendor(vendor); + holder.debug(company.getName() + " 有 " + entities.stream().map(CompanyVendorEntity::getCode).collect(Collectors.joining(", ")) + " 关联项"); + for (CompanyVendorEntity entity : entities) { + // 查询 U8 数据库 + List> ds = repository.findAllPurchaseBillVoucherByVendorCode(entity.getCode()); + holder.debug("供应商关联项: " + entity.getCode() + " 查找到 " + ds.size() + " 条专用发票记录在 " + YongYouU8Service.NAME); + + for (Map map : ds) { + Integer pbvid = (Integer) map.get("PBVID"); + if (pbvid == 0) { + holder.warn("跳过无效专用发票记录:缺少 PBVID"); + continue; + } + holder.debug("发票 #" + pbvid); + PurchaseBillVoucher voucher = voucherMap.get(pbvid); + boolean voucherModified = false; + if (voucher == null) { + voucher = new PurchaseBillVoucher(); + voucher.setCompany(company); + voucher.setRefId(pbvid); + voucherMap.put(pbvid, voucher); + voucherModified = true; + + holder.info("新增发票记录"); + } + + if (applyPurchaseBillVoucherDetail(voucher, map, holder.sub("-- | "))) { + voucherModified = true; + } + + if (voucherModified) { + voucher = voucherService.save(voucher); + voucherMap.put(pbvid, voucher); + } + } + } + } + + List items = voucherItemService.findAll((root, q, cb) -> { + return cb.equal(root.get("voucher").get("company"), company); + }, Sort.unsorted()); + + // 按 order 分组 + Map> itemMap = items.stream().collect(Collectors.groupingBy(PurchaseBillVoucherItem::getVoucher, + Collectors.toMap(PurchaseBillVoucherItem::getRefId, item -> item))); + for (PurchaseBillVoucher voucher : voucherMap.values()) { + + // 查询 U8 数据库 + List> ds = repository.findAllPurchaseBillVoucherItemByPbvId(voucher.getRefId()); + holder.debug("专用发票#" + voucher.getRefId() + "查找到 " + ds.size() + "条条目记录在 " + YongYouU8Service.NAME); + Map subItemMap = itemMap.computeIfAbsent(voucher, k -> new HashMap<>()); + for (Map map : ds) { + Integer refId = (Integer) map.get("ID"); + if (refId == 0) { + holder.warn("跳过无效专用发票记录:缺少 ID"); + continue; + } + PurchaseBillVoucherItem item = subItemMap.remove(refId); + boolean itemModified = false; + if (item == null) { + item = new PurchaseBillVoucherItem(); + item.setVoucher(voucher); + item.setRefId(refId); + itemModified = true; + + holder.info("新增专用发票条目 #" + refId); + } + MessageHolder subHolder = holder.sub("-- | "); + if (applyPurchaseBillVoucherItemDetail(item, map, subHolder)) { + itemModified = true; + } + if (itemModified) { + item = voucherItemService.save(item); + } + } + for (PurchaseBillVoucherItem item : subItemMap.values()) { + holder.info("删除专用发票条目"); + voucherItemService.delete(item); + } + } + } + + public void syncByContract(Contract contract, MessageHolder holder) { + PurchaseBillVoucherService voucherService = getPurchaseBillVoucherService(); + PurchaseBillVoucherItemService voucherItemService = getPurchaseBillVoucherItemService(); + List items = voucherItemService.findAll((root, q, cb) -> { + return cb.equal(root.get("contract"), contract); + }, Sort.unsorted()); + + Map itemMap = items.stream().collect(Collectors.toMap(PurchaseBillVoucherItem::getRefId, item -> item)); + // 查询 U8 数据库 + List> ds = repository.findAllPurchaseBillVoucherItemByContractCode(contract.getCode()); + holder.debug("合同编号#" + contract.getCode() + "查找到 " + ds.size() + "条发票条目记录在 " + YongYouU8Service.NAME); + + for (Map map : ds) { + Integer refId = (Integer) map.get("ID"); + if (refId == 0) { + holder.warn("跳过无效专用发票记录:缺少 ID"); + continue; + } + PurchaseBillVoucherItem item = itemMap.remove(refId); + boolean itemModified = false; + if (item == null) { + item = new PurchaseBillVoucherItem(); + PurchaseBillVoucher voucher = this.updateVoucherByPBVID((Integer) map.get("PBVID"), holder); + item.setVoucher(voucher); + item.setRefId(refId); + itemModified = true; + + holder.info("新增专用发票条目 #" + refId); + } + MessageHolder subHolder = holder.sub("-- | "); + if (applyPurchaseBillVoucherItemDetail(item, map, subHolder)) { + itemModified = true; + } + if (itemModified) { + item = voucherItemService.save(item); + } + } + } + + private PurchaseBillVoucher updateVoucherByPBVID(Integer pbvId, MessageHolder holder) { + PurchaseBillVoucherService voucherService = getPurchaseBillVoucherService(); + PurchaseBillVoucher voucher = voucherService.findByRefId(pbvId); + if (voucher != null) { + return voucher; + } + voucher = new PurchaseBillVoucher(); + // 查询 U8 数据库 + Map map = repository.findPurchaseBillVoucherById(pbvId); + applyPurchaseBillVoucherDetail(voucher, map, holder.sub("-- | ")); + voucher = voucherService.save(voucher); + return voucher; + } + + + private boolean applyPurchaseBillVoucherDetail(PurchaseBillVoucher voucher, Map map, MessageHolder holder) { + String code = String.valueOf(map.get("PBVID")); + String vendorCode = (String) map.get("cVenCode"); + String invoiceNumber = (String) map.get("cPBVCode"); + String personCode = (String) map.get("cPersonCode"); + String inCode = (String) map.get("cInCode"); + + + Timestamp invoiceDate = (Timestamp) map.get("dPBVDate"); + Timestamp voucherDate = (Timestamp) map.get("dVouDate"); + + BigDecimal amount = (BigDecimal) map.get("iSum"); + + String maker = (String) map.get("cPBVMaker"); + String verifier = (String) map.get("cPBVVerifier"); + Timestamp makeTime = (Timestamp) map.get("cmaketime"); + Timestamp auditTime = (Timestamp) map.get("dverifysystime"); + + String description = (String) map.get("cPBVMemo"); + + + boolean modified = false; + + holder.debug("发票号码:" + invoiceNumber); + + if (updateCompanyByVendorCode(voucher::getCompany, voucher::setCompany, vendorCode, holder, "供应商")) { + modified = true; + } + if (updateInvoice(voucher::getInvoice, voucher::setInvoice, invoiceNumber, holder, "发票")) { + modified = true; + } + + if (voucher.getInvoice() != null) { + Invoice invoice = voucher.getInvoice(); + if (updateLocalDate(invoice::getInvoiceDate, invoice::setInvoiceDate, voucherDate, holder, "开票日期")) { + invoice = getInvoiceCtx().getInvoiceService().save(invoice); + voucher.setInvoice(invoice); + } + } + + if (updateEmployeeByCode(voucher::getEmployee, voucher::setEmployee, personCode, holder, "业务员")) { + modified = true; + } + if (updateEmployeeByName(voucher::getMaker, voucher::setMaker, maker, holder, "制单人")) { + modified = true; + } + if (updateEmployeeByName(voucher::getVerifier, voucher::setVerifier, verifier, holder, "审核人")) { + modified = true; + } + if (updateLocalDateTime(voucher::getMakerDate, voucher::setMakerDate, makeTime, holder, "制单时间")) { + modified = true; + } + if (updateLocalDateTime(voucher::getVerifierDate, voucher::setVerifierDate, auditTime, holder, "审核时间")) { + modified = true; + } + if (!Objects.equals(voucher.getDescription(), description)) { + voucher.setDescription(description); + holder.info("描述修改为: " + description); + modified = true; + } + return modified; + } + + + private boolean applyPurchaseBillVoucherItemDetail(PurchaseBillVoucherItem item, Map map, MessageHolder holder) { + String code = String.valueOf(map.get("ID")); + String inventoryCode = (String) map.get("cInvCode"); + String contractCode = (String) map.get("ContractCode"); + + + String title = (String) map.get("cInvCode"); + double quantity = (double) map.get("iPBVQuantity"); + BigDecimal taxRate = (BigDecimal) map.get("iOriTaxPrice"); + BigDecimal taxPrice = (BigDecimal) map.get("iTaxPrice"); + BigDecimal amount = (BigDecimal) map.get("iSum"); + + // RdsId + + // 采购订单条目refId + Integer purchaseOrderItemId = (Integer) map.get("iPOsID"); + + String description = (String) map.get("cdescription"); + Timestamp signDate = (Timestamp) map.get("dSignDate"); + + boolean modified = false; + + holder.debug("条目:" + title + " x " + amount); + if (updateInventory(item::getInventory, item::setInventory, inventoryCode, holder, "商品")) { + modified = true; + } + if (updatePurchaseOrderItem(item::getOrderItem, item::setOrderItem, purchaseOrderItemId, holder, "采购订单条目")) { + modified = true; + } + if (updateContractByContractCode(item::getContract, item::setContract, contractCode, holder, "合同")) { + modified = true; + } + + if (updateNumber(item::getPrice, item::setPrice, taxPrice, holder, "含税单价")) { + modified = true; + } + if (updateNumber(item::getQuantity, item::setQuantity, quantity, holder, "数量")) { + modified = true; + } + if (updateAppendText(item::getDescription, item::setDescription, description, holder, "描述")) { + modified = true; + } + return modified; + } + + boolean updatePurchaseOrderItem(Supplier getter, Consumer setter, Integer orderItemRefId, MessageHolder holder, String topic) { + PurchaseOrderItem item = null; + if (orderItemRefId != null) { + item = getPurchaseOrderItemService().findByRefId(orderItemRefId); + } + if (item == null) { + setter.accept(null); + holder.warn("无效" + topic + ":" + orderItemRefId); + return true; + } else { + if (!Objects.equals(getter.get(), item)) { + setter.accept(item); + holder.info(topic + "修改为: " + item.getRefId()); + return true; + } + } + return false; + } + + boolean updatePurchaseOrder(Supplier getter, Consumer setter, Integer orderRefId, MessageHolder holder, String topic) { + PurchaseOrder order = null; + if (orderRefId != null) { + order = getPurchaseOrdersService().findByRefId(orderRefId); + } + if (order == null) { + setter.accept(null); + holder.warn("无效" + topic + ":" + orderRefId); + return true; + } else { + if (!Objects.equals(getter.get(), order)) { + setter.accept(order); + holder.info(topic + "修改为: " + order.getCode()); + return true; + } + } + return false; + } + + boolean updateContractByContractCode(Supplier getter, Consumer setter, String contractCode, MessageHolder holder, String topic) { + Contract contract = null; + if (contractCode != null) { + contract = getContractCtx().findContractByCode(contractCode); + } + if (contract == null) { + setter.accept(null); + holder.warn("无效" + topic + ":" + contractCode); + return true; + } else { + if (!Objects.equals(getter.get(), contract)) { + setter.accept(contract); + holder.info(topic + "修改为: " + contract.getCode()); + return true; + } + } + return false; + } + + boolean updateInventory(Supplier getter, Consumer setter, String inventoryCode, MessageHolder holder, String topic) { + return getInventoryCtx().syncInventoryDetailByCode(getter, setter, inventoryCode, holder, topic); + } + + private boolean updateInvoice(Supplier getter, Consumer setter, String invoiceNumber, MessageHolder holder, String topic) { + return getInvoiceCtx().updateInvoiceByNumber(getter, setter, invoiceNumber, holder, topic); + } + + +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/PurchaseOrderCtx.java b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/PurchaseOrderCtx.java new file mode 100644 index 0000000..b7bc2b2 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/PurchaseOrderCtx.java @@ -0,0 +1,318 @@ +package com.ecep.contract.manager.cloud.u8.ctx; + +import com.ecep.contract.manager.cloud.u8.YongYouU8Service; +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.contract.model.PurchaseOrder; +import com.ecep.contract.manager.ds.contract.model.PurchaseOrderItem; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ds.contract.service.PurchaseOrderItemService; +import com.ecep.contract.manager.ds.contract.service.PurchaseOrdersService; +import com.ecep.contract.manager.ds.other.model.Inventory; +import com.ecep.contract.manager.ui.MessageHolder; +import com.ecep.contract.manager.util.NumberUtils; +import lombok.Setter; +import org.hibernate.Hibernate; +import org.springframework.data.domain.Sort; + +import java.math.BigDecimal; +import java.sql.Timestamp; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import static com.ecep.contract.manager.SpringApp.getBean; + +public class PurchaseOrderCtx extends AbstractYongYouU8Ctx { + @Setter + private ContractService contractService; + @Setter + private PurchaseOrdersService purchaseOrdersService; + @Setter + private PurchaseOrderItemService purchaseOrderItemService; + + InventoryCtx inventoryCtx; + CompanyBankAccountCtx companyBankAccountCtx; + + InventoryCtx getInventoryCtx() { + if (inventoryCtx == null) { + inventoryCtx = new InventoryCtx(); + inventoryCtx.from(this); + } + return inventoryCtx; + } + + CompanyBankAccountCtx getCompanyBankAccountCtx() { + if (companyBankAccountCtx == null) { + companyBankAccountCtx = new CompanyBankAccountCtx(); + companyBankAccountCtx.from(this); + } + return companyBankAccountCtx; + } + + ContractService getContractService() { + if (contractService == null) { + contractService = getBean(ContractService.class); + } + return contractService; + } + + PurchaseOrdersService getPurchaseOrdersService() { + if (purchaseOrdersService == null) { + purchaseOrdersService = getBean(PurchaseOrdersService.class); + } + return purchaseOrdersService; + } + + PurchaseOrderItemService getPurchaseOrderItemService() { + if (purchaseOrderItemService == null) { + purchaseOrderItemService = getBean(PurchaseOrderItemService.class); + } + return purchaseOrderItemService; + } + + + public List syncByContract(Contract contract, MessageHolder holder) { + PurchaseOrdersService ordersService = getPurchaseOrdersService(); + PurchaseOrderItemService itemService = getPurchaseOrderItemService(); + + List orders = ordersService.findAll((root, q, cb) -> { + return cb.equal(root.get("contract"), contract); + }, Sort.unsorted()); + holder.debug("查找到 " + orders.size() + " 条采购订单记录在数据库中"); + Map ordersMap = orders.stream().collect(Collectors.toMap(PurchaseOrder::getRefId, item -> item)); + + List items = itemService.findAll((root, q, cb) -> { + return cb.equal(root.get("order").get("contract"), contract); + }, Sort.unsorted()); + + // 按 order 分组 + Map> itemMap = items.stream().collect(Collectors.groupingBy(PurchaseOrderItem::getOrder, + Collectors.toMap(PurchaseOrderItem::getRefId, item -> item))); + + // 查询 U8 数据库 + List> ds = repository.findAllPurchaseOrderItemByContractCode(contract.getCode()); + holder.debug("查找到 " + ds.size() + " 条采购订单条目记录在 " + YongYouU8Service.NAME); + + Map> updateMap = new HashMap<>(); + for (Map map : ds) { + Integer poId = (Integer) map.get("POID"); + if (poId == 0) { + holder.warn("跳过无效采购订单记录:缺少 POID"); + continue; + } + PurchaseOrder order = ordersMap.get(poId); + if (order == null) { + order = new PurchaseOrder(); + order.setContract(contract); + order.setRefId(poId); + + order = ordersService.save(order); + ordersMap.put(poId, order); + holder.info("新增采购订单 #" + poId); + } + } + for (Map map : ds) { + // holder.debug("条目:" + map.toString()); + Integer poId = (Integer) map.get("POID"); + if (poId == 0) { + holder.warn("跳过无效采购订单记录:缺少 POID"); + continue; + } + PurchaseOrder order = ordersMap.get(poId); + + boolean itemModified = false; + List updates = updateMap.computeIfAbsent(order, k -> new ArrayList<>()); + + // 获取条目标识并处理 null + Integer refId = (Integer) map.get("ID"); + if (poId == null) { + holder.warn("跳过条目:订单 " + poId + " 缺少 ID"); + continue; + } + Map subItemMap = itemMap.get(order); + PurchaseOrderItem item = null; + if (subItemMap != null) { + item = subItemMap.remove(refId); + } + + if (item == null) { + item = new PurchaseOrderItem(); + item.setOrder(order); + item.setRefId(refId); + itemModified = true; + + holder.info("新增采购订单条目 #" + refId); + } + MessageHolder subHolder = holder.sub("---| "); + if (applyPurchaseOrderItemDetail(item, map, subHolder)) { + itemModified = true; + } + if (itemModified) { + item = itemService.save(item); + } + updates.add(item); + } + + for (PurchaseOrder order : updateMap.keySet()) { + if (applyPurchaseOrderDetail(order, repository.queryPurchaseOrderDetail(order.getRefId()), holder)) { + ordersService.save(order); + } + } + + if (!itemMap.isEmpty()) { + for (Map subMap : itemMap.values()) { + if (!subMap.isEmpty()) { + for (PurchaseOrderItem item : subMap.values()) { + holder.info("删除采购订单条目"); + itemService.delete(item); + } + } + } + + for (PurchaseOrder order : updateMap.keySet()) { + itemMap.remove(order); + } + if (!itemMap.isEmpty()) { + holder.info("剩余 " + itemMap.size() + " 个采购订单条目"); + } + } + + return new ArrayList<>(updateMap.keySet()); + } + + private boolean applyPurchaseOrderDetail(PurchaseOrder order, Map map, MessageHolder holder) { + String code = (String) map.get("cPOID"); + + String venBank = (String) map.get("cVenBank"); + String venBankAccount = (String) map.get("cVenAccount"); + + String venCode = (String) map.get("cVenCode"); + String memo = (String) map.get("cMemo"); + + boolean modified = false; + + if (!Objects.equals(order.getCode(), code)) { + order.setCode(code); + holder.info("订单编号更新为 " + code); + modified = true; + } + + if (updateEmployeeByCode(order::getEmployee, order::setEmployee, (String) map.get("cPersonCode"), holder, "业务员")) { + modified = true; + } + if (updateEmployeeByName(order::getMaker, order::setMaker, (String) map.get("cMaker"), holder, "制单人")) { + modified = true; + } + if (updateEmployeeByName(order::getVerifier, order::setVerifier, (String) map.get("cVerifier"), holder, "审核人")) { + modified = true; + } + if (updateEmployeeByName(order::getCloser, order::setCloser, (String) map.get("cCloser"), holder, "订单关闭人")) { + modified = true; + } + if (updateLocalDateTime(order::getMakerDate, order::setMakerDate, (Timestamp) map.get("cmaketime"), holder, "制单日期")) { + modified = true; + } + if (updateLocalDateTime(order::getModifyDate, order::setModifyDate, (Timestamp) map.get("cModifyTime"), holder, "修改日期")) { + modified = true; + } + if (updateLocalDateTime(order::getVerifierDate, order::setVerifierDate, (Timestamp) map.get("cAuditTime"), holder, "审核日期")) { + modified = true; + } + if (updateLocalDateTime(order::getCloserDate, order::setCloserDate, (Timestamp) map.get("dCloseTime"), holder, "关闭日期")) { + modified = true; + } + if (!Objects.equals(order.getVendorCode(), venCode)) { + order.setVendorCode(venCode); + holder.info("供方代码修改为: " + venCode); + modified = true; + } + + if (!Objects.equals(order.getDescription(), memo)) { + order.setDescription(memo); + holder.info("描述修改为: " + memo); + modified = true; + } + + updateCompanyBankAccount(order.getContract(), venBank, venBankAccount, holder); + + return modified; + } + + private void updateCompanyBankAccount(Contract contract, String bank, String bankAccount, MessageHolder holder) { + if (contract == null) { + return; + } + if (!Hibernate.isInitialized(contract)) { + contract = getContractService().findById(contract.getId()); + } + getCompanyBankAccountCtx().updateBankAccount(contract.getCompany(), bank, bankAccount, holder); + } + + + private boolean applyPurchaseOrderItemDetail(PurchaseOrderItem item, Map map, MessageHolder holder) { + Integer refId = (Integer) map.get("ID"); + String inventoryCode = (String) map.get("cInvCode"); + String contractCode = (String) map.get("ContractCode"); + + String title = (String) map.get("cInvCode"); + double quantity = (double) map.get("iQuantity"); + BigDecimal taxRate = (BigDecimal) map.get("iPerTaxRate"); + BigDecimal taxPrice = (BigDecimal) map.get("iTaxPrice"); + double exclusiveTaxPrice = (double) map.get("iUnitPrice"); + BigDecimal amount = (BigDecimal) map.get("iSum"); + + + Timestamp arriveDate = (Timestamp) map.get("dArriveDate"); + + boolean modified = false; + + holder.debug("条目:" + title + " x " + amount); + if (updateInventory(item::getInventory, item::setInventory, inventoryCode, holder, "商品")) { + modified = true; + } + + if (!Objects.equals(item.getRefId(), refId)) { + item.setRefId(refId); + holder.info("RefId修改为: " + refId); + modified = true; + } + if (!NumberUtils.equals(item.getExclusiveTaxPrice(), exclusiveTaxPrice)) { + item.setExclusiveTaxPrice(exclusiveTaxPrice); + holder.info("不含税单价修改为: " + exclusiveTaxPrice); + modified = true; + } + + if (!NumberUtils.equals(item.getPrice(), taxPrice.doubleValue())) { + item.setPrice(taxPrice.doubleValue()); + holder.info("含税单价修改为: " + taxPrice.doubleValue()); + modified = true; + } + if (!NumberUtils.equals(item.getTaxRate(), taxRate.doubleValue())) { + item.setTaxRate(taxRate.doubleValue()); + holder.info("税率修改为: " + taxRate); + modified = true; + } + if (!NumberUtils.equals(item.getQuantity(), quantity)) { + item.setQuantity(quantity); + holder.info("数量修改为: " + quantity); + modified = true; + } + + if (updateLocalDate(item::getArriveDate, item::setArriveDate, (Timestamp) map.get("dArriveDate"), holder, "开始日期")) { + modified = true; + } + + if (!Objects.equals(item.getDescription(), contractCode)) { + item.setDescription(contractCode); + holder.info("描述修改为: " + contractCode); + modified = true; + } + + return modified; + } + + boolean updateInventory(Supplier getter, Consumer setter, String inventoryCode, MessageHolder holder, String topic) { + return getInventoryCtx().syncInventoryDetailByCode(getter, setter, inventoryCode, holder, topic); + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/PurchaseSettlementVoucherCtx.java b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/PurchaseSettlementVoucherCtx.java new file mode 100644 index 0000000..adb8296 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/PurchaseSettlementVoucherCtx.java @@ -0,0 +1,85 @@ +package com.ecep.contract.manager.cloud.u8.ctx; + +import com.ecep.contract.manager.ds.contract.model.PurchaseSettlementVoucher; +import com.ecep.contract.manager.ds.contract.model.PurchaseSettlementVoucherItem; +import com.ecep.contract.manager.ds.other.model.Inventory; +import com.ecep.contract.manager.ui.MessageHolder; + +import java.sql.Timestamp; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class PurchaseSettlementVoucherCtx extends AbstractYongYouU8Ctx { + InventoryCtx inventoryCtx; + + InventoryCtx getInventoryCtx() { + if (inventoryCtx == null) { + inventoryCtx = new InventoryCtx(); + inventoryCtx.from(this); + } + return inventoryCtx; + } + + + private boolean applyPurchaseSettlementVoucherDetail(PurchaseSettlementVoucher voucher, Map map, MessageHolder holder) { + String code = (String) map.get("cSVCode"); + String vendorCode = (String) map.get("cVenCode"); + String personCode = (String) map.get("cPersonCode"); + double taxRate = (double) map.get("iTaxRate"); + String maker = (String) map.get("cMaker"); + Timestamp date = (Timestamp) map.get("dSVDate"); + + String description = (String) map.get("cSVMemo"); + boolean modified = false; + if (updateText(voucher::getCode, voucher::setCode, code, holder, "编号:")) { + modified = true; + } + if (updateCompanyByVendorCode(voucher::getCompany, voucher::setCompany, vendorCode, holder, "供应商")) { + modified = true; + } + if (updateEmployeeByCode(voucher::getEmployee, voucher::setEmployee, personCode, holder, "业务员")) { + modified = true; + } + if (updateEmployeeByName(voucher::getMaker, voucher::setMaker, maker, holder, "制单人")) { + modified = true; + } + if (updateLocalDate(voucher::getDate, voucher::setDate, date, holder, "日期:")) { + modified = true; + } + if (updateAppendText(voucher::getDescription, voucher::setDescription, description, holder, "描述")) { + modified = true; + } + + return modified; + } + + + private boolean applyPurchaseSettlementVoucherItemDetail(PurchaseSettlementVoucherItem item, Map map, MessageHolder holder) { + String inventoryCode = (String) map.get("cInvCode"); + String pivCode = (String) map.get("cPIVCode"); + String accountant = (String) map.get("cbAccounter"); + Integer rdsID = (Integer) map.get("iRdsID"); + double quantity = (double) map.get("iPBVQuantity"); + String description = (String) map.get("cbMemo"); + boolean modified = false; + if (updateInventory(item::getInventory, item::setInventory, inventoryCode, holder, "商品")) { + modified = true; + } + if (updateEmployeeByName(item::getAccountant, item::setAccountant, accountant, holder, "会计师")) { + modified = true; + } + if (updateNumber(item::getQuantity, item::setQuantity, quantity, holder, "数量")) { + modified = true; + } + if (updateAppendText(item::getDescription, item::setDescription, description, holder, "描述")) { + modified = true; + } + + return modified; + } + + boolean updateInventory(Supplier getter, Consumer setter, String inventoryCode, MessageHolder holder, String topic) { + return getInventoryCtx().syncInventoryDetailByCode(getter, setter, inventoryCode, holder, topic); + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/SalesBillVoucherCtx.java b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/SalesBillVoucherCtx.java new file mode 100644 index 0000000..da6293e --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/SalesBillVoucherCtx.java @@ -0,0 +1,363 @@ +package com.ecep.contract.manager.cloud.u8.ctx; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.cloud.u8.YongYouU8Service; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.contract.model.SalesBillVoucher; +import com.ecep.contract.manager.ds.contract.model.SalesBillVoucherItem; +import com.ecep.contract.manager.ds.contract.model.SalesOrder; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ds.contract.service.SaleOrdersService; +import com.ecep.contract.manager.ds.contract.service.SalesBillVoucherService; +import com.ecep.contract.manager.ds.customer.model.CompanyCustomer; +import com.ecep.contract.manager.ds.customer.model.CompanyCustomerEntity; +import com.ecep.contract.manager.ds.other.model.Inventory; +import com.ecep.contract.manager.ui.MessageHolder; +import com.ecep.contract.manager.util.NumberUtils; +import lombok.Setter; +import org.hibernate.Hibernate; +import org.springframework.data.domain.Sort; +import org.springframework.util.StringUtils; + +import java.math.BigDecimal; +import java.sql.Timestamp; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import static com.ecep.contract.manager.SpringApp.getBean; + +public class SalesBillVoucherCtx extends AbstractYongYouU8Ctx { + @Setter + private ContractService contractService; + @Setter + private SaleOrdersService saleOrdersService; + @Setter + private SalesBillVoucherService salesBillVoucherService; + + InventoryCtx inventoryCtx; + + ContractService getContractService() { + if (contractService == null) { + contractService = getBean(ContractService.class); + } + return contractService; + } + + SaleOrdersService getSaleOrdersService() { + if (saleOrdersService == null) { + saleOrdersService = getBean(SaleOrdersService.class); + } + return saleOrdersService; + } + + SalesBillVoucherService getSalesBillVoucherService() { + if (salesBillVoucherService == null) { + salesBillVoucherService = SpringApp.getBean(SalesBillVoucherService.class); + } + return salesBillVoucherService; + } + + InventoryCtx getInventoryCtx() { + if (inventoryCtx == null) { + inventoryCtx = new InventoryCtx(); + inventoryCtx.from(this); + } + return inventoryCtx; + } + + public void syncByCompany(Company company, MessageHolder holder) { + + List vouchers = salesBillVoucherService.findAll((root, q, cb) -> { + return cb.equal(root.get("company"), company); + }, Sort.unsorted()); + holder.debug("查找到 " + vouchers.size() + " 条专用发票记录在数据库中"); + Map voucherMap = vouchers.stream().collect(Collectors.toMap(SalesBillVoucher::getRefId, item -> item)); + + CompanyCustomer customer = getCompanyCustomerService().findByCompany(company); + if (customer != null) { + List entities = getCompanyCustomerEntityService().findAllByCustomer(customer); + for (CompanyCustomerEntity entity : entities) { + // 查询 U8 数据库 + List> ds = repository.findAllSalesBillVoucherByCustomerCode(entity.getCode()); + holder.debug("查找" + entity.getCode() + "到 " + ds.size() + " 条专用发票记录在 " + YongYouU8Service.NAME); + + for (Map map : ds) { + Integer sbvid = (Integer) map.get("SBVID"); + if (sbvid == 0) { + holder.warn("跳过无效专用发票记录:缺少 SBVID"); + continue; + } + SalesBillVoucher voucher = voucherMap.get(sbvid); + boolean voucherModified = false; + if (voucher == null) { + voucher = new SalesBillVoucher(); + voucher.setCompany(company); + voucher.setRefId(sbvid); + voucherMap.put(sbvid, voucher); + voucherModified = true; + + holder.info("新增专用发票记录 #" + sbvid); + } + if (applySalesBillVoucherDetail(voucher, map, holder)) { + voucherModified = true; + } + + if (voucherModified) { + voucher = salesBillVoucherService.save(voucher); + voucherMap.put(sbvid, voucher); + } + } + } + } + + List items = salesBillVoucherService.findAllItems((root, q, cb) -> { + return cb.equal(root.get("voucher").get("company"), company); + }, Sort.unsorted()); + + // 按 order 分组 + Map> itemMap = items.stream().collect(Collectors.groupingBy(SalesBillVoucherItem::getVoucher, + Collectors.toMap(SalesBillVoucherItem::getRefId, item -> item))); + for (SalesBillVoucher voucher : voucherMap.values()) { + + // 查询 U8 数据库 + List> ds = repository.findAllSalesBillVoucherItemBySBVID(voucher.getRefId()); + holder.debug("专用发票#" + voucher.getRefId() + "查找到 " + ds.size() + "条条目记录在 " + YongYouU8Service.NAME); + Map subItemMap = itemMap.computeIfAbsent(voucher, k -> new HashMap<>()); + for (Map map : ds) { + Integer refId = (Integer) map.get("ID"); + if (refId == 0) { + holder.warn("跳过无效专用发票记录:缺少 ID"); + continue; + } + SalesBillVoucherItem item = subItemMap.remove(refId); + boolean itemModified = false; + if (item == null) { + item = new SalesBillVoucherItem(); + item.setVoucher(voucher); + item.setRefId(refId); + itemModified = true; + + holder.info("新增专用发票条目 #" + refId); + } + MessageHolder subHolder = holder.sub("---| "); + if (applySalesBillVoucherItemDetail(item, map, subHolder)) { + itemModified = true; + } + if (itemModified) { + item = salesBillVoucherService.save(item); + } + } + for (SalesBillVoucherItem item : subItemMap.values()) { + holder.info("删除无效专用发票条目"); + salesBillVoucherService.delete(item); + } + } + } + + public void syncBySalesOrder(SalesOrder order, MessageHolder holder) { + SalesBillVoucherService voucherService = getSalesBillVoucherService(); + List vouchers = voucherService.findAll((root, q, cb) -> { + return cb.equal(root.get("order"), order); + }, Sort.unsorted()); + holder.debug("查找到 " + vouchers.size() + " 条专用发票记录在数据库中"); + Map voucherMap = vouchers.stream().collect(Collectors.toMap(SalesBillVoucher::getRefId, item -> item)); + { + // 查询 U8 数据库 + List> ds = repository.findAllSalesBillVoucherBySalesOrderCode(order.getCode()); + holder.debug("查找" + order.getCode() + "到 " + ds.size() + " 条专用发票记录在 " + YongYouU8Service.NAME); + + for (Map map : ds) { + Integer sbvid = (Integer) map.get("SBVID"); + if (sbvid == 0) { + holder.warn("跳过无效专用发票记录:缺少 SBVID"); + continue; + } + SalesBillVoucher voucher = voucherMap.get(sbvid); + boolean voucherModified = false; + if (voucher == null) { + voucher = new SalesBillVoucher(); + + Contract contract = order.getContract(); + if (!Hibernate.isInitialized(contract)) { + contract = getContractService().findById(contract.getId()); + } + voucher.setCompany(contract.getCompany()); + voucher.setRefId(sbvid); + voucherMap.put(sbvid, voucher); + voucherModified = true; + + holder.info("新增专用发票记录 #" + sbvid); + } + if (applySalesBillVoucherDetail(voucher, map, holder)) { + voucherModified = true; + } + + if (voucherModified) { + voucher = voucherService.save(voucher); + voucherMap.put(sbvid, voucher); + } + } + } + + List items = voucherService.findAllItems((root, q, cb) -> { + return cb.equal(root.get("voucher").get("order"), order); + }, Sort.unsorted()); + + // 按 order 分组 + Map> itemMap = items.stream().collect(Collectors.groupingBy(SalesBillVoucherItem::getVoucher, + Collectors.toMap(SalesBillVoucherItem::getRefId, item -> item))); + for (SalesBillVoucher voucher : voucherMap.values()) { + + // 查询 U8 数据库 + List> ds = repository.findAllSalesBillVoucherItemBySBVID(voucher.getRefId()); + holder.debug("专用发票#" + voucher.getRefId() + "查找到 " + ds.size() + "条条目记录在 " + YongYouU8Service.NAME); + Map subItemMap = itemMap.computeIfAbsent(voucher, k -> new HashMap<>()); + for (Map map : ds) { + Integer refId = (Integer) map.get("AutoID"); + if (refId == 0) { + holder.warn("跳过无效专用发票记录:缺少 AutoID"); + continue; + } + SalesBillVoucherItem item = subItemMap.remove(refId); + boolean itemModified = false; + if (item == null) { + item = new SalesBillVoucherItem(); + item.setVoucher(voucher); + item.setRefId(refId); + itemModified = true; + + holder.info("新增专用发票条目 #" + refId); + } + MessageHolder subHolder = holder.sub("---| "); + if (applySalesBillVoucherItemDetail(item, map, subHolder)) { + itemModified = true; + } + if (itemModified) { + item = voucherService.save(item); + } + } + for (SalesBillVoucherItem item : subItemMap.values()) { + holder.info("删除无效专用发票条目"); + voucherService.delete(item); + } + } + } + + + private boolean applySalesBillVoucherDetail(SalesBillVoucher voucher, Map map, MessageHolder holder) { + String code = String.valueOf(map.get("SBVID")); + String customerCode = (String) map.get("cCusCode"); + String salesOrderCode = (String) map.get("cSOCode"); + String personCode = (String) map.get("cPersonCode"); + String inCode = (String) map.get("cDLCode"); + Timestamp billDate = (Timestamp) map.get("dDate"); + + String maker = (String) map.get("cMaker"); + String checker = (String) map.get("cChecker"); + String modifier = (String) map.get("cmodifier"); + String verifier = (String) map.get("cVerifier"); + Timestamp makeTime = (Timestamp) map.get("cmaketime"); + Timestamp verifyTime = (Timestamp) map.get("dverifysystime"); + Timestamp modifyTime = (Timestamp) map.get("dmodifysystime"); + + String description = (String) map.get("cMemo"); + + + boolean modified = false; + + holder.debug("条目:" + code + " x " + salesOrderCode); + if (updateCompanyByCustomerCode(voucher::getCompany, voucher::setCompany, customerCode, holder, "客户")) { + modified = true; + } + + SalesOrder salesOrder = null; + if (StringUtils.hasText(salesOrderCode)) { + salesOrder = getSaleOrdersService().findByCode(salesOrderCode); + } + if (salesOrder == null) { + voucher.setOrder(null); + holder.warn("无效销售订单:" + salesOrderCode); + modified = true; + } else { + if (!Objects.equals(voucher.getOrder(), salesOrder)) { + voucher.setOrder(salesOrder); + holder.info("销售订单修改为: " + salesOrder.getCode()); + modified = true; + } + } + if (updateEmployeeByCode(voucher::getEmployee, voucher::setEmployee, personCode, holder, "业务员")) { + modified = true; + } + if (updateEmployeeByName(voucher::getMaker, voucher::setMaker, maker, holder, "制单人")) { + modified = true; + } + if (updateEmployeeByName(voucher::getVerifier, voucher::setVerifier, verifier, holder, "审核人")) { + modified = true; + } + + if (updateLocalDateTime(voucher::getMakerDate, voucher::setMakerDate, makeTime, holder, "制单时间")) { + modified = true; + } + if (updateLocalDateTime(voucher::getVerifierDate, voucher::setVerifierDate, verifyTime, holder, "审核时间")) { + modified = true; + } + + if (!Objects.equals(voucher.getDescription(), description)) { + voucher.setDescription(description); + holder.info("描述修改为: " + description); + modified = true; + } + return modified; + } + + + private boolean applySalesBillVoucherItemDetail(SalesBillVoucherItem item, Map map, MessageHolder holder) { + String code = String.valueOf(map.get("ID")); + String inventoryCode = (String) map.get("cInvCode"); + String contractCode = (String) map.get("ContractCode"); + + String title = (String) map.get("cInvCode"); + double quantity = (double) map.get("iQuantity"); + double taxRate = (double) map.get("iTaxRate"); + double taxPrice = (double) map.get("iTaxUnitPrice"); + BigDecimal amount = (BigDecimal) map.get("iSum"); + + + Timestamp signDate = (Timestamp) map.get("dSignDate"); + + boolean modified = false; + + holder.debug("条目:" + title + " x " + amount); + + if (updateInventory(item::getInventory, item::setInventory, inventoryCode, holder, "商品")) { + modified = true; + } + + if (!NumberUtils.equals(item.getPrice(), taxPrice)) { + item.setPrice(taxPrice); + holder.info("含税单价修改为: " + taxPrice); + modified = true; + } + if (!NumberUtils.equals(item.getQuantity(), quantity)) { + item.setQuantity(quantity); + holder.info("数量修改为: " + quantity); + modified = true; + } + if (!Objects.equals(item.getDescription(), contractCode)) { + item.setDescription(contractCode); + holder.info("描述修改为: " + contractCode); + modified = true; + } + return modified; + } + + boolean updateInventory(Supplier getter, Consumer setter, String inventoryCode, MessageHolder holder, String topic) { + return getInventoryCtx().syncInventoryDetailByCode(getter, setter, inventoryCode, holder, topic); + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/SalesOrderCtx.java b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/SalesOrderCtx.java new file mode 100644 index 0000000..2d7faa9 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/SalesOrderCtx.java @@ -0,0 +1,257 @@ +package com.ecep.contract.manager.cloud.u8.ctx; + +import com.ecep.contract.manager.cloud.u8.YongYouU8Service; +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.contract.model.SalesOrder; +import com.ecep.contract.manager.ds.contract.model.SalesOrderItem; +import com.ecep.contract.manager.ds.contract.service.SaleOrdersService; +import com.ecep.contract.manager.ds.contract.service.SalesOrderItemService; +import com.ecep.contract.manager.ui.MessageHolder; +import com.ecep.contract.manager.util.NumberUtils; +import lombok.Setter; +import org.springframework.data.domain.Sort; + +import java.math.BigDecimal; +import java.sql.Timestamp; +import java.util.*; +import java.util.stream.Collectors; + +import static com.ecep.contract.manager.SpringApp.getBean; + +public class SalesOrderCtx extends AbstractYongYouU8Ctx { + + @Setter + private SaleOrdersService saleOrdersService; + @Setter + private SalesOrderItemService orderItemService; + + SaleOrdersService getSaleOrdersService() { + if (saleOrdersService == null) { + saleOrdersService = getBean(SaleOrdersService.class); + } + return saleOrdersService; + } + + SalesOrderItemService getOrderItemService() { + if (orderItemService == null) { + orderItemService = getBean(SalesOrderItemService.class); + } + return orderItemService; + } + + + public List syncByContract(Contract contract, MessageHolder holder) { + SaleOrdersService saleOrdersService = getSaleOrdersService(); + SalesOrderItemService orderItemService = getOrderItemService(); + + List orders = saleOrdersService.findAll((root, q, cb) -> { + return cb.equal(root.get("contract"), contract); + }, Sort.unsorted()); + holder.debug("查找到 " + orders.size() + " 条销售订单记录在数据库中"); + Map ordersMap = orders.stream().collect(Collectors.toMap(SalesOrder::getCode, item -> item)); + + List items = orderItemService.findAll((root, q, cb) -> { + return cb.equal(root.get("order").get("contract"), contract); + }, Sort.unsorted()); + + // 按 order 分组 + Map> itemMap = items.stream().collect(Collectors.groupingBy(SalesOrderItem::getOrder, + Collectors.toMap(SalesOrderItem::getCode, item -> item))); + + // 查询 U8 数据库 + List> ds = repository.findAllSalesOrderItemByContractCode(contract.getCode()); + holder.debug("查找到 " + ds.size() + " 条销售订单条目记录在 " + YongYouU8Service.NAME); + + Map> updateMap = new HashMap<>(); + for (Map map : ds) { + String orderCode = Optional.ofNullable(map.get("cSOCode")).map(Object::toString).orElse(""); + if (orderCode.isEmpty()) { + holder.warn("跳过无效销售订单记录:缺少 cSOCode"); + continue; + } + SalesOrder order = ordersMap.get(orderCode); + if (order == null) { + order = new SalesOrder(); + order.setContract(contract); + order.setCode(orderCode); + + order = saleOrdersService.save(order); + ordersMap.put(orderCode, order); + holder.info("新增销售订单 #" + orderCode); + } + } + for (Map map : ds) { + // holder.debug("条目:" + map.toString()); + String orderCode = Optional.ofNullable(map.get("cSOCode")).map(Object::toString).orElse(""); + if (orderCode.isEmpty()) { + holder.warn("跳过无效销售订单记录:缺少 cSOCode"); + continue; + } + SalesOrder order = ordersMap.get(orderCode); + + boolean itemModified = false; + List updates = updateMap.computeIfAbsent(order, k -> new ArrayList<>()); + + // 获取条目标识并处理 null + String refId = Optional.ofNullable(map.get("iSOsID")).map(Object::toString).orElse(""); + if (refId.isEmpty()) { + holder.warn("跳过条目:订单 " + orderCode + " 缺少 iSOsID"); + continue; + } + Map subItemMap = itemMap.get(order); + SalesOrderItem item = null; + if (subItemMap != null) { + item = subItemMap.remove(refId); + } + + if (item == null) { + item = new SalesOrderItem(); + item.setOrder(order); + item.setCode(refId); + itemModified = true; + + holder.info("新增销售订单条目 #" + refId); + } + MessageHolder subHolder = holder.sub("---| "); + if (applySaleOrderItemDetail(item, map, subHolder)) { + itemModified = true; + } + if (itemModified) { + item = orderItemService.save(item); + } + updates.add(item); + } + + for (SalesOrder order : updateMap.keySet()) { + holder.debug("销售订单 #" + order.getCode()); + if (applySalesOrderDetail(order, repository.querySalesOrderDetail(order.getCode()), holder)) { + saleOrdersService.save(order); + } + } + + if (!itemMap.isEmpty()) { + for (Map subMap : itemMap.values()) { + if (!subMap.isEmpty()) { + for (SalesOrderItem item : subMap.values()) { + holder.info("删除销售订单条目"); + orderItemService.delete(item); + } + } + } + + for (SalesOrder order : updateMap.keySet()) { + itemMap.remove(order); + } + if (!itemMap.isEmpty()) { + holder.info("剩余 " + itemMap.size() + " 个销售订单条目"); + } + } + + return new ArrayList<>(updateMap.keySet()); + + } + + private boolean applySalesOrderDetail(SalesOrder order, Map map, MessageHolder holder) { + String code = (String) map.get("cSOCode"); + + String cCloser = (String) map.get("cCloser"); + + String memo = (String) map.get("cMemo"); + + boolean modified = false; + + if (!Objects.equals(order.getCode(), code)) { + order.setCode(code); + holder.info("订单编号更新为 " + code); + modified = true; + } + + if (updateEmployeeByCode(order::getEmployee, order::setEmployee, (String) map.get("cPersonCode"), holder, "业务员")) { + modified = true; + } + if (updateEmployeeByName(order::getMaker, order::setMaker, (String) map.get("cMaker"), holder, "制单人")) { + modified = true; + } + if (updateEmployeeByName(order::getVerifier, order::setVerifier, (String) map.get("cVerifier"), holder, "审核人")) { + modified = true; + } + + if (updateLocalDate(order::getMakerDate, order::setMakerDate, (Timestamp) map.get("dcreatesystime"), holder, "制单日期")) { + modified = true; + } + if (updateLocalDate(order::getVerifierDate, order::setVerifierDate, (Timestamp) map.get("dverifysystime"), holder, "审核日期")) { + modified = true; + } + + if (!Objects.equals(order.getDescription(), memo)) { + order.setDescription(memo); + holder.info("描述修改为: " + memo); + modified = true; + } + + return modified; + } + + + private boolean applySaleOrderItemDetail(SalesOrderItem item, Map map, MessageHolder holder) { + String code = String.valueOf(map.get("iSOsID")); + String spec = (String) map.get("cInvName"); + String title = (String) map.get("cInvName"); + double quantity = (double) map.get("iQuantity"); + double taxRate = (double) map.get("iTaxRate"); + double taxPrice = (double) map.get("iTaxUnitPrice"); + double exclusiveTaxPrice = (double) map.get("iUnitPrice"); + BigDecimal amount = (BigDecimal) map.get("iSum"); + + String memo = (String) map.get("cMemo"); + + boolean modified = false; + + holder.debug("条目:" + title + " x " + amount); + + + if (!Objects.equals(item.getCode(), code)) { + item.setCode(code); + holder.info("代码修改为: " + code); + modified = true; + } + if (!Objects.equals(item.getName(), title)) { + item.setName(title); + holder.info("名称修改为: " + title); + modified = true; + } + if (!NumberUtils.equals(item.getExclusiveTaxPrice(), exclusiveTaxPrice)) { + item.setExclusiveTaxPrice(exclusiveTaxPrice); + holder.info("不含税单价修改为: " + exclusiveTaxPrice); + modified = true; + } + if (!NumberUtils.equals(item.getPrice(), taxPrice)) { + item.setPrice(taxPrice); + holder.info("含税单价修改为: " + taxPrice); + modified = true; + } + if (!NumberUtils.equals(item.getTaxRate(), taxRate)) { + item.setTaxRate(taxRate); + holder.info("税率修改为: " + taxRate); + modified = true; + } + if (!NumberUtils.equals(item.getQuantity(), quantity)) { + item.setQuantity(quantity); + holder.info("数量修改为: " + quantity); + modified = true; + } + if (updateLocalDate(item::getStartDate, item::setStartDate, (Timestamp) map.get("dPreDate"), holder, "开始日期")) { + modified = true; + } + if (updateLocalDate(item::getEndDate, item::setEndDate, (Timestamp) map.get("dPreMoDate"), holder, "结束日期")) { + modified = true; + } + + if (!Objects.equals(item.getDescription(), memo)) { + item.setDescription(memo); + modified = true; + } + + return modified; + } +} diff --git a/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/VendorCtx.java b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/VendorCtx.java new file mode 100644 index 0000000..0f43e91 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/cloud/u8/ctx/VendorCtx.java @@ -0,0 +1,325 @@ +package com.ecep.contract.manager.cloud.u8.ctx; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.cloud.old.OldVersionService; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.vendor.model.CompanyVendor; +import com.ecep.contract.manager.ds.vendor.model.CompanyVendorEntity; +import com.ecep.contract.manager.ds.vendor.model.VendorCatalog; +import com.ecep.contract.manager.ds.vendor.service.CompanyVendorEntityService; +import com.ecep.contract.manager.ds.vendor.service.CompanyVendorService; +import com.ecep.contract.manager.ui.MessageHolder; +import lombok.Setter; +import org.hibernate.Hibernate; +import org.springframework.util.StringUtils; + +import java.time.Instant; +import java.time.LocalDate; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class VendorCtx extends AbstractYongYouU8Ctx { + private static final String AUTO_CREATE_VENDOR_AFTER = "cloud.u8.auto-create-vendor-after"; + @Setter + private CompanyVendorService companyVendorService; + @Setter + private CompanyCtx companyCtx; + @Setter + private ContractCtx contractCtx; + @Setter + private CompanyBankAccountCtx companyBankAccountCtx; + + public CompanyCtx getCompanyCtx() { + if (companyCtx == null) { + companyCtx = new CompanyCtx(); + companyCtx.from(this); + } + return companyCtx; + } + + ContractCtx getContractCtx() { + if (contractCtx == null) { + contractCtx = new ContractCtx(); + contractCtx.from(this); + } + return contractCtx; + } + + CompanyBankAccountCtx getCompanyBankAccountCtx() { + if (companyBankAccountCtx == null) { + companyBankAccountCtx = new CompanyBankAccountCtx(); + companyBankAccountCtx.from(this); + } + return companyBankAccountCtx; + } + + /** + * 更新供应商相关项(相关项是 U8 系统中的 Vendor 表数据,因为同一个公司有多个相关项,因此需要一个一对多的关系来处理)详情 + * + * @param item 供应商相关项 + * @param unitCode 供应商相关项编码 + * @param holder 消息 + */ + public CompanyVendorEntity updateVendorEntityDetailByCode(CompanyVendorEntity item, String unitCode, MessageHolder holder) { + if (applyEntityDetail(item, repository.findVendorByVendCode(unitCode), holder)) { + item = save(item); + } + return item; + } + + public boolean applyEntityDetail(CompanyVendorEntity item, Map map, MessageHolder holder) { + if (map == null || map.isEmpty()) { + holder.warn("Vendor 中未检索到供应商数据"); + return false; + } + String name = (String) map.get("cVenName"); + String abbName = (String) map.get("cVenAbbName"); + String venCode = (String) map.get("cVenCode"); + String classCode = (String) map.get("cVCCode"); + + String createPerson = (String) map.get("cCreatePerson"); + String modifyPerson = (String) map.get("cModifyPerson"); + java.sql.Date devDate = (java.sql.Date) map.get("devDate"); + java.sql.Timestamp modifyDate = (java.sql.Timestamp) map.get("dModifyDate"); + java.sql.Timestamp createDatetime = (java.sql.Timestamp) map.get("dVenCreateDatetime"); + + String bank = (String) map.get("cVenBank"); + String bankAccount = (String) map.get("cVenAccount"); + String address = (String) map.get("cVenAddress"); + String phone = (String) map.get("cVenPhone"); + String person = (String) map.get("cVenPerson"); + + + boolean modified = false; + if (updateText(item::getName, item::setName, name, holder, "名称")) { + modified = true; + } + if (updateText(item::getAbbName, item::setAbbName, abbName, holder, "简称")) { + modified = true; + } + if (updateText(item::getCode, item::setCode, venCode, holder, "供应商编号")) { + modified = true; + } + if (updateVendorCatalog(item::getCatalog, item::setCatalog, classCode, holder, "分类")) { + modified = true; + } + if (updateEmployeeByName(item::getCreator, item::setCreator, createPerson, holder, "创建人")) { + modified = true; + } + if (updateEmployeeByName(item::getModifier, item::setModifier, modifyPerson, holder, "修改人")) { + modified = true; + } + if (updateLocalDate(item::getDevelopDate, item::setDevelopDate, devDate, holder, "开发日期")) { + modified = true; + } + if (updateLocalDate(item::getModifyDate, item::setModifyDate, modifyDate, holder, "修改日期")) { + modified = true; + } + + if (modified) { + LocalDate today = LocalDate.now(); + if (item.getUpdatedDate() == null || item.getUpdatedDate().isBefore(today)) { + item.setUpdatedDate(today); + holder.info("更新日期更新为 " + today); + } + } + + + CompanyVendor vendor = item.getVendor(); + if (vendor == null) { + // 如果没有关联供应商,则根据供应商名称或别名查找公司 + Company company = findOrCreateCompanyByVendorEntity(item, holder); + if (company != null) { + vendor = getCompanyVendorService().findByCompany(company); + if (vendor == null) { + vendor = createVendorByVendorEntity(item, holder); + if (vendor != null) { + vendor.setCompany(company); + vendor = getCompanyVendorService().save(vendor); + } + } + if (vendor != null) { + item.setVendor(vendor); + modified = true; + } + } + } + if (vendor != null) { + if (!Hibernate.isInitialized(vendor)) { + vendor = getCompanyVendorService().findById(vendor.getId()); + } + Company company = vendor.getCompany(); + if (company != null) { + getCompanyBankAccountCtx().updateBankAccount(company, bank, bankAccount, holder.sub(item.getName())); + } + } + return modified; + } + + private boolean updateVendorCatalog(Supplier getter, Consumer setter, String catalogCode, MessageHolder holder, String topic) { + VendorCatalog catalog = null; + if (StringUtils.hasText(catalogCode)) { + catalog = getCompanyVendorService().findCatalogByCode(catalogCode); + } + if (catalog == null) { + setter.accept(null); + holder.warn("无效" + topic + ":" + catalogCode); + return true; + } else { + if (!Objects.equals(getter.get(), catalog)) { + if (!Hibernate.isInitialized(catalog)) { + catalog = getCompanyVendorService().findCatalogByCode(catalogCode); + } + setter.accept(catalog); + holder.info(topic + "修改为: " + catalog.getName()); + return true; + } + } + return false; + } + + /** + * 同步供应商 + * + * @param company 要同步的供应商企业 + * @param holder 状态输出 + * @return 是否更新了供应商信息 + */ + public boolean syncVendor(Company company, MessageHolder holder) { + CompanyVendor companyVendor = getCompanyVendorService().findByCompany(company); + if (companyVendor == null) { + holder.warn("供应商未创建, 如需要请手动创建."); + return false; + } + + // 检索相关项 + List entities = getCompanyVendorEntityService().findAllByVendor(companyVendor); + if (entities.isEmpty()) { + holder.error("供应商关联任何相关项"); + return false; + } + + boolean updated = false; + boolean companyModified = false; + + // 更新相关项 + for (CompanyVendorEntity entity : entities) { + if (!StringUtils.hasText(entity.getCode())) { + holder.warn("相关项:" + entity.getCode() + " 无效,跳过"); + continue; + } + if (applyEntityDetail(entity, repository.findVendorByVendCode(entity.getCode()), holder)) { + entity = getCompanyVendorEntityService().save(entity); + } + if (updateCompanyNameAndAbbNameByVendorEntity(company, entity, holder)) { + companyModified = true; + } + } + + if (companyModified) { + company = getCompanyService().save(company); + updated = true; + } + + // 更新供应商的开发日期 + if (updateVendorDevelopDate(companyVendor, entities, holder)) { + companyVendor = getCompanyVendorService().save(companyVendor); + updated = true; + } + + // 同步供应商关联的合同 + for (CompanyVendorEntity entity : entities) { + if (getContractCtx().syncByVendorEntity(companyVendor, entity, holder)) { + updated = true; + } + } + return updated; + } + + private boolean updateCompanyNameAndAbbNameByVendorEntity(Company company, CompanyVendorEntity entity, MessageHolder holder) { + CompanyService companyService = getCompanyService(); + if (company == null) { + return false; + } + if (!Hibernate.isInitialized(company)) { + company = companyService.findById(company.getId()); + } + boolean modified = false; + CompanyCtx companyCtx = getCompanyCtx(); + if (companyCtx.updateCompanyNameIfAbsent(company, entity.getName(), holder)) { + modified = true; + } + if (companyCtx.updateCompanyAbbNameIfAbsent(company, entity.getAbbName(), holder)) { + modified = true; + } + return modified; + } + + private boolean updateVendorDevelopDate(CompanyVendor companyVendor, List entities, MessageHolder holder) { + LocalDate developDate = null; + for (CompanyVendorEntity entity : entities) { + // 取最早的开发日期 + if (developDate == null || entity.getDevelopDate().isBefore(developDate)) { + developDate = entity.getDevelopDate(); + } + } + return updateLocalDate(companyVendor::getDevelopDate, companyVendor::setDevelopDate, developDate, holder, "开发日期"); + } + + public CompanyVendorEntity findOrCreateByCode(String venCode, MessageHolder subHolder) { + CompanyVendorEntityService service = getCompanyVendorEntityService(); + CompanyVendorEntity entity = service.findByCode(venCode); + if (entity == null) { + entity = new CompanyVendorEntity(); + entity.setCode(venCode); + entity = service.save(entity); + subHolder.info("创建供应商相关项: " + venCode); + } + return entity; + } + + private CompanyVendor createVendorByVendorEntity(CompanyVendorEntity entity, MessageHolder holder) { + LocalDate developDate = entity.getDevelopDate(); + if (developDate == null) { + holder.warn(entity.getName() + " 没有设置开发日期,跳过"); + return null; + } + // 创建发展日期从2023年1月1日后的 + LocalDate start = LocalDate.of(2023, 1, 1); + String autoCreateAfter = getConfService().getString(AUTO_CREATE_VENDOR_AFTER); + if (StringUtils.hasText(autoCreateAfter)) { + start = LocalDate.parse(autoCreateAfter); + } + if (developDate.isBefore(start)) { + // start 之前的不自动创建 + holder.warn(entity.getName() + " 的发展日期 " + developDate + " 是 " + start + " 之前的, 按规定不自动创建供应商, 如有需要请手动创建"); + return null; + } + + CompanyVendor companyVendor = new CompanyVendor(); + int nextId = SpringApp.getBean(OldVersionService.class).newCompanyVendor(entity.getName()); + companyVendor.setId(nextId); + companyVendor.setCatalog(entity.getCatalog()); + companyVendor.setDevelopDate(developDate); + holder.info("新供应商:" + entity.getName() + "分配编号:" + nextId); + companyVendor.setCreated(Instant.now()); + return companyVendor; + } + + + private Company findOrCreateCompanyByVendorEntity(CompanyVendorEntity entity, MessageHolder holder) { + String name = entity.getName(); + String abbName = entity.getAbbName(); + return getCompanyCtx().findOrCreateByNameOrAbbName(name, abbName, entity.getDevelopDate(), holder); + } + + public CompanyVendorEntity save(CompanyVendorEntity entity) { + return getCompanyVendorEntityService().save(entity); + } + + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/DsRepositoriesConfig.java b/src/main/java/com/ecep/contract/manager/ds/DsRepositoriesConfig.java new file mode 100644 index 0000000..aa67299 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/DsRepositoriesConfig.java @@ -0,0 +1,59 @@ +package com.ecep.contract.manager.ds; + +import com.zaxxer.hikari.HikariDataSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.jdbc.DataSourceBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.core.env.Environment; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.data.repository.config.BootstrapMode; +import org.springframework.jdbc.core.JdbcTemplate; + +import javax.sql.DataSource; + +import static com.ecep.contract.manager.AppV2.*; + +@Configuration +@EnableJpaRepositories(bootstrapMode = BootstrapMode.LAZY) +public class DsRepositoriesConfig { + private static final Logger logger = LoggerFactory.getLogger(DsRepositoriesConfig.class); + + @Bean + // @ConfigurationProperties(prefix = "spring.datasource") + @Primary + public DataSource dataSource(Environment env) { + if (logger.isDebugEnabled()) { + logger.debug("SpringApp.dataSource, env:{}", env); + } + + String host = env.getProperty("db.server.host", DEFAULT_DB_HOST); + String port = env.getProperty("db.server.port", DEFAULT_DB_PORT); + String database = env.getProperty("db.server.database", DEFAULT_DB_DATABASE); + String username = env.getProperty("db.server.username", DEFAULT_DB_USERNAME); + String password = env.getProperty("db.server.password", DEFAULT_DB_PASSWORD); + + String url = "jdbc:mysql://" + host + ":" + port + "/" + database; + if (logger.isDebugEnabled()) { + logger.debug("db server url:{},user:{}", url, username); + } + + + return DataSourceBuilder.create() + .type(HikariDataSource.class) + .url(url) + .username(username) + .password(password) + .driverClassName("com.mysql.cj.jdbc.Driver") + .build(); + } + + @Primary + @Bean + public JdbcTemplate jdbcTemplate(@Qualifier("dataSource") DataSource dataSource) { + return new JdbcTemplate(dataSource); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/MonthDayConverter.java b/src/main/java/com/ecep/contract/manager/ds/MonthDayConverter.java new file mode 100644 index 0000000..278aabd --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/MonthDayConverter.java @@ -0,0 +1,26 @@ +package com.ecep.contract.manager.ds; + +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +import java.time.MonthDay; +import java.time.format.DateTimeFormatter; + +@Converter(autoApply = true) +public class MonthDayConverter implements AttributeConverter { + + // 定义日期格式(月-日,两位数) + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("MM-dd"); + + @Override + public String convertToDatabaseColumn(MonthDay attribute) { + // 将 MonthDay 转换为数据库存储的字符串(MM-dd) + return attribute != null ? attribute.format(FORMATTER) : null; + } + + @Override + public MonthDay convertToEntityAttribute(String dbData) { + // 将数据库字符串(MM-dd)转换为 MonthDay + return dbData != null ? MonthDay.parse(dbData, FORMATTER) : null; + } +} \ No newline at end of file diff --git a/src/main/java/com/ecep/contract/manager/ds/MyRepository.java b/src/main/java/com/ecep/contract/manager/ds/MyRepository.java new file mode 100644 index 0000000..41f7712 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/MyRepository.java @@ -0,0 +1,9 @@ +package com.ecep.contract.manager.ds; + +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.repository.ListCrudRepository; + +public interface MyRepository extends ListCrudRepository, JpaSpecificationExecutor { + + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/BlackReasonType.java b/src/main/java/com/ecep/contract/manager/ds/company/BlackReasonType.java new file mode 100644 index 0000000..834abbf --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/BlackReasonType.java @@ -0,0 +1,15 @@ +package com.ecep.contract.manager.ds.company; + +/** + * Date : 2024/2/3 + */ +public enum BlackReasonType { + /** + * 黑名单 + */ + BLACK, + /** + * 警示名单 + */ + GRAY +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/CompanyContactStringConverter.java b/src/main/java/com/ecep/contract/manager/ds/company/CompanyContactStringConverter.java new file mode 100644 index 0000000..4d60986 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/CompanyContactStringConverter.java @@ -0,0 +1,19 @@ +package com.ecep.contract.manager.ds.company; + +import com.ecep.contract.manager.ds.company.model.CompanyContact; +import com.ecep.contract.manager.ds.company.service.CompanyContactService; +import com.ecep.contract.manager.ds.other.EntityStringConverter; + +public class CompanyContactStringConverter extends EntityStringConverter { + + public CompanyContactStringConverter() { + + } + + public CompanyContactStringConverter(CompanyContactService service) { + setInitialized(employee -> service.findById(employee.getId())); + // setFromString(service::findByName); + } + + +} \ No newline at end of file diff --git a/src/main/java/com/ecep/contract/manager/ds/company/CompanyFileType.java b/src/main/java/com/ecep/contract/manager/ds/company/CompanyFileType.java new file mode 100644 index 0000000..e80798a --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/CompanyFileType.java @@ -0,0 +1,35 @@ +package com.ecep.contract.manager.ds.company; + +/** + * 公司文件类型(资质文件) + */ +public enum CompanyFileType { + /** + * 普通文件,一般文件 + */ + General, + /** + * 资信评估 + */ + CreditReport, + /** + * 营业执照 + */ + BusinessLicense, + /** + * 资质证书 + */ + QualificationCertificate, + /** + * 企业信用信息公示报告 + */ + CreditInfoPublicityReport, + /** + * 操作证 + */ + OperationCertificate, + /** + * 框架协议 + */ + FrameworkAgreement; +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/CompanyFileUtils.java b/src/main/java/com/ecep/contract/manager/ds/company/CompanyFileUtils.java new file mode 100644 index 0000000..799bb65 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/CompanyFileUtils.java @@ -0,0 +1,212 @@ +package com.ecep.contract.manager.ds.company; + +import com.ecep.contract.manager.cloud.tyc.CloudTycService; +import com.ecep.contract.manager.ds.company.model.CompanyFile; +import com.ecep.contract.manager.ds.company.service.CompanyFileService; +import com.ecep.contract.manager.util.MyDateTimeUtils; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.ecep.contract.manager.ds.company.CompanyFileType.CreditReport; + +public class CompanyFileUtils { + + public final static String PDF = ".pdf"; + public final static String DOC = ".doc"; + public final static String DOCX = ".docx"; + public final static String XLS = ".xls"; + public final static String XLSX = ".xlsx"; + public final static String PNG = ".png"; + public final static String JPG = ".jpg"; + public final static String JPEG = ".jpeg"; + + public final static String JSON = ".json"; + public final static String FILE_DB_THUMBS = "Thumbs.db"; + public final static String FILE_DB_JSON = "db.json"; + public final static String FILE_BLACK_LIST_JSON = "black_list.json"; + public final static String FILE_B1001_JSON = "b1001.json"; + + + public static CompanyFile fillType(String fileName, String companyName, File destDir) { + CompanyFile companyFile = new CompanyFile(); + if (isTycReport(fileName, companyName)) { + File dest = new File(destDir, fileName); + companyFile.setType(CreditReport); + companyFile.setFilePath(dest.getAbsolutePath()); + return companyFile; + } + + + // 包含公司名称 和 天眼查 的文件 + if (fileName.contains(companyName) && fileName.contains(CloudTycService.NAME)) { + LocalDateTime dateTime = LocalDateTime.now(); + DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm"); + int idx = fileName.indexOf("报告"); + String destName = (idx > 0 ? fileName.substring(0, fileName.indexOf("报告") + 2) + "_" : "") + + companyName + "_天眼查_" + dateTime.format(fmt) + "." + StringUtils.getFilenameExtension(fileName); + } + + + for (CompanyFileType value : CompanyFileType.values()) { + + } + return null; + } + + /** + * 检查文件是否符合后缀要求 + * + * @param name 文件名 + * @param fileExtensions 后缀 + */ + public static boolean withExtensions(String name, String... fileExtensions) { + for (String fn : fileExtensions) { + if (name.endsWith(fn)) { + return true; + } + } + return false; + } + + /** + * 从文件名判断是否是天眼查的报告文件 + */ + public static boolean isTycReport(String fileName, String companyName) { + // 文件名中必须包含 天眼查 字样 + if (!fileName.contains(CloudTycService.NAME)) { + return false; + } + + // 文件名中必须包含 公司名称 + if (!fileName.contains(companyName)) { + return false; + } + + // 文件名后缀需符合要求 + return withExtensions(fileName, PDF, DOC, DOCX); + } + + public static boolean isCompanyFile(File file) { + String fileName = file.getName(); + if (fileName.equals(FILE_DB_JSON)) { + return true; + } + if (fileName.equals(FILE_B1001_JSON)) { + return true; + } + + if (fileName.equals(FILE_BLACK_LIST_JSON)) { + return true; + } + if (fileName.equals("合同列表.json")) { + return true; + } + + //营业执照 + if (fileName.contains(CompanyFileService.BUSINESS_LICENSE)) { + return true; + } + // 操作证 + if (fileName.contains(CompanyFileService.OPERATION_CERTIFICATE)) { + return true; + } + // 许可证 + if (fileName.contains(CompanyFileService.PERMIT_CERTIFICATE)) { + return true; + } + // 登记证 + if (fileName.contains(CompanyFileService.REGISTRATION_CERTIFICATE)) { + return true; + } + // 组织机构代码证 + if (fileName.contains(CompanyFileService.ORGANIZATION_CODE_CERTIFICATE)) { + return true; + } + + return false; + } + + /** + * 根据文件名中提取的日期信息更新对象的日期,如果文件名称中没有可识别的日期,则不进行任何更新 + * 日期提取方法参考 {@link MyDateTimeUtils#pickLocalDate(String)} + * + * @param file 日期来源文件对象 + * @param vendorFile 要更新的对象 + * @param getter 对象的日期获取方法 + * @param setter 对象的日期设置方法 + * @param 对象泛型 + * @return 是否更新了日期 + */ + public static boolean fillApplyDateAbsent(File file, T vendorFile, Function getter, BiConsumer setter) { + LocalDate applyDate = getter.apply(vendorFile); + if (applyDate != null) { + return false; + } + + boolean modified = false; + String fileName = file.getName(); + // 从文件的名称中提取日期 + LocalDate picked = MyDateTimeUtils.pickLocalDate(fileName); + if (picked != null) { + // 如果提取出的日期不为空,则通过setter函数将这个日期设置到vendorFile中,并将modified标志设置为true。 + setter.accept(vendorFile, picked); + modified = true; + } + return modified; + } + + public static boolean isHiddenFile(File file) { + String fileName = file.getName(); + if (fileName.equals(FILE_DB_THUMBS)) { + return true; + } + return fileName.startsWith("~$"); + } + + public static String escapeFileName(String fileName) { + String patternStr = "[\\\\/:*?\"<>|]"; + Pattern pattern = Pattern.compile(patternStr); + Matcher matcher = pattern.matcher(fileName); + return matcher.replaceAll("_"); + } + + /** + * 返回 district 中关于省份的部门zi + * + * @param district 地区 + * @return 省份名称 + */ + public static String getParentPrefixByDistrict(String district) { + int indexOf = district.indexOf("省"); + if (indexOf != -1) { + return district.substring(0, indexOf); + } + + indexOf = district.indexOf("自治区"); + if (indexOf != -1) { + return district.substring(0, 2); + } + + indexOf = district.indexOf("市"); + if (indexOf != -1) { + return district.substring(0, indexOf); + } + return null; + } + + public static boolean exists(String path) { + if (!StringUtils.hasText(path)) { + return false; + } + File file = new File(path); + return file.exists(); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/CompanyStringConverter.java b/src/main/java/com/ecep/contract/manager/ds/company/CompanyStringConverter.java new file mode 100644 index 0000000..1bba877 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/CompanyStringConverter.java @@ -0,0 +1,28 @@ +package com.ecep.contract.manager.ds.company; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.other.EntityStringConverter; +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 { + + @Lazy + @Autowired + CompanyService service; + public CompanyStringConverter() { + + } + + @PostConstruct + private void init() { + setInitialized(project -> service.findById(project.getId())); + setSuggestion(service::search); + setFromString(service::findByName); + } +} \ No newline at end of file diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/AbstCompanyBasedTabSkin.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/AbstCompanyBasedTabSkin.java new file mode 100644 index 0000000..cb4e9e8 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/AbstCompanyBasedTabSkin.java @@ -0,0 +1,21 @@ +package com.ecep.contract.manager.ds.company.controller; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.company.vo.CompanyViewModel; +import com.ecep.contract.manager.ui.AbstEntityBasedTabSkin; +import com.ecep.contract.manager.ui.TabSkin; +import lombok.Setter; + +public abstract class AbstCompanyBasedTabSkin + extends AbstEntityBasedTabSkin + implements TabSkin { + + public AbstCompanyBasedTabSkin(CompanyWindowController controller) { + super(controller); + } + + protected CompanyService getCompanyService() { + return controller.getViewModelService(); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/AbstCompanyTableTabSkin.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/AbstCompanyTableTabSkin.java new file mode 100644 index 0000000..a03d8d1 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/AbstCompanyTableTabSkin.java @@ -0,0 +1,56 @@ +package com.ecep.contract.manager.ds.company.controller; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.company.vo.CompanyBasedViewModel; +import com.ecep.contract.manager.ds.company.vo.CompanyViewModel; +import com.ecep.contract.manager.ds.other.model.IdentityEntity; +import com.ecep.contract.manager.ds.other.vo.IdentityViewModel; +import com.ecep.contract.manager.ui.AbstEntityTableTabSkin; +import com.ecep.contract.manager.ui.TabSkin; +import com.ecep.contract.manager.ui.TableOfTabSkin; +import com.ecep.contract.manager.util.SpecificationUtils; +import lombok.Setter; +import org.springframework.data.jpa.domain.Specification; + +public abstract class AbstCompanyTableTabSkin> + extends AbstEntityTableTabSkin + implements TabSkin, TableOfTabSkin { + + @Setter + private CompanyService companyService; + + public AbstCompanyTableTabSkin(CompanyWindowController controller) { + super(controller); + viewModel = controller.getViewModel(); + } + + @Override + protected TV createNewViewModel() { + TV model = super.createNewViewModel(); + if (model instanceof CompanyBasedViewModel m) { + m.getCompany().set(getEntity()); + } + return model; + } + + protected CompanyService getCompanyService(){ + return getParentService(); + } + + protected CompanyService getParentService() { + if (companyService == null) { + companyService = getBean(CompanyService.class); + } + return companyService; + } + + @Override + public Specification getSpecification(Company parent) { + return SpecificationUtils.and(getSpecification(), (root, query, builder) -> { + return builder.equal(root.get("company"), parent); + }); + } + + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyManagerSkin.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyManagerSkin.java new file mode 100644 index 0000000..4c82da0 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyManagerSkin.java @@ -0,0 +1,83 @@ +package com.ecep.contract.manager.ds.company.controller; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.service.CompanyOldNameService; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.company.vo.CompanyViewModel; +import com.ecep.contract.manager.ui.AbstEntityManagerSkin; +import com.ecep.contract.manager.ui.ManagerSkin; +import javafx.application.Platform; +import javafx.event.ActionEvent; +import javafx.scene.control.Alert; +import javafx.scene.control.TextInputDialog; +import lombok.Setter; + +import java.util.List; +import java.util.Optional; + +public class CompanyManagerSkin + extends AbstEntityManagerSkin + implements ManagerSkin { + + @Setter + private CompanyOldNameService companyOldNameService; + + public CompanyManagerSkin(CompanyManagerWindowController controller) { + super(controller); + } + + public CompanyService getCompanyService() { + return controller.getViewModelService(); + } + + public CompanyOldNameService getCompanyOldNameService() { + if (companyOldNameService == null) { + companyOldNameService = getBean(CompanyOldNameService.class); + } + return companyOldNameService; + } + + @Override + public void initializeTable() { + controller.idColumn.setCellValueFactory(param -> param.getValue().getId()); + controller.nameColumn.setCellValueFactory(param -> param.getValue().getName()); + controller.uniscidColumn.setCellValueFactory(param -> param.getValue().getUid()); + controller.entStatusColumn.setCellValueFactory(param -> param.getValue().getEntStatus()); + controller.createdColumn.setCellValueFactory(param -> param.getValue().getCreated()); + controller.memoColumn.setCellValueFactory(param -> param.getValue().getMemo()); + + Platform.runLater(() -> { + getTableView().getSortOrder().add(controller.createdColumn); + }); + } + + @Override + protected void onTableRowDoubleClickedAction(CompanyViewModel item) { + showInOwner(CompanyWindowController.class, item); + } + + @Override + protected void onTableCreateNewAction(ActionEvent event) { + Platform.runLater(() -> { + TextInputDialog dialog = new TextInputDialog(); + dialog.setTitle("新建公司"); + dialog.setHeaderText("请输入新建的公司名称,请输入公司名字全称"); + Optional optional = dialog.showAndWait(); + if (optional.isPresent()) { + CompanyService companyService = getCompanyService(); + String newCompanyName = optional.get(); + List list = companyService.findAllByName(newCompanyName); + if (list == null || list.isEmpty()) { + // 未登记过 + Company company = companyService.createNewCompany(newCompanyName); + Company saved = companyService.save(company); + CompanyWindowController.show(saved, getTableView().getScene().getWindow()); + } else { + Alert alert = new Alert(Alert.AlertType.INFORMATION); + alert.setTitle("新建公司"); + alert.setHeaderText("公司名称 " + newCompanyName + " 已经存在,是否打开公司页面"); + } + } + }); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyManagerWindowController.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyManagerWindowController.java new file mode 100644 index 0000000..63f52da --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyManagerWindowController.java @@ -0,0 +1,75 @@ +package com.ecep.contract.manager.ds.company.controller; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.company.tasker.CompanyFilesRebuildTasker; +import com.ecep.contract.manager.ds.company.vo.CompanyViewModel; +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ui.AbstManagerWindowController; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.ViewModelService; +import com.ecep.contract.manager.util.UITools; +import javafx.event.ActionEvent; +import javafx.scene.control.TableColumn; +import javafx.stage.Stage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; + +@Lazy +@Scope("prototype") +@Component +@FxmlPath("/ui/company/company-manager.fxml") +public class CompanyManagerWindowController + extends AbstManagerWindowController { + + // columns + public TableColumn idColumn; + public TableColumn nameColumn; + public TableColumn uniscidColumn; + public TableColumn entStatusColumn; + public TableColumn createdColumn; + public TableColumn memoColumn; + + @Autowired + private CompanyService companyService; + + @Override + public CompanyService getViewModelService() { + return companyService; + } + + @Override + public void show(Stage stage) { + super.show(stage); + getTitle().set("公司管理"); + } + + @Override + protected CompanyManagerSkin createDefaultSkin() { + return new CompanyManagerSkin(this); + } + + public void onCompanyCreateNewAction(ActionEvent event) { + getSkin().onTableCreateNewAction(event); + } + + /** + * 对所有公司的文件进行重置 + *

+ * 弹出对话框模式,对话框中有进度条和重置的消息 + * + * @param event 事件 + */ + public void onReBuildFilesAction(ActionEvent event) { + CompanyFilesRebuildTasker task = new CompanyFilesRebuildTasker(); + UITools.showTaskDialogAndWait("公司文件重置", task, null); + } + + public void onVerifyAction(ActionEvent event) { + show(CompanyVerifyWindowController.class, null); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinBankAccount.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinBankAccount.java new file mode 100644 index 0000000..510e53b --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinBankAccount.java @@ -0,0 +1,113 @@ +package com.ecep.contract.manager.ds.company.controller; + +import com.ecep.contract.manager.ds.company.controller.bank_account.BankAccountWindowController; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyBankAccount; +import com.ecep.contract.manager.ds.company.service.CompanyBankAccountService; +import com.ecep.contract.manager.ds.company.vo.CompanyBankAccountViewModel; +import com.ecep.contract.manager.ds.other.model.Bank; +import com.ecep.contract.manager.ds.other.service.BankService; +import com.ecep.contract.manager.ui.EditableEntityTableTabSkin; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.TabSkin; +import javafx.scene.control.*; +import javafx.scene.input.KeyCode; +import lombok.Setter; +import org.hibernate.Hibernate; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.util.StringUtils; + +import java.util.List; + +/** + * 子合同 + */ +@FxmlPath("/ui/company/company-tab-bank-account.fxml") +public class CompanyTabSkinBankAccount + extends AbstCompanyTableTabSkin + implements TabSkin, EditableEntityTableTabSkin { + /** + * 银行账户 + */ + public TableColumn bankAccountTable_idColumn; + public TableColumn bankAccountTable_bankColumn; + public TableColumn bankAccountTable_openingBankColumn; + public TableColumn bankAccountTable_accountColumn; + public TextField bankAccountSearchKeyField; + public Button bankAccountSearchBtn; + public MenuItem bankAccountTable_menu_refresh; + public MenuItem bankAccountTable_menu_add; + public MenuItem bankAccountTable_menu_del; + + @Setter + private CompanyBankAccountService bankAccountService; + @Setter + private BankService bankService; + + public CompanyTabSkinBankAccount(CompanyWindowController controller) { + super(controller); + } + @Override + public Tab getTab() { + return controller.bankAccountTab; + } + + @Override + public void initializeTab() { + bankAccountSearchKeyField.setOnKeyReleased(event -> { + if (event.getCode() == KeyCode.ENTER) { + bankAccountSearchBtn.fire(); + } + }); + bankAccountSearchBtn.setOnAction(this::onTableRefreshAction); + + bankAccountTable_idColumn.setCellValueFactory(param -> param.getValue().getId()); + bankAccountTable_bankColumn.setCellValueFactory(param -> param.getValue().getBank()); + bankAccountTable_bankColumn.setCellFactory(param -> new TableCell<>() { + @Override + protected void updateItem(Bank item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setText(null); + return; + } + if (!Hibernate.isInitialized(item)) { + item = getBankService().findById(item.getId()); + setItem(item); + } + setText(item.getName()); + } + }); + + bankAccountTable_openingBankColumn.setCellValueFactory(param -> param.getValue().getOpeningBank()); + bankAccountTable_accountColumn.setCellValueFactory(param -> param.getValue().getAccount()); + + + bankAccountTable_menu_refresh.setOnAction(this::onTableRefreshAction); + bankAccountTable_menu_add.setOnAction(this::onTableAddAction); + bankAccountTable_menu_del.setOnAction(this::onTableDeleteAction); + + super.initializeTab(); + } + + @Override + protected void onTableRowDoubleClickedAction(CompanyBankAccountViewModel item) { + BankAccountWindowController.show(item, controller.root.getScene().getWindow()); + } + + + public CompanyBankAccountService getViewModelService() { + if (bankAccountService == null) { + bankAccountService = getBean(CompanyBankAccountService.class); + } + return bankAccountService; + } + + public BankService getBankService() { + if (bankService == null) { + bankService = getBean(BankService.class); + } + return bankService; + } + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinBase.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinBase.java new file mode 100644 index 0000000..60738ee --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinBase.java @@ -0,0 +1,204 @@ +package com.ecep.contract.manager.ds.company.controller; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyOldName; +import com.ecep.contract.manager.ds.company.service.CompanyOldNameService; +import com.ecep.contract.manager.ui.TabSkin; +import javafx.event.ActionEvent; +import javafx.geometry.Insets; +import javafx.scene.control.*; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.stage.Modality; +import javafx.util.converter.LocalDateStringConverter; +import lombok.Setter; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.time.format.DateTimeFormatter; +import java.util.List; + +/** + * + */ + +public class CompanyTabSkinBase + extends AbstCompanyBasedTabSkin + implements TabSkin { + @Setter + private CompanyOldNameService companyOldNameService; + + public CompanyTabSkinBase(CompanyWindowController controller) { + super(controller); + } + + @Override + public Tab getTab() { + return controller.baseInfoTab; + } + + @Override + public void initializeTab() { + + LocalDateStringConverter localDateStringConverter = new LocalDateStringConverter(DateTimeFormatter.ISO_LOCAL_DATE, null); + + controller.nameField.textProperty().bind(viewModel.getName()); + controller.shortNameField.textProperty().bindBidirectional(viewModel.getShortName()); + controller.entStatusField.textProperty().bindBidirectional(viewModel.getEntStatus()); + + controller.uidField.textProperty().bindBidirectional(viewModel.getUid()); + controller.setupDateField.setConverter(localDateStringConverter); + controller.setupDateField.valueProperty().bindBidirectional(viewModel.getSetupDate()); + controller.entTypeField.textProperty().bindBidirectional(viewModel.getEntType()); + controller.industryField.textProperty().bindBidirectional(viewModel.getIndustry()); + controller.districtField.textProperty().bindBidirectional(viewModel.getDistrict()); + controller.pathExistField.selectedProperty().bind(viewModel.getPathExist()); + controller.pathField.textProperty().bind(viewModel.getPath()); + controller.createdDateField.setConverter(localDateStringConverter); + controller.createdDateField.valueProperty().bindBidirectional(viewModel.getCreated()); + + controller.telephoneField.textProperty().bindBidirectional(viewModel.getTelephone()); + controller.regAddressField.textProperty().bindBidirectional(viewModel.getRegAddress()); + controller.addressField.textProperty().bindBidirectional(viewModel.getAddress()); + controller.registeredCapitalField.textProperty().bindBidirectional(viewModel.getRegisteredCapital()); + controller.registeredCapitalCurrencyField.textProperty().bindBidirectional(viewModel.getRegisteredCapitalCurrency()); + controller.legalRepresentativeField.textProperty().bindBidirectional(viewModel.getLegalRepresentative()); + + controller.operationPeriodBeginField.setConverter(localDateStringConverter); + controller.operationPeriodBeginField.valueProperty().bindBidirectional(viewModel.getOperationPeriodBegin()); + controller.operationPeriodEndField.setConverter(localDateStringConverter); + controller.operationPeriodEndField.valueProperty().bindBidirectional(viewModel.getOperationPeriodEnd()); + + controller.memoField.textProperty().bindBidirectional(viewModel.getMemo()); + controller.versionLabel.textProperty().bind(viewModel.getVersion().asString()); + + controller.companyRenameBtn.setOnAction(this::onCompanyRenameAction); + controller.companyPathCreateBtn.setOnAction(this::onCompanyPathCreatePathAction); + controller.companyPathChangeBtn.setOnAction(this::onCompanyPathChangePathAction); + controller.companyPathSameAsNameBtn.setOnAction(this::onCompanyPathSameAsNameAction); + } + + private void onCompanyPathCreatePathAction(ActionEvent event) { + Company company = getEntity(); + if (getCompanyService().makePathAbsent(company)) { + save(company); + } else { + setStatus("目录存在或创建失败"); + } + } + + private void onCompanyPathChangePathAction(ActionEvent event) { + } + + private void onCompanyPathSameAsNameAction(ActionEvent event) { + Company company = getEntity(); + String path = company.getPath(); + if (!StringUtils.hasText(path)) { + return; + } + File file = new File(path); + if (!file.exists()) { + return; + } + + if (file.getName().equals(company.getName())) { + return; + } + File dest = new File(file.getParent(), company.getName()); + if (file.renameTo(dest)) { + company.setPath(dest.getAbsolutePath()); + save(company); + setStatus("目录变更为" + dest.getName()); + } + } + + private void onCompanyRenameAction(ActionEvent event) { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION); + alert.initOwner(controller.root.getScene().getWindow()); + alert.initModality(Modality.WINDOW_MODAL); + VBox layout = new VBox(); + + TextField nameField = new TextField(); + Label nameLabel = new Label(""); + + CheckBox saveAsOldName = new CheckBox(); + CheckBox ambiguity = new CheckBox(); + Label ambiguityLabel = new Label("当曾用名不是工商注册过的名字时,请勾选 歧义 标记"); + layout.setPadding(new Insets(12)); + layout.setSpacing(8); + + nameField.setPromptText("新的公司名称"); + nameField.setText(viewModel.getName().get()); + nameField.setEditable(true); + nameLabel.setTextFill(Color.GRAY); + + saveAsOldName.setText("旧名称是否设置为曾用名"); + ambiguity.setText("是否标记为歧义"); + ambiguity.visibleProperty().bind(saveAsOldName.selectedProperty()); + ambiguityLabel.setTextFill(Color.GRAY); + ambiguityLabel.visibleProperty().bind(saveAsOldName.selectedProperty()); + + layout.getChildren().addAll(nameField, nameLabel, saveAsOldName, ambiguity, ambiguityLabel); + + + alert.setContentText("context"); + alert.setHeaderText(null); + alert.setTitle("企业更名"); + + alert.getDialogPane().setContent(layout); + +// alert.setResultConverter(param -> { +// +// return null; +// }); + + + alert.setOnCloseRequest(dialogEvent -> { + ButtonType buttonType = alert.getResult(); + if (buttonType != ButtonType.OK) { + return; + } + String newName = nameField.getText(); + String oldName = viewModel.getName().get(); + if (oldName.equals(newName)) { + nameLabel.setText("名称没有修改"); + nameLabel.setTextFill(Color.PERU); + dialogEvent.consume(); + return; + } + + viewModel.getName().set(newName); + save(); + + // TODO 修改文件夹 + if (!saveAsOldName.isSelected()) { + return; + } + + Company company = getEntity(); + List oldNames = getCompanyOldNameService().findAllByCompanyAndName(company, oldName); + if (oldNames.isEmpty()) { + CompanyOldName companyOldName = new CompanyOldName(); + companyOldName.setCompanyId(company.getId()); + companyOldName.setName(oldName); + companyOldName.setAmbiguity(ambiguity.isSelected()); + companyOldName.setMemo("名称更名 " + oldName + " -> " + newName); + getCompanyOldNameService().save(companyOldName); + + CompanyTabSkinOldName tabSkin = controller.getTabSkin(CompanyTabSkinOldName.class); + if (tabSkin != null) { + tabSkin.loadTableDataSet(); + } + } + }); + + alert.showAndWait(); + } + + private CompanyOldNameService getCompanyOldNameService() { + if (companyOldNameService == null) { + companyOldNameService = getBean(CompanyOldNameService.class); + } + return companyOldNameService; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinBlackReason.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinBlackReason.java new file mode 100644 index 0000000..8b4a933 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinBlackReason.java @@ -0,0 +1,145 @@ +package com.ecep.contract.manager.ds.company.controller; + +import com.ecep.contract.manager.cloud.rk.BlackListUpdateContext; +import com.ecep.contract.manager.cloud.rk.CloudRk; +import com.ecep.contract.manager.cloud.rk.CloudRkService; +import com.ecep.contract.manager.ds.company.BlackReasonType; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyBlackReason; +import com.ecep.contract.manager.ds.company.service.CompanyBlackReasonService; +import com.ecep.contract.manager.ds.company.vo.CompanyBlackReasonViewModel; +import com.ecep.contract.manager.ui.EditableEntityTableTabSkin; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.TabSkin; +import com.ecep.contract.manager.util.UITools; +import javafx.event.ActionEvent; +import javafx.scene.control.*; +import javafx.scene.input.KeyCode; +import lombok.Setter; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.util.StringUtils; + +import java.time.LocalDate; +import java.util.List; + +/** + * 子合同 + */ +@FxmlPath("/ui/company/company-tab-black-list.fxml") +public class CompanyTabSkinBlackReason + extends AbstCompanyTableTabSkin + implements TabSkin, EditableEntityTableTabSkin { + /** + * 以下是黑名单列定义 + */ + public TableColumn blackReasonTable_idColumn; + public TableColumn blackReasonTable_typeColumn; + public TableColumn blackReasonTable_applyNameColumn; + public TableColumn blackReasonTable_blackReasonColumn; + public TableColumn blackReasonTable_descriptionColumn; + public TableColumn blackReasonTable_applyDateColumn; + public TableColumn blackReasonTable_updateTimeColumn; + public TableColumn blackReasonTable_createTimeColumn; + public TableColumn blackReasonTable_includeDateColumn; + public TableColumn blackReasonTable_keyColumn; + + public TextField blackListSearchKeyField; + public Button blackListSearchBtn; + public MenuItem blackReasonTable_menu_refresh; + public MenuItem blackReasonTable_menu_update; + + @Setter + private CompanyBlackReasonService companyBlackReasonService; + @Setter + private CloudRkService cloudRkService; + + public CompanyTabSkinBlackReason(CompanyWindowController controller) { + super(controller); + } + + @Override + public Tab getTab() { + return controller.blackReasonTab; + } + + @Override + public void initializeTab() { + + blackListSearchKeyField.setOnKeyReleased(event -> { + if (event.getCode() == KeyCode.ENTER) { + blackListSearchBtn.fire(); + } + }); + + blackListSearchBtn.setOnAction(this::onTableRefreshAction); + + blackReasonTable_idColumn.setCellValueFactory(param -> param.getValue().getId()); + blackReasonTable_typeColumn.setCellValueFactory(param -> param.getValue().getType()); + blackReasonTable_applyNameColumn.setCellValueFactory(param -> param.getValue().getApplyName()); + blackReasonTable_blackReasonColumn.setCellValueFactory(param -> param.getValue().getBlackReason()); + blackReasonTable_descriptionColumn.setCellValueFactory(param -> param.getValue().getDescription()); + + blackReasonTable_applyDateColumn.setCellValueFactory(param -> param.getValue().getApplyDate()); + blackReasonTable_updateTimeColumn.setCellValueFactory(param -> param.getValue().getUpdateTime()); + blackReasonTable_createTimeColumn.setCellValueFactory(param -> param.getValue().getUpdateTime()); + blackReasonTable_includeDateColumn.setCellValueFactory(param -> param.getValue().getCreateTime()); + + blackReasonTable_keyColumn.setCellValueFactory(param -> param.getValue().getKey()); + + blackReasonTable_menu_refresh.setOnAction(this::onTableRefreshAction); + blackReasonTable_menu_update.setOnAction(this::onTableUpdateAction); + + + super.initializeTab(); + } + + + private void onTableUpdateAction(ActionEvent event) { + Company company = getParent(); + + CloudRkService cloudRkService = getCloudRkService(); + CloudRk cloudRk = cloudRkService.getOrCreateCloudRk(company); + BlackListUpdateContext context = cloudRkService.createBlackListUpdateContext().join(); + if (cloudRkService.checkBlackListUpdateElapse(company, cloudRk, context)) { + try { + cloudRkService.updateBlackList(company, cloudRk, context); + } catch (Exception e) { + UITools.showExceptionAndWait("更新黑名单时发生错误", e); + } + } + } + + @Override + protected void onTableRowDoubleClickedAction(CompanyBlackReasonViewModel item) { +// ContractWindowController.show(item, controller.root.getScene().getWindow()); + } + + @Override + public CompanyBlackReason loadRowData(CompanyBlackReasonViewModel row) { + return getViewModelService().findById(row.getId().get()); + } + + @Override + public CompanyBlackReason saveRowData(CompanyBlackReason entity) { + return getViewModelService().save(entity); + } + + @Override + public void deleteRowData(CompanyBlackReason entity) { + getViewModelService().delete(entity); + } + + protected CompanyBlackReasonService getViewModelService() { + if (companyBlackReasonService == null) { + companyBlackReasonService = getBean(CompanyBlackReasonService.class); + } + return companyBlackReasonService; + } + + public CloudRkService getCloudRkService() { + if (cloudRkService == null) { + cloudRkService = getBean(CloudRkService.class); + } + return cloudRkService; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinContact.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinContact.java new file mode 100644 index 0000000..d34f742 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinContact.java @@ -0,0 +1,106 @@ +package com.ecep.contract.manager.ds.company.controller; + +import com.ecep.contract.manager.ds.company.controller.contact.CompanyContactWindowController; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyContact; +import com.ecep.contract.manager.ds.company.service.CompanyContactService; +import com.ecep.contract.manager.ds.company.vo.CompanyContactViewModel; +import com.ecep.contract.manager.ui.EditableEntityTableTabSkin; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.TabSkin; +import com.ecep.contract.manager.ui.ViewModelService; +import javafx.event.ActionEvent; +import javafx.scene.control.*; +import javafx.scene.input.KeyCode; +import lombok.Setter; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.util.StringUtils; + +import java.time.LocalDate; +import java.util.List; + +/** + * 子合同 + */ +@FxmlPath("/ui/company/company-tab-contact.fxml") +public class CompanyTabSkinContact + extends AbstCompanyTableTabSkin + implements TabSkin, EditableEntityTableTabSkin { + + /** + * 以下是联系人列定义 + */ + public TableColumn contactTable_idColumn; + public TableColumn contactTable_nameColumn; + public TableColumn contactTable_positionColumn; + public TableColumn contactTable_phoneColumn; + public TableColumn contactTable_emailColumn; + public TableColumn contactTable_addressColumn; + public TableColumn contactTable_u8CodeColumn; + public TableColumn contactTable_createdColumn; + + public TextField contactSearchKeyField; + public Button contactSearchBtn; + public MenuItem contactTable_menu_refresh; + public MenuItem contactTable_menu_add; + public MenuItem contactTable_menu_del; + @Setter + private CompanyContactService contactService; + + public CompanyTabSkinContact(CompanyWindowController controller) { + super(controller); + } + + @Override + public Tab getTab() { + return controller.contactTab; + } + + + @Override + public void initializeTab() { + contactSearchKeyField.setOnKeyReleased(event -> { + if (event.getCode() == KeyCode.ENTER) { + contactSearchBtn.fire(); + } + }); + contactSearchBtn.setOnAction(this::onTableRefreshAction); + + contactTable_idColumn.setCellValueFactory(param -> param.getValue().getId()); + contactTable_nameColumn.setCellValueFactory(param -> param.getValue().getName()); + contactTable_positionColumn.setCellValueFactory(param -> param.getValue().getPosition()); + contactTable_phoneColumn.setCellValueFactory(param -> param.getValue().getPhone()); + contactTable_emailColumn.setCellValueFactory(param -> param.getValue().getEmail()); + contactTable_addressColumn.setCellValueFactory(param -> param.getValue().getAddress()); + contactTable_u8CodeColumn.setCellValueFactory(param -> param.getValue().getU8Code()); + contactTable_createdColumn.setCellValueFactory(param -> param.getValue().getCreated()); + + contactTable_menu_refresh.setOnAction(this::onTableRefreshAction); + contactTable_menu_add.setOnAction(this::onTableAddAction); + contactTable_menu_del.setOnAction(this::onTableDeleteAction); + + + super.initializeTab(); + } + + @Override + protected void onTableAddAction(ActionEvent event) { + CompanyContact contact = new CompanyContact(); + contact.setCompany(getParent()); + contact.setCreated(LocalDate.now()); + getViewModelService().save(contact); + loadTableDataSet(); + } + + @Override + protected void onTableRowDoubleClickedAction(CompanyContactViewModel item) { + CompanyContactWindowController.show(item, controller.root.getScene().getWindow()); + } + + public CompanyContactService getViewModelService() { + if (contactService == null) { + contactService = getBean(CompanyContactService.class); + } + return contactService; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinContract.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinContract.java new file mode 100644 index 0000000..f1e090b --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinContract.java @@ -0,0 +1,266 @@ +package com.ecep.contract.manager.ds.company.controller; + +import com.ecep.contract.manager.Desktop; +import com.ecep.contract.manager.cloud.u8.YongYouU8Service; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.contract.controller.ContractGroupTableCell; +import com.ecep.contract.manager.ds.contract.controller.ContractKindTableCell; +import com.ecep.contract.manager.ds.contract.controller.ContractTypeTableCell; +import com.ecep.contract.manager.ds.contract.controller.ContractWindowController; +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.contract.model.ContractGroup; +import com.ecep.contract.manager.ds.contract.model.ContractKind; +import com.ecep.contract.manager.ds.contract.model.ContractType; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ds.contract.tasker.ContractRepairByCompanyTask; +import com.ecep.contract.manager.ds.contract.vo.ContractViewModel; +import com.ecep.contract.manager.ds.other.EmployeeStringConverter; +import com.ecep.contract.manager.ds.other.model.Employee; +import com.ecep.contract.manager.ui.EditableEntityTableTabSkin; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.TabSkin; +import com.ecep.contract.manager.ui.util.ContractGroupStringConverter; +import com.ecep.contract.manager.ui.util.LocalDateTimeTableCell; +import com.ecep.contract.manager.util.SpecificationUtils; +import com.ecep.contract.manager.util.UITools; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.scene.control.*; +import javafx.scene.control.cell.TextFieldTableCell; +import javafx.scene.input.KeyCode; +import lombok.Setter; +import org.springframework.beans.BeansException; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.NoSuchElementException; +import java.util.concurrent.CompletableFuture; + +/** + * 子合同 + */ +@FxmlPath("/ui/company/company-tab-contract.fxml") +public class CompanyTabSkinContract + extends AbstCompanyTableTabSkin + implements TabSkin, EditableEntityTableTabSkin { + + public TableColumn contractTable_idColumn; + public TableColumn contractTable_codeColumn; + public TableColumn contractTable_nameColumn; + public TableColumn contractTable_stateColumn; + public TableColumn contractTable_groupColumn; + public TableColumn contractTable_typeColumn; + public TableColumn contractTable_kindColumn; + public TableColumn contractTable_parentCodeColumn; + public TableColumn contractTable_orderDateColumn; + public TableColumn contractTable_startDateColumn; + public TableColumn contractTable_endDateColumn; + public TableColumn contractTable_setupPersonColumn; + public TableColumn contractTable_setupDateColumn; + public TableColumn contractTable_inurePersonColumn; + public TableColumn contractTable_inureDateColumn; + public TableColumn contractTable_varyPersonColumn; + public TableColumn contractTable_varyDateColumn; + public TableColumn contractTable_createdColumn; + + public ComboBox contractGroupSelector; + public TextField contractSearchKeyField; + public Button contractSearchBtn; + public Button contractTabToolBtn1; + public MenuItem contractTable_menu_refresh; + public MenuItem contractTable_menu_add; + public MenuItem contractTable_menu_open_in_explorer; + public MenuItem contractTable_menu_update; + public MenuItem contractTable_menu_del; + + @Setter + private ContractService contractService; + @Setter + private YongYouU8Service u8Service; + @Setter + private EmployeeStringConverter employeeStringConverter; + + public CompanyTabSkinContract(CompanyWindowController controller) { + super(controller); + } + + @Override + public Tab getTab() { + return controller.contractTab; + } + + @Override + public Specification getSpecification(Company parent) { + Specification spec = super.getSpecification(parent); + ContractGroup selectedGroup = contractGroupSelector.getValue(); + if (selectedGroup != null) { + spec = SpecificationUtils.and(spec, (root, query, builder) -> { + return builder.equal(root.get("group"), selectedGroup); + }); + } + return spec; + } + + @Override + public void initializeTab() { + + if (u8Service == null) { + try { + u8Service = getBean(YongYouU8Service.class); + } catch (BeansException ignored) { + } + } + + contractSearchKeyField.setOnKeyReleased(event -> { + if (event.getCode() == KeyCode.ENTER) { + contractSearchBtn.fire(); + } + }); + + contractSearchBtn.setOnAction(this::onTableRefreshAction); + + + ObservableList contractGroups = FXCollections.observableArrayList(); + contractGroups.add(null); + contractGroups.addAll(contractService.findAllGroups()); + contractGroupSelector.setItems(contractGroups); + contractGroupSelector.setConverter(new ContractGroupStringConverter(contractGroups)); + contractSearchKeyField.setOnKeyReleased(event -> { + if (event.getCode() == KeyCode.ENTER) { + contractSearchBtn.fire(); + } + }); + + contractTabToolBtn1.setOnAction(event -> { + CompletableFuture.runAsync(() -> { + // 计算主合同编号 + for (ContractViewModel model : dataSet) { + Contract contract = contractService.findById(model.getId().get()); + if (contract == null) { + continue; + } + try { + if (contractService.updateParentCode(contract)) { + Contract updated = contractService.save(contract); + model.update(updated); + } + } catch (NoSuchElementException e) { + model.getParentCode().set(e.getMessage()); + } + } + }); + }); + + contractTable_idColumn.setCellValueFactory(param -> param.getValue().getId()); + contractTable_codeColumn.setCellValueFactory(param -> param.getValue().getCode()); + contractTable_nameColumn.setCellValueFactory(param -> param.getValue().getName()); + contractTable_stateColumn.setCellValueFactory(param -> param.getValue().getState()); + contractTable_groupColumn.setCellValueFactory(param -> param.getValue().getGroup()); + contractTable_groupColumn.setCellFactory(param -> new ContractGroupTableCell(getViewModelService())); + + contractTable_typeColumn.setCellValueFactory(param -> param.getValue().getType()); + contractTable_typeColumn.setCellFactory(param -> new ContractTypeTableCell(getViewModelService())); + + contractTable_kindColumn.setCellValueFactory(param -> param.getValue().getKind()); + contractTable_kindColumn.setCellFactory(param -> new ContractKindTableCell(getViewModelService())); + + + contractTable_parentCodeColumn.setCellValueFactory(param -> param.getValue().getParentCode()); + contractTable_orderDateColumn.setCellValueFactory(param -> param.getValue().getOrderDate()); + contractTable_startDateColumn.setCellValueFactory(param -> param.getValue().getStartDate()); + contractTable_endDateColumn.setCellValueFactory(param -> param.getValue().getEndDate()); + contractTable_setupPersonColumn.setCellValueFactory(param -> param.getValue().getSetupPerson()); + contractTable_setupPersonColumn.setCellFactory(TextFieldTableCell.forTableColumn(getEmployeeStringConverter())); + + contractTable_setupDateColumn.setCellValueFactory(param -> param.getValue().getSetupDate()); +// contractTable_setupDateColumn.setSortable(true); +// contractTable_setupDateColumn.setSortType(TableColumn.SortType.DESCENDING); + contractTable_inurePersonColumn.setCellValueFactory(param -> param.getValue().getInurePerson()); + contractTable_inurePersonColumn.setCellFactory(TextFieldTableCell.forTableColumn(getEmployeeStringConverter())); + + contractTable_inureDateColumn.setCellValueFactory(param -> param.getValue().getInureDate()); + + contractTable_varyPersonColumn.setCellValueFactory(param -> param.getValue().getVaryPerson()); + contractTable_varyPersonColumn.setCellFactory(TextFieldTableCell.forTableColumn(getEmployeeStringConverter())); + + contractTable_varyDateColumn.setCellValueFactory(param -> param.getValue().getVaryDate()); + contractTable_createdColumn.setCellValueFactory(param -> param.getValue().getCreated()); + contractTable_createdColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); + + + contractTable_menu_refresh.setOnAction(this::onTableRefreshAction); +// contractTable_menu_add.setOnAction(this::onTableAddAction); + contractTable_menu_del.setOnAction(this::onTableDeleteAction); + contractTable_menu_open_in_explorer.setOnAction(this::onTableOpenInExplorerAction); + contractTable_menu_update.setOnAction(this::onTableUpdateAction); + + + super.initializeTab(); + } + + + private void onTableUpdateAction(ActionEvent event) { + ContractRepairByCompanyTask task = new ContractRepairByCompanyTask(); + task.setContractService(contractService); + task.setCompany(getParent()); + UITools.showTaskDialogAndWait("同步合同", task, null); + if (task.isRepaired()) { + loadTableDataSet(); + } + } + + private void onTableOpenInExplorerAction(ActionEvent event) { + ContractViewModel selectedItem = getSelectedItem(); + if (selectedItem != null) { + showInExplorer(selectedItem.getPath().get()); + } + } + + private void showInExplorer(String path) { + if (!StringUtils.hasText(path)) { + setStatus("文件/目录为空,无法打开"); + return; + } + File file = new File(path); + if (!file.exists()) { + if (file.isFile()) { + setStatus("文件 " + file.getAbsolutePath() + " 不存在,请确认"); + } else { + setStatus("目录 " + file.getAbsolutePath() + " 不存在,请确认"); + } + return; + } + + try { + Desktop.showInExplorer(file); + setStatus("打开文件/目录 " + path); + } catch (Exception e) { + setStatus("打开文件错误:" + e.getMessage()); + } + } + + @Override + protected void onTableRowDoubleClickedAction(ContractViewModel item) { + ContractWindowController.show(item, controller.root.getScene().getWindow()); + } + + protected ContractService getViewModelService() { + if (contractService == null) { + contractService = getBean(ContractService.class); + } + return contractService; + } + + private EmployeeStringConverter getEmployeeStringConverter() { + if (employeeStringConverter == null) { + employeeStringConverter = getBean(EmployeeStringConverter.class); + } + return employeeStringConverter; + } + + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinFile.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinFile.java new file mode 100644 index 0000000..3d6b01a --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinFile.java @@ -0,0 +1,378 @@ +package com.ecep.contract.manager.ds.company.controller; + +import com.ecep.contract.manager.Desktop; +import com.ecep.contract.manager.MyProperties; +import com.ecep.contract.manager.cloud.tyc.CloudTycService; +import com.ecep.contract.manager.ds.company.CompanyFileType; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyFile; +import com.ecep.contract.manager.ds.company.model.CompanyFileTypeLocal; +import com.ecep.contract.manager.ds.company.service.CompanyFileService; +import com.ecep.contract.manager.ds.company.vo.CompanyFileViewModel; +import com.ecep.contract.manager.ui.EditableEntityTableTabSkin; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.TabSkin; +import com.ecep.contract.manager.util.MyDateTimeUtils; +import com.ecep.contract.manager.util.UITools; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.collections.FXCollections; +import javafx.collections.ObservableMap; +import javafx.event.ActionEvent; +import javafx.event.Event; +import javafx.scene.control.*; +import lombok.Setter; +import org.springframework.util.FileSystemUtils; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.io.IOException; +import java.time.LocalDate; +import java.util.List; +import java.util.function.Consumer; + +/** + * + */ +@FxmlPath("/ui/company/company-tab-file.fxml") +public class CompanyTabSkinFile + extends AbstCompanyTableTabSkin + implements TabSkin, EditableEntityTableTabSkin { + + public TableColumn idColumn; + public TableColumn typeColumn; + public TableColumn filePathColumn; + public TableColumn applyDateColumn; + public TableColumn expiringDateColumn; + + public Button fileTable_file_move_btn; + public Button fileTable_file_retrieve_from_download_dir_btn; + public Button fileTable_file_reset_btn; + public MenuItem fileTable_menu_refresh; + public MenuItem fileTable_menu_del; + public MenuItem fileTable_menu_copy_as_matched_by_contract; + + @Setter + private CompanyFileService companyFileService; + @Setter + private MyProperties myProperties; + private final ObservableMap fileTypeLocalMap = FXCollections.observableHashMap(); + + public CompanyTabSkinFile(CompanyWindowController controller) { + super(controller); + setDragAndDrop(true); + setDragAndDropFileHandler(this::moveFileToCompany); + } + + CompanyFileService getCompanyFileService() { + if (companyFileService == null) { + companyFileService = getBean(CompanyFileService.class); + } + return companyFileService; + } + + @Override + public Tab getTab() { + return controller.fileTab; + } + + @Override + public void initializeTable() { + super.initializeTable(); + fileTable_file_move_btn.setOnAction(this::onTableMoveFileAction); + fileTable_file_retrieve_from_download_dir_btn.setOnAction(this::onTableRetrieveFromDownloadDirAction); + fileTable_file_reset_btn.setOnAction(this::onTableResetAction); + + + idColumn.setCellValueFactory(param -> param.getValue().getId()); + + typeColumn.setCellValueFactory(param -> Bindings.valueAt(fileTypeLocalMap, param.getValue().getType()).map(CompanyFileTypeLocal::getValue)); + filePathColumn.setCellValueFactory(param -> param.getValue().getFilePath()); + filePathColumn.setCellFactory(param -> new FileTableFilePathTableCell()); + applyDateColumn.setCellValueFactory(param -> param.getValue().getApplyDate()); + expiringDateColumn.setCellValueFactory(param -> param.getValue().getExpiringDate()); + + + fileTable_menu_refresh.setOnAction(this::onTableRefreshAction); + fileTable_menu_del.setOnAction(this::onTableDeleteAction); + fileTable_menu_copy_as_matched_by_contract.setOnAction(this::onTableCopyAsMatchedByContractAction); + fileTable_menu_copy_as_matched_by_contract.setOnMenuValidation(this::onTableCopyAsMatchedMenuValidation); + + fileTypeLocalMap.putAll(getCompanyFileService().findAllFileTypes(getLocale().toLanguageTag())); + } + + private void onTableResetAction(ActionEvent event) { + runAsync(() -> { + if (getViewModelService().reBuildingFiles(getParent(), this::setStatus)) { + loadTableDataSet(); + } + }); + } + + /** + * 从 下载目录 中查找相关的资质文件 + */ + private void onTableRetrieveFromDownloadDirAction(ActionEvent event) { + Company company = getParent(); + MyProperties myProperties = getMyProperties(); + File dir = myProperties.getDownloadDirectory(); + if (!dir.exists()) { + setStatus("下载目录 " + dir.getAbsolutePath() + " 不存在,请检查"); + return; + } + + setStatus("开始检索 下载 文件夹:" + dir.getAbsolutePath() + "..."); + File[] files = dir.listFiles(File::isFile); + if (files == null) { + setStatus("检索 下载 文件夹失败"); + return; + } + if (files.length == 0) { + setStatus("下载 文件夹没有文件"); + return; + } + setStatus("下载 文件夹中共有文件 " + files.length + " 个文件"); + + if (getParentService().retrieveFromDownloadFiles(company, files, this::setStatus)) { + // fixed if update + viewModel.update(company); + loadTableDataSet(); + } + } + + /** + * 把文件从 老系统中移到 \\10.84.209.8\项目信息\相关方信息 目录中 + */ + private void onTableMoveFileAction(ActionEvent event) { + CompanyFileService companyFileService = getViewModelService(); + Company company = getParent(); + List list = companyFileService.findByCompany(company); + if (list.isEmpty()) { + return; + } + if (getParentService().makePathAbsent(company)) { + save(company); + } + + String path = company.getPath(); + if (!StringUtils.hasText(path)) { + setStatus("异常, 企业目录未设置"); + return; + } + File companyPath = new File(path); + for (CompanyFile companyFile : list) { + String filePath = companyFile.getFilePath(); + if (StringUtils.hasText(filePath)) { + File file = new File(filePath); + if (file.exists()) { + if (file.getParentFile().equals(companyPath)) { + continue; + } + File dest = new File(companyPath, file.getName()); + if (file.renameTo(dest)) { + companyFile.setFilePath(dest.getAbsolutePath()); + companyFileService.save(companyFile); + setStatus(file.getName() + " 移动到 " + companyPath.getName()); + } + } + } + } + } + + /** + * + */ + private void onTableCopyAsMatchedByContractAction(ActionEvent event) { + UITools.showDialogAndWait("复制资信评估报告", "按当前评估报告复制一个合同中最匹配的", list -> { + onTableCopyAsMatchedAction_(msg -> { + Platform.runLater(() -> { + list.add(msg); + }); + }); + }); + } + + private void onTableCopyAsMatchedAction_(Consumer state) { + Company company = getParent(); + + CompanyFileViewModel selectedItem = getSelectedItem(); + if (selectedItem == null) { + state.accept("未选择行"); + return; + } + if (selectedItem.getApplyDate().get() == null) { + state.accept("有效日期不能未空"); + return; + } + + LocalDate nextCreditReportDate = null; + try { + nextCreditReportDate = getViewModelService().getNextCreditReportDate(company, state); + if (nextCreditReportDate == null) { + state.accept("没有找到下一个咨询评估日期"); + return; + } + } catch (Exception e) { + state.accept("获取下一个咨询评估日期失败:" + e.getMessage()); + return; + } + + state.accept("下一个咨询评估日期:" + nextCreditReportDate); + + if (!nextCreditReportDate.isBefore(selectedItem.getApplyDate().get())) { + state.accept("咨询评估日期晚于下一个咨询评估日期"); + return; + } + + + File src = new File(selectedItem.getFilePath().get()); + if (!src.exists()) { + state.accept("当前选择行的文件不存在"); + return; + } + + String srcDate = MyDateTimeUtils.format(selectedItem.getApplyDate().get()); + String destDate = MyDateTimeUtils.format(nextCreditReportDate); + String srcFileName = src.getName(); + String destFileName; + + // 天眼查的报告 + if (CloudTycService.isTycReport(srcFileName)) { + state.accept("天眼查的报告按标准格式命名"); + String name = company.getName() + "_" + CloudTycService.NAME; + if (srcFileName.contains(CloudTycService.TYC_ENTERPRISE_BASIC_REPORT)) { + name = name + "_" + CloudTycService.TYC_ENTERPRISE_BASIC_REPORT; + } else if (srcFileName.contains(CloudTycService.TYC_ENTERPRISE_MAJOR_REPORT)) { + name = name + "_" + CloudTycService.TYC_ENTERPRISE_MAJOR_REPORT; + } else if (srcFileName.contains(CloudTycService.TYC_ENTERPRISE_ANALYSIS_REPORT)) { + name = name + "_" + CloudTycService.TYC_ENTERPRISE_ANALYSIS_REPORT; + } + destFileName = name + "_" + destDate + "_cp." + StringUtils.getFilenameExtension(srcFileName); + } else { + if (srcFileName.contains(srcDate)) { + // 如果文件名中包含日期,则替换为新日期 + destFileName = srcFileName.replace(srcDate, destDate + "_cp"); + } else { + // 如果文件名中不包含日期,则添加日期 + destFileName = company.getName() + "_" + destDate + "_cp." + StringUtils.getFilenameExtension(srcFileName); + } + } + + state.accept("新文件名:" + destFileName); + + File dest = new File(src.getParent(), destFileName); + try { + FileSystemUtils.copyRecursively(src, dest); + state.accept("新文件复制成功"); + } catch (IOException e) { + state.accept("新文件复制失败:" + e.getMessage()); + } + + CompanyFile companyFile = new CompanyFile(); + companyFile.setFilePath(dest.getAbsolutePath()); + companyFile.setApplyDate(nextCreditReportDate); + companyFile.setExpiringDate(nextCreditReportDate.plusYears(1)); + companyFile.setType(CompanyFileType.CreditReport); + companyFile.setCompany(company); + saveRowData(companyFile); + + state.accept("新文件已记录"); + + loadTableDataSet(); + state.accept("文件表已刷新"); + } + + /** + * 当fileTable选中的行是咨询评估时,可用 + * + * @param event event + */ + public void onTableCopyAsMatchedMenuValidation(Event event) { + //当fileTable选中的行是咨询评估时,可用 + CompanyFileViewModel selectedItem = getSelectedItem(); + if (selectedItem == null) { + event.consume(); + return; + } + CompanyFileType type = selectedItem.getType().get(); + if (type != CompanyFileType.CreditReport) { + event.consume(); + return; + } + } + + @Override + protected boolean deleteRow(CompanyFileViewModel row) { + String path = row.getFilePath().get(); + if (super.deleteRow(row)) { + File file = new File(path); + if (file.exists()) { + UITools.showConfirmation("数据记录已经删除,请确认是否删除物理文件", path).thenAccept(buttonType -> { + if (buttonType == ButtonType.OK) { + if (file.delete()) { + setStatus("删除文件 " + path); + } + } + }); + } + return true; + } + return false; + } + + + private void moveFileToCompany(List files) { + String path = viewModel.getPath().get(); + if (!StringUtils.hasText(path)) { + setStatus("未设置目录"); + return; + } + File dir = new File(path); + if (!dir.exists()) { + setStatus("目录错误,不存在"); + return; + } + } + + @Override + protected void onTableRowDoubleClickedAction(CompanyFileViewModel item) { + String path = item.getFilePath().get(); + if (StringUtils.hasText(path)) { + File file = new File(path); + if (!file.exists()) { + setStatus("文件不存在 " + file.getName()); + return; + } + Desktop.showInExplorer(file); + } + } + + class FileTableFilePathTableCell extends TableCell { + @Override + protected void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setText(""); + return; + } + String path = viewModel.getPath().get(); + if (StringUtils.hasText(path)) { + if (item.startsWith(path)) { + item = "~" + item.substring(path.length()); + } + } + setText(item); + } + } + + public MyProperties getMyProperties() { + if (myProperties == null) { + myProperties = getBean(MyProperties.class); + } + return myProperties; + } + + protected CompanyFileService getViewModelService() { + return getCompanyFileService(); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinInvoice.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinInvoice.java new file mode 100644 index 0000000..a2fa8cd --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinInvoice.java @@ -0,0 +1,116 @@ +package com.ecep.contract.manager.ds.company.controller; + +import com.ecep.contract.manager.cloud.u8.YongYouU8Service; +import com.ecep.contract.manager.ds.company.model.Invoice; +import com.ecep.contract.manager.ds.company.service.InvoiceService; +import com.ecep.contract.manager.ds.company.vo.InvoiceViewModel; +import com.ecep.contract.manager.ds.contract.service.PurchaseBillVoucherService; +import com.ecep.contract.manager.ds.other.EmployeeStringConverter; +import com.ecep.contract.manager.ui.EditableEntityTableTabSkin; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.TabSkin; +import javafx.scene.control.Button; +import javafx.scene.control.Tab; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import lombok.Setter; +import org.springframework.beans.BeansException; + +import java.time.LocalDate; + +/** + * 子合同 + */ +@FxmlPath("/ui/company/company-tab-invoice.fxml") +public class CompanyTabSkinInvoice + extends AbstCompanyTableTabSkin + implements TabSkin, EditableEntityTableTabSkin { + + public TableColumn idColumn; + public TableColumn codeColumn; + public TableColumn dateColumn; + public TableColumn descriptionColumn; + + public TextField searchKeyField; + public Button searchBtn; + + @Setter + private PurchaseBillVoucherService purchaseBillVoucherService; + @Setter + private InvoiceService invoiceService; + @Setter + private YongYouU8Service u8Service; + @Setter + private EmployeeStringConverter employeeStringConverter; + + public CompanyTabSkinInvoice(CompanyWindowController controller) { + super(controller); + } + + @Override + public Tab getTab() { + return controller.invoiceTab; + } + + @Override + protected InvoiceService getViewModelService() { + return getInvoiceService(); + } + + @Override + public void initializeTab() { + + if (u8Service == null) { + try { + u8Service = getBean(YongYouU8Service.class); + } catch (BeansException ignored) { + } + } + + searchKeyField.setOnKeyReleased(event -> { + if (event.getCode() == KeyCode.ENTER) { + searchBtn.fire(); + } + }); + + searchBtn.setOnAction(this::onTableRefreshAction); + searchKeyField.setOnKeyReleased(event -> { + if (event.getCode() == KeyCode.ENTER) { + searchBtn.fire(); + } + }); + + idColumn.setCellValueFactory(param -> param.getValue().getId()); + codeColumn.setCellValueFactory(param -> param.getValue().getCode()); + dateColumn.setCellValueFactory(param -> param.getValue().getInvoiceDate()); + descriptionColumn.setCellValueFactory(param -> param.getValue().getDescription()); + super.initializeTab(); + } + + @Override + protected void onTableRowDoubleClickedAction(InvoiceViewModel item) { + // ContractWindowController.show(item, controller.root.getScene().getWindow()); + } + + private PurchaseBillVoucherService getPurchaseBillVoucherService() { + if (purchaseBillVoucherService == null) { + purchaseBillVoucherService = getBean(PurchaseBillVoucherService.class); + } + return purchaseBillVoucherService; + } + + private EmployeeStringConverter getEmployeeStringConverter() { + if (employeeStringConverter == null) { + employeeStringConverter = getBean(EmployeeStringConverter.class); + } + return employeeStringConverter; + } + + private InvoiceService getInvoiceService() { + if (invoiceService == null) { + invoiceService = getBean(InvoiceService.class); + } + return invoiceService; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinOldName.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinOldName.java new file mode 100644 index 0000000..8667723 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinOldName.java @@ -0,0 +1,208 @@ +package com.ecep.contract.manager.ds.company.controller; + +import com.ecep.contract.manager.ds.company.controller.old_name.CompanyOldNameWindowController; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyOldName; +import com.ecep.contract.manager.ds.company.service.CompanyOldNameService; +import com.ecep.contract.manager.ds.company.vo.CompanyOldNameViewModel; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.TabSkin; +import com.ecep.contract.manager.util.SpecificationUtils; +import javafx.application.Platform; +import javafx.event.ActionEvent; +import javafx.scene.control.*; +import javafx.scene.control.cell.CheckBoxTableCell; +import javafx.scene.input.KeyCode; +import lombok.Setter; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.util.StringUtils; + +import java.time.LocalDate; +import java.util.*; + +/** + *曾用名 + */ +@FxmlPath("/ui/company/company-tab-oldname.fxml") +public class CompanyTabSkinOldName + extends AbstCompanyTableTabSkin + implements TabSkin { + + /** + * 以下是曾用名列定义 + */ + public TableColumn oldNameTable_idColumn; + public TableColumn oldNameTable_nameColumn; + public TableColumn oldNameTable_beginDateColumn; + public TableColumn oldNameTable_endDateColumn; + public TableColumn oldNameTable_ambiguityColumn; + public TableColumn oldNameTable_pathColumn; + public TableColumn oldNameTable_memoColumn; + + public TextField oldNameSearchKeyField; + public Button oldNameSearchBtn; + public Button oldNameTable_create_btn; + public Button oldNameTable_merge_btn; + public MenuItem oldNameTable_menu_refresh; + public MenuItem oldNameTable_menu_del; + + @Setter + private CompanyOldNameService companyOldNameService; + + public CompanyTabSkinOldName(CompanyWindowController controller) { + super(controller); + } + + @Override + public Tab getTab() { + return controller.oldNameTab; + } + + @Override + public Specification getSpecification(Company parent) { + return SpecificationUtils.and(getSpecification(), (root, query, builder) -> { + return builder.equal(root.get("companyId"), parent.getId()); + }); + } + + @Override + public void initializeTab() { + + oldNameSearchKeyField.setOnKeyReleased(event -> { + if (event.getCode() == KeyCode.ENTER) { + oldNameSearchBtn.fire(); + } + }); + + oldNameSearchBtn.setOnAction(this::onTableRefreshAction); + + oldNameTable_create_btn.setOnAction(this::onTableAddAction); + oldNameTable_merge_btn.setOnAction(this::onTableMergeAction); + + oldNameTable_idColumn.setCellValueFactory(param -> param.getValue().getId()); + oldNameTable_nameColumn.setCellValueFactory(param -> param.getValue().getName()); + + oldNameTable_pathColumn.setCellFactory(param -> new FileTableFilePathTableCell()); + + oldNameTable_ambiguityColumn.setCellValueFactory(param -> param.getValue().getAmbiguity()); + oldNameTable_ambiguityColumn.setCellFactory(c -> new CheckBoxTableCell<>()); + + oldNameTable_beginDateColumn.setCellValueFactory(param -> param.getValue().getBeginDate()); + oldNameTable_endDateColumn.setCellValueFactory(param -> param.getValue().getEndDate()); + + oldNameTable_memoColumn.setCellValueFactory(param -> param.getValue().getMemo()); + + oldNameTable_menu_refresh.setOnAction(this::onTableRefreshAction); + oldNameTable_menu_del.setOnAction(this::onTableDeleteAction); + + super.initializeTab(); + } + + + @Override + protected void onTableAddAction(ActionEvent event) { + // + TextInputDialog dialog = new TextInputDialog(); + dialog.setHeaderText("请输入曾用名"); + dialog.setTitle("新建曾用名"); + Optional optional = dialog.showAndWait(); + if (optional.isEmpty()) { + return; + } + CompanyOldName oldName = new CompanyOldName(); + oldName.setName(optional.get()); + oldName.setCompanyId(viewModel.getId().get()); + oldName.setAmbiguity(true); + getViewModelService().save(oldName); + loadTableDataSet(); + } + + @Override + protected boolean deleteRow(CompanyOldNameViewModel row) { + CompanyOldName entity = getViewModelService().findById(row.getId().get()); + if (entity != null) { + getViewModelService().delete(entity); + } + return true; + } + + private void onTableMergeAction(ActionEvent event) { + Company updater = getParent(); + HashSet nameSet = new HashSet<>(); + nameSet.add(updater.getName()); + + List removes = new ArrayList<>(); + for (CompanyOldNameViewModel viewModel : dataSet) { + if (!nameSet.add(viewModel.getName().get())) { + // fixed 曾用名中有重复时,删除重复的 + getViewModelService().delete(viewModel); + removes.add(viewModel); + } + } + if (!removes.isEmpty()) { + Platform.runLater(() -> { + dataSet.removeAll(removes); + }); + setStatus("移除重复的曾用名" + removes.size()); + } + + int size = nameSet.size(); + int count = 1; + int merge = 0; + for (String name : nameSet) { + controller.setRightStatus(count + "/" + size); + if (StringUtils.hasText(name)) { + List list = getParentService().findAllByName(name); + for (Company company : list) { + // fixed 曾用名中有可能有 updater 的名字,会导致自己删除自己 + if (Objects.equals(company.getId(), updater.getId())) { + continue; + } + try { + getCompanyService().merge(company, updater); + setStatus("并户 " + company.getName() + "[" + company.getId() + "] 到当前公司"); + merge++; + } catch (Exception e) { + throw new RuntimeException("合并 " + company.getName() + " -> " + updater.getName() + " 失败", e); + } + } + } + count++; + } + if (merge == 0) { + setStatus("没有需要并户的公司"); + } + controller.setRightStatus(""); + } + + @Override + protected void onTableRowDoubleClickedAction(CompanyOldNameViewModel item) { + CompanyOldNameWindowController.show(item, controller.root.getScene().getWindow()); + } + + class FileTableFilePathTableCell extends TableCell { + @Override + protected void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setText(""); + return; + } + String path = viewModel.getPath().get(); + if (StringUtils.hasText(path)) { + if (item.startsWith(path)) { + item = "~" + item.substring(path.length()); + } + } + setText(item); + } + } + + + protected CompanyOldNameService getViewModelService() { + if (companyOldNameService == null) { + companyOldNameService = getBean(CompanyOldNameService.class); + } + return companyOldNameService; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinOther.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinOther.java new file mode 100644 index 0000000..bb275b4 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinOther.java @@ -0,0 +1,647 @@ +package com.ecep.contract.manager.ds.company.controller; + +import com.ecep.contract.manager.Desktop; +import com.ecep.contract.manager.cloud.rk.CloudRk; +import com.ecep.contract.manager.cloud.rk.CloudRkInfoViewModel; +import com.ecep.contract.manager.cloud.rk.CloudRkService; +import com.ecep.contract.manager.cloud.rk.ctx.CloudRkCtx; +import com.ecep.contract.manager.cloud.tyc.CloudTyc; +import com.ecep.contract.manager.cloud.tyc.CloudTycInfoViewModel; +import com.ecep.contract.manager.cloud.tyc.CloudTycService; +import com.ecep.contract.manager.cloud.u8.CloudYu; +import com.ecep.contract.manager.cloud.u8.CloudYuInfoViewModel; +import com.ecep.contract.manager.cloud.u8.YongYouU8Service; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyExtendInfo; +import com.ecep.contract.manager.ds.company.service.CompanyExtendInfoService; +import com.ecep.contract.manager.ds.company.vo.CompanyExtendInfoViewModel; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.MessageHolder; +import com.ecep.contract.manager.ui.TabSkin; +import com.ecep.contract.manager.ui.util.DelayOnceExecutor; +import com.ecep.contract.manager.util.MyDateTimeUtils; +import com.ecep.contract.manager.util.UITools; +import javafx.application.Platform; +import javafx.event.ActionEvent; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.layout.Border; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.scene.web.WebView; +import javafx.stage.Stage; +import javafx.util.converter.NumberStringConverter; +import lombok.Setter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; +import org.springframework.util.StringUtils; +import org.w3c.dom.html.HTMLDocument; + +import java.io.File; +import java.io.IOException; +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; + +@FxmlPath("/ui/company/company-tab-other.fxml") +public class CompanyTabSkinOther + extends AbstCompanyBasedTabSkin + implements TabSkin { + private static final Logger logger = LoggerFactory.getLogger(CompanyTabSkinOther.class); + /** + * 以下是云平台数据 + */ + public TitledPane rkCloudPane; + public TextField cloudRkIdField; + public TextField cloudRkCloudIdField; + public TextField cloudRkLatestField; + public TextField cloudRkCustomerGradeField; + public TextField cloudRkCustomerScoreField; + public TextField cloudRkCustomerDescriptionField; + + public TextField cloudRkVendorGradeField; + public TextField cloudRkVendorScoreField; + public TextField cloudRkVendorDescriptionField; + + public TextField cloudRkBlackListUpdatedField; + public TextField cloudRkCloudLatestField; + public TextField cloudRkEntUpdateField; + public TextField cloudRkCreditRankField; + public TextField cloudRkCreditRankDescriptionField; + public Label cloudRkVersionLabel; + public CheckBox cloudRkAutoUpdateField; + + + // Tyc // + public TitledPane tycCloudPane; + public TextField cloudTycIdField; + public TextField cloudTycCloudIdField; + public TextField cloudTycLatestField; + public Label cloudTycVersionLabel; + public TextField tycCloudPaneCloudScore; + // public Hyperlink tycCloudPaneHyperLink; + public Button tycCloudPaneSaveButton; + + + // Yu // + public TitledPane yuCloudPane; + public TextField cloudYuIdField; + public TextField cloudYuCloudIdField; + public TextField cloudYuLatestField; + public Label cloudYuVersionLabel; + public Button yuCloudPaneSaveButton; + + // Extend Info // + public TitledPane extendInfoPane; + public TextField extendInfoIdField; + public TextField extendInfoLatestField; + public Label extendInfoVersionLabel; + public CheckBox extendInfoDisableVerifyField; + public Button extendInfoPaneSaveButton; + + private final CloudRkInfoViewModel rkCloudInfoViewModel = new CloudRkInfoViewModel(); + private final CloudTycInfoViewModel tycCloudInfoViewModel = new CloudTycInfoViewModel(); + private final CloudYuInfoViewModel yuCloudInfoViewModel = new CloudYuInfoViewModel(); + private final CompanyExtendInfoViewModel extendInfoViewModel = new CompanyExtendInfoViewModel(); + + + + @Setter + private CloudRkService cloudRkService; + @Setter + private CloudTycService cloudTycService; + @Setter + private YongYouU8Service yongYouU8Service; + @Setter + private CompanyExtendInfoService extendInfoService; + + public CompanyTabSkinOther(CompanyWindowController controller) { + super(controller); + } + + @Override + public Tab getTab() { + return controller.otherTab; + } + + @Override + public void initializeTab() { + initializeCloudRkPane(); + initializeCloudTycPane(); + initializeCloudYuPane(); + initializeExtendInfoPane(); + listenTabSelectionChanged(); + } + + @Override + public void onTabShown() { + Company parent = getEntity(); + updateRKCloudPane(parent, rkCloudPane); + updateTYCCloudPane(parent, tycCloudPane); + updateYuCloudPane(parent, yuCloudPane); + updateExtendInfoPane(parent, extendInfoPane); + } + + + /** + * 初始化 相关方平台信息 Titled Pane + */ + private void initializeCloudRkPane() { + // 记录编号 + cloudRkIdField.textProperty().bind(rkCloudInfoViewModel.getId().asString()); + // 平台编号 + cloudRkCloudIdField.textProperty().bindBidirectional(rkCloudInfoViewModel.getCloudId()); + cloudRkLatestField.textProperty().bind(rkCloudInfoViewModel.getLatest().map(MyDateTimeUtils::format)); + cloudRkVersionLabel.textProperty().bind(rkCloudInfoViewModel.getVersion().asString("Ver:%s")); + + cloudRkAutoUpdateField.selectedProperty().bindBidirectional(rkCloudInfoViewModel.getAutoUpdate()); + + cloudRkCustomerGradeField.textProperty().bind(rkCloudInfoViewModel.getCustomerGrade()); + cloudRkCustomerScoreField.textProperty().bind(rkCloudInfoViewModel.getCustomerScore().asString()); + cloudRkCustomerDescriptionField.textProperty().bind(rkCloudInfoViewModel.getCustomerDescription()); + + cloudRkVendorGradeField.textProperty().bind(rkCloudInfoViewModel.getVendorGrade()); + cloudRkVendorScoreField.textProperty().bind(rkCloudInfoViewModel.getVendorScore().asString()); + cloudRkVendorDescriptionField.textProperty().bind(rkCloudInfoViewModel.getVendorDescription()); + + cloudRkCreditRankField.textProperty().bind(rkCloudInfoViewModel.getRank()); + cloudRkCreditRankDescriptionField.textProperty().bind(rkCloudInfoViewModel.getRankDescription()); + cloudRkCloudLatestField.textProperty().bind(rkCloudInfoViewModel.getCloudLatest().map(MyDateTimeUtils::format)); + cloudRkEntUpdateField.textProperty().bind(rkCloudInfoViewModel.getCloudEntUpdate().map(MyDateTimeUtils::format)); + cloudRkBlackListUpdatedField.textProperty().bind(rkCloudInfoViewModel.getCloudBlackListUpdated().map(MyDateTimeUtils::format)); + + DelayOnceExecutor saveExecutor = new DelayOnceExecutor(() -> { + save(rkCloudInfoViewModel); + cloudRkAutoUpdateField.setBorder(null); + }, 2, TimeUnit.SECONDS).exception(e -> logger.error(e.getMessage(), e)); + cloudRkAutoUpdateField.setOnMouseClicked(event -> { + cloudRkAutoUpdateField.setBorder(Border.stroke(Color.RED)); + saveExecutor.tick(); + }); + } + + public void save(CloudRkInfoViewModel viewModel) { + int infoId = viewModel.getId().get(); + CloudRkService service = getCloudRkService(); + CloudRk cloudRk = service.findById(infoId); + if (cloudRk == null) { + throw new RuntimeException("CloudRk not found"); + } + if (viewModel.copyTo(cloudRk)) { + CloudRk saved = service.save(cloudRk); + if (Platform.isFxApplicationThread()) { + viewModel.update(saved); + } else { + Platform.runLater(() -> { + viewModel.update(saved); + }); + } + } + } + + /** + * 杂项页中,集团相关方panel中更新action + */ + public void onCloudRkUpdateButtonClickedAction(ActionEvent event) { + Button button = (Button) event.getSource(); + button.setDisable(true); + CompletableFuture.runAsync(() -> { + try { + onCloudRkUpdateButtonClicked(event); + } catch (Exception e) { + UITools.showExceptionAndWait("", e); + } + }).whenComplete((v, e) -> { + button.setDisable(false); + }); + } + + /** + * 从集团相关方平台中更新公司信息 + * + * @see #onCloudRkUpdateButtonClickedAction(ActionEvent) + */ + private void onCloudRkUpdateButtonClicked(ActionEvent actionEvent) throws IOException { + Button button = (Button) actionEvent.getSource(); + + Company company = getEntity(); + + CloudRkCtx cloudRkCtx = new CloudRkCtx(); + cloudRkCtx.setCloudRkService(getCloudRkService()); + CloudRk cloudRk = cloudRkCtx.getOrCreateCloudRk(company); + + MessageHolder holder = (level, message) -> { + setStatus(message); + if (level == Level.WARNING) { + logger.warn("{} {}", getEntity().getName(), message); + } + if (level == Level.SEVERE) { + logger.error("{} {}", getEntity().getName(), message); + } + }; + + if (!StringUtils.hasText(rkCloudInfoViewModel.getCloudId().get())) { + holder.info("没有 " + CloudRkService.NAME + " 编号,准备使用模糊查询接口获取编号..."); + // use fuzzy to find cloud id + try { + // + List entInfos = cloudRkCtx.queryEnterpriseWithFuzzy(company, cloudRk, holder); + if (entInfos.isEmpty()) { + cloudRk.setCloudId("-"); + cloudRkCtx.save(cloudRk); + holder.error("没有查询到相关企业信息"); + return; + } + String companyName = company.getName(); + Optional optional = entInfos.stream() + .filter(n -> n.getName().equals(companyName)).findAny(); + if (optional.isPresent()) { + // 查询接口中返回的结果中有跟公司名称相同的,则直接使用 + cloudRk.setCloudId(optional.get().getId()); + } else { + // 手动选择 + CloudRkService.EntInfo selectedItem = showEnterpriseChooser(entInfos); + if (selectedItem != null) { + cloudRk.setCloudId(selectedItem.getId()); + rkCloudInfoViewModel.update(cloudRkCtx.save(cloudRk)); + // 重新发起 + button.getOnAction().handle(actionEvent); + } + + Platform.runLater(() -> { + + + }); + return; + } + + } catch (Exception e) { + // 异常 + UITools.showExceptionAndWait(CloudRkService.NAME + " 中使用模糊查询接口获取企业平台编号发生错误", e); + return; + } + + } + + if (!StringUtils.hasText(cloudRk.getCloudId())) { + setStatus("没有平台编号, 无法更新"); + return; + } + if (cloudRk.getCloudId().equals("-")) { + setStatus("更新已经屏蔽"); + return; + } + + + try { + rkCloudInfoViewModel.getLatest().set(null); + setStatus("尝试从 " + CloudRkService.NAME + " 中更新企业信息..."); + if (cloudRkCtx.updateEnterpriseInfo(company, cloudRk, holder)) { + company = save(company); + } + + setStatus("尝试从 " + CloudRkService.NAME + " 中更新黑名单..."); + if (cloudRkCtx.updateBlackList(company, cloudRk, holder)) { + CompanyTabSkinOldName tabSkin = controller.getTabSkin(CompanyTabSkinOldName.class); + if (tabSkin != null) { + tabSkin.loadTableDataSet(); + } + } + + setStatus("尝试从 " + CloudRkService.NAME + " 中更新企业评分..."); + if (cloudRkCtx.updateEnterpriseCredit(company, cloudRk, holder)) { + // cloudRk modified + } + setStatus("尝试从 " + CloudRkService.NAME + " 中更新客户评级..."); + if (cloudRkCtx.updateCustomerScore(company, cloudRk, holder)) { + // cloudRk modified + } + setStatus("尝试从 " + CloudRkService.NAME + " 中更新供应商评分..."); + if (cloudRkCtx.updateVendorScore(company, cloudRk, holder)) { + // cloudRk modified + } + setStatus(""); + } catch (Exception e) { + // 异常 + logger.error("更新 {} 时发生错误", company.getName(), e); + UITools.showExceptionAndWait(CloudRkService.NAME + " 中使用报表接口更新企业基本注册信息时发生错误", e); + return; + } finally { + cloudRk.setLatestUpdate(Instant.now()); + CloudRk cloudRk1 = cloudRkCtx.save(cloudRk); + Platform.runLater(() -> { + rkCloudInfoViewModel.update(cloudRk1); + }); + } + } + + private CloudRkService.EntInfo showEnterpriseChooser(List entInfos) { + // TODO 转换为 独立窗口类,通过 getResult() 返回操作 + Alert alert = new Alert(Alert.AlertType.CONFIRMATION); + alert.setWidth(350); + alert.setHeaderText(CloudRkService.NAME + " 返回多个结果, 请选择确认"); + ListView listView = new ListView<>(); + listView.setCellFactory(param -> new ListCell<>() { + @Override + protected void updateItem(CloudRkService.EntInfo item, boolean empty) { + super.updateItem(item, empty); + if (item == null || empty) { + setText(null); + // setBackground(Background.EMPTY); + } else { + setText((item.isNowName() ? "☑" : "☐") + " " + item.getName()); + } + + } + }); + listView.getItems().addAll(entInfos); + alert.getDialogPane().setContent(listView); + if (alert.showAndWait().get() != ButtonType.OK) { + return null; + } + return listView.getSelectionModel().getSelectedItem(); + } + + + public void onTycCloudPaneHyperLinkClickedAction(ActionEvent event) { + String cloudId = tycCloudInfoViewModel.getCloudId().get(); + String url = null; + if (StringUtils.hasText(cloudId)) { + url = String.format(CloudTycService.URL_COMPANY, cloudId); + } else { + Company company = getEntity(); + url = String.format(CloudTycService.URL_COMPANY_SEARCH, company.getName()); + } + Desktop.showInBrowse(url); + } + + public void onTycCloudPaneHyperLinkInnerViewClickedAction(ActionEvent event) { + Company company = getEntity(); + String cloudId = tycCloudInfoViewModel.getCloudId().get(); + String url = "https://www.tianyancha.com/search?key=" + company.getName(); + Stage stage = new Stage(); + WebView webView = new WebView(); + webView.getEngine().locationProperty().addListener((ob, old, n) -> { + System.out.println("location " + old + " -> " + n); + }); + webView.getEngine().getLoadWorker().exceptionProperty().addListener((ob, old, n) -> { + System.out.println("LoadWorker exception:" + n); + }); + webView.getEngine().setCreatePopupHandler(p -> { + System.out.println("Popup = " + p); + return webView.getEngine(); + }); + + webView.getEngine().setPromptHandler(p -> { + System.out.println("Prompt = " + p); + return null; + }); + + webView.getEngine().setConfirmHandler(p -> { + System.out.println("Confirm = " + p); + return null; + }); + webView.getEngine().getLoadWorker().stateProperty().addListener((ob, old, n) -> { + System.out.println("LoadWorker state:" + n); + }); + webView.getEngine().getLoadWorker().progressProperty().addListener((ob, old, n) -> { + System.out.println("LoadWorker progress:" + n); + }); + + File directory = new File("."); + System.out.println(". = " + directory.getAbsolutePath()); + webView.getEngine().setUserDataDirectory(directory); + + webView.getEngine().load(url); + webView.getEngine().documentProperty().addListener(((observable, oldValue, newValue) -> { + if (newValue == null) { + return; + } + HTMLDocument doc = (HTMLDocument) newValue; + setStatus("Open Url : " + doc.getURL()); + stage.setTitle(doc.getTitle()); + // updateByTycSearch(company, doc); + })); + StackPane root = new StackPane(); + root.getChildren().add(webView); + Scene scene = new Scene(root); + stage.setScene(scene); + stage.show(); + } + + /** + * 杂项 天眼查页,更新按钮 action + */ + public void onCloudTycUpdateButtonClickedAction(ActionEvent event) { + Button button = (Button) event.getSource(); + button.setDisable(true); + CompletableFuture.runAsync(() -> { + try { + onCloudTycUpdateButtonClicked(event); + } catch (Exception e) { + UITools.showExceptionAndWait("", e); + } + }).whenComplete((v, e) -> { + button.setDisable(false); + }); + } + + /** + * 更新 天眼查 资讯资信 + * + * @see #onCloudTycUpdateButtonClickedAction(ActionEvent) + */ + private void onCloudTycUpdateButtonClicked(ActionEvent event) { + // TODO 待实现 + setStatus("待实现"); +// CompletableFuture.delayedExecutor(2, TimeUnit.SECONDS).execute(() -> { +// button.setDisable(false); +// }); + } + + + /** + * 初始化 天眼查平台 Titled Pane + */ + private void initializeCloudTycPane() { + // 记录编号 + cloudTycIdField.textProperty().bind(tycCloudInfoViewModel.getId().asString()); + // 平台编号 + cloudTycCloudIdField.textProperty().bindBidirectional(tycCloudInfoViewModel.getCloudId()); + cloudTycLatestField.textProperty().bind(tycCloudInfoViewModel.getLatest().map(MyDateTimeUtils::format)); + cloudTycVersionLabel.textProperty().bind(tycCloudInfoViewModel.getVersion().asString("Ver:%s")); + + + TextField cloudIdField = cloudTycCloudIdField; + TextField cloudScoreField = tycCloudPaneCloudScore; + + DelayOnceExecutor saveExecutor = new DelayOnceExecutor(() -> { + cloudTycService.save(tycCloudInfoViewModel); + cloudIdField.setBorder(null); + cloudScoreField.setBorder(null); + tycCloudPaneSaveButton.setDisable(true); + setStatus("保存成功"); + }, 2, TimeUnit.SECONDS).exception(e -> logger.error(e.getMessage(), e)); + + cloudIdField.setOnKeyPressed(event -> { + cloudIdField.setBorder(Border.stroke(Color.RED)); + tycCloudPaneSaveButton.setDisable(false); + saveExecutor.tick(); + }); + cloudScoreField.setOnKeyPressed(event -> { + cloudScoreField.setBorder(Border.stroke(Color.RED)); + tycCloudPaneSaveButton.setDisable(false); + saveExecutor.tick(); + }); + + + cloudScoreField.textProperty().bindBidirectional(tycCloudInfoViewModel.getScore(), new NumberStringConverter()); + tycCloudPaneSaveButton.setOnAction(event -> { + saveExecutor.tickNow(); + }); + } + + + private void initializeCloudYuPane() { + try { + yongYouU8Service = getBean(YongYouU8Service.class); + } catch (BeansException e) { + yuCloudPane.setDisable(true); + } + + cloudYuIdField.textProperty().bind(yuCloudInfoViewModel.getId().asString()); + cloudYuCloudIdField.textProperty().bindBidirectional(yuCloudInfoViewModel.getCloudId()); + cloudYuLatestField.textProperty().bind(yuCloudInfoViewModel.getLatest().map(MyDateTimeUtils::format)); + cloudYuVersionLabel.textProperty().bind(yuCloudInfoViewModel.getVersion().asString("Ver:%s")); + + + Button button = yuCloudPaneSaveButton; + + // 更新 + button.setOnAction(event -> { + button.setDisable(true); + CompletableFuture.runAsync(() -> { + try { + onCloudYuUpdateButtonClicked(event); + } catch (Exception e) { + UITools.showExceptionAndWait("", e); + } + }).whenComplete((v, e) -> { + button.setDisable(false); + }); + }); + + } + + private void onCloudYuUpdateButtonClicked(ActionEvent event) { + Company company = getEntity(); + // service.syncVendor(company); + + } + + + private void initializeExtendInfoPane() { + // 记录编号 + extendInfoIdField.textProperty().bind(extendInfoViewModel.getId().asString()); + extendInfoDisableVerifyField.selectedProperty().bindBidirectional(extendInfoViewModel.getDisableVerify()); + extendInfoVersionLabel.textProperty().bind(extendInfoViewModel.getVersion().asString("Ver:%s")); + + DelayOnceExecutor saveExecutor = new DelayOnceExecutor(() -> { + save(extendInfoViewModel); + extendInfoDisableVerifyField.setBorder(null); + }, 2, TimeUnit.SECONDS).exception(e -> logger.error(e.getMessage(), e)); + extendInfoDisableVerifyField.setOnMouseClicked(event -> { + extendInfoDisableVerifyField.setBorder(Border.stroke(Color.RED)); + saveExecutor.tick(); + }); + + extendInfoPaneSaveButton.setOnAction(event -> { + saveExecutor.tickNow(); + }); + } + + public void save(CompanyExtendInfoViewModel viewModel) { + int infoId = viewModel.getId().get(); + CompanyExtendInfoService service = getExtendInfoService(); + CompanyExtendInfo cloudRk = service.findById(infoId); + if (cloudRk == null) { + throw new RuntimeException("CloudRk not found"); + } + if (viewModel.copyTo(cloudRk)) { + CompanyExtendInfo saved = service.save(cloudRk); + if (Platform.isFxApplicationThread()) { + viewModel.update(saved); + } else { + Platform.runLater(() -> { + viewModel.update(saved); + }); + } + } + } + + private void updateRKCloudPane(Company company, TitledPane pane) { + CloudRkInfoViewModel viewModel = rkCloudInfoViewModel; + CloudRk cloudRk = getCloudRkService().getOrCreateCloudRk(company); + Platform.runLater(() -> { + viewModel.update(cloudRk); + }); + } + + private void updateTYCCloudPane(Company company, TitledPane pane) { + CloudTycInfoViewModel viewModel = tycCloudInfoViewModel; + CloudTyc cloudTyc = getCloudTycService().getOrCreateCloudTyc(company); + Platform.runLater(() -> { + viewModel.update(cloudTyc); + }); + } + + private void updateYuCloudPane(Company company, TitledPane pane) { + CloudYuInfoViewModel viewModel = yuCloudInfoViewModel; + if (yongYouU8Service == null) { + setStatus("未启用 " + YongYouU8Service.NAME + " 服务"); + return; + } + CloudYu cloudYu = yongYouU8Service.getOrCreateCloudYu(company); + Platform.runLater(() -> { + viewModel.update(cloudYu); + }); + } + + private void updateExtendInfoPane(Company company, TitledPane pane) { + CompanyExtendInfoViewModel viewModel = extendInfoViewModel; + CompanyExtendInfoService service = getExtendInfoService(); + CompanyExtendInfo extendInfo = service.findByCompany(company); + Platform.runLater(() -> { + viewModel.update(extendInfo); + }); + } + + CloudRkService getCloudRkService() { + if (cloudRkService == null) { + cloudRkService = getBean(CloudRkService.class); + } + return cloudRkService; + } + + CloudTycService getCloudTycService() { + if (cloudTycService == null) { + cloudTycService = getBean(CloudTycService.class); + } + return cloudTycService; + } + + CompanyExtendInfoService getExtendInfoService() { + if (extendInfoService == null) { + extendInfoService = getBean(CompanyExtendInfoService.class); + } + return extendInfoService; + } + + YongYouU8Service getYongYouU8Service() { + return yongYouU8Service; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinPurchaseBillVoucher.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinPurchaseBillVoucher.java new file mode 100644 index 0000000..96127bf --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTabSkinPurchaseBillVoucher.java @@ -0,0 +1,157 @@ +package com.ecep.contract.manager.ds.company.controller; + +import com.ecep.contract.manager.cloud.u8.YongYouU8Service; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.Invoice; +import com.ecep.contract.manager.ds.company.service.InvoiceService; +import com.ecep.contract.manager.ds.contract.model.PurchaseBillVoucher; +import com.ecep.contract.manager.ds.contract.service.PurchaseBillVoucherService; +import com.ecep.contract.manager.ds.contract.vo.PurchaseBillVoucherViewModel; +import com.ecep.contract.manager.ds.other.EmployeeStringConverter; +import com.ecep.contract.manager.ds.other.model.Employee; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.TabSkin; +import com.ecep.contract.manager.ui.ViewModelService; +import com.ecep.contract.manager.ui.util.LocalDateTimeTableCell; +import javafx.scene.control.*; +import javafx.scene.control.cell.TextFieldTableCell; +import javafx.scene.input.KeyCode; +import lombok.Setter; +import org.springframework.beans.BeansException; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.util.StringUtils; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 子合同 + */ +@FxmlPath("/ui/company/company-tab-purchase-bill-voucher.fxml") +public class CompanyTabSkinPurchaseBillVoucher + extends AbstCompanyTableTabSkin + implements TabSkin { + + public TableColumn contractTable_idColumn; + public TableColumn contractTable_refIdColumn; + public TableColumn invoiceColumn; + public TableColumn contractTable_codeColumn; + public TableColumn employeeColumn; + public TableColumn makerColumn; + public TableColumn makerDateColumn; + public TableColumn modifyDateColumn; + public TableColumn verifierColumn; + public TableColumn verifierDateColumn; + public TableColumn descriptionColumn; + + public TextField searchKeyField; + public Button searchBtn; + public MenuItem contractTable_menu_refresh; + public MenuItem contractTable_menu_add; + public MenuItem contractTable_menu_del; + + + @Setter + private PurchaseBillVoucherService purchaseBillVoucherService; + @Setter + private InvoiceService invoiceService; + @Setter + private YongYouU8Service u8Service; + @Setter + private EmployeeStringConverter employeeStringConverter; + + public CompanyTabSkinPurchaseBillVoucher(CompanyWindowController controller) { + super(controller); + } + + @Override + public Tab getTab() { + return controller.purchaseBillVoucherTab; + } + + @Override + public void initializeTab() { + + if (u8Service == null) { + try { + u8Service = getBean(YongYouU8Service.class); + } catch (BeansException ignored) { + } + } + + searchKeyField.setOnKeyReleased(event -> { + if (event.getCode() == KeyCode.ENTER) { + searchBtn.fire(); + } + }); + + searchBtn.setOnAction(this::onTableRefreshAction); + searchKeyField.setOnKeyReleased(event -> { + if (event.getCode() == KeyCode.ENTER) { + searchBtn.fire(); + } + }); + + contractTable_idColumn.setCellValueFactory(param -> param.getValue().getId()); + contractTable_refIdColumn.setCellValueFactory(param -> param.getValue().getRefId()); + contractTable_codeColumn.setCellValueFactory(param -> param.getValue().getCode()); + + employeeColumn.setCellValueFactory(param -> param.getValue().getEmployee()); + employeeColumn.setCellFactory(TextFieldTableCell.forTableColumn(getEmployeeStringConverter())); + + invoiceColumn.setCellValueFactory(param -> param.getValue().getInvoice()); + invoiceColumn.setCellFactory(param -> new InvoiceTableCell<>(getInvoiceService())); + + + makerColumn.setCellValueFactory(param -> param.getValue().getMaker()); + makerColumn.setCellFactory(TextFieldTableCell.forTableColumn(getEmployeeStringConverter())); + makerDateColumn.setCellValueFactory(param -> param.getValue().getMakerDate()); + makerDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); + modifyDateColumn.setCellValueFactory(param -> param.getValue().getModifyDate()); + modifyDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); + + verifierColumn.setCellValueFactory(param -> param.getValue().getVerifier()); + verifierColumn.setCellFactory(TextFieldTableCell.forTableColumn(getEmployeeStringConverter())); + verifierDateColumn.setCellValueFactory(param -> param.getValue().getVerifierDate()); + verifierDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); + + descriptionColumn.setCellValueFactory(param -> param.getValue().getDescription()); + + contractTable_menu_refresh.setOnAction(this::onTableRefreshAction); + contractTable_menu_del.setOnAction(this::onTableDeleteAction); + + super.initializeTab(); + } + + @Override + protected void onTableRowDoubleClickedAction(PurchaseBillVoucherViewModel item) { + // ContractWindowController.show(item, controller.root.getScene().getWindow()); + } + + @Override + protected PurchaseBillVoucherService getViewModelService() { + return getPurchaseBillVoucherService(); + } + + private PurchaseBillVoucherService getPurchaseBillVoucherService() { + if (purchaseBillVoucherService == null) { + purchaseBillVoucherService = getBean(PurchaseBillVoucherService.class); + } + return purchaseBillVoucherService; + } + + private EmployeeStringConverter getEmployeeStringConverter() { + if (employeeStringConverter == null) { + employeeStringConverter = getBean(EmployeeStringConverter.class); + } + return employeeStringConverter; + } + + private InvoiceService getInvoiceService() { + if (invoiceService == null) { + invoiceService = getBean(InvoiceService.class); + } + return invoiceService; + } + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTableCell.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTableCell.java new file mode 100644 index 0000000..75adb3c --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyTableCell.java @@ -0,0 +1,41 @@ +package com.ecep.contract.manager.ds.company.controller; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import org.hibernate.Hibernate; + +import static com.ecep.contract.manager.SpringApp.getBean; + +public class CompanyTableCell extends javafx.scene.control.TableCell { + + + private CompanyService companyService; + + + public CompanyTableCell() { + } + + public CompanyTableCell(CompanyService companyService) { + this.companyService = companyService; + } + + private CompanyService getCompanyService() { + if (companyService == null) { + companyService = getBean(CompanyService.class); + } + return companyService; + } + + @Override + protected void updateItem(Company item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setText(null); + return; + } + if (!Hibernate.isInitialized(item)) { + item = getCompanyService().findById(item.getId()); + } + setText(item.getName()); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyVerifyWindowController.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyVerifyWindowController.java new file mode 100644 index 0000000..0ba20cf --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyVerifyWindowController.java @@ -0,0 +1,15 @@ +package com.ecep.contract.manager.ds.company.controller; + +import com.ecep.contract.manager.ui.BaseController; +import com.ecep.contract.manager.ui.FxmlPath; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Lazy +@Scope("prototype") +@Component +@FxmlPath("/ui/company/company-verify.fxml") +public class CompanyVerifyWindowController extends BaseController { + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyWindowController.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyWindowController.java new file mode 100644 index 0000000..72a8aeb --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/CompanyWindowController.java @@ -0,0 +1,314 @@ +package com.ecep.contract.manager.ds.company.controller; + +import com.ecep.contract.manager.Desktop; +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.service.CompanyFileService; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.company.tasker.CompanyCompositeUpdateTasker; +import com.ecep.contract.manager.ds.company.tasker.CompanyVerifyTasker; +import com.ecep.contract.manager.ds.company.vo.CompanyViewModel; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ds.customer.controller.CompanyCustomerWindowController; +import com.ecep.contract.manager.ds.customer.model.CompanyCustomer; +import com.ecep.contract.manager.ds.customer.service.CompanyCustomerService; +import com.ecep.contract.manager.ds.vendor.controller.CompanyVendorWindowController; +import com.ecep.contract.manager.ds.vendor.model.CompanyVendor; +import com.ecep.contract.manager.ds.vendor.service.CompanyVendorService; +import com.ecep.contract.manager.ui.AbstEntityBasedTabSkin; +import com.ecep.contract.manager.ui.AbstEntityController; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.util.UITools; +import javafx.beans.property.SimpleObjectProperty; +import javafx.event.ActionEvent; +import javafx.scene.control.*; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.Pane; +import javafx.stage.Stage; +import javafx.stage.Window; +import org.hibernate.Hibernate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; + +@Lazy +@Scope("prototype") +@Component +@FxmlPath("/ui/company/company.fxml") +public class CompanyWindowController + extends AbstEntityController { + private static final Logger logger = LoggerFactory.getLogger(CompanyWindowController.class); + + + public static void show(Company company, Window window) { + CompanyViewModel viewModel = new CompanyViewModel(); + if (!Hibernate.isInitialized(company)) { + company = SpringApp.getBean(CompanyService.class).findById(company.getId()); + } + viewModel.update(company); + show(viewModel, window); + } + + /** + * 显示界面 + */ + public static void show(CompanyViewModel viewModel, Window window) { + show(CompanyWindowController.class, viewModel, window); + } + + @Autowired + private CompanyService companyService; + @Autowired + private CompanyFileService companyFileService; + @Autowired + private ContractService contractService; + @Autowired + private CompanyCustomerService companyCustomerService; + @Autowired + private CompanyVendorService companyVendorService; + + public BorderPane root; + public TabPane tabPane; + public Tab baseInfoTab; + public Tab oldNameTab; + public Tab contactTab; + public Tab blackReasonTab; + public Tab customerTab; + public Tab vendorTab; + public Tab bankAccountTab; + public Tab contractTab; + public Tab fileTab; + public Tab invoiceTab; + public Tab purchaseBillVoucherTab; + public Tab otherTab; + /* + + */ + public TextField nameField; + public TextField shortNameField; + public TextField uidField; + public TextField entStatusField; + public DatePicker setupDateField; + public TextField entTypeField; + public TextField industryField; + public TextField districtField; + public CheckBox pathExistField; + public TextField pathField; + public DatePicker createdDateField; + public TextField telephoneField; + public TextField regAddressField; + public TextField addressField; + public TextField registeredCapitalField; + public TextField registeredCapitalCurrencyField; + public TextField legalRepresentativeField; + public DatePicker operationPeriodBeginField; + public DatePicker operationPeriodEndField; + public TextArea memoField; + public Label versionLabel; + public Button companyRenameBtn; + public Button companyPathCreateBtn; + public Button companyPathChangeBtn; + public Button companyPathSameAsNameBtn; + + +// private final CompanyCustomerViewModel companyCustomerViewModel = new CompanyCustomerViewModel(); +// private final CompanyVendorViewModel companyVendorViewModel = new CompanyVendorViewModel(); + + private final SimpleObjectProperty companyCustomerProperty = new SimpleObjectProperty<>(); + private final SimpleObjectProperty companyVendorProperty = new SimpleObjectProperty<>(); + + public Pane customerTab_pane1; + public Button customerTab_openBtn; + public Pane customerTab_pane2; + public Button customerTab_createBtn; + + public Pane vendorTab_pane1; + public Button vendorTab_openBtn; + public Pane vendorTab_pane2; + public Button vendorTab_createBtn; + + + @Override + public void show(Stage stage) { + super.show(stage); + stage.minWidthProperty().set(600); + stage.minHeightProperty().set(450); + getTitle().set("[" + viewModel.getId().get() + "] " + viewModel.getName().getValue() + " 公司详情"); + } + + + @Override + protected Company loadEntity() { + return companyService.findById(viewModel.getId().get()); + } + + @Override + protected Company saveEntity(Company entity) { + return companyService.save(entity); + } + + @Override + protected void registerTabSkins() { + registerTabSkin(baseInfoTab, tab1 -> new CompanyTabSkinBase(this)); + registerTabSkin(oldNameTab, tab1 -> new CompanyTabSkinOldName(this)); + registerTabSkin(contactTab, tab1 -> new CompanyTabSkinContact(this)); + registerTabSkin(blackReasonTab, tab1 -> new CompanyTabSkinBlackReason(this)); + registerTabSkin(bankAccountTab, tab1 -> new CompanyTabSkinBankAccount(this)); + registerTabSkin(contractTab, this::createContractTabSkin); + registerTabSkin(fileTab, this::createFileTabSkin); + registerTabSkin(invoiceTab, tab -> new CompanyTabSkinInvoice(this)); + registerTabSkin(purchaseBillVoucherTab, tab -> new CompanyTabSkinPurchaseBillVoucher(this)); + registerTabSkin(otherTab, tab -> new CompanyTabSkinOther(this)); + + + initializeVendorTab(); + initializeCustomerTab(); + } + + + @Override + protected > K registerTabSkin(Tab tab, Function func) { + K skin = super.registerTabSkin(tab, func); + if (skin instanceof AbstCompanyBasedTabSkin b) { + b.setViewModel(viewModel); + } + return skin; + } + + @Override + public CompanyService getViewModelService() { + return companyService; + } + + + private CompanyTabSkinContract createContractTabSkin(Tab tab) { + CompanyTabSkinContract skin = new CompanyTabSkinContract(this); + skin.setContractService(contractService); + return skin; + } + + private CompanyTabSkinFile createFileTabSkin(Tab tab) { + CompanyTabSkinFile skin = new CompanyTabSkinFile(this); + skin.setCompanyFileService(companyFileService); + return skin; + } + + private void initializeCustomerTab() { + customerTab.setOnSelectionChanged(event -> { + if (logger.isDebugEnabled()) { + logger.debug("customerTab OnSelectionChanged"); + } + if (customerTab.isSelected()) { + onCustomerTabShown(); + } + }); + + + customerTab_pane1.visibleProperty().bind(customerTab_pane2.visibleProperty().not()); + customerTab_pane2.visibleProperty().bind(companyCustomerProperty.isNull()); + customerTab_createBtn.setOnAction(event -> { + // TODO 创建 + }); + + customerTab_openBtn.setOnAction(event -> { + CompanyCustomerWindowController.show(companyCustomerProperty.get(), root.getScene().getWindow()); + }); + } + + private void onCustomerTabShown() { + if (logger.isDebugEnabled()) { + logger.debug("onCustomerTabShown"); + } + getLoadedFuture().thenAcceptAsync(company -> { + companyCustomerProperty.set(companyCustomerService.findByCompany(company)); + }).exceptionally(ex -> { + UITools.showExceptionAndWait(ex.getMessage(), ex); + return null; + }); + } + + private void initializeVendorTab() { + vendorTab.setOnSelectionChanged(event -> { + if (logger.isDebugEnabled()) { + logger.debug("vendorTab OnSelectionChanged"); + } + if (vendorTab.isSelected()) { + onVendorTabShown(); + } + }); + + vendorTab_pane1.visibleProperty().bind(vendorTab_pane2.visibleProperty().not()); + vendorTab_pane2.visibleProperty().bind(companyVendorProperty.isNull()); + vendorTab_createBtn.setOnAction(event -> { + // TODO 创建 + }); + + vendorTab_openBtn.setOnAction(event -> { + CompanyVendorWindowController.show(companyVendorProperty.get(), root.getScene().getWindow()); + }); + } + + private void onVendorTabShown() { + if (logger.isDebugEnabled()) { + logger.debug("onVendorTabShown"); + } + getLoadedFuture().thenAcceptAsync(company -> { + if (logger.isDebugEnabled()) { + logger.debug("onVendorTabShown company {}", company.getName()); + } + companyVendorProperty.set(companyVendorService.findByCompany(company)); + }).exceptionally(ex -> { + UITools.showExceptionAndWait(ex.getMessage(), ex); + return null; + }); + } + + /** + * 合并更新企业信息 + */ + public void onCompanyCompositeUpdateAction(ActionEvent event) { + CompanyCompositeUpdateTasker task = new CompanyCompositeUpdateTasker(); + task.setCompany(getEntity()); + UITools.showTaskDialogAndWait("更新企业信息", task, null); + } + + /** + * 企业合规检查 + */ + public void onCompanyVerifyAction(ActionEvent event) { + Company company = getEntity(); + + CompanyVerifyTasker task = new CompanyVerifyTasker(); + task.setCompanyService(companyService); + task.setCompany(company); + UITools.showTaskDialogAndWait("企业合规性验证", task, null); + } + + public void onCompanyOpenInExplorerAction(ActionEvent event) { + Company company = getEntity(); + String path = company.getPath(); + + CompletableFuture.runAsync(() -> { + if (!StringUtils.hasText(path)) { + ButtonType buttonType = UITools.showConfirmation("目录未设置", "是否创建目录").join(); + if (buttonType == ButtonType.OK) { + if (companyService.makePathAbsent(company)) { + save(company); + } + } else { + setStatus("未设置目录"); + return; + } + } + Desktop.checkAndShowInExplorer(company.getPath(), this::setStatus); + }); + } + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/InvoiceTableCell.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/InvoiceTableCell.java new file mode 100644 index 0000000..9697dca --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/InvoiceTableCell.java @@ -0,0 +1,40 @@ +package com.ecep.contract.manager.ds.company.controller; + +import com.ecep.contract.manager.ds.company.model.Invoice; +import com.ecep.contract.manager.ds.company.service.InvoiceService; +import javafx.scene.control.TableCell; +import org.hibernate.Hibernate; + +import static com.ecep.contract.manager.SpringApp.getBean; + +class InvoiceTableCell extends TableCell { + private InvoiceService invoiceService; + + public InvoiceTableCell() { + } + + public InvoiceTableCell(InvoiceService invoiceService) { + this.invoiceService = invoiceService; + } + + private InvoiceService getInvoiceService() { + if (invoiceService == null) { + invoiceService = getBean(InvoiceService.class); + } + return invoiceService; + } + + @Override + protected void updateItem(Invoice item, boolean empty) { + super.updateItem(item, empty); + if (item == null || empty) { + setText(null); + setGraphic(null); + } else { + if (!Hibernate.isInitialized(item)) { + item = getInvoiceService().findById(item.getId()); + } + setText(item.getCode()); + } + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/bank_account/BankAccountBaseTabSkin.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/bank_account/BankAccountBaseTabSkin.java new file mode 100644 index 0000000..0d8cbbb --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/bank_account/BankAccountBaseTabSkin.java @@ -0,0 +1,83 @@ +package com.ecep.contract.manager.ds.company.controller.bank_account; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.company.CompanyStringConverter; +import com.ecep.contract.manager.ds.company.controller.CompanyWindowController; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyBankAccount; +import com.ecep.contract.manager.ds.company.service.CompanyBankAccountService; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.company.vo.CompanyBankAccountViewModel; +import com.ecep.contract.manager.ds.other.BankStringConverter; +import com.ecep.contract.manager.ds.other.service.BankService; +import com.ecep.contract.manager.ui.AbstEntityBasedTabSkin; +import com.ecep.contract.manager.ui.TabSkin; +import com.ecep.contract.manager.util.UITools; +import javafx.scene.control.Tab; +import javafx.scene.control.TextField; +import lombok.Setter; + +public class BankAccountBaseTabSkin extends AbstEntityBasedTabSkin implements TabSkin { + @Setter + private CompanyService companyService; + @Setter + private BankService bankService; + + public BankAccountBaseTabSkin(BankAccountWindowController controller) { + super(controller); + viewModel = controller.getViewModel(); + } + + @Override + public Tab getTab() { + return controller.baseInfoTab; + } + + @Override + public void initializeTab() { + initializeBaseTabCompanyFieldAutoCompletion(controller.companyField); + initializeBaseTabBankFieldAutoCompletion(controller.bankField); + + controller.openingBankField.textProperty().bindBidirectional(viewModel.getOpeningBank()); + controller.bankAccountField.textProperty().bindBidirectional(viewModel.getAccount()); + + +// controller.descriptionField.textProperty().bindBidirectional(viewModel.getDescription()); + controller.createdField.textProperty().bind(viewModel.getCreated().asString()); + controller.versionLabel.textProperty().bind(viewModel.getVersion().asString()); + + controller.relativeCompanyBtn.disableProperty().bind(viewModel.getCompany().isNull()); + controller.relativeCompanyBtn.setOnAction(event -> { + Company company = viewModel.getCompany().get(); + if (company != null) { + CompanyWindowController.show(company, controller.root.getScene().getWindow()); + } + }); + } + + private void initializeBaseTabCompanyFieldAutoCompletion(TextField textField) { + CompanyStringConverter converter = SpringApp.getBean(CompanyStringConverter.class); + UITools.autoCompletion(textField, viewModel.getCompany(), converter::suggest, converter); + } + + + private void initializeBaseTabBankFieldAutoCompletion(TextField textField) { + BankStringConverter converter = SpringApp.getBean(BankStringConverter.class); + UITools.autoCompletion(textField, viewModel.getBank(), converter::suggest, converter); + } + + + public BankService getBankService() { + if (bankService == null) { + bankService = getBean(BankService.class); + } + return bankService; + } + + public CompanyService getCompanyService() { + if (companyService == null) { + companyService = getBean(CompanyService.class); + } + return companyService; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/bank_account/BankAccountWindowController.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/bank_account/BankAccountWindowController.java new file mode 100644 index 0000000..fac257f --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/bank_account/BankAccountWindowController.java @@ -0,0 +1,61 @@ +package com.ecep.contract.manager.ds.company.controller.bank_account; + +import com.ecep.contract.manager.ds.company.model.CompanyBankAccount; +import com.ecep.contract.manager.ds.company.service.CompanyBankAccountService; +import com.ecep.contract.manager.ds.company.vo.CompanyBankAccountViewModel; +import com.ecep.contract.manager.ui.AbstEntityController; +import com.ecep.contract.manager.ui.FxmlPath; +import javafx.scene.control.*; +import javafx.scene.layout.BorderPane; +import javafx.stage.Window; +import javafx.stage.WindowEvent; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Lazy +@Scope("prototype") +@Component +@FxmlPath("/ui/company/bank-account.fxml") +public class BankAccountWindowController extends AbstEntityController { + public BorderPane root; + public TabPane tabPane; + + public Tab baseInfoTab; + public TextField companyField; + public Button relativeCompanyBtn; + public TextField bankField; + public TextField openingBankField; + public TextField bankAccountField; + public TextField createdField; + public TextArea descriptionField; + public Label versionLabel; + + public Tab entityTab; + + + public static void show(CompanyBankAccountViewModel viewModel, Window window) { + show(BankAccountWindowController.class, viewModel, window); + } + + @Autowired + private CompanyBankAccountService bankAccountService; + + @Override + public void onShown(WindowEvent event) { + super.onShown(event); + getTitle().set("[" + viewModel.getId().get() + "] 银行账户详情"); + } + + @Override + protected void registerTabSkins() { + registerTabSkin(baseInfoTab, tab -> new BankAccountBaseTabSkin(this)); + } + + @Override + public CompanyBankAccountService getViewModelService() { + return bankAccountService; + } + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/contact/CompanyContactWindowController.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/contact/CompanyContactWindowController.java new file mode 100644 index 0000000..594c4a9 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/contact/CompanyContactWindowController.java @@ -0,0 +1,179 @@ +package com.ecep.contract.manager.ds.company.controller.contact; + +import com.ecep.contract.manager.ds.company.model.CompanyContact; +import com.ecep.contract.manager.ds.company.repository.CompanyContactRepository; +import com.ecep.contract.manager.ui.BaseController; +import com.ecep.contract.manager.ds.company.vo.CompanyContactViewModel; +import com.ecep.contract.manager.util.FxmlUtils; +import com.ecep.contract.manager.util.UITools; +import javafx.application.Platform; +import javafx.scene.control.*; +import javafx.scene.layout.BorderPane; +import javafx.stage.Modality; +import javafx.stage.Stage; +import javafx.stage.Window; +import javafx.stage.WindowEvent; +import javafx.util.converter.LocalDateStringConverter; +import lombok.Getter; +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.Optional; +import java.util.concurrent.CompletableFuture; +@Lazy +@Scope("prototype") +@Component +public class CompanyContactWindowController extends BaseController { + private static final Logger logger = LoggerFactory.getLogger(CompanyContactWindowController.class); + /** + * 显示界面 + */ + public static void show(CompanyContactViewModel viewModel, Window window) { + String key = toKey(viewModel); + // 一个合同一个界面 + if (toFront(key)) { + return; + } + FxmlUtils.newLoaderAsyncWithRunLater("/ui/contact.fxml", null, loader -> { + CompanyContactWindowController controller = loader.getController(); + controller.setViewModel(viewModel); + controller.show(loader, window, Modality.NONE, key); + }); + } + + private static String toKey(CompanyContactViewModel viewModel) { + return viewModel.getClass().getName() + "-" + viewModel.getId().get(); + } + + public Tab baseInfoTab; + public BorderPane root; + public TabPane tabPane; + + @Getter + @Setter + private CompanyContactViewModel viewModel; + @Autowired + private CompanyContactRepository companyContactRepository; + + public TextField nameField; + public TextField positionField; + public TextField phoneField; + public DatePicker createdField; + public TextField u8CodeField; + public TextField emailField; + public TextField addressField; + public TextArea descriptionField; + public Label versionLabel; + public Button saveBtn; + + private CompletableFuture companyContactLoadedFuture; + + @Override + public void show(Stage stage) { + super.show(stage); + getTitle().bind(viewModel.getName().map(v -> "[" + viewModel.getId().get() + "] " + viewModel.getName().getValue() + " 曾用名详情")); + } + + @Override + public void onShown(WindowEvent windowEvent) { + super.onShown(windowEvent); + if (logger.isDebugEnabled()) { + logger.debug("onShown"); + } + + initializeBaseTab(); + + companyContactLoadedFuture = CompletableFuture.supplyAsync(() -> { + Optional optional = companyContactRepository.findById(viewModel.getId().get()); + if (optional.isPresent()) { + CompanyContact oldName = optional.get(); + Platform.runLater(() -> { + viewModel.update(oldName); + viewModel.bindListener(); + if (logger.isDebugEnabled()) { + logger.debug("bind ViewModel({}) Listener", viewModel.getName().get()); + } + tabPane.getSelectionModel().getSelectedItem().getOnSelectionChanged().handle(null); + }); + return oldName; + } + return null; + }); + } + + + private void initializeBaseTab() { + baseInfoTab.setOnSelectionChanged(event -> { + if (logger.isDebugEnabled()) { + logger.debug("baseInfoTab OnSelectionChanged"); + } + if (baseInfoTab.isSelected()) { + onBaseTabShown(); + } + }); + saveBtn.disableProperty().bind(viewModel.getChanged().not()); + saveBtn.setOnAction(event -> { + try { + CompanyContact contact = companyContactLoadedFuture.join(); + viewModel.copyTo(contact); + CompanyContact saved = companyContactRepository.save(contact); + viewModel.update(saved); + companyContactLoadedFuture = CompletableFuture.completedFuture(saved); + } catch (Exception e) { + UITools.showExceptionAndWait("保存失败,请检查", e); + } + }); + + nameField.textProperty().bindBidirectional(viewModel.getName()); + positionField.textProperty().bindBidirectional(viewModel.getPosition()); + phoneField.textProperty().bindBidirectional(viewModel.getPhone()); + emailField.textProperty().bindBidirectional(viewModel.getEmail()); + addressField.textProperty().bindBidirectional(viewModel.getAddress()); + LocalDateStringConverter converter = new LocalDateStringConverter(DateTimeFormatter.ISO_LOCAL_DATE, null); + createdField.setConverter(converter); + createdField.valueProperty().bindBidirectional(viewModel.getCreated()); + u8CodeField.textProperty().bind(viewModel.getU8Code()); + descriptionField.textProperty().bindBidirectional(viewModel.getMemo()); + versionLabel.textProperty().bind(viewModel.getVersion().asString()); + } + + /** + * 基础页显示时回调函数 + */ + private void onBaseTabShown() { + if (logger.isDebugEnabled()) { + logger.debug("onBaseTabShown"); + } + companyContactLoadedFuture.thenAcceptAsync(contact -> { + if (logger.isDebugEnabled()) { + logger.debug("onBaseTabShown company contact {}", contact.getName()); + } +// viewModel.update(contact); + }).exceptionally(ex -> { + UITools.showExceptionAndWait(ex.getMessage(), ex); + return null; + }); + } + + public void onHidden(WindowEvent windowEvent) { + if (viewModel != null) { + try { + viewModel.unBindListener(); + if (logger.isDebugEnabled()) { + logger.debug("unbind ViewModel({}) Listener", viewModel.getName().get()); + } + } catch (InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + super.onHidden(windowEvent); + } +} + diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/old_name/CompanyOldNameTabSkinBase.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/old_name/CompanyOldNameTabSkinBase.java new file mode 100644 index 0000000..c57e6fd --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/old_name/CompanyOldNameTabSkinBase.java @@ -0,0 +1,65 @@ +package com.ecep.contract.manager.ds.company.controller.old_name; + +import com.ecep.contract.manager.ds.company.model.CompanyOldName; +import com.ecep.contract.manager.ds.company.service.CompanyOldNameService; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.company.vo.CompanyOldNameViewModel; +import com.ecep.contract.manager.ui.AbstEntityBasedTabSkin; +import com.ecep.contract.manager.ui.TabSkin; +import javafx.scene.control.Tab; +import javafx.util.converter.LocalDateStringConverter; +import lombok.Setter; + +import java.time.format.DateTimeFormatter; + + +public class CompanyOldNameTabSkinBase + extends AbstEntityBasedTabSkin + implements TabSkin { + @Setter + private CompanyService companyService; + @Setter + private CompanyOldNameService companyOldNameService; + + public CompanyOldNameTabSkinBase(CompanyOldNameWindowController controller) { + super(controller); + } + + @Override + public Tab getTab() { + return controller.baseInfoTab; + } + + @Override + public void initializeTab() { + + controller.nameField.textProperty().bind(viewModel.getName()); + controller.ambiguityField.selectedProperty().bindBidirectional(viewModel.getAmbiguity()); + LocalDateStringConverter converter = new LocalDateStringConverter(DateTimeFormatter.ISO_LOCAL_DATE, null); + controller.startDateField.setConverter(converter); + controller.startDateField.valueProperty().bindBidirectional(viewModel.getBeginDate()); + controller.endDateField.setConverter(converter); + controller.endDateField.valueProperty().bindBidirectional(viewModel.getEndDate()); + + controller.pathField.textProperty().bind(viewModel.getPath()); + controller.descriptionField.textProperty().bindBidirectional(viewModel.getMemo()); + controller.versionLabel.textProperty().bind(viewModel.getVersion().asString()); + + controller.saveBtn.disableProperty().bind(viewModel.getChanged().not()); + controller.saveBtn.setOnAction(this::onSaveAction); + } + + public CompanyService getCompanyService() { + if (companyService == null) { + companyService = getBean(CompanyService.class); + } + return companyService; + } + + public CompanyOldNameService getCompanyOldNameService() { + if (companyOldNameService == null) { + companyOldNameService = getBean(CompanyOldNameService.class); + } + return companyOldNameService; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/old_name/CompanyOldNameTabSkinFile.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/old_name/CompanyOldNameTabSkinFile.java new file mode 100644 index 0000000..95cc91c --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/old_name/CompanyOldNameTabSkinFile.java @@ -0,0 +1,363 @@ +package com.ecep.contract.manager.ds.company.controller.old_name; + +import com.ecep.contract.manager.Desktop; +import com.ecep.contract.manager.MyProperties; +import com.ecep.contract.manager.ds.company.CompanyFileType; +import com.ecep.contract.manager.ds.company.model.CompanyFile; +import com.ecep.contract.manager.ds.company.model.CompanyOldName; +import com.ecep.contract.manager.ds.company.service.CompanyFileService; +import com.ecep.contract.manager.ds.company.service.CompanyOldNameService; +import com.ecep.contract.manager.ds.company.vo.CompanyFileViewModel; +import com.ecep.contract.manager.ds.company.vo.CompanyOldNameViewModel; +import com.ecep.contract.manager.ui.AbstEntityTableTabSkin; +import com.ecep.contract.manager.ui.TabSkin; +import com.ecep.contract.manager.util.SpecificationUtils; +import com.ecep.contract.manager.util.UITools; +import javafx.application.Platform; +import javafx.event.ActionEvent; +import javafx.event.Event; +import javafx.scene.control.Tab; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableView; +import lombok.Setter; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.util.List; +import java.util.function.Consumer; + +/** + * + */ +public class CompanyOldNameTabSkinFile + extends AbstEntityTableTabSkin + implements TabSkin { + @Setter + private CompanyOldNameService companyOldNameService; + @Setter + private CompanyFileService companyFileService; + @Setter + private MyProperties myProperties; + + public CompanyOldNameTabSkinFile(CompanyOldNameWindowController controller) { + super(controller); + setDragAndDrop(true); + setDragAndDropFileHandler(this::moveFileToCompany); + } + + @Override + public Tab getTab() { + return controller.fileTab; + } + + @Override + public TableView getTableView() { + return controller.fileTable; + } + + @Override + protected CompanyFileService getViewModelService() { + return companyFileService; + } + + @Override + public Specification getSpecification(CompanyOldName parent) { + return SpecificationUtils.and(getSpecification(), (root, query, builder) -> { + return builder.equal(root.get("company").get("id"), parent.getCompanyId()); + }); + } + + @Override + public void initializeTab() { +// controller.fileTable_file_move_btn.setOnAction(this::onTableMoveFileAction); +// controller.fileTable_file_retrieve_from_download_dir_btn.setOnAction(this::onTableRetrieveFromDownloadDirAction); +// controller.fileTable_file_reset_btn.setOnAction(this::onTableResetAction); +// +// +// controller.fileTable_idColumn.setCellValueFactory(param -> param.getValue().getId()); +// ObservableMap observableMapByLocal = getBean(CompanyFileTypeLocalRepository.class).getObservableMapByLocal(); +// controller.fileTable_typeColumn.setCellValueFactory(param -> Bindings.valueAt(observableMapByLocal, +// param.getValue().getType()).map(CompanyFileTypeLocal::getValue)); +// controller.fileTable_filePathColumn.setCellValueFactory(param -> param.getValue().getFilePath()); +// controller.fileTable_filePathColumn.setCellFactory(param -> new FileTableFilePathTableCell()); +// controller.fileTable_applyDateColumn.setCellValueFactory(param -> param.getValue().getApplyDate()); +// controller.fileTable_expiringDateColumn.setCellValueFactory(param -> param.getValue().getExpiringDate()); +// +// +// controller.fileTable_menu_refresh.setOnAction(this::onTableRefreshAction); +// controller.fileTable_menu_del.setOnAction(this::onTableDeleteAction); +// controller.fileTable_menu_copy_as_matched_by_contract.setOnAction(this::onTableCopyAsMatchedByContractAction); +// controller.fileTable_menu_copy_as_matched_by_contract.setOnMenuValidation(this::onTableCopyAsMatchedMenuValidation); + + + super.initializeTab(); + } + + private void onTableResetAction(ActionEvent event) { + CompanyOldName oldName = getParent(); +// CompletableFuture.runAsync(() -> { +// if (getCompanyFileService().reBuildingFiles(oldName, this::setStatus)) { +// loadTableDataSet(); +// } +// }); + } + + /** + * 从 下载目录 中查找相关的资质文件 + */ + private void onTableRetrieveFromDownloadDirAction(ActionEvent event) { +// CompanyOldName oldName = getParent(); +// MyProperties myProperties = getMyProperties(); +// File dir = myProperties.getDownloadDirectory(); +// if (!dir.exists()) { +// setStatus("下载目录 " + dir.getAbsolutePath() + " 不存在,请检查"); +// return; +// } +// +// setStatus("开始检索 下载 文件夹:" + dir.getAbsolutePath() + "..."); +// File[] files = dir.listFiles(File::isFile); +// if (files == null) { +// setStatus("检索 下载 文件夹失败"); +// return; +// } +// if (files.length == 0) { +// setStatus("下载 文件夹没有文件"); +// return; +// } +// setStatus("下载 文件夹中共有文件 " + files.length + " 个文件"); +// +// if (getCompanyOldNameService().retrieveFromDownloadFiles(oldName, files, this::setStatus)) { +// // fixed if update +// viewModel.update(oldName); +// loadTableDataSet(); +// } + } + + /** + * 把文件从 老系统中移到 \\10.84.209.8\项目信息\相关方信息 目录中 + */ + private void onTableMoveFileAction(ActionEvent event) { +// CompanyFileService companyFileService = getCompanyFileService(); +// CompanyOldName oldName = getParent(); +// List list = companyFileService.findByCompany(oldName); +// if (list.isEmpty()) { +// return; +// } +// if (getCompanyService().makePathAbsent(oldName)) { +// save(oldName); +// } +// +// String path = oldName.getPath(); +// if (!StringUtils.hasText(path)) { +// setStatus("异常, 企业目录未设置"); +// return; +// } +// File companyPath = new File(path); +// for (CompanyFile companyFile : list) { +// String filePath = companyFile.getFilePath(); +// if (StringUtils.hasText(filePath)) { +// File file = new File(filePath); +// if (file.exists()) { +// if (file.getParentFile().equals(companyPath)) { +// continue; +// } +// File dest = new File(companyPath, file.getName()); +// if (file.renameTo(dest)) { +// companyFile.setFilePath(dest.getAbsolutePath()); +// companyFileService.save(companyFile); +// setStatus(file.getName() + " 移动到 " + companyPath.getName()); +// } +// } +// } +// } + } + + /** + * + */ + private void onTableCopyAsMatchedByContractAction(ActionEvent event) { + UITools.showDialogAndWait("复制资信评估报告", "按当前评估报告复制一个合同中最匹配的", list -> { + onTableCopyAsMatchedAction_(msg -> { + Platform.runLater(() -> { + list.add(msg); + }); + }); + }); + } + + private void onTableCopyAsMatchedAction_(Consumer state) { +// CompanyOldName oldName = getParent(); +// +// CompanyFileViewModel selectedItem = table.getSelectionModel().getSelectedItem(); +// if (selectedItem == null) { +// state.accept("未选择行"); +// return; +// } +// if (selectedItem.getApplyDate().get() == null) { +// state.accept("有效日期不能未空"); +// return; +// } +// +// LocalDate nextCreditReportDate = null; +// try { +// nextCreditReportDate = companyFileService.getNextCreditReportDate(oldName, state); +// if (nextCreditReportDate == null) { +// state.accept("没有找到下一个咨询评估日期"); +// return; +// } +// } catch (Exception e) { +// state.accept("获取下一个咨询评估日期失败:" + e.getMessage()); +// return; +// } +// +// state.accept("下一个咨询评估日期:" + nextCreditReportDate); +// +// if (!nextCreditReportDate.isBefore(selectedItem.getApplyDate().get())) { +// state.accept("咨询评估日期晚于下一个咨询评估日期"); +// return; +// } +// +// +// File src = new File(selectedItem.getFilePath().get()); +// if (!src.exists()) { +// state.accept("当前选择行的文件不存在"); +// return; +// } +// +// String srcDate = MyDateTimeUtils.format(selectedItem.getApplyDate().get()); +// String destDate = MyDateTimeUtils.format(nextCreditReportDate); +// String srcFileName = src.getName(); +// String destFileName; +// +// // 天眼查的报告 +// if (CloudTycService.isTycReport(srcFileName)) { +// state.accept("天眼查的报告按标准格式命名"); +// String name = oldName.getName() + "_" + CloudTycService.NAME; +// if (srcFileName.contains(CloudTycService.TYC_ENTERPRISE_BASIC_REPORT)) { +// name = name + "_" + CloudTycService.TYC_ENTERPRISE_BASIC_REPORT; +// } else if (srcFileName.contains(CloudTycService.TYC_ENTERPRISE_MAJOR_REPORT)) { +// name = name + "_" + CloudTycService.TYC_ENTERPRISE_MAJOR_REPORT; +// } else if (srcFileName.contains(CloudTycService.TYC_ENTERPRISE_ANALYSIS_REPORT)) { +// name = name + "_" + CloudTycService.TYC_ENTERPRISE_ANALYSIS_REPORT; +// } +// destFileName = name + "_" + destDate + "_cp." + StringUtils.getFilenameExtension(srcFileName); +// } else { +// if (srcFileName.contains(srcDate)) { +// // 如果文件名中包含日期,则替换为新日期 +// destFileName = srcFileName.replace(srcDate, destDate + "_cp"); +// } else { +// // 如果文件名中不包含日期,则添加日期 +// destFileName = oldName.getName() + "_" + destDate + "_cp." + StringUtils.getFilenameExtension(srcFileName); +// } +// } +// +// state.accept("新文件名:" + destFileName); +// +// File dest = new File(src.getParent(), destFileName); +// try { +// FileSystemUtils.copyRecursively(src, dest); +// state.accept("新文件复制成功"); +// } catch (IOException e) { +// state.accept("新文件复制失败:" + e.getMessage()); +// } +// +// CompanyFile companyFile = new CompanyFile(); +// companyFile.setFilePath(dest.getAbsolutePath()); +// companyFile.setApplyDate(nextCreditReportDate); +// companyFile.setExpiringDate(nextCreditReportDate.plusYears(1)); +// companyFile.setType(CompanyFileType.CreditReport); +// companyFile.setCompany(oldName); +// companyFileService.save(companyFile); +// +// state.accept("新文件已记录"); +// +// loadTableDataSet(); +// state.accept("文件表已刷新"); + } + + /** + * 当fileTable选中的行是咨询评估时,可用 + * + * @param event event + */ + public void onTableCopyAsMatchedMenuValidation(Event event) { + //当fileTable选中的行是咨询评估时,可用 + CompanyFileViewModel selectedItem = getSelectedItem(); + if (selectedItem == null) { + event.consume(); + return; + } + CompanyFileType type = selectedItem.getType().get(); + if (type != CompanyFileType.CreditReport) { + event.consume(); + return; + } + } + + + private void moveFileToCompany(List files) { + String path = viewModel.getPath().get(); + if (!StringUtils.hasText(path)) { + setStatus("未设置目录"); + return; + } + File dir = new File(path); + if (!dir.exists()) { + setStatus("目录错误,不存在"); + return; + } + } + + @Override + protected void onTableRowDoubleClickedAction(CompanyFileViewModel item) { + String path = item.getFilePath().get(); + if (StringUtils.hasText(path)) { + File file = new File(path); + if (!file.exists()) { + setStatus("文件不存在 " + file.getName()); + return; + } + Desktop.showInExplorer(file); + } + } + + class FileTableFilePathTableCell extends TableCell { + @Override + protected void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setText(""); + return; + } + String path = viewModel.getPath().get(); + if (StringUtils.hasText(path)) { + if (item.startsWith(path)) { + item = "~" + item.substring(path.length()); + } + } + setText(item); + } + } + + public MyProperties getMyProperties() { + if (myProperties == null) { + myProperties = getBean(MyProperties.class); + } + return myProperties; + } + + private CompanyFileService getCompanyFileService() { + if (companyFileService == null) { + companyFileService = getBean(CompanyFileService.class); + } + return companyFileService; + } + + public CompanyOldNameService getCompanyOldNameService() { + if (companyOldNameService == null) { + companyOldNameService = getBean(CompanyOldNameService.class); + } + return companyOldNameService; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/controller/old_name/CompanyOldNameWindowController.java b/src/main/java/com/ecep/contract/manager/ds/company/controller/old_name/CompanyOldNameWindowController.java new file mode 100644 index 0000000..fc4a178 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/controller/old_name/CompanyOldNameWindowController.java @@ -0,0 +1,132 @@ +package com.ecep.contract.manager.ds.company.controller.old_name; + +import com.ecep.contract.manager.ds.company.model.CompanyOldName; +import com.ecep.contract.manager.ds.company.service.CompanyOldNameService; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.company.vo.CompanyFileViewModel; +import com.ecep.contract.manager.ds.company.vo.CompanyOldNameViewModel; +import com.ecep.contract.manager.ui.AbstEntityController; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.ViewModelService; +import com.ecep.contract.manager.util.UITools; +import javafx.event.ActionEvent; +import javafx.scene.control.*; +import javafx.scene.layout.BorderPane; +import javafx.stage.Window; +import javafx.stage.WindowEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.util.concurrent.CompletableFuture; + +import static com.ecep.contract.manager.Desktop.showInExplorer; + +@Lazy +@Scope("prototype") +@Component +@FxmlPath("/ui/company/company_old_name.fxml") +public class CompanyOldNameWindowController extends AbstEntityController { + private static final Logger logger = LoggerFactory.getLogger(CompanyOldNameWindowController.class); + + /** + * 显示界面 + */ + public static void show(CompanyOldNameViewModel viewModel, Window window) { + show(CompanyOldNameWindowController.class, viewModel, window); + } + + public Tab baseInfoTab; + public BorderPane root; + public TabPane tabPane; + public Tab fileTab; + + + @Autowired + private CompanyOldNameService companyOldNameService; + @Autowired + private CompanyService companyService; + + public TextField nameField; + public CheckBox ambiguityField; + public DatePicker startDateField; + public DatePicker endDateField; + public TextField pathField; + public TextArea descriptionField; + + public Label versionLabel; + public Button saveBtn; + public Button saveBtn2; + + + public TableView fileTable; + + + @Override + public void onShown(WindowEvent windowEvent) { + super.onShown(windowEvent); + getTitle().set("[" + viewModel.getId().get() + "] " + viewModel.getName().getValue() + " 曾用名详情"); + } + + @Override + protected CompanyOldName loadEntity() { + return companyOldNameService.findById(viewModel.getId().get()); + } + + @Override + protected CompanyOldName saveEntity(CompanyOldName entity) { + return companyOldNameService.save(entity); + } + + @Override + protected void registerTabSkins() { + registerTabSkin(baseInfoTab, this::createBaseTabSkin); + registerTabSkin(fileTab, this::createFileTabSkin); + } + + @Override + public CompanyOldNameService getViewModelService() { + return companyOldNameService; + } + + private CompanyOldNameTabSkinBase createBaseTabSkin(Tab tab) { + CompanyOldNameTabSkinBase skin = new CompanyOldNameTabSkinBase(this); + skin.setCompanyOldNameService(companyOldNameService); + return skin; + } + + private CompanyOldNameTabSkinFile createFileTabSkin(Tab tab) { + CompanyOldNameTabSkinFile skin = new CompanyOldNameTabSkinFile(this); + skin.setCompanyOldNameService(companyOldNameService); +// skin.setCompanyFileService(companyFileService); + return skin; + } + + + public void onOldCompanyOpenInExplorerAction(ActionEvent event) { + CompanyOldName companyOldName = getEntity(); + String path = companyOldName.getPath(); + + CompletableFuture.runAsync(() -> { + if (!StringUtils.hasText(path)) { + ButtonType buttonType = UITools.showConfirmation("目录未设置", "是否创建目录").join(); + if (buttonType == ButtonType.OK) { + if (companyOldNameService.makePathAbsent(companyOldName)) { + save(companyOldName); + } + } else { + setStatus("未设置目录"); + return; + } + } + File file = new File(companyOldName.getPath()); + showInExplorer(file); + }); + } +} + diff --git a/src/main/java/com/ecep/contract/manager/ds/company/model/Company.java b/src/main/java/com/ecep/contract/manager/ds/company/model/Company.java new file mode 100644 index 0000000..954b21a --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/model/Company.java @@ -0,0 +1,194 @@ +package com.ecep.contract.manager.ds.company.model; + + +import com.ecep.contract.manager.ds.other.model.IdentityEntity; +import com.ecep.contract.manager.ds.other.model.NamedEntity; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.proxy.HibernateProxy; + +import java.time.LocalDate; +import java.util.Objects; + +/** + * 公司信息类,保存企业信息 + */ +@Getter +@Setter +@Entity +@Table(name = "COMPANY") +@ToString +public class Company implements IdentityEntity, NamedEntity, com.ecep.contract.manager.ds.other.model.Entity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + /** + * 公司名称 + */ + private String name; + + + /** + * 统一社会信用代码 + *

+ * 编码结构 + *

+     *      1       |     1    |         6        |    9   |  1  
+ * 登记管理部门代码|机构类别代码|登记管理机关行政区划码|主体标识码|校验码 + *
+ *
    + *
  • 登记管理部门代码(第 1 位):
    + * 例如,9 代表工商部门登记的企业等。
  • + *
  • 机构类别代码(第 2 位):不同的数字表示不同类型的机构。如 1 表示企业,2 表示个体工商户等。
  • + *
  • 登记管理机关行政区划码(第 3 - 8 位):这 6 位数字代表了登记管理机关所在的行政区划,与身份证号码的前 6 位类似,可对应到具体的地区。
  • + *
  • 主体标识码(组织机构代码,第 9 - 17 位):它是对中华人民共和国境内依法注册、依法登记的机关、企事业单位、社会团体和民办非企业单位等机构颁发的一个在全国范围内唯一的、始终不变的代码。
  • + *
+ *

+ */ + @Column(name = "UNISCID", length = 30) + private String uniscid; + /** + * 简称 + */ + @Column(name = "SHORT_NAME") + private String shortName; + /** + * 文件夹存在 + */ + @Column(name = "PATH_EXIST") + private Boolean pathExist; + /** + * 文件夹路径 + */ + @Column(name = "PATH") + private String path; + /** + * 记录创建日期 + */ + @Column(name = "CREATED") + private LocalDate created; + /** + * 企业状态 + */ + @Column(name = "ENT_STATUS") + private String entStatus; + /** + * 企业类型 + */ + @Column(name = "ENT_TYPE") + private String entType; + + /** + * 区域 + */ + @Column(name = "DISTRICT") + private String district; + + /** + * 行业 + */ + @Column(name = "INDUSTRY") + private String industry; + + /** + * 电话 + */ + @Column(name = "TELEPHONE") + private String telephone; + + /** + * 注册地址 + */ + + @Column(name = "REG_ADDR") + private String regAddr; + + /** + * 通讯地址 + */ + @ToString.Exclude + @Column(name = "ADDRESS") + private String address; + + /** + * 成立日期 + */ + @Column(name = "SETUP_DATE") + private LocalDate setupDate; + + /** + * 营业期限起始 + */ + @ToString.Exclude + @Column(name = "OPERATION_PERIOD_BEGIN") + private LocalDate operationPeriodBegin; + + /** + * 营业期限截至 + */ + @ToString.Exclude + @Column(name = "OPERATION_PERIOD_END") + private LocalDate operationPeriodEnd; + + /** + * 注册资金 + */ + @ToString.Exclude + @Column(name = "REGISTERED_CAPITAL") + private String registeredCapital; + + /** + * 资本金币种 + */ + @ToString.Exclude + @Column(name = "REGISTERED_CAPITAL_CURRENCY") + private String registeredCapitalCurrency; + + /** + * 法人 + */ + @ToString.Exclude + @Column(name = "LEGAL_REPRESENTATIVE") + private String legalRepresentative; + + /** + * 备注 + */ + @ToString.Exclude + @Column(name = "MEMO") + private String memo; + + @Version + @ColumnDefault("0") + @Column(name = "VERSION", nullable = false) + @ToString.Exclude + private int version; + + @Override + public String toPrettyString() { + return getName(); + } + + @Override + public final boolean equals(Object object) { + if (this == object) return true; + if (object == null) return false; + Class oEffectiveClass = object instanceof HibernateProxy ? ((HibernateProxy) object).getHibernateLazyInitializer().getPersistentClass() : object.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + if (thisEffectiveClass != oEffectiveClass) return false; + Company company = (Company) object; + return getId() != null && Objects.equals(getId(), company.getId()); + } + + @Override + public final int hashCode() { + return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); + } + + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyBankAccount.java b/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyBankAccount.java new file mode 100644 index 0000000..4c7ca89 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyBankAccount.java @@ -0,0 +1,51 @@ +package com.ecep.contract.manager.ds.company.model; + +import com.ecep.contract.manager.ds.other.model.Bank; +import com.ecep.contract.manager.ds.other.model.Entity; +import com.ecep.contract.manager.ds.other.model.IdentityEntity; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@jakarta.persistence.Entity +@Table(name = "COMPANY_BANK_ACCOUNT", schema = "supplier_ms") +public class CompanyBankAccount implements IdentityEntity, Entity, CompanyBasedEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID", nullable = false) + private Integer id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "COMPANY_ID") + @ToString.Exclude + private Company company; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "BANK_ID") + @ToString.Exclude + private Bank bank; + + /** + * 开户行 + */ + @Column(name = "OPENING_BANK") + private String openingBank; + + /** + * 账号 + */ + @Column(name = "ACCOUNT") + private String account; + + @Column(name = "DESCRIPTION") + private String description; + + + @Override + public String toPrettyString() { + return account; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyBasedEntity.java b/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyBasedEntity.java new file mode 100644 index 0000000..8176d21 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyBasedEntity.java @@ -0,0 +1,8 @@ +package com.ecep.contract.manager.ds.company.model; + +public interface CompanyBasedEntity { + + Company getCompany(); + + void setCompany(Company company); +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyBlackReason.java b/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyBlackReason.java new file mode 100644 index 0000000..5afcc1b --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyBlackReason.java @@ -0,0 +1,61 @@ +package com.ecep.contract.manager.ds.company.model; + +import com.ecep.contract.manager.ds.company.BlackReasonType; +import com.ecep.contract.manager.ds.other.model.IdentityEntity; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; + +import java.time.LocalDate; + +@Getter +@Setter +@Entity +@Table(name = "COMPANY_BLACK_REASON", schema = "supplier_ms", indexes = { + @Index(name = "COMPANY_ID", columnList = "COMPANY_ID") +}) +// @org.springframework.data.relational.core.mapping.Table("COMPANY_BLACK_REASON") +@JsonIgnoreProperties(ignoreUnknown = true) +public class CompanyBlackReason implements IdentityEntity, CompanyBasedEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID", nullable = false) + private Integer id; + + @ManyToOne(fetch = FetchType.LAZY) + @OnDelete(action = OnDeleteAction.CASCADE) + @JoinColumn(name = "COMPANY_ID") + private Company company; + + @Column(name = "TYPE") + private BlackReasonType type; + + @Column(name = "APPLY_NAME") + private String applyName; + + @Column(name = "APPLY_DATE") + private LocalDate applyDate; + + @Column(name = "UPDATE_TIME") + private LocalDate updateTime; + + @Column(name = "CREATE_TIME") + private LocalDate createTime; + + @Column(name = "INCLUDE_DATE") + private LocalDate includeDate; + + @Column(name = "BLACK_REASON") + private String blackReason; + + @Lob + @Column(name = "DESCRIPTION", columnDefinition = "TEXT") + private String description; + + @Column(name = "`KEY`") + private String key; + +} \ No newline at end of file diff --git a/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyContact.java b/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyContact.java new file mode 100644 index 0000000..e0ddee9 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyContact.java @@ -0,0 +1,91 @@ +package com.ecep.contract.manager.ds.company.model; + +import com.ecep.contract.manager.ds.other.model.IdentityEntity; +import com.ecep.contract.manager.ds.other.model.NamedEntity; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.proxy.HibernateProxy; + +import java.time.LocalDate; +import java.util.Objects; + +@Getter +@Setter +@Entity +@Table(name = "COMPANY_CONTACT", schema = "supplier_ms", indexes = { + @Index(name = "CUSTOMER_ID", columnList = "COMPANY_ID") +}) +@ToString +public class CompanyContact implements IdentityEntity, NamedEntity, com.ecep.contract.manager.ds.other.model.Entity, CompanyBasedEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID", nullable = false) + private Integer id; + + @Column(name = "NAME") + private String name; + + + @Column(name = "PHONE") + private String phone; + + @Column(name = "EMAIL") + private String email; + + /** + * 地址 + */ + @Column(name = "ADDRESS") + private String address; + + /** + * 职位 + */ + @Column(name = "POSITION") + private String position; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "COMPANY_ID") + @ToString.Exclude + private Company company; + + @Column(name = "U8_CODE") + private String u8Code; + + @Column(name = "MEMO") + private String memo; + + @Column(name = "CREATED") + private LocalDate created; + + @Version + @ColumnDefault("0") + @Column(name = "VERSION", nullable = false) + private int version; + + @Override + public String toPrettyString() { + return getName(); + } + + @Override + public final boolean equals(Object object) { + if (this == object) return true; + if (object == null) return false; + Class oEffectiveClass = object instanceof HibernateProxy ? ((HibernateProxy) object).getHibernateLazyInitializer().getPersistentClass() : object.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + if (thisEffectiveClass != oEffectiveClass) return false; + CompanyContact that = (CompanyContact) object; + return getId() != null && Objects.equals(getId(), that.getId()); + } + + @Override + public final int hashCode() { + return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); + } + + +} \ No newline at end of file diff --git a/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyContract.java b/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyContract.java new file mode 100644 index 0000000..2d2d85c --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyContract.java @@ -0,0 +1,48 @@ +package com.ecep.contract.manager.ds.company.model; + +import com.ecep.contract.manager.cloud.old.CompanyContactUtils; +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.other.model.IdentityEntity; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.util.Map; + +@Getter +@Setter +@Entity +@Table(name = "COMPANY_CONTRACT", schema = "supplier_ms", indexes = { + @Index(name = "RK_CONTRACT_ID", columnList = "CONTRACT_ID"), + @Index(name = "RK_COMPANY_ID", columnList = "COMPANY_ID") +}, uniqueConstraints = { + +}) +@ToString(exclude = {"company", "contract"}) +public class CompanyContract implements IdentityEntity, CompanyBasedEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID", nullable = false) + private Integer id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "COMPANY_ID") + private Company company; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "CONTRACT_ID") + private Contract contract; + + /** + * 要和{@link CompanyContactUtils#getContactKey(Map)}一致 + */ + public static String getContactKey(CompanyContact contact) { + if (contact == null) { + return null; + } + return contact.getName() + + contact.getPhone() + + contact.getEmail(); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyExtendInfo.java b/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyExtendInfo.java new file mode 100644 index 0000000..60e8c8d --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyExtendInfo.java @@ -0,0 +1,56 @@ +package com.ecep.contract.manager.ds.company.model; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.proxy.HibernateProxy; + +import java.util.Objects; + +@Getter +@Setter +@Entity +@Table(name = "COMPANY_EXTEND_INFO", schema = "supplier_ms") +@ToString +public class CompanyExtendInfo { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID", nullable = false) + private Integer id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "COMPANY_ID") + @ToString.Exclude + private Company company; + + /** + * 是否禁用核验 + */ + @Column(name = "DISABLE_VERIFY") + private boolean disableVerify; + + @Version + @ColumnDefault("0") + @Column(name = "VERSION", nullable = false) + @ToString.Exclude + private int version; + + + @Override + public final boolean equals(Object object) { + if (this == object) return true; + if (object == null) return false; + Class oEffectiveClass = object instanceof HibernateProxy ? ((HibernateProxy) object).getHibernateLazyInitializer().getPersistentClass() : object.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + if (thisEffectiveClass != oEffectiveClass) return false; + CompanyExtendInfo that = (CompanyExtendInfo) object; + return getId() != null && Objects.equals(getId(), that.getId()); + } + + @Override + public final int hashCode() { + return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyFile.java b/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyFile.java new file mode 100644 index 0000000..7583569 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyFile.java @@ -0,0 +1,52 @@ +package com.ecep.contract.manager.ds.company.model; + +import com.ecep.contract.manager.ds.company.CompanyFileType; +import com.ecep.contract.manager.ds.other.model.IdentityEntity; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +import java.time.LocalDate; + +/** + * 公司文件 + */ +@Getter +@Setter +@Entity +@Table(name = "COMPANY_FILE") +@ToString +public class CompanyFile implements IdentityEntity, CompanyBasedEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID", nullable = false) + private Integer id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "COMPANY_ID") + @ToString.Exclude + private Company company; + + @Column(name = "TYPE") + @Enumerated(EnumType.STRING) + private CompanyFileType type; + + /** + * 生效日期(签署) + */ + @Column(name = "APPLY_DATE") + private LocalDate applyDate; + /** + * 有效期(签署) + */ + @Column(name = "EXP_DATE") + private LocalDate expiringDate; + + /** + * 资质文件路径,全路径,网盘地址 + */ + @Column(name = "FILE_PATH") + private String filePath; +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyFileTypeLocal.java b/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyFileTypeLocal.java new file mode 100644 index 0000000..80436b2 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyFileTypeLocal.java @@ -0,0 +1,17 @@ +package com.ecep.contract.manager.ds.company.model; + +import com.ecep.contract.manager.ds.company.CompanyFileType; +import com.ecep.contract.manager.ds.other.model.BaseEnumEntity; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@Entity +@Table(name = "COMPANY_FILE_TYPE_LOCAL") +@ToString +public class CompanyFileTypeLocal extends BaseEnumEntity { + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyInvoiceInfo.java b/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyInvoiceInfo.java new file mode 100644 index 0000000..7ebfc1f --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyInvoiceInfo.java @@ -0,0 +1,71 @@ +package com.ecep.contract.manager.ds.company.model; + +import com.ecep.contract.manager.ds.other.model.IdentityEntity; +import com.ecep.contract.manager.ds.other.model.NamedEntity; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.proxy.HibernateProxy; + +import java.util.Objects; + +/** + * 公司发票信息 + */ +@Getter +@Setter +@Entity +@Table(name = "COMPANY_INVOICE_INFO", schema = "supplier_ms") +@ToString +public class CompanyInvoiceInfo implements IdentityEntity, NamedEntity, com.ecep.contract.manager.ds.other.model.Entity, CompanyBasedEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID", nullable = false) + private Integer id; + + @Column(name = "NAME") + private String name; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "COMPANY_ID") + @ToString.Exclude + private Company company; + + @Column(name = "TAX") + private String taxId; // 税号(纳税人识别号) + + @Column(name = "ADDR") + private String address; // 地址 + + @Column(name = "TEL") + private String phone; // 电话 + + @Column(name = "BANK") + private String bankName; // 开户行 + + @Column(name = "BANK_ACCOUNT") + private String bankAccount; // 银行账号 + + @Override + public String toPrettyString() { + return getName() + getTaxId(); + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + if (thisEffectiveClass != oEffectiveClass) return false; + CompanyInvoiceInfo that = (CompanyInvoiceInfo) o; + return getId() != null && Objects.equals(getId(), that.getId()); + } + + @Override + public final int hashCode() { + return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyOldName.java b/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyOldName.java new file mode 100644 index 0000000..6af01e2 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/model/CompanyOldName.java @@ -0,0 +1,72 @@ +package com.ecep.contract.manager.ds.company.model; + +import com.ecep.contract.manager.ds.other.model.IdentityEntity; +import com.ecep.contract.manager.ds.other.model.NamedEntity; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.proxy.HibernateProxy; + +import java.time.LocalDate; +import java.util.Objects; + +@Getter +@Setter +@Entity +@Table(name = "COMPANY_OLDNAME") +@ToString +public class CompanyOldName implements IdentityEntity, NamedEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID", nullable = false) + private Integer id; + + @Column(name = "COMPANY_ID", nullable = false) + private Integer companyId; + + @Column(name = "NAME") + private String name; + + @Column(name = "BEGIN_DATE") + private LocalDate beginDate; + + @Column(name = "END_DATE") + private LocalDate endDate; + + /** + * 是否模糊的,当已录入的系统的名称有错误时,使用别名处理 + */ + @Column(name = "AMBIGUITY") + private Boolean ambiguity; + + @Column(name = "PATH") + private String path; + + @Column(name = "MEMO") + @ToString.Exclude + private String memo; + + @Version + @ColumnDefault("0") + @Column(name = "VERSION", nullable = false) + @ToString.Exclude + private int version; + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + if (thisEffectiveClass != oEffectiveClass) return false; + CompanyOldName that = (CompanyOldName) o; + return getId() != null && Objects.equals(getId(), that.getId()); + } + + @Override + public final int hashCode() { + return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); + } +} \ No newline at end of file diff --git a/src/main/java/com/ecep/contract/manager/ds/company/model/Invoice.java b/src/main/java/com/ecep/contract/manager/ds/company/model/Invoice.java new file mode 100644 index 0000000..9604ab6 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/model/Invoice.java @@ -0,0 +1,78 @@ +package com.ecep.contract.manager.ds.company.model; + +import com.ecep.contract.manager.ds.other.model.IdentityEntity; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.hibernate.proxy.HibernateProxy; +import org.springframework.util.StringUtils; + +import java.time.LocalDate; +import java.util.Objects; + +/** + * 发票 + */ +@Getter +@Setter +@Entity +@Table(name = "INVOICE", schema = "supplier_ms") +@ToString +public class Invoice implements IdentityEntity, com.ecep.contract.manager.ds.other.model.Entity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "ID", nullable = false) + private Integer id; + + /** + * 发票号 + */ + @Column(name = "CODE") + private String code; + + /** + * 发票所属公司 + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "COMPANY_ID") + @ToString.Exclude + private Company company; + + /** + * 发票开票日期 + */ + @Column(name = "INVOICE_DATE") + private LocalDate invoiceDate; + + /** + * 备注 + */ + @Column(name = "DESCRIPTION", columnDefinition = "TEXT") + private String description; + + @Override + public String toPrettyString() { + if (StringUtils.hasText(getCode())) { + return getCode(); + } + return "#" + getId(); + } + + @Override + public final boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + Class oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass(); + Class thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass(); + if (thisEffectiveClass != oEffectiveClass) return false; + Invoice invoice = (Invoice) o; + return getId() != null && Objects.equals(getId(), invoice.getId()); + } + + @Override + public final int hashCode() { + return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode(); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/model/update-schema.sql b/src/main/java/com/ecep/contract/manager/ds/company/model/update-schema.sql new file mode 100644 index 0000000..ee780d7 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/model/update-schema.sql @@ -0,0 +1,11 @@ +CREATE TABLE supplier_ms.COMPANY_BANK_ACCOUNT +( + ID INT AUTO_INCREMENT NOT NULL, + BANK_ID INT NULL, + OPENING_BANK VARCHAR(255) NULL, + ACCOUNT VARCHAR(255) NULL, + CONSTRAINT pk_company_bank_account PRIMARY KEY (ID) +); + +ALTER TABLE supplier_ms.COMPANY_BANK_ACCOUNT + ADD CONSTRAINT FK_COMPANY_BANK_ACCOUNT_ON_BANK FOREIGN KEY (BANK_ID) REFERENCES supplier_ms.BANK (ID); \ No newline at end of file diff --git a/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyBankAccountRepository.java b/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyBankAccountRepository.java new file mode 100644 index 0000000..ad4099f --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyBankAccountRepository.java @@ -0,0 +1,14 @@ +package com.ecep.contract.manager.ds.company.repository; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyBankAccount; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +import java.util.Optional; + +public interface CompanyBankAccountRepository + extends JpaRepository, JpaSpecificationExecutor { + + Optional findByCompanyAndAccount(Company company, String account); +} \ No newline at end of file diff --git a/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyBlackReasonRepository.java b/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyBlackReasonRepository.java new file mode 100644 index 0000000..51cc5c0 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyBlackReasonRepository.java @@ -0,0 +1,19 @@ +package com.ecep.contract.manager.ds.company.repository; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyBlackReason; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface CompanyBlackReasonRepository + // curd + extends CrudRepository, PagingAndSortingRepository, JpaSpecificationExecutor { + + List findAllByCompany(Company company); + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyContactRepository.java b/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyContactRepository.java new file mode 100644 index 0000000..94443d7 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyContactRepository.java @@ -0,0 +1,28 @@ +package com.ecep.contract.manager.ds.company.repository; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyContact; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface CompanyContactRepository extends + CrudRepository, + PagingAndSortingRepository, + JpaRepository, + JpaSpecificationExecutor { + + Optional findFirstByCompany(Company company); + + List findAllByCompany(Company company); + + List findAllByCompanyAndName(Company company, String name); + + int deleteAllByCompany(Company company); +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyContractRepository.java b/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyContractRepository.java new file mode 100644 index 0000000..52e2d2d --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyContractRepository.java @@ -0,0 +1,44 @@ +package com.ecep.contract.manager.ds.company.repository; + +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyContract; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +@Repository +public interface CompanyContractRepository extends + // JDBC interfaces + CrudRepository, PagingAndSortingRepository, + // JPA interfaces + JpaRepository, JpaSpecificationExecutor { + + List findByCompanyId(int companyId); + + List findByCompany(Company company); + + Stream findStreamByCompany(Company company); + + Optional findByContractId(int contractId); + + Optional findByContract(Contract contract); + + Optional findByCompanyAndContract(Company company, Contract contract); + + List findAllByCompany(Company company); + + /** + * Delete all company contracts by company + * + * @param company Company + * @return 删除的记录行数 + */ + int deleteAllByCompany(Company company); +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyExtendInfoRepository.java b/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyExtendInfoRepository.java new file mode 100644 index 0000000..531b348 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyExtendInfoRepository.java @@ -0,0 +1,19 @@ +package com.ecep.contract.manager.ds.company.repository; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyExtendInfo; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; + +import java.util.List; + +public interface CompanyExtendInfoRepository extends + // JDBC interfaces + CrudRepository, PagingAndSortingRepository, + // JPA + JpaRepository, JpaSpecificationExecutor { + + List findByCompany(Company company); +} \ No newline at end of file diff --git a/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyFileRepository.java b/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyFileRepository.java new file mode 100644 index 0000000..7488ab3 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyFileRepository.java @@ -0,0 +1,17 @@ +package com.ecep.contract.manager.ds.company.repository; + +import com.ecep.contract.manager.ds.MyRepository; +import com.ecep.contract.manager.ds.company.CompanyFileType; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyFile; + +import java.util.List; + +public interface CompanyFileRepository extends + // JDBC interfaces + MyRepository { + + List findByCompany(Company company); + + List findByCompanyAndType(Company company, CompanyFileType type); +} \ No newline at end of file diff --git a/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyFileTypeLocalRepository.java b/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyFileTypeLocalRepository.java new file mode 100644 index 0000000..332a356 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyFileTypeLocalRepository.java @@ -0,0 +1,36 @@ +package com.ecep.contract.manager.ds.company.repository; + +import com.ecep.contract.manager.ds.company.CompanyFileType; +import com.ecep.contract.manager.ds.company.model.CompanyFileTypeLocal; +import com.ecep.contract.manager.ds.contract.ContractFileType; +import com.ecep.contract.manager.ds.contract.model.ContractFileTypeLocal; +import com.ecep.contract.manager.ds.other.repository.BaseEnumEntityRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CompanyFileTypeLocalRepository extends BaseEnumEntityRepository { + + @Override + default CompanyFileType[] getEnumConstants() { + return CompanyFileType.values(); + } + + @Override + default CompanyFileTypeLocal newEntity() { + return new CompanyFileTypeLocal(); + } + + CompanyFileTypeLocal findByTypeAndLang(CompanyFileType type, String lang); + + default CompanyFileTypeLocal getCompleteByTypeAndLang(CompanyFileType type, String lang) { + CompanyFileTypeLocal v = findByTypeAndLang(type, lang); + if (v == null) { + v = newEntity(); + v.setType(type); + v.setLang(lang); + v.setValue(type.name()); + v = save(v); + } + return v; + } +} \ No newline at end of file diff --git a/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyInvoiceInfoRepository.java b/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyInvoiceInfoRepository.java new file mode 100644 index 0000000..e9bebaa --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyInvoiceInfoRepository.java @@ -0,0 +1,21 @@ +package com.ecep.contract.manager.ds.company.repository; + +import com.ecep.contract.manager.ds.company.model.CompanyInvoiceInfo; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; + +/** + * 公司发票信息 Repository + */ +@Repository +public interface CompanyInvoiceInfoRepository extends + // JDBC interfaces + CrudRepository, PagingAndSortingRepository, + // JPA interfaces + JpaRepository, JpaSpecificationExecutor { + + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyOldNameRepository.java b/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyOldNameRepository.java new file mode 100644 index 0000000..4674fe7 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyOldNameRepository.java @@ -0,0 +1,37 @@ +package com.ecep.contract.manager.ds.company.repository; + +import com.ecep.contract.manager.ds.company.model.CompanyOldName; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Repository +public interface CompanyOldNameRepository extends + // JDBC interfaces + CrudRepository, PagingAndSortingRepository, + // JPA interfaces + JpaRepository, JpaSpecificationExecutor { + + + List findAllByCompanyId(Integer companyId); + + List findAllByCompanyIdAndName(Integer companyId, String name); + + List findAllByName(String name); + + List findByNameLike(String searchText); + + List findByMemoLike(String searchText); + + + @Modifying + @Transactional + int deleteAllByCompanyId(Integer companyId); + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyRepository.java b/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyRepository.java new file mode 100644 index 0000000..00f2249 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/repository/CompanyRepository.java @@ -0,0 +1,35 @@ +package com.ecep.contract.manager.ds.company.repository; + +import com.ecep.contract.manager.ds.company.model.Company; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +@Repository +public interface CompanyRepository extends + // JDBC interfaces + CrudRepository, PagingAndSortingRepository, + // JPA interfaces + JpaRepository, JpaSpecificationExecutor { + + List findAllByName(String name); + + Optional findFirstByName(String name); + + List findAllByShortName(String shortName); + + /** + * @param uniscid 统一社会信用代码 + */ + List findAllByUniscid(String uniscid); + + @Query("select u from Company u") + Stream findAllAsStream(); +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/repository/InvoiceRepository.java b/src/main/java/com/ecep/contract/manager/ds/company/repository/InvoiceRepository.java new file mode 100644 index 0000000..39c20a7 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/repository/InvoiceRepository.java @@ -0,0 +1,21 @@ +package com.ecep.contract.manager.ds.company.repository; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.Invoice; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; + +import java.util.List; + +public interface InvoiceRepository extends + // JDBC interfaces + CrudRepository, PagingAndSortingRepository, + // JPA + JpaRepository, JpaSpecificationExecutor { + + List findByCompany(Company company); + + Invoice findByCode(String invoiceNumber); +} \ No newline at end of file diff --git a/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyBankAccountService.java b/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyBankAccountService.java new file mode 100644 index 0000000..65263d2 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyBankAccountService.java @@ -0,0 +1,121 @@ +package com.ecep.contract.manager.ds.company.service; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyBankAccount; +import com.ecep.contract.manager.ds.company.repository.CompanyBankAccountRepository; +import com.ecep.contract.manager.ds.company.vo.CompanyBankAccountViewModel; +import com.ecep.contract.manager.ui.MessageHolder; +import com.ecep.contract.manager.ui.ViewModelService; +import com.ecep.contract.manager.util.SpecificationUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.context.annotation.Lazy; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.List; +import java.util.Objects; + +@Lazy +@Service +@CacheConfig(cacheNames = "company-bank-account") +public class CompanyBankAccountService implements ViewModelService { + private static final Logger logger = LoggerFactory.getLogger(CompanyBankAccountService.class); + @Lazy + @Autowired + private CompanyBankAccountRepository repository; + + + public CompanyBankAccount findByAccount(Company company, String account) { + return repository.findByCompanyAndAccount(company, account).orElse(null); + } + + public void updateBankAccount(Company company, String bank, String bankAccount, MessageHolder holder) { + boolean modified = false; + if (!StringUtils.hasText(bankAccount)) { + // 空账户不用存储 + return; + } + + CompanyBankAccount account = findByAccount(company, bankAccount); + if (account == null) { + account = new CompanyBankAccount(); + account.setCompany(company); + account.setAccount(bankAccount); + holder.info("新增银行账户" + bankAccount); + modified = true; + } + + if (StringUtils.hasText(bank)) { + // 更新开户行 + if (!Objects.equals(account.getOpeningBank(), bank)) { + account.setOpeningBank(bank); + holder.info("更新开户行" + bank); + modified = true; + } + } + + if (modified) { + save(account); + } + } + + public CompanyBankAccount save(CompanyBankAccount account) { + return repository.save(account); + } + + public List findAll(Specification spec, Sort sort) { + return repository.findAll(spec, sort); + } + + @Override + public CompanyBankAccount findById(Integer id) { + return repository.findById(id).orElse(null); + } + + + @Override + public Specification getSpecification(String searchText) { + if (!StringUtils.hasText(searchText)) { + return null; + } + return (root, query, builder) -> { + return builder.or( + builder.and( + builder.isNotNull(root.get("bank")), + builder.or( + builder.like(root.get("bank").get("name"), "%" + searchText + "%"), + builder.like(root.get("bank").get("code"), "%" + searchText + "%") + ) + ), + builder.like(root.get("openingBank"), "%" + searchText + "%"), + builder.like(root.get("account"), "%" + searchText + "%") + ); + }; + } + + @Override + public Page findAll(Specification spec, Pageable pageable) { + return repository.findAll(spec, pageable); + } + + public void delete(CompanyBankAccount entity) { + repository.delete(entity); + } + + public List searchByCompany(Company company, String searchText) { + Specification spec = getSpecification(searchText); + if (company != null) { + spec = SpecificationUtils.and(spec, (root, query, builder) -> { + return builder.equal(root.get("company"), company); + }); + } + return repository.findAll(spec, Pageable.ofSize(10)).getContent(); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyBasicService.java b/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyBasicService.java new file mode 100644 index 0000000..f9ac53f --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyBasicService.java @@ -0,0 +1,482 @@ +package com.ecep.contract.manager.ds.company.service; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.customer.CompanyCustomerFileType; +import com.ecep.contract.manager.ds.customer.model.CompanyCustomerFile; +import com.ecep.contract.manager.ds.other.model.CompanyBasicFile; +import com.ecep.contract.manager.ds.other.model.HolidayTable; +import com.ecep.contract.manager.ds.vendor.CompanyVendorFileType; +import com.ecep.contract.manager.ds.other.repository.HolidayTableRepository; +import com.ecep.contract.manager.ds.vendor.model.CompanyVendorFile; +import com.ecep.contract.manager.ds.company.CompanyFileUtils; +import org.hibernate.Hibernate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; + +public abstract class CompanyBasicService { + private static final Logger logger = LoggerFactory.getLogger(CompanyBasicService.class); + + public static String formatCompanyVendorId(int itemId) { + if (itemId > 99) { + return String.valueOf(itemId); + } else if (itemId > 9) { + return "0" + itemId; + } else if (itemId > 0) { + return "00" + itemId; + } else { + throw new IllegalArgumentException(); + } + } + + /** + * 调整日期到工作日 + * + * @param date 要调整的日期 + * @return 调整的日期 + */ + public static LocalDate adjustToWorkDay(LocalDate date) { + if (date.getDayOfWeek() == DayOfWeek.SATURDAY) { + // 再减去1天,到周五 + date = date.plusDays(-1); + } else if (date.getDayOfWeek() == DayOfWeek.SUNDAY) { + // 再减去2天,到周五 + date = date.plusDays(-2); + } + + HolidayTableRepository holidayTableRepository = SpringApp.getBean(HolidayTableRepository.class); + //TODO 跳过节假日 + int tryDays = 15; + while (tryDays-- > 0) { + HolidayTable holidayTable = holidayTableRepository.findById(date).orElse(null); + if (holidayTable == null) { + // 没有节假日定义,检查是否是工作日 + DayOfWeek dayOfWeek = date.getDayOfWeek(); + if (dayOfWeek == DayOfWeek.SATURDAY) { + date = date.plusDays(-1); + continue; + } + if (dayOfWeek == DayOfWeek.SUNDAY) { + date = date.plusDays(-2); + continue; + } + break; + } + if (!holidayTable.isHoliday()) { + // 不是节假日 + break; + } + date = date.plusDays(-1); + } + + return date; + } + + @Lazy + @Autowired + protected CompanyService companyService; + + + protected boolean isEditableFile(String fileName) { + return CompanyFileUtils.withExtensions(fileName, + CompanyFileUtils.XLS, CompanyFileUtils.XLSX, + CompanyFileUtils.DOC, CompanyFileUtils.DOCX); + } + + protected boolean isArchiveFile(String fileName) { + return CompanyFileUtils.withExtensions(fileName, + CompanyFileUtils.PNG, CompanyFileUtils.PDF, + CompanyFileUtils.JPG, CompanyFileUtils.JPEG); + } + + public abstract , ID> void deleteFile(F file); + + protected , ID> boolean fetchDbFiles(List dbFiles, Map map, Consumer status) { + boolean modified = false; + List editFiles = new ArrayList<>(); + // 排除掉数据库中重复的 + for (F dbFile : dbFiles) { + String filePath = dbFile.getFilePath(); + // 没有文件信息,无效记录,删除 + if (!StringUtils.hasText(filePath)) { + deleteFile(dbFile); + continue; + } + + // 文件不存在或者隐藏文件,删除记录 + File file = new File(filePath); + if (!file.exists() || CompanyFileUtils.isHiddenFile(file)) { + deleteFile(dbFile); + continue; + } + + // old 是冲突的,按dbFiles顺序,旧的(list前面的) + F old = map.put(filePath, dbFile); + // 目录有重复删除 + if (old != null) { + deleteFile(old); + } + + String editFilePath = dbFile.getEditFilePath(); + if (!Objects.equals(filePath, editFilePath)) { + // 没有文件信息,无效记录,删除 + if (StringUtils.hasText(editFilePath)) { + File editFile = new File(editFilePath); + if (editFile.exists()) { + editFiles.add(editFile); + } else { + dbFile.setEditFilePath(""); + } + } + } + + // 类型未设置,补充类型 + if (dbFile.getType() == null) { + if (fillFileAsDefaultType(dbFile, file, status)) { + modified = true; + } + } + } + + for (File editFile : editFiles) { + String editFilePath = editFile.getAbsolutePath(); + F dup = map.remove(editFilePath); + // 目录有重复删除 + if (dup != null) { + deleteFile(dup); + } + } + + return modified; + } + + /** + * dbFile 的 Type 未设置时,设置为默认类型 + * + * @param dbFile 要设置的 对象 + * @param file 相关文件对象 + * @param status 状态输出 + * @param 类型类 + * @param 文件类 + * @return 是否修改了 + * @see CompanyVendorFile + * @see CompanyVendorFileType + * @see CompanyCustomerFile + * @see CompanyCustomerFileType + */ + protected abstract > boolean fillFileAsDefaultType(F dbFile, File file, Consumer status); + + + protected void moveFileToCompany(Company company, List needMoveToCompanyPath) { + if (needMoveToCompanyPath.isEmpty()) { + return; + } + if (!Hibernate.isInitialized(company)) { + company = companyService.findById(company.getId()); + } + String companyPath = company.getPath(); + if (!StringUtils.hasText(companyPath)) { + return; + } + for (File file : needMoveToCompanyPath) { + File dest = new File(companyPath, file.getName()); + if (file.renameTo(dest)) { + // + if (logger.isInfoEnabled()) { + logger.info("{} -> {}", file.getAbsolutePath(), dest.getAbsolutePath()); + } + } else { + // + if (dest.exists()) { + if (file.delete()) { + if (logger.isInfoEnabled()) { + logger.info("Delete File {}", file.getAbsolutePath()); + } + } + } + } + } + + // 公司目录下文件更新后,待公司文件自行处理 + + } + + /** + * 遍历扫描 path 目录下的文件, + * + * @param path + * @param needMoveToCompanyPath + * @param retrieveFiles + * @param map + * @param status + * @param + * @param + */ + protected > void fetchFiles( + String path, + List needMoveToCompanyPath, + List retrieveFiles, + Map map, + Consumer status + ) { + if (!StringUtils.hasText(path)) { + return; + } + File dir = new File(path); + if (!dir.exists()) { + return; + } + File[] files = dir.listFiles(); + if (files == null) { + return; + } + List step1 = new ArrayList<>(); + for (File file : files) { + // 只处理文件 + if (!file.isFile() || CompanyFileUtils.isHiddenFile(file)) { + continue; + } + + if (CompanyFileUtils.isCompanyFile(file)) { + needMoveToCompanyPath.add(file); + continue; + } + // 先把所有文件加到列表中 + step1.add(file); + } + + if (!step1.isEmpty()) { + // 第一步,处理已经存在的 + for (File file : new ArrayList<>(step1)) { + String filePath = file.getAbsolutePath(); + if (map.containsKey(filePath)) { + // 已记录 + F customerFile = map.get(filePath); + if (fillFile(customerFile, file, step1, status)) { + retrieveFiles.add(customerFile); + } + step1.remove(file); + } + } + + // 第二步骤,再处理 + while (!step1.isEmpty()) { + File file = step1.removeLast(); + F filled = fillFileType(file, step1, status); + if (filled != null) { + retrieveFiles.add(filled); + } + } + } + } + + /** + * 填充文件类型 + * + * @param file + * @param fileList + * @param status 状态输出 + * @param 类型类 + * @param 文件类 + */ + protected abstract > F fillFileType(File file, List fileList, Consumer status); + + /** + * @param customerFile 文件对象 + * @param file 相关文件 + * @param fileList 待处理文件列表 + * @param status 状态输出 + * @param 类型类 + * @param 文件类 + * @return true 有修改 + */ + protected > boolean fillFile(F customerFile, File file, List fileList, Consumer status) { + String fileName = file.getName(); + boolean modified = CompanyFileUtils.fillApplyDateAbsent(file, customerFile, F::getSignDate, F::setSignDate); + // 评估表 + if (isEvaluationFile(fileName)) { + if (fillFileAsEvaluationFile(customerFile, file, fileList, status)) { + modified = true; + } + } else { + if (!Objects.equals(file.getAbsolutePath(), customerFile.getFilePath())) { + customerFile.setFilePath(file.getAbsolutePath()); + modified = true; + } + } + return modified; + } + + /** + * 以评价表单模板。填充文件信息 + * + * @param customerFile 文件对象 + * @param file 相关文件 + * @param fileList 待处理文件列表 + * @param status 状态输出 + * @param 类型类 + * @param 文件类 + * @return true:文件对象有修改,否则返回false + */ + protected > boolean fillFileAsEvaluationFile(F customerFile, File file, List fileList, Consumer status) { + boolean modified = setFileTypeAsEvaluationForm(customerFile); + String fileName = file.getName(); + + // 文件全路径 + String fileAbsolutePath = file.getAbsolutePath(); + + if (isEditableFile(fileName)) { + // 是可编辑文件时 + if (!Objects.equals(fileAbsolutePath, customerFile.getEditFilePath())) { + customerFile.setEditFilePath(fileAbsolutePath); + modified = true; + } + + if (useAsEditableFile(customerFile, file, fileList, status)) { + modified = true; + } + } else if (isArchiveFile(fileName)) { + // 存档文件时 + if (!Objects.equals(fileAbsolutePath, customerFile.getFilePath())) { + customerFile.setFilePath(fileAbsolutePath); + modified = true; + } + + if (useAsArchiveFile(customerFile, file, fileList, status)) { + modified = true; + } + } else { + customerFile.setFilePath(file.getAbsolutePath()); + } + return modified; + } + + private > boolean useAsEditableFile(F customerFile, File file, List fileList, Consumer status) { + if (fileList == null) { + return false; + } + + // 检查存档文件 + if (StringUtils.hasText(customerFile.getFilePath())) { + // 移除 存档文件 + File archiveFile = new File(customerFile.getFilePath()); + fileList.remove(archiveFile); + return false; + } + + // 未关联存档文件,去找 fileList 查 + if (fileList.isEmpty()) { + // 文件全路径 + String fileAbsolutePath = file.getAbsolutePath(); + // 当没有其他文件时,与 EditFile 相同 + if (!Objects.equals(fileAbsolutePath, customerFile.getFilePath())) { + customerFile.setFilePath(fileAbsolutePath); + return true; + } + return false; + } + + File archiveFile = null; + // 文件名 + String fileName = file.getName(); + // 文件名,不含后缀 + String name = StringUtils.stripFilenameExtension(fileName); + for (File f : fileList) { + // 查找存档文件 + if (f.getName().startsWith(name) && isArchiveFile(f.getName())) { + archiveFile = f; + break; + } + } + // 没有匹配到文件 + if (archiveFile == null) { + return false; + } + + if (fileList.remove(archiveFile)) { + customerFile.setFilePath(archiveFile.getAbsolutePath()); + return true; + } + + return false; + } + + private > boolean useAsArchiveFile(F customerFile, File file, List fileList, Consumer status) { + if (fileList == null) { + return false; + } + + // 未关联存档文件,去找 fileList 查 + if (fileList.isEmpty()) { + return false; + } + + // 检查可编辑文件 + if (StringUtils.hasText(customerFile.getEditFilePath())) { + // 移除 可编辑文件 + File editFile = new File(customerFile.getEditFilePath()); + fileList.remove(editFile); + return false; + } + + File editFile = null; + // 文件名 + String fileName = file.getName(); + // 文件名,不含后缀 + String name = StringUtils.stripFilenameExtension(fileName); + for (File f : fileList) { + // 查找存档文件 + if (f.getName().startsWith(name) && isEditableFile(f.getName())) { + editFile = f; + break; + } + } + // 没有匹配到文件 + if (editFile == null) { + return false; + } + + if (fileList.remove(editFile)) { + String editFilePath = editFile.getAbsolutePath(); + if (!Objects.equals(editFilePath, customerFile.getEditFilePath())) { + customerFile.setEditFilePath(editFilePath); + return true; + } + } + + return false; + } + + + /** + * 设置文件类型为表单文件 + * + * @param file 文件对象 + * @param 类型类 + * @param 文件类 + * @return true 文件对象有修改,否则false + */ + protected abstract > boolean setFileTypeAsEvaluationForm(F file); + + /** + * 判定 参数 fileName 是否是一个评价表文件 + * + * @param fileName 文件名称 + * @return true 是评价表,否则false + */ + protected abstract boolean isEvaluationFile(String fileName); + + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyBlackReasonService.java b/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyBlackReasonService.java new file mode 100644 index 0000000..fad0464 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyBlackReasonService.java @@ -0,0 +1,68 @@ +package com.ecep.contract.manager.ds.company.service; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyBlackReason; +import com.ecep.contract.manager.ds.company.repository.CompanyBlackReasonRepository; +import com.ecep.contract.manager.ds.company.vo.CompanyBlackReasonViewModel; +import com.ecep.contract.manager.ui.ViewModelService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.List; + +@Lazy +@Service +public class CompanyBlackReasonService implements ViewModelService { + private static final Logger logger = LoggerFactory.getLogger(CompanyContactService.class); + + @Autowired + private CompanyBlackReasonRepository repository; + + + public CompanyBlackReason findById(Integer id) { + return repository.findById(id).orElse(null); + } + + @Override + public Specification getSpecification(String searchText) { + if (!StringUtils.hasText(searchText)) { + return null; + } + return (root, query, builder) -> { + return builder.or( + builder.like(root.get("applyName"), "%" + searchText + "%"), + builder.like(root.get("blackReason"), "%" + searchText + "%"), + builder.like(root.get("description"), "%" + searchText + "%") + ); + }; + } + + @Override + public Page findAll(Specification spec, Pageable pageable) { + return repository.findAll(spec, pageable); + } + + public void delete(CompanyBlackReason entity) { + repository.delete(entity); + } + + public List findAll(Specification spec, Sort by) { + return repository.findAll(spec, by); + } + + public List findAllByCompany(Company company) { + return repository.findAllByCompany(company); + } + + public CompanyBlackReason save(CompanyBlackReason companyBlackReason) { + return repository.save(companyBlackReason); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyContactService.java b/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyContactService.java new file mode 100644 index 0000000..621e0af --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyContactService.java @@ -0,0 +1,109 @@ +package com.ecep.contract.manager.ds.company.service; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyContact; +import com.ecep.contract.manager.ds.company.repository.CompanyContactRepository; +import com.ecep.contract.manager.ds.company.vo.CompanyContactViewModel; +import com.ecep.contract.manager.ui.ViewModelService; +import com.ecep.contract.manager.util.MyStringUtils; +import com.ecep.contract.manager.util.SpecificationUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.context.annotation.Lazy; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.List; + +/** + * 公司联系人服务 + */ +@Lazy +@Service +@CacheConfig(cacheNames = "company-contact") +public class CompanyContactService implements ViewModelService { + private static final Logger logger = LoggerFactory.getLogger(CompanyContactService.class); + + @Autowired + private CompanyContactRepository companyContactRepository; + + public CompanyContact save(CompanyContact contact) { + return companyContactRepository.save(contact); + } + + public void resetTo(Company from, Company to) { + // 曾用名 关联到 updater + List list = companyContactRepository.findAllByCompany(from); + if (list.isEmpty()) { + return; + } + for (CompanyContact oldName : list) { + oldName.setMemo(MyStringUtils.appendIfAbsent(oldName.getMemo(), "转自 " + from.getId())); + oldName.setCompany(to); + } + companyContactRepository.saveAll(list); + } + + public void deleteByCompany(Company company) { + int deleted = companyContactRepository.deleteAllByCompany(company); + if (deleted > 0) { + if (logger.isInfoEnabled()) { + logger.info("Delete {} records by company:#{}", deleted, company.getId()); + } + } + } + + public void delete(CompanyContact entity) { + companyContactRepository.delete(entity); + } + + public CompanyContact findFirstByCompany(Company company) { + return companyContactRepository.findFirstByCompany(company).orElse(null); + } + + public CompanyContact findById(Integer id) { + return companyContactRepository.findById(id).orElse(null); + } + + @Override + public Specification 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("phone"), "%" + searchText + "%"), + builder.like(root.get("email"), "%" + searchText + "%"), + builder.like(root.get("position"), "%" + searchText + "%"), + builder.like(root.get("memo"), "%" + searchText + "%") + ); + }; + } + + @Override + public Page findAll(Specification spec, Pageable pageable) { + return companyContactRepository.findAll(spec, pageable); + } + + public List searchByCompany(Company company, String userText) { + Specification spec = SpecificationUtils.and((root, query, builder) -> { + return builder.equal(root.get("company"), company); + }, getSpecification(userText)); + return companyContactRepository.findAll(spec); + } + + public List findAll(Specification spec, Sort sort) { + return companyContactRepository.findAll(spec, sort); + } + + public List findAllByCompanyAndName(Company company, String contactName) { + return companyContactRepository.findAllByCompanyAndName(company, contactName); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyExtendInfoService.java b/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyExtendInfoService.java new file mode 100644 index 0000000..3341201 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyExtendInfoService.java @@ -0,0 +1,74 @@ +package com.ecep.contract.manager.ds.company.service; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyExtendInfo; +import com.ecep.contract.manager.ds.company.repository.CompanyExtendInfoRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.Caching; +import org.springframework.context.annotation.Lazy; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 公司文件服务 + */ +@Lazy +@Service +@CacheConfig(cacheNames = "company-extend-info") +public class CompanyExtendInfoService { + private static final Logger logger = LoggerFactory.getLogger(CompanyExtendInfoService.class); + @Autowired + private CompanyExtendInfoRepository repository; + + @Cacheable(key = "#p0") + public CompanyExtendInfo findById(int id) { + return repository.findById(id).orElse(null); + } + + + public List findAll(Specification spec, Sort sort) { + return repository.findAll(spec, sort); + } + + @Caching( + evict = { + @CacheEvict(key = "#p0.id"), + @CacheEvict(key = "'byCompany-'+#p0.company.id") + } + ) + public void delete(CompanyExtendInfo extendInfo) { + repository.delete(extendInfo); + } + + @Caching( + evict = { + @CacheEvict(key = "#p0.id"), + @CacheEvict(key = "'byCompany-'+#p0.company.id") + } + ) + public CompanyExtendInfo save(CompanyExtendInfo extendInfo) { + return repository.save(extendInfo); + } + + @Cacheable(key = "'byCompany-'+#p0") + public CompanyExtendInfo findByCompany(Company company) { + List list = repository.findByCompany(company); + if (list.isEmpty()) { + CompanyExtendInfo extendInfo = new CompanyExtendInfo(); + extendInfo.setCompany(company); + extendInfo.setDisableVerify(false); + return repository.save(extendInfo); + } + return list.getFirst(); + } + + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyFileService.java b/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyFileService.java new file mode 100644 index 0000000..d46c91e --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyFileService.java @@ -0,0 +1,631 @@ +package com.ecep.contract.manager.ds.company.service; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.cloud.rk.CloudRkService; +import com.ecep.contract.manager.cloud.tyc.CloudTycService; +import com.ecep.contract.manager.ds.company.CompanyFileType; +import com.ecep.contract.manager.ds.company.CompanyFileUtils; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyFile; +import com.ecep.contract.manager.ds.company.model.CompanyFileTypeLocal; +import com.ecep.contract.manager.ds.company.model.CompanyOldName; +import com.ecep.contract.manager.ds.company.repository.CompanyFileRepository; +import com.ecep.contract.manager.ds.company.repository.CompanyFileTypeLocalRepository; +import com.ecep.contract.manager.ds.company.repository.CompanyOldNameRepository; +import com.ecep.contract.manager.ds.company.vo.CompanyFileViewModel; +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ui.ViewModelService; +import com.ecep.contract.manager.util.MyDateTimeUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Lazy; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.time.LocalDate; +import java.util.*; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.ecep.contract.manager.ds.company.CompanyFileType.*; + +/** + * 公司文件服务 + */ +@Lazy +@Service +@CacheConfig(cacheNames = "company-file") +public class CompanyFileService implements ViewModelService { + public final static String ENTERPRISE_REPORT = "企业信用报告"; + public final static String BUSINESS_LICENSE = "营业执照"; + public final static String ORGANIZATION_CODE_CERTIFICATE = "组织机构代码证"; + public final static String OPERATION_CERTIFICATE = "操作证"; + public final static String PERMIT_CERTIFICATE = "许可证"; + public final static String REGISTRATION_CERTIFICATE = "登记证"; + private static final Logger logger = LoggerFactory.getLogger(CompanyFileService.class); + @Lazy + @Autowired + private CompanyFileRepository companyFileRepository; + @Lazy + @Autowired + private CompanyOldNameRepository companyOldNameRepository; + @Lazy + @Autowired + private CompanyFileTypeLocalRepository fileTypeLocalRepository; + + @Cacheable(key = "#p0") + public CompanyFile findById(Integer id) { + return companyFileRepository.findById(id).orElse(null); + } + + @Override + public Page findAll(Specification spec, Pageable pageable) { + return companyFileRepository.findAll(spec, pageable); + } + + public List findFileByCompanyAndType(Company company, CompanyFileType type) { + return companyFileRepository.findByCompanyAndType(company, type); + } + + @Override + public Specification getSpecification(String searchText) { + if (!StringUtils.hasText(searchText)) { + return null; + } + return (root, query, builder) -> { + return builder.or(builder.like(root.get("filePath"), "%" + searchText + "%")); + }; + } + + + @Cacheable(key = "'type-locals-'+#p0") + public Map findAllFileTypes(String lang) { + return fileTypeLocalRepository.getCompleteMapByLocal(lang); + } +// public List findAllFileTypes(String lang) { +// Map map = fileTypeLocalRepository.getCompleteMapByLocal(lang); +// List list = new ArrayList<>(map.values()); +// list.sort((o1, o2) -> Objects.compare(o1.getValue(), o2.getValue(), String::compareTo)); +// return list; +// } + + /** + * 根据公司的合同的资信报告文件,推算下一个咨询报告日期 + * + * @param company 公司 + * @param state 状态输出 + * @return 下一个咨询报告日期 + */ + public LocalDate getNextCreditReportDate(Company company, Consumer state) { + // 检索全部合同 + ContractService contractService = SpringApp.getBean(ContractService.class); + List contractList = contractService.findAllByCompany(company); + if (contractList.isEmpty()) { + state.accept("未发现已登记的合同"); + return null; + } + + LocalDate minDate = LocalDate.of(2023, 1, 1); + List contracts = contractList.stream() + .filter(v -> !v.getSetupDate().isBefore(minDate)) + .sorted(Comparator.comparing(Contract::getSetupDate)) + .toList(); + if (contracts.isEmpty()) { + state.accept("没有发现待处理的合同"); + return null; + } + // 检索资信报告 + List files = findFileByCompanyAndType(company, CompanyFileType.CreditReport); + if (files.isEmpty()) { + Contract first = contracts.getFirst(); + // 没有资信报告,返回第一个合同的提交日期 + state.accept("没有资信报告,推荐使用第一个合同 " + first.getCode() + " 的日期 " + first.getSetupDate()); + return first.getSetupDate(); + } + + for (Contract contract : contracts) { + if (files.stream().noneMatch(v -> MyDateTimeUtils.dateValidFilter(contract.getSetupDate(), v.getApplyDate(), v.getExpiringDate(), 1))) { + state.accept("发现未匹配的合同 " + contract.getCode() + " " + contract.getSetupDate()); + return contract.getSetupDate(); + } + } + + return null; + } + + /** + * 验证企业文件 + *

+ * 检查是否有类型为{@link CompanyFileType#CreditReport 资讯评估}的文件,且参数verifyDate在文件的{@link CompanyFile#getApplyDate()}和{@link CompanyFile#getExpiringDate()} ()}指定验证日期内有 + *

+ * 2023-01-01后要求有资信评估报告 + * + * @param company 检查的公司对象 + * @param verifyDate 检查日期 + * @param status 状态输出 + * @see CompanyFile + * @see CompanyFileType + */ + public void verify(Company company, LocalDate verifyDate, Consumer status) { + if (verifyDate.isBefore(LocalDate.of(2023, 1, 1))) { + // 不检查2023-01-01之前的资信评估报告 + return; + } + + // 查询公司的资信评估报告 + List files = findFileByCompanyAndType(company, CompanyFileType.CreditReport); + CompanyFile companyFile = files.stream() + .filter(v -> v.getApplyDate() != null && v.getExpiringDate() != null) + .filter(v -> MyDateTimeUtils.dateValidFilter(verifyDate, v.getApplyDate(), v.getExpiringDate(), 30)) + .findFirst().orElse(null); + if (companyFile == null) { + List dates = new ArrayList<>(); + + files.stream() + .filter(v -> v.getApplyDate() != null && !verifyDate.isBefore(v.getApplyDate())) + .max(Comparator.comparing(CompanyFile::getApplyDate)) + .map(CompanyFile::getApplyDate) + .ifPresent(dates::add); + + files.stream() + .filter(v -> v.getExpiringDate() != null && !verifyDate.isAfter(v.getExpiringDate())) + .min(Comparator.comparing(CompanyFile::getApplyDate)) + .map(CompanyFile::getApplyDate) + .ifPresent(dates::add); + + if (dates.isEmpty()) { + status.accept("未匹配到资信评估报告"); + } else if (dates.size() == 1) { + status.accept("未匹配到资信评估报告, 最接近日期:" + dates.getFirst()); + } else { + LocalDate localDate = dates.stream().max(LocalDate::compareTo).orElse(null); + status.accept("未匹配到资信评估报告, 最接近日期:" + localDate); + } + } + } + + /** + * 保存企业文件 + * + * @param companyFile 企业文件 + * @return 保存后的企业文件 + */ + public CompanyFile save(CompanyFile companyFile) { + return companyFileRepository.save(companyFile); + } + + public List findAll(Specification spec, Sort sort) { + return companyFileRepository.findAll(spec, sort); + } + + public void deleteById(int id) { + companyFileRepository.deleteById(id); + } + + public void delete(CompanyFile file) { + companyFileRepository.delete(file); + } + + public List findByCompany(Company company) { + return companyFileRepository.findByCompany(company); + } + + /** + * 重置 公司文件 + * + * @param company 要重置的公司对象 + * @param status 输出 + */ + public boolean reBuildingFiles(Company company, Consumer status) { + List dbFiles = companyFileRepository.findByCompany(company); + List retrieveFiles = new ArrayList<>(); + boolean modfied = false; + + Map map = new HashMap<>(); + // 排除掉数据库中重复的 + for (CompanyFile dbFile : dbFiles) { + String filePath = dbFile.getFilePath(); + // 没有文件信息,无效记录,删除 + if (!StringUtils.hasText(filePath)) { + companyFileRepository.delete(dbFile); + modfied = true; + continue; + } + + // 目录不存在,删除 + File dir = new File(filePath); + if (!dir.exists()) { + companyFileRepository.delete(dbFile); + modfied = true; + continue; + } + + CompanyFile old = map.put(filePath, dbFile); + // 目录有重复删除 + if (old != null) { + companyFileRepository.delete(old); + modfied = true; + } + } + + + Map directoryMap = new HashMap<>(); + + // 公司目录 + if (StringUtils.hasText(company.getPath())) { + File dir = new File(company.getPath()); + directoryMap.put(company.getName(), dir); + } + + // 获取所有曾用名 + for (CompanyOldName companyOldName : companyOldNameRepository.findAllByCompanyId(company.getId())) { + String path = companyOldName.getPath(); + if (StringUtils.hasText(path)) { + File dir = new File(path); + directoryMap.put(companyOldName.getName(), dir); + } + } + + for (Map.Entry entry : directoryMap.entrySet()) { + String companyName = entry.getKey(); + File dir = entry.getValue(); + if (!StringUtils.hasText(companyName)) { + continue; + } + if (dir == null || !dir.exists()) { + continue; + } + + File[] files = dir.listFiles(); + if (files == null) { + // 文件系统出错或者没有相关文件 + continue; + } + for (File file : files) { + // 只处理文件 + if (!file.isFile() || CompanyFileUtils.isHiddenFile(file)) { + continue; + } + String filePath = file.getAbsolutePath(); + if (!map.containsKey(filePath)) { + // 未记录 + CompanyFile filled = fillFileType(file, status); + retrieveFiles.add(filled); + } + } + } + + status.accept("导入 " + retrieveFiles.size() + " 个文件"); + if (retrieveFiles.isEmpty()) { + return modfied; + } + + //update db + retrieveFiles.forEach(v -> v.setCompany(company)); + companyFileRepository.saveAll(retrieveFiles); + return true; + } + + /** + * 从文件名生成公司文件对象,文件已经存在公司对应的存储目录下 + * + * @param file 文件 + * @param status 状态输出 + * @return 公司文件对象 + */ + private CompanyFile fillFileType(File file, Consumer status) { + String fileName = file.getName(); + CompanyFile companyFile = new CompanyFile(); + companyFile.setType(General); + companyFile.setFilePath(file.getAbsolutePath()); + fillApplyDateAndExpiringDateAbsent(file, companyFile); + + // 天眼查 基础版企业信用报告 + if (fileName.contains(CloudTycService.TYC_ENTERPRISE_BASIC_REPORT) + || fileName.contains(CloudTycService.TYC_ENTERPRISE_MAJOR_REPORT) + || fileName.contains(CloudTycService.TYC_ENTERPRISE_ANALYSIS_REPORT) + ) { + companyFile.setType(CreditReport); + fillExpiringDateAbsent(companyFile); + return companyFile; + } + + // 天眼查 企业信用信息公示报告 + if (fileName.contains(CloudTycService.TYC_ENTERPRISE_CREDIT_REPORT)) { + companyFile.setType(CreditInfoPublicityReport); + return companyFile; + } + + // 集团相关方平台 元素征信 企业征信报告 + if (fileName.contains(CloudRkService.VENDOR_NAME) && fileName.contains(CloudRkService.ENTERPRISE_CREDIT_REPORT)) { + companyFile.setType(CreditReport); + fillExpiringDateAbsent(companyFile); + return companyFile; + } + + // 营业执照 + if (fileName.contains(BUSINESS_LICENSE)) { + companyFile.setType(BusinessLicense); + return companyFile; + } + + // 其他企业信用报告 + if (fileName.contains(ENTERPRISE_REPORT)) { + companyFile.setType(CreditReport); + fillExpiringDateAbsent(companyFile); + return companyFile; + } + return companyFile; + } + + /** + * 补齐有效期 + */ + private void fillExpiringDateAbsent(CompanyFile file) { + LocalDate expiringDate = file.getExpiringDate(); + if (expiringDate == null) { + LocalDate applyDate = file.getApplyDate(); + if (applyDate != null) { + expiringDate = applyDate.plusYears(1); + file.setExpiringDate(expiringDate); + } + } + } + + + private static void fillApplyDateAndExpiringDateAbsent(File file, CompanyFile companyFile) { + LocalDate applyDate = companyFile.getApplyDate(); + if (applyDate != null) { + return; + } + + String fileName = file.getName(); + Pattern pattern = Pattern.compile(MyDateTimeUtils.REGEX_DATE); + Matcher matcher = pattern.matcher(fileName); + while (matcher.find()) { + // 找到第一个日期,记作起始日期 + String date = matcher.group(); + try { + LocalDate n = LocalDate.parse(date); + companyFile.setApplyDate(n); + + // 如果 截至日期未设置,则第二个日期记作截至日期(如有) + LocalDate expiringDate = companyFile.getExpiringDate(); + if (expiringDate == null) { + while (matcher.find()) { + date = matcher.group(); + try { + n = LocalDate.parse(date); + companyFile.setExpiringDate(n); + break; + } catch (Exception e) { + if (logger.isDebugEnabled()) { + logger.debug("parse date failure, it used to set ExpiringDate, {} from {} by Regex:{}, @{}", + date, fileName, MyDateTimeUtils.REGEX_DATE, companyFile); + } + } + } + } + break; + } catch (Exception e) { + if (logger.isDebugEnabled()) { + logger.debug("parse date failure, it used to set ApplyDate, {} from {} by Regex:{}, @{}", + date, fileName, MyDateTimeUtils.REGEX_DATE, companyFile); + } + } + } + + } + + /** + * 移动文件到企业目录下 + * + * @param company 企业对象 + * @param files 要被移动的文件集合,需要从中选择需要的 + * @param status 状态输出 + */ + public boolean retrieveFromDownloadFiles(Company company, File[] files, Consumer status) { + Map map = new HashMap<>(); + File home = new File(company.getPath()); + map.put(company.getName(), home); + List retrieveFiles = new ArrayList<>(); + + // 获取所有曾用名 + for (CompanyOldName companyOldName : companyOldNameRepository.findAllByCompanyId(company.getId())) { + String name = companyOldName.getName(); + if (!StringUtils.hasText(name)) { + continue; + } + File dir = null; + String path = companyOldName.getPath(); + if (StringUtils.hasText(path)) { + dir = new File(path); + } + map.put(name, dir); + } + + // 对所有文件进行遍历 + for (int i = 0; i < files.length; i++) { + File file = files[i]; + // 只处理文件 + if (!file.isFile()) { + continue; + } + String prefix = (i + 1) + "/" + files.length + ":"; + Consumer inner = (str) -> { + status.accept(prefix + str); + }; + + String fileName = file.getName(); + inner.accept(fileName); + for (Map.Entry entry : map.entrySet()) { + String companyName = entry.getKey(); + // 必须要包含公司名称否则无法区分 + if (!fileName.contains(companyName)) { + continue; + } + + // 文件存储的目的地目录 + File dir = entry.getValue(); + if (dir == null) { + dir = home; + } + + CompanyFile filled = fillDownloadFileType(company, file, companyName, dir, inner); + if (filled != null) { + retrieveFiles.add(filled); + } + + } + } + + status.accept("导入 " + retrieveFiles.size() + " 个文件"); + if (retrieveFiles.isEmpty()) { + return false; + } + + //update db + retrieveFiles.forEach(v -> v.setCompany(company)); + companyFileRepository.saveAll(retrieveFiles); + return true; + } + + + /** + * 从文件名生成公司文件对象 + * 文件从下载目录中导入 + * + * @param company 公司对象 + * @param file 导入的文件对象 + * @param companyName 公司名称 + * @param destDir 目标目录 + * @param status 状态输出 + * @return 生成的公司文件对象,如果无法转换则返回null + */ + private CompanyFile fillDownloadFileType(Company company, File file, String companyName, File destDir, Consumer status) { + String fileName = file.getName(); + // 天眼查的报告 + // 目前只有 基础版企业信用报告, 企业信用信息公示报告下载保存时的文件名中没有天眼查 + if (CloudTycService.isTycReport(fileName)) { + CompanyFile companyFile = new CompanyFile(); + companyFile.setType(CreditReport); + fillApplyDateAbsent(file, companyFile); + + String destFileName = fileName; + // 重命名 基础版企业信用报告 + for (String report : Arrays.asList( + CloudTycService.TYC_ENTERPRISE_ANALYSIS_REPORT, + CloudTycService.TYC_ENTERPRISE_BASIC_REPORT, + CloudTycService.TYC_ENTERPRISE_MAJOR_REPORT)) { + + if (fileName.contains(report)) { + LocalDate applyDate = companyFile.getApplyDate(); + if (applyDate == null) { + applyDate = LocalDate.now(); + companyFile.setApplyDate(applyDate); + } + String formatted = MyDateTimeUtils.format(applyDate); + String extension = StringUtils.getFilenameExtension(fileName); + destFileName = String.format("%s_%s_%s_%s.%s", + companyName, CloudTycService.NAME, report, formatted, extension); + } + } + + // 重新设置 企业分析报告 未普通文件 +// if (fileName.contains(CloudTycService.TYC_ENTERPRISE_ANALYSIS_REPORT)) { +// companyFile.setType(General); +// } + + File dest = new File(destDir, destFileName); + // 移动文件 + if (!file.renameTo(dest)) { + // 移动失败时 + status.accept(fileName + " 无法移动到 " + dest.getAbsolutePath()); + return null; + } + + status.accept(fileName + " 移动到 " + dest.getAbsolutePath()); + companyFile.setFilePath(dest.getAbsolutePath()); + + // + if (companyFile.getExpiringDate() == null) { + if (companyFile.getApplyDate() != null) { + companyFile.setExpiringDate(companyFile.getApplyDate().plusYears(1)); + } + } + return companyFile; + } + + + // 企业信用信息公示报告 + if (fileName.contains(CloudTycService.TYC_ENTERPRISE_CREDIT_REPORT)) { + CompanyFile companyFile = new CompanyFile(); + companyFile.setType(CreditInfoPublicityReport); + fillApplyDateAbsent(file, companyFile); + File dest = new File(destDir, fileName); + + // 移动文件 + if (!file.renameTo(dest)) { + if (dest.exists()) { + // 尝试删除已经存在的文件 + if (!dest.delete()) { + status.accept("覆盖时,无法删除已存在的文件 " + dest.getAbsolutePath()); + return null; + } + if (file.renameTo(dest)) { + Optional one = companyFileRepository.findOne(((root, query, builder) -> { + return builder.and( + builder.equal(root.get("filePath"), dest.getAbsolutePath()), + builder.equal(root.get("company"), company) + ); + })); + if (one.isPresent()) { + companyFile = one.get(); + } + } else { + status.accept(fileName + " 无法覆盖到 " + dest.getAbsolutePath()); + return null; + } + } else { + status.accept(fileName + " 无法移动到 " + dest.getAbsolutePath()); + return null; + } + } + + status.accept(fileName + " 移动到 " + dest.getAbsolutePath()); + companyFile.setFilePath(dest.getAbsolutePath()); + + return companyFile; + } + return null; + } + + + /** + * 当 ApplyDate 未设置时,尝试使用文件名中包含的日期 + */ + private static void fillApplyDateAbsent(File file, CompanyFile companyFile) { + LocalDate applyDate = companyFile.getApplyDate(); + if (applyDate != null) { + return; + } + String fileName = file.getName(); + // 从文件名中提取日期 + LocalDate picked = MyDateTimeUtils.pickLocalDate(fileName); + if (picked != null) { + companyFile.setApplyDate(picked); + } + } + + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyInvoiceInfoService.java b/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyInvoiceInfoService.java new file mode 100644 index 0000000..3ef2b5a --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyInvoiceInfoService.java @@ -0,0 +1,52 @@ +package com.ecep.contract.manager.ds.company.service; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyInvoiceInfo; +import com.ecep.contract.manager.ds.company.repository.CompanyInvoiceInfoRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Lazy; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 公司发票信息服务 + */ +@Lazy +@Service +@CacheConfig(cacheNames = "company-invoice-info") +public class CompanyInvoiceInfoService { + @Lazy + @Autowired + private CompanyInvoiceInfoRepository repository; + + @Cacheable(key = "#p0") + public CompanyInvoiceInfo findById(int id) { + return repository.findById(id).orElse(null); + } + + + public List searchByCompany(Company company, String searchText) { + Specification spec = (root, query, builder) -> { + return builder.or( + builder.like(root.get("name"), "%" + searchText + "%"), + builder.like(root.get("taxId"), "%" + searchText + "%"), + builder.like(root.get("address"), "%" + searchText + "%"), + builder.like(root.get("phone"), "%" + searchText + "%"), + builder.like(root.get("bankName"), "%" + searchText + "%"), + builder.like(root.get("bankAccount"), "%" + searchText + "%") + ); + }; + + if (company != null) { + spec = spec.and((root, query, builder) -> { + return builder.equal(root.get("company"), company); + }); + } + return repository.findAll(spec, Pageable.ofSize(10)).getContent(); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyOldNameService.java b/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyOldNameService.java new file mode 100644 index 0000000..8da529c --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyOldNameService.java @@ -0,0 +1,213 @@ +package com.ecep.contract.manager.ds.company.service; + +import com.ecep.contract.manager.ds.company.CompanyFileUtils; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyOldName; +import com.ecep.contract.manager.ds.company.repository.CompanyOldNameRepository; +import com.ecep.contract.manager.ds.company.vo.CompanyOldNameViewModel; +import com.ecep.contract.manager.ui.ViewModelService; +import com.ecep.contract.manager.util.MyStringUtils; +import com.ecep.contract.manager.util.SpecificationUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.time.LocalDate; +import java.util.List; + +@Lazy +@Service +public class CompanyOldNameService implements ViewModelService { + private static final Logger logger = LoggerFactory.getLogger(CompanyOldNameService.class); + @Lazy + @Autowired + private CompanyOldNameRepository companyOldNameRepository; + @Lazy + @Autowired + private CompanyService companyService; + + + public CompanyOldName findById(Integer id) { + return companyOldNameRepository.findById(id).orElse(null); + } + + @Override + public Specification getSpecification(String searchText) { + if (!StringUtils.hasText(searchText)) { + return null; + } + return SpecificationUtils.andWith(searchText, this::buildSearchSpecification); + } + + protected Specification buildSearchSpecification(String searchText) { + return (root, query, builder) -> { + return builder.or( + builder.like(root.get("name"), "%" + searchText + "%"), + builder.like(root.get("memo"), "%" + searchText + "%") + ); + }; + } + + + public CompanyOldName save(CompanyOldName companyOldName) { + return companyOldNameRepository.save(companyOldName); + } + + public boolean makePathAbsent(CompanyOldName companyOldName) { + String path = companyOldName.getPath(); + if (StringUtils.hasText(path)) { + File file = new File(path); + if (file.exists()) { + return false; + } + } + + File dir = makePath(companyOldName); + if (dir == null) { + return false; + } + if (!dir.exists()) { + return false; + } + companyOldName.setPath(dir.getAbsolutePath()); + return true; + } + + public File makePath(CompanyOldName companyOldName) { + String oldName = companyOldName.getName(); + File basePath = companyService.getBasePath(); + Company company = companyService.findById(companyOldName.getCompanyId()); + String district = company.getDistrict(); + if (StringUtils.hasText(district)) { + String parentPrefix = CompanyFileUtils.getParentPrefixByDistrict(district); + if (parentPrefix != null) { + File parent = new File(basePath, parentPrefix); + if (!parent.exists()) { + if (!parent.mkdir()) { + return null; + } + } + String fileName = CompanyFileUtils.escapeFileName(oldName); + File dir = new File(parent, fileName); + if (!dir.exists()) { + if (!dir.mkdir()) { + return null; + } + } + return dir; + } + } + return null; + } + + + public List findAllByName(String name) { + return companyOldNameRepository.findAllByName(name); + } + + public List findAll(Specification spec, Sort sort) { + return companyOldNameRepository.findAll(spec, sort); + } + + public List findAllByCompany(Company company) { + return companyOldNameRepository.findAllByCompanyId(company.getId()); + } + + public CompanyOldName findMatchByDate(Company company, LocalDate date) { + List oldNames = findAllByCompany(company); + if (oldNames == null || oldNames.isEmpty()) { + return null; + } + return oldNames.stream().filter(v -> { + if (v.getAmbiguity()) { + return false; + } + if (v.getBeginDate() != null && date.isBefore(v.getBeginDate())) { + return false; + } + if (v.getEndDate() != null && date.isAfter(v.getEndDate())) { + return false; + } + return true; + }).findFirst().orElse(null); + } + + public void deleteById(int id) { + companyOldNameRepository.deleteById(id); + } + + public void delete(CompanyOldNameViewModel model) { + int id = model.getId().get(); + deleteById(id); + } + + public void delete(CompanyOldName entity) { + companyOldNameRepository.delete(entity); + } + + public List findAllByCompanyAndName(Company company, String oldName) { + return companyOldNameRepository.findAllByCompanyIdAndName(company.getId(), oldName); + } + + /** + * 将 from 的曾用名 关联到 to + * + * @param from from + * @param to to + */ + public void resetTo(Company from, Company to) { + // 曾用名 关联到 to + List list = companyOldNameRepository.findAllByCompanyId(from.getId()); + if (list.isEmpty()) { + return; + } + for (CompanyOldName oldName : list) { + oldName.setMemo(MyStringUtils.appendIfAbsent(oldName.getMemo(), "转自 " + from.getId())); + oldName.setCompanyId(to.getId()); + } + companyOldNameRepository.saveAll(list); + } + + public void deleteByCompany(Company company) { + int deleted = companyOldNameRepository.deleteAllByCompanyId(company.getId()); + if (deleted > 0) { + if (logger.isInfoEnabled()) { + logger.info("Delete {} records by company:#{}", deleted, company.getId()); + } + } + } + + /** + * 根据提供的搜索文本查询公司旧名称列表。 + *

+ * 该函数使用JPA的Specification接口构建查询条件,查找公司旧名称中包含指定文本的记录。 + * 查询结果最多返回10条记录。 + * + * @param searchText 用于搜索的文本,将匹配公司旧名称中包含该文本的记录。 + * @return 包含匹配的公司旧名称的列表,列表中的每个元素都是一个CompanyOldName对象。 + */ + public List search(String searchText) { + return companyOldNameRepository.findAll(getSpecification(searchText), Pageable.ofSize(10)).getContent(); + } + + @Override + public Page findAll(Specification spec, Pageable pageable) { + return companyOldNameRepository.findAll(spec, pageable); + } + + public CompanyOldName createNew(Company company, String name, boolean ambiguity) { + CompanyOldName companyOldName = new CompanyOldName(); + companyOldName.setCompanyId(company.getId()); + companyOldName.setName(name); + companyOldName.setAmbiguity(ambiguity); + return companyOldName; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyService.java b/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyService.java new file mode 100644 index 0000000..e9eb9ed --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/service/CompanyService.java @@ -0,0 +1,616 @@ +package com.ecep.contract.manager.ds.company.service; + +import com.ecep.contract.manager.cloud.rk.CloudRkService; +import com.ecep.contract.manager.cloud.tyc.CloudTycService; +import com.ecep.contract.manager.cloud.u8.YongYouU8Service; +import com.ecep.contract.manager.ds.company.CompanyFileUtils; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyOldName; +import com.ecep.contract.manager.ds.company.repository.CompanyRepository; +import com.ecep.contract.manager.ds.company.vo.CompanyViewModel; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ds.customer.model.CompanyCustomer; +import com.ecep.contract.manager.ds.customer.service.CompanyCustomerService; +import com.ecep.contract.manager.ds.other.service.SysConfService; +import com.ecep.contract.manager.ds.vendor.model.CompanyVendor; +import com.ecep.contract.manager.ds.vendor.service.CompanyVendorService; +import com.ecep.contract.manager.ui.ViewModelService; +import com.ecep.contract.manager.util.MyDateTimeUtils; +import com.ecep.contract.manager.util.MyStringUtils; +import com.ecep.contract.manager.util.SpecificationUtils; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Predicate; +import jakarta.transaction.Transactional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.Caching; +import org.springframework.context.annotation.Lazy; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.time.LocalDate; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 公司服务 + */ +@Lazy +@Service +@CacheConfig(cacheNames = "company") +public class CompanyService implements ViewModelService { + private static final Logger logger = LoggerFactory.getLogger(CompanyService.class); + private static final String COMPANY_BASE_PATH = "company.base.path"; + + @Lazy + @Autowired + private CompanyRepository companyRepository; + @Lazy + @Autowired + private SysConfService confService; + @Lazy + @Autowired + private CompanyFileService companyFileService; + @Lazy + @Autowired + private ContractService contractService; + @Lazy + @Autowired + private CompanyVendorService companyVendorService; + @Lazy + @Autowired + private CompanyCustomerService companyCustomerService; + @Lazy + @Autowired + private CompanyOldNameService companyOldNameService; + @Lazy + @Autowired + private CompanyContactService companyContactService; + @Lazy + @Autowired + private CloudRkService cloudRkService; + @Lazy + @Autowired + private CloudTycService cloudTycService; + @Lazy + @Autowired(required = false) + private YongYouU8Service yongYouU8Service; + + @Cacheable(key = "#p0") + public Company findById(Integer id) { + return companyRepository.findById(id).orElse(null); + } + + @Cacheable(key = "'name-'+#p0") + public Company findByName(String name) { + return companyRepository.findFirstByName(name).orElse(null); + } + + /** + * 查找名称是 name 的记录 + * + * @param name 查询的公司名称 + * @return 记录列表 + */ + public List findAllByName(String name) { + return companyRepository.findAllByName(name); + } + + public Page findAll(Specification spec, Pageable pageable) { + return companyRepository.findAll(spec, pageable); + } + + + /** + * 查找 + * 重复的删除 + * + * @param uniscid 统一社会信用代码 + * @return 公司对象 + */ + public Company findAndRemoveDuplicateCompanyByUniscid(String uniscid) { + // 根据统一社会信用代码去查询 + List companies = companyRepository.findAllByUniscid(uniscid); + if (companies.isEmpty()) { + return null; + } + if (companies.size() == 1) { + return companies.getFirst(); + } else { + List result = removeDuplicatesByUniscid(companies); + if (!result.isEmpty()) { + return result.getFirst(); + } + } + return null; + } + + /** + * 查找公司,根据名称或简称查询 + *

+ * 1. 先按照 name 查找Company记录,如果有多个记录只保留一条,保留规则参考 #removeDuplicatesByUniscid 方法 + * 2. 第一步没有匹配到时,根据名称从曾用名中查询 + * 3. 上一步没有匹配到时,根据简称从公司数据库查询匹配简称的公司 + * 4. 上一步没有匹配到时,根据简称从曾用名中查询 + * 根据简称去查询,如果有多个记录只保留一条,保留规则参考 #removeDuplicatesByUniscid 方法 + * 重复的删除 + * + * @param name 企业名称 + * @param abbName 别名 + * @return 公司对象 + */ + public Company findAndRemoveDuplicateCompanyByNameOrAbbName(String name, String abbName) { + Company updater = null; + { + // 根据公司全名去查询 + List companies = companyRepository.findAllByName(name); + if (companies.isEmpty()) { + if (logger.isDebugEnabled()) { + logger.debug("No record match by {}", name); + } + } else if (companies.size() == 1) { + updater = companies.getFirst(); + } else { + List result = removeDuplicatesByUniscid(companies); + if (!result.isEmpty()) { + updater = result.getFirst(); + } + } + } + + if (updater == null) { + // 根据公司名称去曾用名中查询 + List oldNames = companyOldNameService.findAllByName(name); + if (!oldNames.isEmpty()) { + CompanyOldName oldName = oldNames.getFirst(); + Optional optional = companyRepository.findById(oldName.getCompanyId()); + if (optional.isPresent()) { + updater = optional.get(); + } + } + } + + if (updater == null && StringUtils.hasText(abbName) && !Objects.equals(abbName, name)) { + // 根据公司全面去查询 + List companies = companyRepository.findAllByShortName(abbName); + if (!companies.isEmpty()) { + updater = companies.removeFirst(); + } + + if (updater == null) { + // 根据公司名称去曾用名中查询 + List oldNames = companyOldNameService.findAllByName(abbName); + if (!oldNames.isEmpty()) { + CompanyOldName oldName = null; + Optional optional1 = oldNames.stream().filter(CompanyOldName::getAmbiguity).findFirst(); + oldName = optional1.orElseGet(oldNames::getFirst); + Optional optional = companyRepository.findById(oldName.getCompanyId()); + if (optional.isPresent()) { + updater = optional.get(); + } + } + } + } + + return updater; + } + + /** + * 删除 列表中 Uniscid 重复的 + */ + private List removeDuplicatesByUniscid(List list) { + List result = new ArrayList<>(); + List removes = new ArrayList<>(); + Set uniqueUniscidSet = new HashSet<>(); + for (Company company : list) { + // 名称相同后,统一社会信用编号 也相同的话,删除 + if (uniqueUniscidSet.add(company.getUniscid())) { + // 没有记录过时 + result.add(company); + } else { + // 有重复时 + removes.add(company); + } + } + + + Company updater = result.getFirst(); + // 合并重复的 + for (Company company : removes) { + try { + merge(company, updater); + } catch (Exception e) { + logger.error("合并 {} -> {} 时发生错误:{}", company.toPrettyString(), updater.toPrettyString(), e.getMessage(), e); + throw e; + } + } + return result; + } + + + /** + * 删除 + *

+ * 删除前需要把关联数据解除 + *

    + *
  • {@link CompanyVendorService#deleteByCompany(Company)}
  • + *
  • {@link CompanyCustomerService#deleteByCompany(Company)}
  • + *
  • {@link CompanyOldNameService#deleteByCompany(Company)}
  • + *
  • {@link CompanyContactService#deleteByCompany(Company)}
  • + *
  • {@link ContractService#deleteByCompany(Company)}
  • + *
  • {@link CompanyContactService#deleteByCompany(Company)}
  • + *
+ * 或者 把关联数据转移到其他公司 + *
    + *
  • {@link CompanyVendorService#resetTo(Company, Company)}
  • + *
  • {@link CompanyCustomerService#resetTo(Company, Company)}
  • + *
  • {@link CompanyOldNameService#resetTo(Company, Company)}
  • + *
  • {@link CompanyContactService#resetTo(Company, Company)}
  • + *
  • {@link ContractService#resetTo(Company, Company)}
  • + *
  • {@link CompanyContactService#resetTo(Company, Company)}
  • + *
+ *

+ * + * @param company 要删除的公司对象 + */ + @Caching( + evict = { + @CacheEvict(key = "#p0.id"), + @CacheEvict(key = "'name-'+#p0.name") + } + ) + public void delete(Company company) { + cloudRkService.deleteByCompany(company); + cloudTycService.deleteByCompany(company); + yongYouU8Service.deleteByCompany(company); + + companyOldNameService.deleteByCompany(company); + companyContactService.deleteByCompany(company); + // 供应商和客户 + companyVendorService.deleteByCompany(company); + companyCustomerService.deleteByCompany(company); + + contractService.deleteByCompany(company); + companyContactService.deleteByCompany(company); + + companyRepository.delete(company); + if (logger.isInfoEnabled()) { + logger.info("Delete Company {}", company); + } + } + + /** + * 合并 from 到 to + *

+ * 下述关联数据重设绑定到 to + *

    + *
  1. 曾用名
  2. + *
  3. 供应商
  4. + *
  5. 客户
  6. + *
  7. 合同
  8. + *
  9. 公司合同
  10. + *
+ *

+ */ + public void merge(Company from, Company to) { + // cloudRkService.findById(from.getId()); + cloudRkService.resetTo(from, to); + cloudTycService.resetTo(from, to); + yongYouU8Service.resetTo(from, to); + + companyOldNameService.resetTo(from, to); + companyContactService.resetTo(from, to); + // 供应商和客户 + companyVendorService.resetTo(from, to); + companyCustomerService.resetTo(from, to); + + contractService.resetTo(from, to); + companyContactService.resetTo(from, to); + companyRepository.delete(from); + if (logger.isInfoEnabled()) { + logger.info("Merge {} to {}", from, to); + } + } + + /** + * 保存实体对象,异步方法 + * + * @param company 要保存的实体对象 + * @return 返回异步调用 + */ + public CompletableFuture asyncSave(Company company) { + return CompletableFuture.completedFuture(companyRepository.save(company)); + } + + + /** + * 保存实体对象 + * + * @param company 要保存的实体对象 + * @return 返回异步调用 + */ + @Caching( + evict = { + @CacheEvict(key = "#p0.id"), + @CacheEvict(key = "'name-'+#p0.name") + } + ) + public Company save(Company company) { + return companyRepository.save(company); + } + + @Override + public CompanyViewModel from(Company entity) { + return CompanyViewModel.from(entity); + } + + + public long count() { + return companyRepository.count(); + } + + public long count(Specification spec) { + return companyRepository.count(spec); + } + + @Transactional + public void findAllWithStream(Consumer> consumer) { + try (Stream stream = companyRepository.findAllAsStream()) { + consumer.accept(stream); + } + } + + public File getVendorBasePath() { + return new File(confService.getString(CompanyVendorService.KEY_BASE_PATH)); + } + + public File getCustomerBasePath() { + return new File(confService.getString(CompanyCustomerService.KEY_BASE_PATH)); + } + + public File getBasePath() { + return new File(confService.getString(COMPANY_BASE_PATH)); + } + + /** + * 创建公司目录,如果公司目录未设置,或者未存在 + * + * @param company 要创建目录的公司对象 + * @return 是否创建了目录 + */ + public boolean makePathAbsent(Company company) { + String path = company.getPath(); + if (StringUtils.hasText(path)) { + File file = new File(path); + if (file.exists()) { + return false; + } + } + + File dir = makePath(company); + if (dir == null) { + return false; + } + if (!dir.exists()) { + return false; + } + company.setPath(dir.getAbsolutePath()); + company.setPathExist(true); + return true; + } + + + /** + * 创建企业目录 + * + * @param company 要创建的企业对象 + * @return 目录 + */ + public File makePath(Company company) { + File basePath = getBasePath(); + if (!basePath.exists()) { + return null; + } + String companyName = company.getName(); + String district = company.getDistrict(); + if (StringUtils.hasText(district)) { + String parentPrefix = CompanyFileUtils.getParentPrefixByDistrict(district); + if (parentPrefix != null) { + File parent = new File(basePath, parentPrefix); + if (!parent.exists()) { + if (!parent.mkdir()) { + return null; + } + } + String fileName = CompanyFileUtils.escapeFileName(companyName); + File dir = new File(parent, fileName); + if (!dir.exists()) { + if (!dir.mkdir()) { + return null; + } + } + return dir; + } + } + return null; + } + + /** + * 移动文件到企业目录下 + * + * @param company 企业对象 + * @param files 要被移动的文件集合,需要从中选择需要的 + * @param status 状态输出 + */ + public boolean retrieveFromDownloadFiles(Company company, File[] files, Consumer status) { + // + boolean companyChanged = makePathAbsent(company); + + if (!StringUtils.hasText(company.getPath())) { + // fixed 要退出,需要保存 + if (companyChanged) { + save(company); + } + status.accept("存储目录未设置,请检查"); + return false; + } + + File home = new File(company.getPath()); + if (!home.exists()) { + // fixed 要退出,需要保存 + if (companyChanged) { + company = save(company); + } + status.accept(company.getPath() + " 不存在,无法访问,请检查或者修改"); + return false; + } + + boolean retrieved = companyFileService.retrieveFromDownloadFiles(company, files, status); + if (companyChanged) { + save(company); + } + return retrieved; + } + + + /** + * 验证企业状态 + * + * @param company 要验证的公司 + * @param verifyDate 验证日期 + * @param status 状态输出 + */ + public void verifyEnterpriseStatus(Company company, LocalDate verifyDate, Consumer status) { + // 检查营业状态 + String entStatus = company.getEntStatus(); + if (StringUtils.hasText(entStatus)) { + if (entStatus.contains("注销")) { + LocalDate end = company.getOperationPeriodEnd(); + LocalDate begin = company.getOperationPeriodBegin(); + if (begin == null || end == null) { + // 注销时间未知,无法判断是否在 verifyDate 之后注销 + status.accept("营业状态异常:" + entStatus); + } else { + if (!MyDateTimeUtils.dateValidFilter(verifyDate, begin, end, 0)) { + status.accept("营业状态异常:" + entStatus); + } + } + } + } else { + status.accept("营业状态异常:未设置"); + } + } + + @Override + public Specification getSpecification(String searchText) { + if (!StringUtils.hasText(searchText)) { + return null; + } + // 判断是否全是数字 + boolean isAllDigit = MyStringUtils.isAllDigit(searchText); + if (isAllDigit) { + return (root, query, builder) -> { + return builder.or( + builder.like(root.get("id").as(String.class), "%" + searchText + "%"), + builder.like(root.get("uniscid"), "%" + searchText + "%") + ); + }; + } + + Specification spec = null; + Set idSet = companyOldNameService.search(searchText).stream().map(CompanyOldName::getCompanyId).collect(Collectors.toSet()); + if (!idSet.isEmpty()) { + spec = SpecificationUtils.or(spec, (root, query, builder) -> { + return root.get("id").in(idSet); + }); + } + + List searchedVendors = companyVendorService.search(searchText); + if (!searchedVendors.isEmpty()) { + spec = SpecificationUtils.or(spec, (root, query, builder) -> { + return builder.in(root.get("id")).value(searchedVendors.stream() + .map(CompanyVendor::getCompany) + .filter(Objects::nonNull) + .map(Company::getId) + .collect(Collectors.toSet())); + }); + } + + List searchedCustomers = companyCustomerService.search(searchText); + if (!searchedCustomers.isEmpty()) { + spec = SpecificationUtils.or(spec, (root, query, builder) -> { + return builder.in(root.get("id")).value(searchedCustomers.stream() + .map(CompanyCustomer::getCompany) + .filter(Objects::nonNull) + .map(Company::getId) + .collect(Collectors.toSet())); + }); + } + + return SpecificationUtils.or(spec, SpecificationUtils.andWith(searchText, this::buildSearchSpecification)); + } + + protected Specification buildSearchSpecification(String searchText) { + return (root, query, builder) -> buildSearchPredicate(searchText, root, query, builder); + } + + public Predicate buildSearchPredicate(String searchText, Path root, @Nullable CriteriaQuery query, CriteriaBuilder builder) { + return builder.or( + builder.like(root.get("name"), "%" + searchText + "%"), + builder.like(root.get("shortName"), "%" + searchText + "%"), + builder.like(root.get("uniscid"), "%" + searchText + "%"), + builder.like(root.get("legalRepresentative"), "%" + searchText + "%"), + builder.like(root.get("regAddr"), "%" + searchText + "%"), + builder.like(root.get("address"), "%" + searchText + "%"), + builder.like(root.get("memo"), "%" + searchText + "%") + ); + } + + /** + * 检索企业 + * 企业名称中模糊搜索 + * + * @param searchText 搜索文本 + * @return 企业列表,返回前10个企业 + */ + public List search(String searchText) { + Specification spec = getSpecification(searchText); + return companyRepository.findAll(spec, Pageable.ofSize(10)).getContent(); + } + + + public Company createNewCompany(String name) { + Company company = new Company(); + company.setName(name); + company.setCreated(LocalDate.now()); + return company; + } + + + public List getAllNames(Company company) { + List list = new ArrayList<>(); + list.add(company.getName()); + companyOldNameService.findAllByCompany(company).forEach(oldName -> { + // 歧义的曾用名不采用 + if (oldName.getAmbiguity()) { + return; + } + list.add(oldName.getName()); + }); + return list; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/service/InvoiceService.java b/src/main/java/com/ecep/contract/manager/ds/company/service/InvoiceService.java new file mode 100644 index 0000000..72f8711 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/service/InvoiceService.java @@ -0,0 +1,75 @@ +package com.ecep.contract.manager.ds.company.service; + +import com.ecep.contract.manager.ds.company.model.Invoice; +import com.ecep.contract.manager.ds.company.repository.InvoiceRepository; +import com.ecep.contract.manager.ds.company.vo.InvoiceViewModel; +import com.ecep.contract.manager.ui.ViewModelService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cache.annotation.CacheConfig; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Lazy; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.List; + +@Lazy +@Service +@CacheConfig(cacheNames = "invoice") +public class InvoiceService implements ViewModelService { + private static final Logger logger = LoggerFactory.getLogger(InvoiceService.class); + + @Autowired + private InvoiceRepository repository; + + + @Cacheable(key = "#p0") + public Invoice findById(Integer id) { + return repository.findById(id).orElse(null); + } + + @Override + public Specification getSpecification(String searchText) { + if (!StringUtils.hasText(searchText)) { + return null; + } + + return (root, query, builder) -> { + return builder.or( + builder.like(root.get("code"), "%" + searchText + "%"), + builder.like(root.get("description"), "%" + searchText + "%") + ); + + }; + } + + @Override + public Page findAll(Specification spec, Pageable pageable) { + return repository.findAll(spec, pageable); + } + + @CacheEvict(key = "#p0.id") + public void delete(Invoice entity) { + repository.delete(entity); + } + + public List findAll(Specification spec, Sort by) { + return repository.findAll(spec, by); + } + + public Invoice findByCode(String invoiceNumber) { + return repository.findByCode(invoiceNumber); + } + + @CacheEvict(key = "#p0.id") + public Invoice save(Invoice invoice) { + return repository.save(invoice); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/tasker/CompanyCompositeUpdateTasker.java b/src/main/java/com/ecep/contract/manager/ds/company/tasker/CompanyCompositeUpdateTasker.java new file mode 100644 index 0000000..d92ad78 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/tasker/CompanyCompositeUpdateTasker.java @@ -0,0 +1,147 @@ +package com.ecep.contract.manager.ds.company.tasker; + +import com.ecep.contract.manager.cloud.rk.CloudRk; +import com.ecep.contract.manager.cloud.rk.CloudRkService; +import com.ecep.contract.manager.cloud.rk.ctx.CloudRkCtx; +import com.ecep.contract.manager.cloud.tyc.CloudTycService; +import com.ecep.contract.manager.cloud.u8.CloudYu; +import com.ecep.contract.manager.cloud.u8.YongYouU8Service; +import com.ecep.contract.manager.cloud.u8.ctx.CompanyCtx; +import com.ecep.contract.manager.cloud.u8.ctx.ContractCtx; +import com.ecep.contract.manager.cloud.u8.ctx.CustomerCtx; +import com.ecep.contract.manager.cloud.u8.ctx.VendorCtx; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ui.MessageHolder; +import com.ecep.contract.manager.ui.Tasker; +import lombok.Setter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.BeansException; + +import java.time.Instant; +import java.time.LocalDate; + +/** + * 合并更新 + */ +public class CompanyCompositeUpdateTasker extends Tasker { + private static final Logger logger = LoggerFactory.getLogger(CompanyCompositeUpdateTasker.class); + + CloudRkCtx cloudRkCtx = new CloudRkCtx(); + + @Setter + private CloudRkService cloudRkService; + @Setter + private CloudTycService cloudTycService; + @Setter + private YongYouU8Service yongYouU8Service; + @Setter + private Company company; + + @Override + protected Object execute(MessageHolder holder) throws Exception { + + updateProgress(0.1, 1); + syncFromCloudRk(holder); + updateProgress(0.3, 1); + syncFromYongYouU8(holder); + updateProgress(0.6, 1); + syncFromCloudTyc(holder); + return null; + } + + + private void syncFromCloudRk(MessageHolder holder) { + holder.debug("1. 从 " + CloudRkService.NAME + " 更新..."); + try { + cloudRkService = getBean(CloudRkService.class); + } catch (BeansException e) { + holder.warn("未启用 " + CloudRkService.NAME + " 服务"); + return; + } + CloudRk cloudRk = cloudRkService.getOrCreateCloudRk(company); + if (cloudRk == null) { + holder.error("无法创建或获取 CloudRk 对象"); + return; + } + + try { + cloudRkCtx.setCloudRkService(cloudRkService); + if (cloudRkCtx.syncCompany(company, cloudRk, holder)) { + + } + } catch (Exception e) { + cloudRk.setDescription(e.getMessage()); + } finally { + cloudRk.setLatestUpdate(Instant.now()); + cloudRkService.save(cloudRk); + } + } + + private void syncFromYongYouU8(MessageHolder holder) { + holder.debug("2. 从 " + YongYouU8Service.NAME + " 更新..."); + try { + yongYouU8Service = getBean(YongYouU8Service.class); + } catch (BeansException e) { + holder.warn("未启用 " + YongYouU8Service.NAME + " 服务"); + return; + } + + CloudYu cloudYu = yongYouU8Service.getOrCreateCloudYu(company); + if (cloudYu == null) { + holder.error("无法创建或获取 CloudYu 对象"); + return; + } + try { + CompanyCtx companyCtx = new CompanyCtx(); + yongYouU8Service.initialize(companyCtx); + if (companyCtx.syncCompany(company, holder)) { + holder.info("更新"); + } + + VendorCtx vendorCtx = new VendorCtx(); + yongYouU8Service.initialize(vendorCtx); + vendorCtx.setCompanyCtx(companyCtx); + if (vendorCtx.syncVendor(company, holder)) { + cloudYu.setVendorUpdateDate(LocalDate.now()); + } + + CustomerCtx customerCtx = new CustomerCtx(); + yongYouU8Service.initialize(customerCtx); + customerCtx.setCompanyCtx(companyCtx); + if (customerCtx.syncCustomer(company, holder)) { + cloudYu.setCustomerUpdateDate(LocalDate.now()); + } + + ContractCtx contractCtx = new ContractCtx(); + yongYouU8Service.initialize(contractCtx); + contractCtx.syncContract(company, holder); + + cloudYu.setCloudLatest(Instant.now()); + cloudYu.setExceptionMessage(""); + } catch (Exception e) { + String message = e.getMessage(); + holder.error("同步过程中发生错误: " + message); + // 保留255个字符 + if (message.length() > 255) { + message = message.substring(0, 255); + } + cloudYu.setExceptionMessage(message); + } finally { + cloudYu.setLatestUpdate(Instant.now()); + yongYouU8Service.save(cloudYu); + } + } + + private void syncFromCloudTyc(MessageHolder holder) { + holder.debug("3. 从 " + CloudTycService.NAME + " 更新..."); + try { + cloudTycService = getBean(CloudTycService.class); + } catch (BeansException e) { + holder.warn("未启用 " + CloudTycService.NAME + " 服务"); + return; + } + cloudTycService.syncCompany(company, holder); + updateProgress(1, 1); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/tasker/CompanyFilesRebuildTasker.java b/src/main/java/com/ecep/contract/manager/ds/company/tasker/CompanyFilesRebuildTasker.java new file mode 100644 index 0000000..89c748c --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/tasker/CompanyFilesRebuildTasker.java @@ -0,0 +1,71 @@ +package com.ecep.contract.manager.ds.company.tasker; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.service.CompanyFileService; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ui.MessageHolder; +import com.ecep.contract.manager.ui.Tasker; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 对所有公司的文件进行重置 + */ +public class CompanyFilesRebuildTasker extends Tasker { + private CompanyFileService companyFileService; + + public CompanyFilesRebuildTasker() { + updateTitle("合同文件重置"); + } + + @Override + protected Object execute(MessageHolder holder) throws Exception { + Pageable pageRequest = PageRequest.ofSize(200); + AtomicInteger counter = new AtomicInteger(0); + Specification spec = null; +// Specification spec = (root, query, cb) -> { +// return cb.greaterThan(root.get("created"), Instant.now().minusSeconds(TimeUnit.DAYS.toSeconds(360))); +// }; + updateTitle("遍历所有公司,对每个可以公司的文件进行“重置”操作"); + long total = getCompanyService().count(spec); + while (true) { + if (isCancelled()) { + break; + } + + + Page page = getCompanyService().findAll(spec, pageRequest); + if (page.isEmpty()) { + break; + } + + for (Company company : page) { + if (isCancelled()) { + break; + } + + String prefix = counter.get() + " / " + total + "> " + company.getName() + " #" + company.getId() + "> "; + getCompanyFileService().reBuildingFiles(company, holder.sub(prefix)::info); + + updateProgress(counter.incrementAndGet(), total); + } + if (!page.hasNext()) { + break; + } + pageRequest = page.nextPageable(); + } + + return super.call(); + } + + private CompanyFileService getCompanyFileService() { + if (companyFileService == null) { + companyFileService = getBean(CompanyFileService.class); + } + return companyFileService; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/tasker/CompanyVerifyTasker.java b/src/main/java/com/ecep/contract/manager/ds/company/tasker/CompanyVerifyTasker.java new file mode 100644 index 0000000..d944c8e --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/tasker/CompanyVerifyTasker.java @@ -0,0 +1,92 @@ +package com.ecep.contract.manager.ds.company.tasker; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.contract.tasker.ContractVerifyComm; +import com.ecep.contract.manager.ui.MessageHolder; +import com.ecep.contract.manager.ui.Tasker; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDate; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; + +public class CompanyVerifyTasker extends Tasker { + @Setter + private CompanyService companyService; + @Getter + @Setter + private Company company; + + + ContractVerifyComm comm = new ContractVerifyComm(); + AtomicBoolean verified = new AtomicBoolean(true); + + public CompanyService getCompanyService() { + if (companyService == null) { + companyService = getBean(CompanyService.class); + } + return companyService; + } + + @Override + protected Object call() throws Exception { + comm.getVerifyCompanyPath().set(false); + comm.getVerifyCompanyStatus().set(false); + comm.getVerifyCompanyCredit().set(false); + return execute(new MessageHolderImpl() { + @Override + public void addMessage(Level level, String message) { + super.addMessage(level, message); + if (level.intValue() > Level.INFO.intValue()) { + verified.set(false); + } + } + }); + } + + @Override + protected Object execute(MessageHolder holder) throws Exception { + updateTitle("验证企业是否符合合规要求"); + + verify(company, holder); + + if (verified.get()) { + updateMessage(Level.CONFIG, "合规验证通过"); + } else { + updateMessage(Level.SEVERE, "合规验证不通过"); + } + return null; + } + + /** + * 核验公司名下的所有合同 + * + * @param company 公司 + * @param holder 输出 + */ + private void verify(Company company, MessageHolder holder) { + LocalDate now = LocalDate.now(); + getCompanyService().verifyEnterpriseStatus(company, now, holder::info); + + // 验证所有的合同 + List list = comm.getContractService().findAllByCompany(company); + if (list.isEmpty()) { + holder.debug("!没有相关合同!"); + return; + } + holder.debug("检索到相关合同 " + list.size() + " 个"); + AtomicInteger counter = new AtomicInteger(0); + long total = list.size(); + for (Contract contract : list) { + holder.debug("核验合同:" + contract.getCode() + ", " + contract.getName()); + comm.verify(company, contract, holder.sub("-- ")); + updateProgress(counter.incrementAndGet(), total); + } + updateProgress(1, 1); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyBankAccountViewModel.java b/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyBankAccountViewModel.java new file mode 100644 index 0000000..4e10dfa --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyBankAccountViewModel.java @@ -0,0 +1,73 @@ +package com.ecep.contract.manager.ds.company.vo; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyBankAccount; +import com.ecep.contract.manager.ds.company.model.CompanyBasedEntity; +import com.ecep.contract.manager.ds.other.model.Bank; +import com.ecep.contract.manager.ds.other.vo.BaseViewModel; +import com.ecep.contract.manager.ds.other.vo.IdentityViewModel; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDate; +import java.util.Objects; + +@Data +@EqualsAndHashCode(callSuper = false) +public class CompanyBankAccountViewModel extends IdentityViewModel implements CompanyBasedViewModel { + private SimpleIntegerProperty id = new SimpleIntegerProperty(); + + private SimpleObjectProperty company = new SimpleObjectProperty<>(); + private SimpleObjectProperty bank = new SimpleObjectProperty<>(); + + private SimpleStringProperty openingBank = new SimpleStringProperty(); + private SimpleStringProperty account = new SimpleStringProperty(); + + private SimpleObjectProperty created = new SimpleObjectProperty<>(); + + private SimpleIntegerProperty version = new SimpleIntegerProperty(); + + @Override + protected void updateFrom(CompanyBankAccount v) { + getId().set(v.getId()); + getCompany().set(v.getCompany()); + getBank().set(v.getBank()); + getOpeningBank().set(v.getOpeningBank()); + getAccount().set(v.getAccount()); + } + + @Override + public boolean copyTo(CompanyBankAccount v) { + boolean modified = super.copyTo(v); + if (!Objects.equals(id.get(), v.getId())) { + v.setId(id.get()); + modified = true; + } + if (!Objects.equals(company.get(), v.getCompany())) { + v.setCompany(company.get()); + modified = true; + } + if (!Objects.equals(bank.get(), v.getBank())) { + v.setBank(bank.get()); + modified = true; + } + if (!Objects.equals(openingBank.get(), v.getOpeningBank())) { + v.setOpeningBank(openingBank.get()); + modified = true; + } + if (!Objects.equals(account.get(), v.getAccount())) { + v.setAccount(account.get()); + modified = true; + } + return modified; + } + + public static CompanyBankAccountViewModel from(CompanyBankAccount v) { + CompanyBankAccountViewModel vm = new CompanyBankAccountViewModel(); + vm.updateFrom(v); + return vm; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyBasedViewModel.java b/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyBasedViewModel.java new file mode 100644 index 0000000..5d43401 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyBasedViewModel.java @@ -0,0 +1,8 @@ +package com.ecep.contract.manager.ds.company.vo; + +import com.ecep.contract.manager.ds.company.model.Company; +import javafx.beans.property.SimpleObjectProperty; + +public interface CompanyBasedViewModel { + SimpleObjectProperty getCompany(); +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyBlackReasonViewModel.java b/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyBlackReasonViewModel.java new file mode 100644 index 0000000..77f0434 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyBlackReasonViewModel.java @@ -0,0 +1,102 @@ +package com.ecep.contract.manager.ds.company.vo; + +import com.ecep.contract.manager.ds.company.BlackReasonType; +import com.ecep.contract.manager.ds.company.model.CompanyBlackReason; +import com.ecep.contract.manager.ds.other.vo.BaseViewModel; +import com.ecep.contract.manager.ds.other.vo.IdentityViewModel; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +import java.time.LocalDate; +import java.util.Objects; + +@Data +@EqualsAndHashCode(callSuper = false) +@ToString +public class CompanyBlackReasonViewModel extends IdentityViewModel { + private SimpleIntegerProperty id = new SimpleIntegerProperty(); + private SimpleObjectProperty type = new SimpleObjectProperty<>(); + + private SimpleStringProperty applyName = new SimpleStringProperty(); + private SimpleStringProperty blackReason = new SimpleStringProperty(); + private SimpleStringProperty description = new SimpleStringProperty(); + private SimpleStringProperty key = new SimpleStringProperty(); + + private SimpleObjectProperty applyDate = new SimpleObjectProperty<>(); + private SimpleObjectProperty updateTime = new SimpleObjectProperty<>(); + private SimpleObjectProperty createTime = new SimpleObjectProperty<>(); + private SimpleObjectProperty includeDate = new SimpleObjectProperty<>(); + + + public static CompanyBlackReasonViewModel from(CompanyBlackReason reason) { + CompanyBlackReasonViewModel model = new CompanyBlackReasonViewModel(); + model.update(reason); + return model; + } + + @Override + protected void updateFrom(CompanyBlackReason v) { + getId().set(v.getId()); + getType().set(v.getType()); + getApplyName().set(v.getApplyName()); + getBlackReason().set(v.getBlackReason()); + getDescription().set(v.getDescription()); + getKey().set(v.getKey()); + getApplyDate().set(v.getApplyDate()); + getUpdateTime().set(v.getUpdateTime()); + getCreateTime().set(v.getCreateTime()); + getIncludeDate().set(v.getIncludeDate()); + } + + @Override + public boolean copyTo(CompanyBlackReason v) { + boolean modified = super.copyTo(v); + if (!Objects.equals(id.get(), v.getId())) { + v.setId(id.get()); + modified = true; + } + if (!Objects.equals(type.get(), v.getType())) { + v.setType(type.get()); + modified = true; + } + if (!Objects.equals(applyName.get(), v.getApplyName())) { + v.setApplyName(applyName.get()); + modified = true; + } + if (!Objects.equals(blackReason.get(), v.getBlackReason())) { + v.setBlackReason(blackReason.get()); + modified = true; + } + if (!Objects.equals(description.get(), v.getDescription())) { + v.setDescription(description.get()); + modified = true; + } + if (!Objects.equals(key.get(), v.getKey())) { + v.setKey(key.get()); + modified = true; + } + if (!Objects.equals(applyDate.get(), v.getApplyDate())) { + v.setApplyDate(applyDate.get()); + modified = true; + } + if (!Objects.equals(updateTime.get(), v.getUpdateTime())) { + v.setUpdateTime(updateTime.get()); + modified = true; + } + if (!Objects.equals(createTime.get(), v.getCreateTime())) { + v.setCreateTime(createTime.get()); + modified = true; + } + if (!Objects.equals(includeDate.get(), v.getIncludeDate())) { + v.setIncludeDate(includeDate.get()); + modified = true; + } + + return modified; + } + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyContactViewModel.java b/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyContactViewModel.java new file mode 100644 index 0000000..e41edc0 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyContactViewModel.java @@ -0,0 +1,60 @@ +package com.ecep.contract.manager.ds.company.vo; + +import com.ecep.contract.manager.ds.company.model.CompanyContact; +import com.ecep.contract.manager.ds.other.vo.BaseViewModel; +import com.ecep.contract.manager.ds.other.vo.IdentityViewModel; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDate; + +@Data +@EqualsAndHashCode(callSuper = false) +public class CompanyContactViewModel extends IdentityViewModel { + private SimpleIntegerProperty id = new SimpleIntegerProperty(); + private SimpleStringProperty name = new SimpleStringProperty(); + private SimpleStringProperty position = new SimpleStringProperty(); + private SimpleStringProperty phone = new SimpleStringProperty(); + private SimpleStringProperty email = new SimpleStringProperty(); + private SimpleStringProperty address = new SimpleStringProperty(); + private SimpleStringProperty u8Code = new SimpleStringProperty(); + private SimpleStringProperty memo = new SimpleStringProperty(); + private SimpleIntegerProperty version = new SimpleIntegerProperty(); + private SimpleObjectProperty created = new SimpleObjectProperty<>(); + + public static CompanyContactViewModel from(CompanyContact contact) { + CompanyContactViewModel model = new CompanyContactViewModel(); + model.update(contact); + return model; + } + + + public void updateFrom(CompanyContact v) { + id.set(v.getId()); + name.set(v.getName()); + position.set(v.getPosition()); + phone.set(v.getPhone()); + email.set(v.getEmail()); + address.set(v.getAddress()); + u8Code.set(v.getU8Code()); + created.set(v.getCreated()); + memo.set(v.getMemo()); + version.set(v.getVersion()); + } + + public boolean copyTo(CompanyContact v) { + v.setId(id.get()); + v.setName(name.get()); + v.setPosition(position.get()); + v.setPhone(phone.get()); + v.setEmail(email.get()); + v.setAddress(address.get()); + v.setU8Code(u8Code.get()); + v.setCreated(created.get()); + v.setMemo(memo.get()); + return true; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyExtendInfoViewModel.java b/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyExtendInfoViewModel.java new file mode 100644 index 0000000..32e4e58 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyExtendInfoViewModel.java @@ -0,0 +1,60 @@ +package com.ecep.contract.manager.ds.company.vo; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyExtendInfo; +import com.ecep.contract.manager.ds.other.vo.BaseViewModel; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Objects; + +@Data +@EqualsAndHashCode(callSuper = false) +public class CompanyExtendInfoViewModel extends BaseViewModel { + private SimpleIntegerProperty id = new SimpleIntegerProperty(); + /** + * 关联的公司 + */ + private SimpleObjectProperty company = new SimpleObjectProperty<>(); + /** + * 是否禁用核验 + */ + private SimpleBooleanProperty disableVerify = new SimpleBooleanProperty(); + private SimpleIntegerProperty version = new SimpleIntegerProperty(); + + public static CompanyExtendInfoViewModel from(CompanyExtendInfo v) { + CompanyExtendInfoViewModel vm = new CompanyExtendInfoViewModel(); + vm.updateFrom(v); + return vm; + } + + @Override + protected void updateFrom(CompanyExtendInfo v) { + super.updateFrom(v); + getId().set(v.getId()); + getCompany().set(v.getCompany()); + getDisableVerify().set(v.isDisableVerify()); + getVersion().set(v.getVersion()); + } + + @Override + public boolean copyTo(CompanyExtendInfo v) { + boolean modified = super.copyTo(v); + if (!Objects.equals(id.get(), v.getId())) { + v.setId(id.get()); + modified = true; + } + if (!Objects.equals(company.get(), v.getCompany())) { + v.setCompany(company.get()); + modified = true; + } + if (!Objects.equals(disableVerify.get(), v.isDisableVerify())) { + v.setDisableVerify(disableVerify.get()); + modified = true; + } + return modified; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyFileViewModel.java b/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyFileViewModel.java new file mode 100644 index 0000000..76545ab --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyFileViewModel.java @@ -0,0 +1,77 @@ +package com.ecep.contract.manager.ds.company.vo; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.CompanyFile; +import com.ecep.contract.manager.ds.company.CompanyFileType; +import com.ecep.contract.manager.ds.other.vo.BaseViewModel; +import com.ecep.contract.manager.ds.other.vo.IdentityViewModel; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDate; +import java.util.Objects; + +/** + * View Model for {@link CompanyFile} + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class CompanyFileViewModel extends IdentityViewModel { + SimpleIntegerProperty id = new SimpleIntegerProperty(); + SimpleObjectProperty company = new SimpleObjectProperty<>(); + SimpleObjectProperty type = new SimpleObjectProperty<>(); + SimpleObjectProperty applyDate = new SimpleObjectProperty<>(); + SimpleObjectProperty expiringDate = new SimpleObjectProperty<>(); + SimpleStringProperty filePath = new SimpleStringProperty(); + + + @Override + protected void updateFrom(CompanyFile v) { + id.set(v.getId()); + company.set(v.getCompany()); + type.set(v.getType()); + applyDate.set(v.getApplyDate()); + expiringDate.set(v.getExpiringDate()); + filePath.set(v.getFilePath()); + } + + public boolean copyTo(CompanyFile v) { + boolean modified = super.copyTo(v); + if (!Objects.equals(id.get(), v.getId())) { + v.setId(id.get()); + modified = true; + } + if (!Objects.equals(company.get(), v.getCompany())) { + v.setCompany(company.get()); + modified = true; + } + + if (!Objects.equals(type.get(), v.getType())) { + v.setType(type.get()); + modified = true; + } + + if (!Objects.equals(applyDate.get(), v.getApplyDate())) { + v.setApplyDate(applyDate.get()); + modified = true; + } + if (!Objects.equals(expiringDate.get(), v.getExpiringDate())) { + v.setExpiringDate(expiringDate.get()); + modified = true; + } + if (!Objects.equals(filePath.get(), v.getFilePath())) { + v.setFilePath(filePath.get()); + modified = true; + } + return modified; + } + + public static CompanyFileViewModel from(CompanyFile file) { + CompanyFileViewModel model = new CompanyFileViewModel(); + model.update(file); + return model; + } +} \ No newline at end of file diff --git a/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyOldNameViewModel.java b/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyOldNameViewModel.java new file mode 100644 index 0000000..bdd7b4e --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyOldNameViewModel.java @@ -0,0 +1,60 @@ +package com.ecep.contract.manager.ds.company.vo; + +import com.ecep.contract.manager.ds.company.model.CompanyOldName; +import com.ecep.contract.manager.ds.other.vo.BaseViewModel; +import com.ecep.contract.manager.ds.other.vo.IdentityViewModel; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDate; + +@Data +@EqualsAndHashCode(callSuper = false) +public class CompanyOldNameViewModel extends IdentityViewModel { + private SimpleIntegerProperty id = new SimpleIntegerProperty(); + private SimpleStringProperty name = new SimpleStringProperty(); + private SimpleStringProperty path = new SimpleStringProperty(); + + + + private SimpleObjectProperty beginDate = new SimpleObjectProperty<>(); + private SimpleObjectProperty endDate = new SimpleObjectProperty<>(); + + private SimpleBooleanProperty ambiguity = new SimpleBooleanProperty(); + private SimpleIntegerProperty version = new SimpleIntegerProperty(); + private SimpleStringProperty memo = new SimpleStringProperty(); + + + public static CompanyOldNameViewModel from(CompanyOldName name) { + CompanyOldNameViewModel model = new CompanyOldNameViewModel(); + model.update(name); + return model; + } + + public void updateFrom(CompanyOldName oldName) { + id.set(oldName.getId()); + name.set(oldName.getName()); + path.set(oldName.getPath()); + memo.set(oldName.getMemo()); + beginDate.set(oldName.getBeginDate()); + endDate.set(oldName.getEndDate()); + ambiguity.set(oldName.getAmbiguity()); + version.set(oldName.getVersion()); + + } + + public boolean copyTo(CompanyOldName c) { + c.setId(id.get()); + c.setName(name.get()); + c.setPath(path.get()); + c.setMemo(memo.get()); + c.setBeginDate(beginDate.get()); + c.setEndDate(endDate.get()); + c.setAmbiguity(ambiguity.get()); + return false; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyViewModel.java b/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyViewModel.java new file mode 100644 index 0000000..38b47eb --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/vo/CompanyViewModel.java @@ -0,0 +1,174 @@ +package com.ecep.contract.manager.ds.company.vo; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.other.vo.BaseViewModel; +import com.ecep.contract.manager.ds.other.vo.IdentityViewModel; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDate; +import java.util.Objects; + + +@Data +@EqualsAndHashCode(callSuper = false) +public class CompanyViewModel extends IdentityViewModel { + private SimpleStringProperty name = new SimpleStringProperty(); + private SimpleStringProperty shortName = new SimpleStringProperty(); + private SimpleStringProperty uid = new SimpleStringProperty(); + private SimpleBooleanProperty pathExist = new SimpleBooleanProperty(); + private SimpleStringProperty path = new SimpleStringProperty(); + private SimpleObjectProperty setupDate = new SimpleObjectProperty<>(); + private SimpleStringProperty entStatus = new SimpleStringProperty(); + private SimpleStringProperty entType = new SimpleStringProperty(); + private SimpleStringProperty district = new SimpleStringProperty(); + private SimpleStringProperty industry = new SimpleStringProperty(); + + private SimpleStringProperty regAddress = new SimpleStringProperty(); + private SimpleStringProperty address = new SimpleStringProperty(); + + private SimpleStringProperty telephone = new SimpleStringProperty(); + private SimpleObjectProperty operationPeriodBegin = new SimpleObjectProperty<>(); + private SimpleObjectProperty operationPeriodEnd = new SimpleObjectProperty<>(); + private SimpleStringProperty registeredCapital = new SimpleStringProperty(); + private SimpleStringProperty registeredCapitalCurrency = new SimpleStringProperty(); + private SimpleStringProperty legalRepresentative = new SimpleStringProperty(); + + private SimpleStringProperty memo = new SimpleStringProperty(); + private SimpleObjectProperty created = new SimpleObjectProperty<>(); + + private SimpleIntegerProperty version = new SimpleIntegerProperty(); + + public static CompanyViewModel from(Company company) { + CompanyViewModel model = new CompanyViewModel(); + model.update(company); + return model; + } + + @Override + protected void updateFrom(Company company) { + super.updateFrom(company); + name.set(company.getName()); + shortName.set(company.getShortName()); + uid.set(company.getUniscid()); + if (company.getPathExist() != null) { + pathExist.set(company.getPathExist()); + } + path.set(company.getPath()); + setupDate.set(company.getSetupDate()); + entStatus.set(company.getEntStatus()); + entType.set(company.getEntType()); + district.set(company.getDistrict()); + industry.set(company.getIndustry()); + + regAddress.set(company.getRegAddr()); + address.set(company.getAddress()); + + telephone.set(company.getTelephone()); + operationPeriodBegin.set(company.getOperationPeriodBegin()); + operationPeriodEnd.set(company.getOperationPeriodEnd()); + registeredCapital.set(company.getRegisteredCapital()); + registeredCapitalCurrency.set(company.getRegisteredCapitalCurrency()); + legalRepresentative.set(company.getLegalRepresentative()); + + created.set(company.getCreated()); + memo.set(company.getMemo()); + version.set(company.getVersion()); + } + + public boolean copyTo(Company v) { + boolean modified = super.copyTo(v); + if (!Objects.equals(name.get(), v.getName())) { + v.setName(name.get()); + modified = true; + } + if (!Objects.equals(shortName.get(), v.getShortName())) { + v.setShortName(shortName.get()); + modified = true; + } + if (!Objects.equals(uid.get(), v.getUniscid())) { + v.setUniscid(uid.get()); + modified = true; + } + + if (!Objects.equals(pathExist.get(), v.getPathExist())) { + v.setPathExist(pathExist.get()); + modified = true; + } + if (!Objects.equals(path.get(), v.getPath())) { + v.setPath(path.get()); + modified = true; + } + + if (!Objects.equals(setupDate.get(), v.getSetupDate())) { + v.setSetupDate(setupDate.get()); + modified = true; + } + if (!Objects.equals(entStatus.get(), v.getEntStatus())) { + v.setEntStatus(entStatus.get()); + modified = true; + } + if (!Objects.equals(entType.get(), v.getEntType())) { + v.setEntType(entType.get()); + modified = true; + } + if (!Objects.equals(district.get(), v.getDistrict())) { + v.setDistrict(district.get()); + modified = true; + } + if (!Objects.equals(industry.get(), v.getIndustry())) { + v.setIndustry(industry.get()); + modified = true; + } + + if (!Objects.equals(regAddress.get(), v.getRegAddr())) { + v.setRegAddr(regAddress.get()); + modified = true; + } + if (!Objects.equals(address.get(), v.getAddress())) { + v.setAddress(address.get()); + modified = true; + } + if (!Objects.equals(telephone.get(), v.getTelephone())) { + v.setTelephone(telephone.get()); + modified = true; + } + + if (!Objects.equals(operationPeriodBegin.get(), v.getOperationPeriodBegin())) { + v.setOperationPeriodBegin(operationPeriodBegin.get()); + modified = true; + } + if (!Objects.equals(operationPeriodEnd.get(), v.getOperationPeriodEnd())) { + v.setOperationPeriodEnd(operationPeriodEnd.get()); + modified = true; + } + if (!Objects.equals(registeredCapital.get(), v.getRegisteredCapital())) { + v.setRegisteredCapital(registeredCapital.get()); + modified = true; + } + if (!Objects.equals(registeredCapitalCurrency.get(), v.getRegisteredCapitalCurrency())) { + v.setRegisteredCapitalCurrency(registeredCapitalCurrency.get()); + modified = true; + } + if (!Objects.equals(legalRepresentative.get(), v.getLegalRepresentative())) { + v.setLegalRepresentative(legalRepresentative.get()); + modified = true; + } + + if (!Objects.equals(memo.get(), v.getMemo())) { + v.setMemo(memo.get()); + modified = true; + } + + if (!Objects.equals(created.get(), v.getCreated())) { + v.setCreated(created.get()); + modified = true; + } + + return modified; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/company/vo/InvoiceViewModel.java b/src/main/java/com/ecep/contract/manager/ds/company/vo/InvoiceViewModel.java new file mode 100644 index 0000000..0a0dac5 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/company/vo/InvoiceViewModel.java @@ -0,0 +1,66 @@ +package com.ecep.contract.manager.ds.company.vo; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.model.Invoice; +import com.ecep.contract.manager.ds.other.vo.BaseViewModel; +import com.ecep.contract.manager.ds.other.vo.IdentityViewModel; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDate; +import java.util.Objects; + +@Data +@EqualsAndHashCode(callSuper = false) +public class InvoiceViewModel extends IdentityViewModel { + SimpleIntegerProperty id = new SimpleIntegerProperty(); + SimpleStringProperty code = new SimpleStringProperty(); + SimpleObjectProperty company = new SimpleObjectProperty<>(); + SimpleObjectProperty invoiceDate = new SimpleObjectProperty<>(); + SimpleStringProperty description = new SimpleStringProperty(); + + @Override + protected void updateFrom(Invoice v) { + super.updateFrom(v); + getId().set(v.getId()); + getCode().set(v.getCode()); + getCompany().set(v.getCompany()); + getInvoiceDate().set(v.getInvoiceDate()); + getDescription().set(v.getDescription()); + } + + @Override + public boolean copyTo(Invoice v) { + boolean modified = super.copyTo(v); + if (!Objects.equals(id.get(), v.getId())) { + v.setId(id.get()); + modified = true; + } + if (!Objects.equals(code.get(), v.getCode())) { + v.setCode(code.get()); + modified = true; + } + if (!Objects.equals(company.get(), v.getCompany())) { + v.setCompany(company.get()); + modified = true; + } + if (!Objects.equals(invoiceDate.get(), v.getInvoiceDate())) { + v.setInvoiceDate(invoiceDate.get()); + modified = true; + } + if (!Objects.equals(description.get(), v.getDescription())) { + v.setDescription(description.get()); + modified = true; + } + return modified; + } + + public static InvoiceViewModel from(Invoice invoice) { + InvoiceViewModel vm = new InvoiceViewModel(); + vm.updateFrom(invoice); + return vm; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/ContractFileType.java b/src/main/java/com/ecep/contract/manager/ds/contract/ContractFileType.java new file mode 100644 index 0000000..8577dcb --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/ContractFileType.java @@ -0,0 +1,129 @@ +package com.ecep.contract.manager.ds.contract; + +import lombok.Getter; + +/** + * 合同审批文件 + */ +@Getter +public enum ContractFileType { + /** + * 普通文件,一般的文件当文件没有特殊情况时,默认为普通文件 + */ + General(true, true), + /** + * 成本审批 + */ + Cost(true, false), + /** + * 成本审批表 + */ + CostForm(true, false), + /** + * 采购申请表 + */ + PurchaseRequestForm(false, true), + /** + * 采购合同审批表 + */ + PurchaseContractApprovalForm(false, true), + /** + * 报价 + */ + Quotation(true, false), + /** + * 报价审批表(单) + */ + QuotationApprovalForm(true, false), + /** + * 报价表(单) + */ + QuotationSheet(false, true), + /** + * 合同 + */ + Contract(true, true), + /** + * 销售合同审批表 + */ + ContractApprovalForm(true, false), + /** + * 提货单 + */ + DeliveryOrder(true, true), + /** + * 投标审批表 + */ + BidApprovalForm(true, false), + /** + * 中标通知书 + */ + BidAcceptanceLetter(true, false), + /** + * 项目计划文件(项目策划书) + */ + ProjectPlanningDocument(true, false), + /** + * 提单申请单(发货申请单) + */ + ShippingApplication(true, false), + /** + * 到货签收单 + */ + SignedDeliveryNote(true, false), + /** + * 完工验收单 + */ + AcceptanceForm(true, true), + /** + * 重大项目决策记录单 + */ + CriticalProjectDecisionRecord(true, true) + ; + + final boolean supportCustomer; + final boolean supportVendor; + + ContractFileType(boolean supportCustomer, boolean supportVendor) { + this.supportCustomer = supportCustomer; + this.supportVendor = supportVendor; + } + + /** + * 文件频率 + */ + public enum Frequency { + /** + * 一次性 + */ + Once, + /** + * 每日 + */ + Daily, + /** + * 每周 + */ + Weekly, + /** + * 每两周 + */ + BiWeekly, + /** + * 每月 + */ + Monthly, + /** + * 每季度 + */ + Quarterly, + /** + * 每半年 + */ + HalfYearly, + /** + * 每年 + */ + Yearly; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/ContractPayWay.java b/src/main/java/com/ecep/contract/manager/ds/contract/ContractPayWay.java new file mode 100644 index 0000000..ae61391 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/ContractPayWay.java @@ -0,0 +1,41 @@ +package com.ecep.contract.manager.ds.contract; + +/** + * 付款方式 + */ +public enum ContractPayWay { + + /** + * 付款方式,对应采购合同 + */ + PAY("付"), + /** + * 收款方式, 对应销售合同 + */ + RECEIVE("收"), + + /** + * 其他方式,对应框架协议等 + */ + OTHER("其他"); + + + private final String text; + + ContractPayWay(String text) { + this.text = text; + } + + public static ContractPayWay valueOfText(String way) { + for (ContractPayWay value : values()) { + if (value.getText().equals(way)) { + return value; + } + } + return null; + } + + public String getText() { + return text; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/ContractStringConverter.java b/src/main/java/com/ecep/contract/manager/ds/contract/ContractStringConverter.java new file mode 100644 index 0000000..ab39ea4 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/ContractStringConverter.java @@ -0,0 +1,28 @@ +package com.ecep.contract.manager.ds.contract; + +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ds.other.EntityStringConverter; +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 ContractStringConverter extends EntityStringConverter { + @Lazy + @Autowired + ContractService service; + + public ContractStringConverter() { + } + + @PostConstruct + private void init() { + setInitialized(project -> service.findById(project.getId())); + setSuggestion(service::search); + //TODO 按名称找出,容易出问题 + setFromString(service::findByName); + } +} \ No newline at end of file diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/controller/AbstContractBasedTabSkin.java b/src/main/java/com/ecep/contract/manager/ds/contract/controller/AbstContractBasedTabSkin.java new file mode 100644 index 0000000..3b15c0e --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/controller/AbstContractBasedTabSkin.java @@ -0,0 +1,22 @@ +package com.ecep.contract.manager.ds.contract.controller; + +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ds.contract.vo.ContractViewModel; +import com.ecep.contract.manager.ui.AbstEntityBasedTabSkin; +import com.ecep.contract.manager.ui.TabSkin; +import lombok.Setter; + +public abstract class AbstContractBasedTabSkin + extends AbstEntityBasedTabSkin + implements TabSkin { + + public AbstContractBasedTabSkin(ContractWindowController controller) { + super(controller); + viewModel = controller.getViewModel(); + } + + public ContractService getContractService() { + return controller.contractService; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/controller/AbstContractTableTabSkin.java b/src/main/java/com/ecep/contract/manager/ds/contract/controller/AbstContractTableTabSkin.java new file mode 100644 index 0000000..ff7d67f --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/controller/AbstContractTableTabSkin.java @@ -0,0 +1,32 @@ +package com.ecep.contract.manager.ds.contract.controller; + +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ds.contract.vo.ContractViewModel; +import com.ecep.contract.manager.ds.other.model.IdentityEntity; +import com.ecep.contract.manager.ds.other.vo.IdentityViewModel; +import com.ecep.contract.manager.ui.AbstEntityTableTabSkin; +import com.ecep.contract.manager.ui.TabSkin; +import com.ecep.contract.manager.ui.TableOfTabSkin; +import com.ecep.contract.manager.util.SpecificationUtils; +import org.springframework.data.jpa.domain.Specification; + +public abstract class AbstContractTableTabSkin> + extends AbstEntityTableTabSkin + implements TabSkin, TableOfTabSkin { + + public AbstContractTableTabSkin(ContractWindowController controller) { + super(controller); + } + + public ContractService getContractService() { + return controller.contractService; + } + + @Override + public Specification getSpecification(Contract parent) { + return SpecificationUtils.and(getSpecification(), (root, query, builder) -> { + return builder.equal(root.get("contract"), parent); + }); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractGroupTableCell.java b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractGroupTableCell.java new file mode 100644 index 0000000..c5c06ca --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractGroupTableCell.java @@ -0,0 +1,40 @@ +package com.ecep.contract.manager.ds.contract.controller; + +import com.ecep.contract.manager.ds.contract.model.ContractGroup; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ds.contract.vo.ContractViewModel; +import javafx.scene.control.TableCell; +import org.hibernate.Hibernate; + +import static com.ecep.contract.manager.SpringApp.getBean; + +public class ContractGroupTableCell extends TableCell { + private ContractService contractService; + + public ContractGroupTableCell() { + } + + public ContractGroupTableCell(ContractService contractService) { + this.contractService = contractService; + } + + ContractService getContractService() { + if (contractService == null) { + contractService = getBean(ContractService.class); + } + return contractService; + } + + @Override + protected void updateItem(ContractGroup item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setText(""); + return; + } + if (!Hibernate.isInitialized(item)) { + item = getContractService().findGroupById(item.getId()); + } + setText(item.getName()); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractKindTableCell.java b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractKindTableCell.java new file mode 100644 index 0000000..a5d41d4 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractKindTableCell.java @@ -0,0 +1,41 @@ +package com.ecep.contract.manager.ds.contract.controller; + +import com.ecep.contract.manager.ds.contract.model.ContractKind; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ds.contract.vo.ContractViewModel; +import javafx.scene.control.TableCell; +import org.hibernate.Hibernate; + +import static com.ecep.contract.manager.SpringApp.getBean; + +public class ContractKindTableCell extends TableCell { + private ContractService contractService; + + public ContractKindTableCell() { + } + + public ContractKindTableCell(ContractService contractService) { + this.contractService = contractService; + } + + ContractService getContractService() { + if (contractService == null) { + contractService = getBean(ContractService.class); + } + return contractService; + } + + @Override + protected void updateItem(ContractKind item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setText(""); + return; + } + if (!Hibernate.isInitialized(item)) { + item = getContractService().findKindById(item.getId()); + } + setText(item.getName()); + + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractManagerSkin.java b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractManagerSkin.java new file mode 100644 index 0000000..0089006 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractManagerSkin.java @@ -0,0 +1,128 @@ +package com.ecep.contract.manager.ds.contract.controller; + +import com.ecep.contract.manager.ds.company.controller.CompanyTableCell; +import com.ecep.contract.manager.ds.contract.ContractPayWay; +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.contract.model.ContractGroup; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ds.contract.vo.ContractViewModel; +import com.ecep.contract.manager.ds.other.EmployeeStringConverter; +import com.ecep.contract.manager.ui.AbstEntityManagerSkin; +import com.ecep.contract.manager.ui.ComboBoxUtils; +import com.ecep.contract.manager.ui.ManagerSkin; +import com.ecep.contract.manager.util.MyDateTimeUtils; +import com.ecep.contract.manager.util.SpecificationUtils; +import jakarta.persistence.criteria.Path; +import javafx.application.Platform; +import javafx.scene.control.cell.TextFieldTableCell; +import javafx.util.converter.CurrencyStringConverter; +import javafx.util.converter.LocalDateTimeStringConverter; +import lombok.Setter; +import org.springframework.data.jpa.domain.Specification; + +import java.time.format.DateTimeFormatter; + +public class ContractManagerSkin + extends AbstEntityManagerSkin + implements ManagerSkin { + @Setter + private ContractService contractService; + + public ContractManagerSkin(ContractManagerWindowController controller) { + super(controller); + } + + public ContractService getContractService() { + if (contractService == null) { + contractService = getBean(ContractService.class); + } + return contractService; + } + + @Override + public Specification getSpecification() { + Specification spec = super.getSpecification(); + if (controller.composeViewBtn.isSelected()) { + spec = SpecificationUtils.and(spec, (root, query, builder) -> { + Path parentCode = root.get("parentCode"); + Path payWay = root.get("payWay"); + return builder.or( + builder.equal(payWay, ContractPayWay.RECEIVE), + builder.and( + builder.equal(payWay, ContractPayWay.PAY), + builder.or( + builder.equal(parentCode, ""), + parentCode.isNull() + ) + ) + ); + }); + } + + ContractGroup selectedGroup = controller.groupSelector.getValue(); + if (selectedGroup != null) { + spec = SpecificationUtils.and(spec, (root, query, builder) -> { + return builder.equal(root.get("group"), selectedGroup); + }); + } + return spec; + } + + @SuppressWarnings("unchecked") + @Override + public void initializeTable() { + ComboBoxUtils.initialComboBox(controller.groupSelector, contractService.findAllGroups(), true); + controller.groupSelector.valueProperty().addListener((observable, oldValue, newValue) -> { + loadTableDataSet(false); + }); + + controller.idColumn.setCellValueFactory(param -> param.getValue().getId()); + controller.nameColumn.setCellValueFactory(param -> param.getValue().getName()); + controller.codeColumn.setCellValueFactory(param -> param.getValue().getCode()); + controller.groupColumn.setCellValueFactory(param -> param.getValue().getGroup()); + controller.groupColumn.setCellFactory(param -> new ContractGroupTableCell(contractService)); + controller.typeColumn.setCellValueFactory(param -> param.getValue().getType()); + controller.typeColumn.setCellFactory(param -> new ContractTypeTableCell(contractService)); + controller.kindColumn.setCellValueFactory(param -> param.getValue().getKind()); + controller.kindColumn.setCellFactory(param -> new ContractKindTableCell(contractService)); + + controller.parentCodeColumn.setCellValueFactory(param -> param.getValue().getParentCode()); + + + controller.setupDateColumn.setCellValueFactory(param -> param.getValue().getSetupDate()); + controller.orderDateColumn.setCellValueFactory(param -> param.getValue().getOrderDate()); + controller.startDateColumn.setCellValueFactory(param -> param.getValue().getStartDate()); + + + controller.employeeColumn.setCellValueFactory(param -> param.getValue().getEmployee()); + controller.employeeColumn.setCellFactory(TextFieldTableCell.forTableColumn(getBean(EmployeeStringConverter.class))); + + controller.createdColumn.setCellValueFactory(param -> param.getValue().getCreated()); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(MyDateTimeUtils.DEFAULT_DATETIME_FORMAT_PATTERN); + controller.createdColumn.setCellFactory(TextFieldTableCell.forTableColumn(new LocalDateTimeStringConverter(formatter, null))); + + controller.amountColumn.setCellValueFactory(param -> param.getValue().getAmount()); + controller.amountColumn.setCellFactory(TextFieldTableCell.forTableColumn(new CurrencyStringConverter(getLocale(), "#,##0"))); + + controller.companyColumn.setCellValueFactory(param -> param.getValue().getCompany()); + controller.companyColumn.setCellFactory(param -> new CompanyTableCell<>()); + + Platform.runLater(() -> { + controller.composeViewBtn.selectedProperty().addListener((observable, oldValue, newValue) -> { + loadTableDataSet(false); + }); + controller.parentCodeColumn.visibleProperty().bind(controller.composeViewBtn.selectedProperty().not()); + getTableView().getSortOrder().addAll(controller.createdColumn, controller.setupDateColumn); + }); + } + + @Override + protected void onTableRowDoubleClickedAction(ContractViewModel item) { + showInOwner(item); + } + + private void showInOwner(ContractViewModel model) { + showInOwner(ContractWindowController.class, model); + } + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractManagerWindowController.java b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractManagerWindowController.java new file mode 100644 index 0000000..c61373c --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractManagerWindowController.java @@ -0,0 +1,97 @@ +package com.ecep.contract.manager.ds.contract.controller; + +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.contract.model.ContractGroup; +import com.ecep.contract.manager.ds.contract.model.ContractKind; +import com.ecep.contract.manager.ds.contract.model.ContractType; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ds.contract.tasker.ContractFilesRebuildAllTasker; +import com.ecep.contract.manager.ds.contract.tasker.ContractRepairAllTasker; +import com.ecep.contract.manager.ds.contract.vo.ContractViewModel; +import com.ecep.contract.manager.ds.other.model.Employee; +import com.ecep.contract.manager.ui.AbstManagerWindowController; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.ViewModelService; +import com.ecep.contract.manager.util.UITools; +import javafx.event.ActionEvent; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ComboBox; +import javafx.scene.control.TableColumn; +import javafx.stage.Stage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Lazy +@Scope("prototype") +@Component +@FxmlPath("/ui/contract/contract-manager.fxml") +public class ContractManagerWindowController + extends AbstManagerWindowController { + + public ComboBox groupSelector; + public CheckBox composeViewBtn; + + // columns + public TableColumn idColumn; + public TableColumn nameColumn; + public TableColumn codeColumn; + public TableColumn groupColumn; + public TableColumn typeColumn; + public TableColumn kindColumn; + + public TableColumn parentCodeColumn; + public TableColumn setupDateColumn; + public TableColumn orderDateColumn; + public TableColumn startDateColumn; + public TableColumn employeeColumn; + public TableColumn createdColumn; + public TableColumn amountColumn; + public TableColumn companyColumn; + + @Autowired + private ContractService contractService; + + @Override + public ContractService getViewModelService() { + return contractService; + } + + @Override + protected ContractManagerSkin createDefaultSkin() { + ContractManagerSkin skin = new ContractManagerSkin(this); + skin.setContractService(contractService); + return skin; + } + + @Override + public void show(Stage stage) { + super.show(stage); + getTitle().set("合同管理"); + } + + public void onVerifyAction(ActionEvent event) { + show(ContractVerifyWindowController.class, null); + } + + public void onCreateNewContractAction(ActionEvent event) { + // getSkin().onTableCreateNewAction(event); + } + + public void onContractFilesRebuildAction(ActionEvent event) { + ContractFilesRebuildAllTasker task = new ContractFilesRebuildAllTasker(); + task.setContractService(contractService); + task.setGroup(groupSelector.getValue()); + UITools.showTaskDialogAndWait("合同文件重置", task, null); + } + + public void onContractRepairAllAction(ActionEvent event) { + ContractRepairAllTasker task = new ContractRepairAllTasker(); + UITools.showTaskDialogAndWait("合同同步修复", task, null); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinBase.java b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinBase.java new file mode 100644 index 0000000..e6774fc --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinBase.java @@ -0,0 +1,533 @@ +package com.ecep.contract.manager.ds.contract.controller; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.company.CompanyFileUtils; +import com.ecep.contract.manager.ds.company.CompanyStringConverter; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.contract.ContractPayWay; +import com.ecep.contract.manager.ds.contract.ContractStringConverter; +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.contract.model.ContractType; +import com.ecep.contract.manager.ds.contract.service.ExtendVendorInfoService; +import com.ecep.contract.manager.ds.contract.tasker.ContractRepairComm; +import com.ecep.contract.manager.ds.customer.controller.CompanyCustomerWindowController; +import com.ecep.contract.manager.ds.customer.model.CompanyCustomer; +import com.ecep.contract.manager.ds.customer.service.CompanyCustomerService; +import com.ecep.contract.manager.ds.other.EmployeeStringConverter; +import com.ecep.contract.manager.ds.other.model.Employee; +import com.ecep.contract.manager.ds.project.controller.ProjectWindowController; +import com.ecep.contract.manager.ds.project.model.Project; +import com.ecep.contract.manager.ds.project.model.ProjectSaleType; +import com.ecep.contract.manager.ds.project.service.ProjectService; +import com.ecep.contract.manager.ds.project.service.ProjectStringConverter; +import com.ecep.contract.manager.ds.project.service.SaleTypeService; +import com.ecep.contract.manager.ds.vendor.controller.CompanyVendorWindowController; +import com.ecep.contract.manager.ds.vendor.model.CompanyVendor; +import com.ecep.contract.manager.ds.vendor.service.CompanyVendorService; +import com.ecep.contract.manager.ds.vendor.service.VendorGroupService; +import com.ecep.contract.manager.ui.TabSkin; +import com.ecep.contract.manager.util.MyDateTimeUtils; +import com.ecep.contract.manager.util.UITools; +import javafx.beans.binding.Bindings; +import javafx.event.ActionEvent; +import javafx.scene.control.Tab; +import javafx.scene.control.TextField; +import javafx.scene.control.TextInputDialog; +import javafx.stage.DirectoryChooser; +import javafx.util.StringConverter; +import javafx.util.converter.LocalDateStringConverter; +import javafx.util.converter.LocalDateTimeStringConverter; +import javafx.util.converter.NumberStringConverter; +import lombok.Setter; +import org.controlsfx.control.textfield.TextFields; +import org.controlsfx.glyphfont.Glyph; +import org.hibernate.Hibernate; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.*; + +/** + * + */ +public class ContractTabSkinBase + extends AbstContractBasedTabSkin + implements TabSkin { + + @Setter + private CompanyCustomerService companyCustomerService; + @Setter + private SaleTypeService saleTypeService; + @Setter + private ExtendVendorInfoService extendVendorInfoService; + @Setter + private VendorGroupService vendorGroupService; + + public ContractTabSkinBase(ContractWindowController controller) { + super(controller); + } + + @Override + public Tab getTab() { + return controller.baseInfoTab; + } + + @Override + public void initializeTab() { + controller.openMainContractBtn.setOnAction(this::onContractOpenMainContractAction); + controller.contractRenameBtn.setOnAction(this::onContractRenameAction); + + controller.contractPathCreateBtn.setOnAction(this::onContractCreatePathAction); + controller.contractPathChangeBtn.setOnAction(this::onContractChangePathAction); + controller.contractPathAsNameBtn.setOnAction(this::onContractPathSameAsNameAction); + controller.contractPathAsCodeBtn.setOnAction(this::onContractPathSameAsCodeAction); + controller.linkContractProjectBtn.setText(viewModel.getProject().get() == null ? "0" : "1"); + controller.linkContractProjectBtn.textProperty().bind(viewModel.getProject().map(v -> { + return v == null ? "关联" : "打开"; + })); + + Glyph linkContractProjectBtnIcon = new Glyph("FontAwesome", viewModel.getProject().get() == null ? "UNLINK" : "LINK"); + controller.linkContractProjectBtn.graphicProperty().set(linkContractProjectBtnIcon); + linkContractProjectBtnIcon.iconProperty().bind(viewModel.getProject().map(v -> { + return v == null ? "UNLINK" : "LINK"; + })); + controller.linkContractProjectBtn.setOnAction(this::onLinkContractProjectAction); + + controller.openMainContractBtn.disableProperty().bind(viewModel.getParentCode().isEmpty()); + + // 与主合同编码是否为空绑定按钮可以 + controller.calcMainContractNoBtn.disableProperty().bind(viewModel.getParentCode().isNotEmpty()); + controller.calcMainContractNoBtn.setOnAction(this::calcMainContractNoAction); + + controller.payWayField.textProperty().bind(viewModel.getPayWay().map(ContractPayWay::getText)); + + controller.nameField.textProperty().bind(viewModel.getName()); + controller.codeField.textProperty().bind(viewModel.getCode()); + + controller.parentCodeField.textProperty().bindBidirectional(viewModel.getParentCode()); + parentCodeFieldAutoCompletion(controller.parentCodeField); + + controller.stateField.textProperty().bind(viewModel.getState()); + + initializeBaseTabCompanyFieldAutoCompletion(controller.companyField); + + controller.groupField.textProperty().bind(viewModel.getGroup().map(group -> { + if (!Hibernate.isInitialized(group)) { + group = getContractService().findGroupById(group.getId()); + } + return group.getCode() + " " + group.getName() + " " + group.getTitle(); + })); + + controller.typeField.textProperty().bind(viewModel.getType().map(type -> { + if (!Hibernate.isInitialized(type)) { + type = getContractService().findTypeById(type.getId()); + } + return type.getCode() + " " + type.getCatalog() + " " + type.getName() + " " + type.getTitle() + "(" + type.getDirection() + ")"; + })); + + controller.kindField.textProperty().bind(viewModel.getKind().map(kind -> { + if (!Hibernate.isInitialized(kind)) { + kind = getContractService().findKindById(kind.getId()); + } + return kind.getCode() + " " + kind.getName() + " " + kind.getTitle(); + })); + + // as customer + controller.openRelativeCompanyCustomerBtn.setOnAction(this::onContractOpenRelativeCompanyCustomerAction); + controller.openRelativeCompanyCustomerBtn.disableProperty().bind(Bindings.createBooleanBinding(() -> { + ContractType type = viewModel.getType().get(); + if (type == null) { + return true; + } + Company company = viewModel.getCompany().get(); + if (company == null) { + return true; + } + if (!Hibernate.isInitialized(type)) { + type = getContractService().findTypeById(type.getId()); + } + return !Objects.equals(type.getDirection(), "收"); + }, viewModel.getType())); + controller.openRelativeCompanyCustomerBtn.managedProperty().bind(controller.openRelativeCompanyCustomerBtn.disabledProperty().not()); + controller.openRelativeCompanyCustomerBtn.visibleProperty().bind(controller.openRelativeCompanyCustomerBtn.managedProperty()); + + // as vendor + controller.openRelativeCompanyVendorBtn.setOnAction(this::onContractOpenRelativeCompanyVendorAction); + controller.openRelativeCompanyVendorBtn.disableProperty().bind(Bindings.createBooleanBinding(() -> { + ContractType type = viewModel.getType().get(); + if (type == null) { + return true; + } + Company company = viewModel.getCompany().get(); + if (company == null) { + return true; + } + if (!Hibernate.isInitialized(type)) { + type = getContractService().findTypeById(type.getId()); + } + return !Objects.equals(type.getDirection(), "付"); + }, viewModel.getType())); + controller.openRelativeCompanyVendorBtn.managedProperty().bind(controller.openRelativeCompanyVendorBtn.disabledProperty().not()); + controller.openRelativeCompanyVendorBtn.visibleProperty().bind(controller.openRelativeCompanyVendorBtn.managedProperty()); + + LocalDateStringConverter localDateStringConverter = controller.localDateStringConverter; + + controller.setupDateField.setConverter(localDateStringConverter); + controller.setupDateField.valueProperty().bindBidirectional(viewModel.getSetupDate()); + controller.startDateField.setConverter(localDateStringConverter); + controller.startDateField.valueProperty().bindBidirectional(viewModel.getStartDate()); + controller.endDateField.setConverter(localDateStringConverter); + controller.endDateField.valueProperty().bindBidirectional(viewModel.getEndDate()); + controller.orderDateField.setConverter(localDateStringConverter); + controller.orderDateField.valueProperty().bindBidirectional(viewModel.getOrderDate()); + controller.inureDateField.setConverter(localDateStringConverter); + controller.inureDateField.valueProperty().bindBidirectional(viewModel.getInureDate()); + controller.varyDateField.setConverter(localDateStringConverter); + controller.varyDateField.valueProperty().bindBidirectional(viewModel.getVaryDate()); + + EmployeeStringConverter employeeStringConverter = SpringApp.getBean(EmployeeStringConverter.class); + UITools.autoCompletion(controller.setupPersonField, viewModel.getSetupPerson(), + employeeStringConverter::suggest, employeeStringConverter); + UITools.autoCompletion(controller.inurePersonField, viewModel.getInurePerson(), + employeeStringConverter::suggest, employeeStringConverter); + UITools.autoCompletion(controller.varyPersonField, viewModel.getVaryPerson(), + employeeStringConverter::suggest, employeeStringConverter); + UITools.autoCompletion(controller.employeeField, viewModel.getEmployee(), + employeeStringConverter::suggest, employeeStringConverter); + UITools.autoCompletion(controller.handlerField, viewModel.getHandler(), + employeeStringConverter::suggest, employeeStringConverter); + + initializeBaseTabProjectFieldAutoCompletion(controller.projectField); + + controller.pathField.textProperty().bind(viewModel.getPath()); +// controller.createdField.textProperty().bind(viewModel.getCreated().map(MyDateTimeUtils::format)); + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(MyDateTimeUtils.DEFAULT_DATETIME_FORMAT_PATTERN); + controller.createdField.textProperty().bindBidirectional(viewModel.getCreated(), new LocalDateTimeStringConverter(dateTimeFormatter, null)); + controller.guidField.textProperty().bind(viewModel.getGuid()); + controller.descriptionField.textProperty().bindBidirectional(viewModel.getDescription()); + + + NumberStringConverter numberStringConverter = new NumberStringConverter(controller.getLocale(), "#,##0.00"); + controller.amountField.textProperty().bindBidirectional(viewModel.getAmount(), numberStringConverter); + controller.totalQuantityField.textProperty().bindBidirectional(viewModel.getTotalQuantity(), numberStringConverter); + controller.totalAmountField.textProperty().bindBidirectional(viewModel.getTotalAmount(), numberStringConverter); + controller.totalUnTaxAmountField.textProperty().bindBidirectional(viewModel.getTotalUnTaxAmount(), numberStringConverter); + controller.execQuantityField.textProperty().bindBidirectional(viewModel.getExecQuantity(), numberStringConverter); + controller.execAmountField.textProperty().bindBidirectional(viewModel.getExecAmount(), numberStringConverter); + controller.execUnTaxAmountField.textProperty().bindBidirectional(viewModel.getExecUnTaxAmount(), numberStringConverter); + + controller.versionLabel.textProperty().bind(viewModel.getVersion().asString()); + } + + private void parentCodeFieldAutoCompletion(TextField textField) { + ContractStringConverter converter = SpringApp.getBean(ContractStringConverter.class); + converter.setFormater(Contract::getCode); + TextFields.bindAutoCompletion(textField, converter::suggest, converter); + } + + private void calcMainContractNoAction(ActionEvent event) { + try { + Contract contract = controller.getEntity(); + if (getContractService().updateParentCode(contract)) { + save(contract); + } + } catch (NoSuchElementException e) { + viewModel.getParentCode().set(e.getMessage()); + } + } + + public void onLinkContractProjectAction(ActionEvent event) { + Project project = viewModel.getProject().get(); + if (project != null) { + ProjectWindowController.show(project, null); + return; + } + + String code = viewModel.getParentCode().get(); + if (!StringUtils.hasText(code)) { + code = viewModel.getCode().get(); + if (code.contains("-")) { + code = code.substring(0, code.indexOf("-")); + } + if (!StringUtils.hasText(code)) { + setStatus("未设置合同编号, 无法关联项目"); + return; + } + } + + project = getProjectService().findByCode(code); + if (project == null) { + if (code.contains("-")) { + setStatus("未找到关联项目"); + return; + } + + + project = new Project(); + + String name = viewModel.getName().get(); + if (StringUtils.hasText(name)) { + project.setName(name); + } + + getProjectService().applyCode(project, code); + + // 设置项目的开始创建时间,从合同的创建时间、提交日期、开始日期中取最早的日期 + List dates = new ArrayList<>(); + if (viewModel.getCreated().get() != null) { + dates.add(viewModel.getCreated().get().toLocalDate()); + } + if (viewModel.getSetupDate().get() != null) { + dates.add(viewModel.getSetupDate().get()); + } + if (viewModel.getStartDate().get() != null) { + dates.add(viewModel.getStartDate().get()); + } + dates.stream().min(LocalDate::compareTo).ifPresent(project::setCreated); + + Employee applicant = viewModel.getEmployee().get(); + if (applicant != null) { + project.setApplicant(applicant); + } + if (viewModel.getAmount().get() > 0) { + project.setAmount((int) viewModel.getAmount().get()); + } + + project = getProjectService().save(project); + } + viewModel.getProject().set(project); + + save(); + } + + public void onContractRenameAction(ActionEvent event) { + TextInputDialog dialog = new TextInputDialog(); + dialog.setTitle("合同名称变更"); + dialog.setHeaderText("请输入变更的合同名称"); + dialog.setContentText(controller.nameField.getText()); + + Optional result = dialog.showAndWait(); + if (result.isPresent()) { + System.out.println("You entered: " + result.get()); + } else { + System.out.println("No input provided."); + } + } + + public void onContractOpenMainContractAction(ActionEvent event) { + String parentCode = viewModel.getParentCode().get(); + + Contract parent = getContractService().findByCode(parentCode); + if (parent == null) { + UITools.showAlertAndWait("没有找到上级合同:" + parentCode); + return; + } + ContractWindowController.show(parent, controller.root.getScene().getWindow()); + } + + + /** + * 创建合同存储目录 + */ + public void onContractCreatePathAction(ActionEvent event) { + Contract contract = getEntity(); + ContractRepairComm comm = new ContractRepairComm(); + if (CompanyFileUtils.exists(contract.getPath())) { + File dir = new File(contract.getPath()); + if (!dir.exists()) { + dir = comm.makePath(contract, (level, message) -> controller.setStatus(message)); + contract.setPath(dir.getAbsolutePath()); + save(contract); + } + } + if (comm.makePathAbsent(contract, (level, message) -> controller.setStatus(message))) { + save(contract); + } else { + controller.setStatus("目录存在或创建失败"); + } + } + + public void onContractChangePathAction(ActionEvent event) { + DirectoryChooser chooser = new DirectoryChooser(); + Contract 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) { + if (entity.getPayWay() == ContractPayWay.RECEIVE) { + // 根据项目设置初始目录 + Project project = entity.getProject(); + if (project != null) { + if (!Hibernate.isInitialized(project)) { + project = getProjectService().findById(project.getId()); + } + // 根据项目销售方式设置初始目录 + ProjectSaleType saleType = project.getSaleType(); + if (saleType != null) { + if (!Hibernate.isInitialized(saleType)) { + saleType = getSaleTypeService().findById(saleType.getId()); + } + File dir = new File(saleType.getPath()); + if (saleType.isStoreByYear()) { + dir = new File(dir, "20" + project.getCodeYear()); + } + initialDirectory = dir; + } + } + } else if (entity.getPayWay() == ContractPayWay.PAY) { + // 根据上级合同设置初始目录 + String parentCode = entity.getParentCode(); + if (StringUtils.hasText(parentCode)) { + Contract parent = getContractService().findByCode(parentCode); + if (parent != null) { + if (StringUtils.hasText(parent.getPath())) { + File dir = new File(parent.getPath()); + if (dir.exists()) { + initialDirectory = dir; + } + } + } + } + } + if (initialDirectory == null) { + initialDirectory = getContractService().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 onContractPathSameAsNameAction(ActionEvent event) { + Contract contract = getEntity(); + String path = contract.getPath(); + if (!StringUtils.hasText(path)) { + return; + } + File file = new File(path); + if (!file.exists()) { + return; + } + + if (file.getName().equals(contract.getName())) { + return; + } + File dest = new File(file.getParent(), contract.getName()); + if (file.renameTo(dest)) { + contract.setPath(dest.getAbsolutePath()); + save(contract); + controller.setStatus("目录变更为" + dest.getName()); + } + } + + public void onContractPathSameAsCodeAction(ActionEvent event) { + Contract contract = getEntity(); + String path = contract.getPath(); + if (!StringUtils.hasText(path)) { + return; + } + File file = new File(path); + if (!file.exists()) { + return; + } + if (file.getName().equals(contract.getCode())) { + return; + } + File dest = new File(file.getParent(), contract.getCode()); + if (file.renameTo(dest)) { + contract.setPath(dest.getAbsolutePath()); + save(contract); + controller.setStatus("目录变更为" + dest.getName()); + } + } + + public void onContractOpenRelativeCompanyCustomerAction(ActionEvent event) { + Contract contract = getEntity(); + Company company = contract.getCompany(); + CompanyCustomer companyCustomer = getCompanyCustomerService().findByCompany(company); + CompanyCustomerWindowController.show(companyCustomer, null); + } + + public void onContractOpenRelativeCompanyVendorAction(ActionEvent event) { + Contract contract = getEntity(); + Company company = contract.getCompany(); + CompanyVendorService service = SpringApp.getBean(CompanyVendorService.class); + CompanyVendor companyVendor = service.findByCompany(company); + CompanyVendorWindowController.show(companyVendor, null); + } + + + private void initializeBaseTabCompanyFieldAutoCompletion(TextField textField) { + CompanyStringConverter converter = SpringApp.getBean(CompanyStringConverter.class); + UITools.autoCompletion(textField, viewModel.getCompany(), converter); + } + + private void initializeBaseTabProjectFieldAutoCompletion(TextField textField) { + ProjectStringConverter converter = SpringApp.getBean(ProjectStringConverter.class); + StringConverter stringConverter = new StringConverter<>() { + @Override + public String toString(Project object) { + Project project = converter.prefixObject(object); + if (project == null) { + return ""; + } + return project.getCode(); + } + + @Override + public Project fromString(String string) { + return converter.fromString(string); + } + }; + + converter.setFormater(Project::getCode); + UITools.autoCompletion(textField, viewModel.getProject(), converter::suggest, stringConverter); + } + + + public CompanyCustomerService getCompanyCustomerService() { + if (companyCustomerService == null) { + companyCustomerService = SpringApp.getBean(CompanyCustomerService.class); + } + return companyCustomerService; + } + + public ProjectService getProjectService() { + return controller.projectService; + } + + public SaleTypeService getSaleTypeService() { + if (saleTypeService == null) { + saleTypeService = SpringApp.getBean(SaleTypeService.class); + } + return saleTypeService; + } + + public ExtendVendorInfoService getExtendVendorInfoService() { + if (extendVendorInfoService == null) { + extendVendorInfoService = SpringApp.getBean(ExtendVendorInfoService.class); + } + return extendVendorInfoService; + } + + public VendorGroupService getVendorGroupService() { + if (vendorGroupService == null) { + vendorGroupService = SpringApp.getBean(VendorGroupService.class); + } + return vendorGroupService; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinExtendVendorInfo.java b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinExtendVendorInfo.java new file mode 100644 index 0000000..f2e9d77 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinExtendVendorInfo.java @@ -0,0 +1,157 @@ +package com.ecep.contract.manager.ds.contract.controller; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.contract.model.ExtendVendorInfo; +import com.ecep.contract.manager.ds.contract.service.ExtendVendorInfoService; +import com.ecep.contract.manager.ds.contract.vo.ExtendVendorInfoViewModel; +import com.ecep.contract.manager.ds.vendor.model.VendorGroup; +import com.ecep.contract.manager.ds.vendor.service.VendorGroupService; +import com.ecep.contract.manager.ui.ComboBoxUtils; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.TabSkin; +import com.ecep.contract.manager.util.UITools; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.property.BooleanProperty; +import javafx.scene.control.*; +import javafx.util.converter.NumberStringConverter; +import lombok.Setter; +import org.hibernate.Hibernate; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +@FxmlPath("/ui/contract/contract-tab-ext-vendor-info.fxml") +public class ContractTabSkinExtendVendorInfo + extends AbstContractBasedTabSkin + implements TabSkin { + + + @Setter + private ExtendVendorInfoService extendVendorInfoService; + @Setter + private VendorGroupService vendorGroupService; + + public ComboBox vendorGroupField; + public Label vendorGroupLabel; + public TextField sequenceNumberField; + public CheckBox assignedProviderField; + public CheckBox prePurchaseField; + + CompletableFuture loadedFuture; + private ExtendVendorInfoViewModel viewModel = new ExtendVendorInfoViewModel(); + + public ContractTabSkinExtendVendorInfo(ContractWindowController controller) { + super(controller); + } + + @Override + public Tab getTab() { + return controller.extendVendorInfo; + } + + @Override + public void initializeUIComponents() { + super.initializeUIComponents(); + listenTabSelectionChanged(); + } + + @Override + public BooleanProperty changeProperty() { + return viewModel.getChanged(); + } + + @Override + public void onTabShown() { + if (loadedFuture == null) { + loadedFuture = CompletableFuture.supplyAsync(() -> { + initializeTab(); + Contract contract = getEntity(); + return loadExtendVendorInfo(contract); + }); + } + } + + void updateViewModel(ExtendVendorInfo info) { + if (Platform.isFxApplicationThread()) { + viewModel.update(info); + } else { + Platform.runLater(() -> viewModel.update(info)); + } + } + + private ExtendVendorInfo loadExtendVendorInfo(Contract contract) { + ExtendVendorInfoService service = getExtendVendorInfoService(); + try { + ExtendVendorInfo info = service.findByContract(contract); + if (info == null) { + info = service.newInstanceByContract(contract); + info = service.save(info); + } + updateViewModel(info); + viewModel.bindListener(); + return info; + } catch (Exception e) { + UITools.showExceptionAndWait("加载成本时发生错误", e); + throw e; + } + } + + @Override + public void initializeTab() { + List groups = getVendorGroupService().findAll(); + ComboBoxUtils.initialComboBox(vendorGroupField, groups, true); + vendorGroupField.valueProperty().bindBidirectional(viewModel.getGroup()); + vendorGroupLabel.textProperty().bind(vendorGroupField.valueProperty().map(v -> { + if (v == null) { + return "-"; + } + if (!Hibernate.isInitialized(v)) { + v = getVendorGroupService().findById(v.getId()); + viewModel.getGroup().set(v); + } + return v.getDescription(); + })); + + + sequenceNumberField.textProperty().bindBidirectional(viewModel.getCodeSequenceNumber(), new NumberStringConverter()); + assignedProviderField.selectedProperty().bindBidirectional(viewModel.getAssignedProvider()); + assignedProviderField.disableProperty().bind(Bindings.createBooleanBinding(() -> { + VendorGroup group = viewModel.getGroup().get(); + if (group == null) { + return false; + } + return !group.isPriceComparison(); + }, viewModel.getGroup())); + + prePurchaseField.selectedProperty().bindBidirectional(viewModel.getPrePurchase()); + } + + + @Override + public void save() { + if (loadedFuture != null) { + ExtendVendorInfo vendorInfo = loadedFuture.join(); + if (viewModel.copyTo(vendorInfo)) { + ExtendVendorInfo saved = getExtendVendorInfoService().save(vendorInfo); + updateViewModel(saved); + loadedFuture = CompletableFuture.completedFuture(saved); + } + } + } + + public ExtendVendorInfoService getExtendVendorInfoService() { + if (extendVendorInfoService == null) { + extendVendorInfoService = SpringApp.getBean(ExtendVendorInfoService.class); + } + return extendVendorInfoService; + } + + public VendorGroupService getVendorGroupService() { + if (vendorGroupService == null) { + vendorGroupService = SpringApp.getBean(VendorGroupService.class); + } + return vendorGroupService; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinFiles.java b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinFiles.java new file mode 100644 index 0000000..773b755 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinFiles.java @@ -0,0 +1,820 @@ +package com.ecep.contract.manager.ds.contract.controller; + +import com.ecep.contract.manager.Desktop; +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.cloud.u8.ctx.ContractCtx; +import com.ecep.contract.manager.ds.company.CompanyFileUtils; +import com.ecep.contract.manager.ds.contract.ContractFileType; +import com.ecep.contract.manager.ds.contract.ContractPayWay; +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.contract.model.ContractFile; +import com.ecep.contract.manager.ds.contract.model.ContractFileTypeLocal; +import com.ecep.contract.manager.ds.contract.model.ContractType; +import com.ecep.contract.manager.ds.contract.service.ContractFileService; +import com.ecep.contract.manager.ds.contract.tasker.ContractFilesRebuildTasker; +import com.ecep.contract.manager.ds.contract.tasker.CustomerContractCostFormUpdateTask; +import com.ecep.contract.manager.ds.contract.vo.ContractFileViewModel; +import com.ecep.contract.manager.ui.EditableEntityTableTabSkin; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.TabSkin; +import com.ecep.contract.manager.ui.util.LocalDateFieldTableCell; +import com.ecep.contract.manager.util.MyDateTimeUtils; +import com.ecep.contract.manager.util.UITools; +import javafx.beans.binding.Bindings; +import javafx.collections.FXCollections; +import javafx.collections.MapChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.ObservableMap; +import javafx.event.ActionEvent; +import javafx.scene.control.*; +import javafx.scene.control.cell.TextFieldTableCell; +import javafx.stage.WindowEvent; +import javafx.util.StringConverter; +import lombok.Setter; +import org.apache.pdfbox.Loader; +import org.apache.pdfbox.io.IOUtils; +import org.apache.pdfbox.io.MemoryUsageSetting; +import org.apache.pdfbox.io.RandomAccessStreamCache; +import org.apache.pdfbox.multipdf.PDFMergerUtility; +import org.apache.pdfbox.multipdf.Splitter; +import org.apache.pdfbox.pdfwriter.compress.CompressParameters; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory; +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +import org.springframework.util.StringUtils; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * 合同文件 + */ +@FxmlPath("/ui/contract/contract-tab-file.fxml") +public class ContractTabSkinFiles + extends AbstContractTableTabSkin + implements TabSkin, EditableEntityTableTabSkin { + + public Button fileTableReBuildBtn; + public MenuItem fileTable_menu_refresh; + public MenuItem fileTable_menu_del; + public Menu fileTable_menu_pdf; + public Menu fileTable_menu_update; + public Menu fileTable_menu_change_type; + public Menu fileTable_menu_change_type_and_name; + public TableColumn fileTable_idColumn; + public TableColumn fileTable_typeColumn; + public TableColumn fileTable_filePathColumn; + public TableColumn fileTable_applyDateColumn; + public TableColumn fileTable_descriptionColumn; + + @Setter + private ContractFileService contractFileService; + private final ObservableMap fileTypeLocalMap = FXCollections.observableHashMap(); + + + public ContractTabSkinFiles(ContractWindowController controller) { + super(controller); + setDragAndDrop(true); + setDragAndDropFileHandler(this::moveFileToCustomer); + } + + @Override + protected ContractFileService getViewModelService() { + return getContractFileService(); + } + + public ContractFileService getContractFileService() { + if (contractFileService == null) { + contractFileService = SpringApp.getBean(ContractFileService.class); + } + return contractFileService; + } + + @Override + public Tab getTab() { + return controller.fileTab; + } + + static class ContractFileTypeLocalStringConverter extends StringConverter { + @Override + public String toString(ContractFileTypeLocal local) { + if (local == null) { + return "-"; + } + return local.getValue(); + } + + @Override + public ContractFileTypeLocal fromString(String string) { + return null; + } + } + + @Override + public void initializeTab() { + TableView table = getTableView(); + ContractPayWay payWay = viewModel.getPayWay().get(); + ContractType contractType = viewModel.getType().get(); + boolean isCustomer = payWay == ContractPayWay.RECEIVE; + boolean isVendor = payWay == ContractPayWay.PAY; + + fileTable_menu_refresh.setOnAction(this::onTableRefreshAction); + + table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + + table.getContextMenu().setOnShowing(this::onFileTableContextMenuShowing); + + fileTableReBuildBtn.setOnAction(this::onFileReBuildingAction); + + fileTable_idColumn.setCellValueFactory(param -> param.getValue().getId()); + fileTable_idColumn.setEditable(false); + fileTable_idColumn.setReorderable(false); + + + fileTable_typeColumn.setCellValueFactory(param -> Bindings.valueAt(fileTypeLocalMap, param.getValue().getType())); + fileTable_typeColumn.setCellFactory(param -> new TextFieldTableCell<>(new ContractFileTypeLocalStringConverter())); + // 监听 type map 变化 + fileTypeLocalMap.addListener((MapChangeListener) change -> { + List types = fileTypeLocalMap.values().stream().filter(typeLocal -> { + ContractFileType type = typeLocal.getType(); + if (type == null) { + return false; + } + if (isCustomer && !type.isSupportCustomer()) { + return false; + } + return !isVendor || type.isSupportVendor(); + }).toList(); + + + fileTable_menu_change_type.getItems().setAll(types.stream() + .map(typeLocal -> { + MenuItem item = new MenuItem(); + item.setText(typeLocal.getValue()); + item.getProperties().put("typeLocal", typeLocal); + item.setOnAction(this::onFileTableContextMenuChangeTypeAndNameAction); + return item; + }).toList()); + + fileTable_menu_change_type_and_name.getItems().setAll(types.stream() + .filter(typeLocal -> StringUtils.hasText(typeLocal.getSuggestFileName())) + .map(typeLocal -> { + MenuItem item = new MenuItem(); + item.setText(typeLocal.getValue()); + item.getProperties().put("typeLocal", typeLocal); + item.getProperties().put("rename", true); + item.setOnAction(this::onFileTableContextMenuChangeTypeAndNameAction); + return item; + }).toList()); + }); + + + fileTable_typeColumn.setEditable(false); + + /* 文件名编辑器 */ + fileTable_filePathColumn.setCellValueFactory(param -> param.getValue().getFileName()); + fileTable_filePathColumn.setCellFactory(TextFieldTableCell.forTableColumn()); + fileTable_filePathColumn.setEditable(true); + fileTable_filePathColumn.setOnEditCommit(this::onFilePathColumnEditCommitAction); + + /* 日期编辑器 */ + fileTable_applyDateColumn.setCellValueFactory(param -> param.getValue().getApplyDate()); + fileTable_applyDateColumn.setCellFactory(LocalDateFieldTableCell.forTableColumn()); + fileTable_applyDateColumn.setEditable(true); + fileTable_applyDateColumn.setOnEditCommit(this::onFileTableApplyDateColumnEditCommitAction); + + /* 备注编辑器 */ + fileTable_descriptionColumn.setCellValueFactory(param -> param.getValue().getDescription()); + fileTable_descriptionColumn.setCellFactory(TextFieldTableCell.forTableColumn()); + fileTable_descriptionColumn.setEditable(true); + fileTable_descriptionColumn.setOnEditCommit(this::onFileTableDescriptionColumnEditCommitAction); + + table.setEditable(true); + + fileTable_menu_del.setOnAction(this::onTableDeleteAction); + + MenuItem composePdfMenu = createComposePdfMenu(); + MenuItem splitPDFMenu = createSplitPDFMenu(); + fileTable_menu_pdf.getItems().setAll(splitPDFMenu, composePdfMenu); + fileTable_menu_pdf.setOnMenuValidation(e -> { + splitPDFMenu.setDisable(true); + composePdfMenu.setVisible(false); + SelectionMode selectionMode = table.getSelectionModel().getSelectionMode(); + // 是否多选 + if (table.getSelectionModel().getSelectedItems().size() > 1) { + // 多选时启用合并菜单 + if (selectionMode == SelectionMode.MULTIPLE) { + composePdfMenu.setVisible(true); + } + } else { + ContractFileViewModel selectedItem = table.getSelectionModel().getSelectedItem(); + if (selectedItem != null) { + String extension = StringUtils.getFilenameExtension(selectedItem.getFileName().get()); + splitPDFMenu.setDisable(extension == null || !extension.equals("pdf")); + } + } + }); + // 当 菜单 onShowing 时触发 + fileTable_menu_change_type.setOnMenuValidation(e -> { + System.out.println("fileTable_menu_change_type:setOnMenuValidation"); + }); + // 当 菜单 onShowing 时触发 + fileTable_menu_change_type_and_name.setOnMenuValidation(e -> { + System.out.println("fileTable_menu_change_type_and_name:setOnMenuValidation"); + }); + + + fileTable_menu_update.getItems().setAll( + createCustomerContractCostByTemplateUpdateMenuItem(), + createCustomerContractBidByTemplateUpdateMenuItem(), + createCustomerContractApplyByTemplateUpdateMenuItem(), + createVendorContractRequestByTemplateUpdateMenuItem(), + createVendorContractApplyByTemplateUpdateMenuItem() + ); + + + fileTypeLocalMap.putAll(getContractFileService().findAllFileTypes(getLocale().toLanguageTag())); + super.initializeTab(); + } + + private MenuItem createSplitPDFMenu() { + MenuItem item = new MenuItem(); + item.setText("拆分PDF"); + item.setOnAction(this::onFileTableSplitPDFAction); + return item; + } + + private MenuItem createComposePdfMenu() { + MenuItem item = new MenuItem(); + item.setText("合并PDF"); + item.setOnAction(this::onFileTableComposePDFAction_V2); + return item; + } + + /** + * 合并PDF + * + * @param event 菜单事件 + */ + private void onFileTableComposePDFAction(ActionEvent event) { + Contract contract = getParent(); + if (!StringUtils.hasText(contract.getPath())) { + setStatus("合同未设置路径"); + return; + } + File contractPath = new File(contract.getPath()); + if (!contractPath.exists()) { + setStatus("合同路径不存在,请检查"); + return; + } + + ObservableList selectedItems = getTableView().getSelectionModel().getSelectedItems(); + if (selectedItems.isEmpty()) { + setStatus("请选择文件"); + return; + } + List rows = selectedItems.stream().filter(selectedItem -> { + String fileName = selectedItem.getFileName().get(); + if (!StringUtils.hasText(fileName)) { + return false; + } + File file = new File(contractPath, fileName); + return file.exists(); + }).toList(); + + if (rows.isEmpty()) { + setStatus("请选择PDF文件"); + return; + } + + + PDFMergerUtility merger = new PDFMergerUtility(); + File dest = new File(contractPath, "合并的" + rows.getFirst().getFileName().get()); + merger.setDestinationFileName(dest.getAbsolutePath()); + for (ContractFileViewModel row : rows) { + try { + File file = new File(contractPath, row.getFileName().get()); + String extension = StringUtils.getFilenameExtension(file.getName()); + if (!"pdf".equalsIgnoreCase(extension)) { + merger.addSource(file); + } else { + if (!"jpg".equalsIgnoreCase(extension)) { + PDDocument document = new PDDocument(); + } + } + + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + } + try { + merger.mergeDocuments(MemoryUsageSetting.setupMainMemoryOnly().streamCache); // null + } catch (IOException e) { + throw new RuntimeException(e); + } + + ContractFileViewModel append = new ContractFileViewModel(); + append.getContract().set(contract); + append.getFileName().set(dest.getName()); + append.getType().set(ContractFileType.General); + saveRow(append); + getTableView().getItems().add(append); + + for (ContractFileViewModel row : rows) { + if (deleteRow(row, false)) { + getTableView().getItems().remove(row); + } + } + } + + private void onFileTableComposePDFAction_V2(ActionEvent event) { + Contract contract = getParent(); + if (!StringUtils.hasText(contract.getPath())) { + setStatus("合同未设置路径"); + return; + } + File contractPath = new File(contract.getPath()); + if (!contractPath.exists()) { + setStatus("合同路径不存在,请检查"); + return; + } + + ObservableList selectedItems = getTableView().getSelectionModel().getSelectedItems(); + if (selectedItems.isEmpty()) { + setStatus("请选择文件"); + return; + } + List rows = selectedItems.stream().filter(selectedItem -> { + String fileName = selectedItem.getFileName().get(); + if (!StringUtils.hasText(fileName)) { + return false; + } + File file = new File(contractPath, fileName); + return file.exists(); + }).toList(); + + if (rows.isEmpty()) { + setStatus("请选择PDF文件"); + return; + } + + List files = rows.stream().map(v -> new File(contractPath, v.getFileName().get())).toList(); + PDFMergerUtility merger = new PDFMergerUtility(); + File dest = new File(contractPath, "合并的" + stripFilename(files.getFirst()) + ".pdf"); + String[] imageReaderFormats = null; + List tobeClosed = new ArrayList<>(); + RandomAccessStreamCache.StreamCacheCreateFunction createFunction = IOUtils.createMemoryOnlyStreamCache(); + try (PDDocument destination = new PDDocument(createFunction)) { + for (File file : files) { + String extension = StringUtils.getFilenameExtension(file.getName()); + if ("pdf".equalsIgnoreCase(extension)) { + PDDocument doc = Loader.loadPDF(file); + tobeClosed.add(doc); + merger.appendDocument(destination, doc); + } else { + if (imageReaderFormats == null) { + imageReaderFormats = ImageIO.getReaderFormatNames(); + } + boolean found = false; + for (String format : imageReaderFormats) { + if (format.equals(extension)) { + found = true; + break; + } + } + if (!found) { + continue; + } + + PDPage page = new PDPage(); + // 加载 PNG 图片 + BufferedImage image = ImageIO.read(file); + // 将图片添加到 PDF + try (PDPageContentStream contentStream = + new PDPageContentStream(destination, page)) { + PDImageXObject pdImage = LosslessFactory.createFromImage(destination, image); + float scale = 1f; // 图片缩放比例 + contentStream.drawImage(pdImage, 0, 0, + pdImage.getWidth() * scale, + pdImage.getHeight() * scale); + } + destination.addPage(page); + } + + } + destination.save(dest, CompressParameters.DEFAULT_COMPRESSION); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + for (PDDocument doc : tobeClosed) { + IOUtils.closeQuietly(doc); + } + } + + ContractFileViewModel append = new ContractFileViewModel(); + append.getContract().set(contract); + append.getFileName().set(dest.getName()); + append.getType().set(ContractFileType.General); + saveRow(append); + getTableView().getItems().add(append); + + for (ContractFileViewModel row : rows) { + if (deleteRow(row, false)) { + getTableView().getItems().remove(row); + } + } + } + + private String stripFilename(File file) { + String filename = file.getName(); + String extension = StringUtils.getFilenameExtension(filename); + if (extension == null) { + return filename; + } + return filename.substring(0, filename.length() - (extension.length() + 1)); + } + + private MenuItem createVendorContractRequestByTemplateUpdateMenuItem() { + MenuItem item = new MenuItem(); + item.setText("采购与服务申请表"); + item.visibleProperty().bind(viewModel.getPayWay().isEqualTo(ContractPayWay.PAY)); + return item; + } + + private MenuItem createVendorContractApplyByTemplateUpdateMenuItem() { + MenuItem item = new MenuItem(); + item.setText("采购合同审批表"); + item.visibleProperty().bind(viewModel.getPayWay().isEqualTo(ContractPayWay.PAY)); + return item; + } + + private MenuItem createCustomerContractApplyByTemplateUpdateMenuItem() { + MenuItem item = new MenuItem(); + item.setText("销售合同审批表"); + item.visibleProperty().bind(viewModel.getPayWay().isEqualTo(ContractPayWay.RECEIVE)); + return item; + } + + + private MenuItem createCustomerContractBidByTemplateUpdateMenuItem() { + MenuItem item = new MenuItem(); + item.setText("投标报价审批表"); + + item.visibleProperty().bind(viewModel.getPayWay().isEqualTo(ContractPayWay.RECEIVE)); + return item; + } + + private MenuItem createCustomerContractCostByTemplateUpdateMenuItem() { + MenuItem item = new MenuItem(); + item.setText("成本核算审批表"); + item.visibleProperty().bind(viewModel.getPayWay().isEqualTo(ContractPayWay.RECEIVE)); + item.setOnAction(event -> { + Contract contract = getContractService().findById(viewModel.getId().get()); + if (contract == null) { + setStatus("异常,对应的合同不存在#" + viewModel.getId().get()); + return; + } + if (!StringUtils.hasText(contract.getPath())) { + setStatus("合同目录未创建"); + return; + } + + String template = controller.getConfService().getString(ContractFileService.KEY_CUSTOMER_COST_TEMPLATE); + if (!StringUtils.hasText(template)) { + setStatus("模板文件未配置"); + return; + } + + ContractFileViewModel model = dataSet.stream() + .filter(v -> v.getType().get().equals(ContractFileType.Cost)) + .findFirst().orElse(null); + if (model == null) { + ContractFile file = new ContractFile(); + file.setContract(contract); + file.setType(ContractFileType.Cost); + file.setApplyDate(LocalDate.now()); + + + String fileName = item.getText(); + ContractFileTypeLocal local = fileTypeLocalMap.get(ContractFileType.CostForm); + if (local != null) { + if (StringUtils.hasText(local.getSuggestFileName())) { + fileName = local.getSuggestFileName(); + } + } + String dest = contract.getCode() + "-" + fileName + "." + StringUtils.getFilenameExtension(template); + file.setFileName(dest); + + file = getContractFileService().save(file); + + model = new ContractFileViewModel(); + model.update(file); + + dataSet.add(model); + } + + CustomerContractCostFormUpdateTask task = new CustomerContractCostFormUpdateTask(); + task.setContract(contract); + task.setTemplate(template); + task.setFileName(model.getFileName().get()); + task.setApplyDate(model.getApplyDate().get()); + + UITools.showTaskDialogAndWait("更新 " + model.getFileName().get(), task, null); + + + }); + return item; + } + + private void onFileTableDescriptionColumnEditCommitAction(TableColumn.CellEditEvent event) { + acceptCellEditEvent(event, ContractFileViewModel::getDescription); + } + + private void onFileTableApplyDateColumnEditCommitAction(TableColumn.CellEditEvent event) { + acceptCellEditEvent(event, ContractFileViewModel::getApplyDate); + } + + private void onFilePathColumnEditCommitAction(TableColumn.CellEditEvent event) { + ContractFileViewModel row = event.getRowValue(); + File src = new File(viewModel.getPath().get(), row.getFileName().get()); + File desc = new File(viewModel.getPath().get(), event.getNewValue()); + if (!src.exists()) { + setStatus("原文件不存在, " + src.getAbsolutePath()); + return; + } + if (desc.exists()) { + setStatus("目标文件已存在, " + desc.getAbsolutePath()); + return; + } + if (src.renameTo(desc)) { + acceptCellEditEvent(event, ContractFileViewModel::getFileName); + } + } + + @Override + protected void onTableRowDoubleClickedAction(ContractFileViewModel item) { + String parent = viewModel.getPath().get(); + if (!CompanyFileUtils.exists(parent)) { + setStatus("合同的目录未设置或者无效,请检查!"); + return; + } + + File file = new File(parent, item.getFileName().get()); + if (!file.exists()) { + setStatus("文件不存在 " + file.getName()); + return; + } + Desktop.showInExplorer(file); + } + + public void onFileTableContextMenuShowing(WindowEvent windowEvent) { + ContextMenu contextMenu = (ContextMenu) windowEvent.getSource(); + ContractFileViewModel selectedItem = getSelectedItem(); + if (selectedItem == null) { + // 未选择行 + windowEvent.consume(); + //contextMenu.hide(); + fileTable_menu_del.setVisible(false); + fileTable_menu_change_type.setVisible(false); + fileTable_menu_change_type_and_name.setVisible(false); + return; + } + fileTable_menu_del.setVisible(true); + + // + fileTable_menu_change_type.setVisible(true); + for (MenuItem item : fileTable_menu_change_type.getItems()) { + ContractFileTypeLocal typeLocal = (ContractFileTypeLocal) item.getProperties().get("typeLocal"); + item.setVisible(typeLocal.getType() != selectedItem.getType().get()); + } + + fileTable_menu_change_type_and_name.setVisible(true); + for (MenuItem item : fileTable_menu_change_type.getItems()) { + ContractFileTypeLocal typeLocal = (ContractFileTypeLocal) item.getProperties().get("typeLocal"); + item.setVisible(typeLocal.getType() != selectedItem.getType().get()); + } + + } + + + private void onFileTableContextMenuChangeTypeAndNameAction(ActionEvent event) { + MenuItem item = (MenuItem) event.getSource(); + + ObservableList selectedItems = getTableView().getSelectionModel().getSelectedItems(); + if (selectedItems == null || selectedItems.isEmpty()) { + return; + } + ContractFileTypeLocal typeLocal = (ContractFileTypeLocal) item.getProperties().get("typeLocal"); + if (typeLocal == null) { + return; + } + + for (ContractFileViewModel selectedItem : selectedItems) { + if (typeLocal.getType() == selectedItem.getType().get()) { + continue; + } + + Boolean rename = (Boolean) item.getProperties().get("rename"); + String suggestFileName = typeLocal.getSuggestFileName(); + if (rename != null && rename && StringUtils.hasText(suggestFileName)) { + String fileName = selectedItem.getFileName().get(); + String extension = StringUtils.getFilenameExtension(fileName); + LocalDate localDate = selectedItem.getApplyDate().get(); + String contractCode = viewModel.getCode().get(); + String newFileName = contractCode + "-" + suggestFileName + (localDate == null ? "" : ("-" + MyDateTimeUtils.format(localDate))) + "." + extension; + + // 新旧文件名不一样,需要改名 + if (!Objects.equals(newFileName, fileName)) { + File src = new File(viewModel.getPath().get(), fileName); + File desc = new File(viewModel.getPath().get(), newFileName); + if (src.renameTo(desc)) { + selectedItem.getType().set(typeLocal.getType()); + selectedItem.getFileName().set(newFileName); + saveRow(selectedItem); + } + } else { + setStatus("文件名没有变化"); + } + } + + selectedItem.getType().set(typeLocal.getType()); + saveRow(selectedItem); + setStatus("文件类型修改成功"); + } + } + + @Override + public ContractFile loadRowData(ContractFileViewModel row) { + return getContractFileService().findById(row.getId().get()); + } + + @Override + public ContractFile saveRowData(ContractFile entity) { + return getContractFileService().save(entity); + } + + + @Override + public void deleteRowData(ContractFile entity) { + getContractFileService().delete(entity); + } + + /** + * 重置文件 + *
+ * 依据已存在的文件重建 + */ + public void onFileReBuildingAction(ActionEvent event) { + Contract contract = getParent(); + ContractFilesRebuildTasker task = new ContractFilesRebuildTasker(); + task.setContractService(getContractService()); + task.setContract(contract); + UITools.showTaskDialogAndWait("文件重置", task, null); + + if (task.isRepaired()) { + loadTableDataSet(); + } + } + + private void moveFileToCustomer(List files) { + String path = viewModel.getPath().get(); + if (!StringUtils.hasText(path)) { + setStatus("未设置目录"); + return; + } + File dir = new File(path); + if (!dir.exists()) { + setStatus("目录错误,不存在"); + return; + } + Contract contract = getParent(); + + for (File file : files) { + File dest = new File(dir, file.getName()); + if (file.renameTo(dest)) { + ContractFile ccf = new ContractFile(); + ccf.setContract(contract); + ccf.setType(ContractFileType.General); + ccf.setFileName(dest.getName()); + getContractFileService().save(ccf); + } + } + + loadTableDataSet(); + } + + public void onFileTableSplitPDFAction(ActionEvent event) { + ContractFileViewModel selectedItem = getSelectedItem(); + if (selectedItem == null) { + // 未选择行 + return; + } + + Contract contract = getParent(); + if (!StringUtils.hasText(contract.getPath())) { + setStatus("合同未设置路径"); + return; + } + File contractPath = new File(contract.getPath()); + if (!contractPath.exists()) { + setStatus("合同路径不存在,请检查"); + return; + } + + String fileName = selectedItem.getFileName().get(); + if (!StringUtils.hasText(fileName)) { + setStatus("选中行没有文件信息"); + return; + } + if (!"pdf".equalsIgnoreCase(StringUtils.getFilenameExtension(fileName))) { + setStatus(fileName + "不是PDF文件,支持拆分"); + return; + } + + File pdfFile = new File(contractPath, fileName); + if (!pdfFile.exists()) { + setStatus(fileName + "不存在,请检查"); + return; + } + + // TODO 放到其他线程中, UI线程卡了 + ContractCtx contractCtx = new ContractCtx(); + + + try (PDDocument pdDocument = Loader.loadPDF(pdfFile)) { + Splitter splitter = new Splitter(); + List pages = splitter.split(pdDocument); + String name = StringUtils.stripFilenameExtension(fileName); + for (int i = 0; i < pages.size(); i++) { + PDDocument page = pages.get(i); + File outputFile = new File(contractPath, name + "-" + (i + 1) + ".pdf"); + page.save(outputFile); + page.close(); + ContractFile contractFile = new ContractFile(); + contractFile.setContract(contract); + contractCtx.syncContractFile(contractFile, outputFile, (lv, message) -> { + setStatus(message); + }); + ContractFile saved = getContractFileService().save(contractFile); + dataSet.add(ContractFileViewModel.from(saved)); + } + } catch (Exception e) { + setStatus(e.getMessage()); + return; + } + + // loadFileTableDataSet(contract); + } + + @Override + protected void onTableDeleteAction(ActionEvent event) { + Contract contract = getParent(); + String path = contract.getPath(); + if (!StringUtils.hasText(path)) { + setStatus("未设置目录"); + return; + } + super.onTableDeleteAction(event); + } + + @Override + public boolean deleteRow(ContractFileViewModel row) { + return deleteRow(row, true); + } + + protected boolean deleteRow(ContractFileViewModel row, boolean confirm) { + boolean deleted = super.deleteRow(row); + if (deleted) { + Contract contract = getParent(); + File file = new File(contract.getPath(), row.getFileName().get()); + if (!file.exists()) { + setStatus(file.getAbsolutePath() + " 文件不存在, 无法删除"); + } else { + if (confirm) { + UITools.showConfirmation("数据记录已经删除,请确认是否删除物理文件", file.getAbsolutePath()) + .thenAccept(buttonType -> { + if (buttonType == ButtonType.OK) { + if (file.delete()) { + setStatus("删除文件 " + file.getAbsolutePath()); + } + } + }); + } else { + if (file.delete()) { + setStatus("删除文件 " + file.getAbsolutePath()); + } + } + } + } + return deleted; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinItems.java b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinItems.java new file mode 100644 index 0000000..f9f1d83 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinItems.java @@ -0,0 +1,373 @@ +package com.ecep.contract.manager.ds.contract.controller; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.contract.ContractPayWay; +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.contract.model.ContractItem; +import com.ecep.contract.manager.ds.contract.service.ContractItemService; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ds.contract.vo.ContractItemComposeViewModel; +import com.ecep.contract.manager.ds.contract.vo.ContractItemViewModel; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.TabSkin; +import com.ecep.contract.manager.ui.ViewModelService; +import javafx.application.Platform; +import javafx.beans.property.SimpleListProperty; +import javafx.collections.ObservableList; +import javafx.geometry.Pos; +import javafx.scene.control.*; +import javafx.util.Callback; +import javafx.util.Duration; +import lombok.Setter; +import org.hibernate.Hibernate; +import org.springframework.util.StringUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +@FxmlPath("/ui/contract/contract-tab-item.fxml") +public class ContractTabSkinItems + extends AbstContractTableTabSkin + implements TabSkin { + @Setter + ContractItemService itemService; + + /* 以下是合同内容 */ + public TableColumn itemTable_idColumn; + public TableColumn itemTable_titleColumn; + public TableColumn itemTable_specificationColumn; + public TableColumn itemTable_unitColumn; + + public TableColumn itemTable_columnGroup1; + + public TableColumn itemTable_exclusiveTaxPriceColumn1; + public TableColumn itemTable_taxRateColumn1; + public TableColumn itemTable_taxPriceColumn1; + public TableColumn itemTable_quantityColumn1; + public TableColumn itemTable_exclusiveTaxAmountColumn1; + public TableColumn itemTable_taxAmountColumn1; + + public TableColumn itemTable_columnGroup2; + + public TableColumn itemTable_exclusiveTaxPriceColumn2; + public TableColumn itemTable_taxRateColumn2; + public TableColumn itemTable_taxPriceColumn2; + public TableColumn itemTable_quantityColumn2; + public TableColumn itemTable_exclusiveTaxAmountColumn2; + public TableColumn itemTable_taxAmountColumn2; + public TableColumn itemTable_remarkColumn; + + public ContractTabSkinItems(ContractWindowController controller) { + super(controller); + } + + @Override + protected ContractItemService getViewModelService() { + return itemService; + } + + @Override + public Tab getTab() { + return controller.itemTab; + } + + @Override + public void initializeUIComponents() { + super.initializeUIComponents(); + itemTable_columnGroup1.visibleProperty().bind(viewModel.getPayWay().isEqualTo(ContractPayWay.RECEIVE)); + //itemTable_columnGroup2.visibleProperty().bind(viewModel.getPayWay().isEqualTo(ContractPayWay.PAY)); + } + + static class UnitTableCell extends TableCell { + { + setAlignment(Pos.CENTER); + } + + @Override + protected void updateItem(String item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setText(null); + return; + } + setText(item); + } + } + + ContractItemComposeViewModel createNew(String key, ContractItem item) { + ContractItemComposeViewModel m = new ContractItemComposeViewModel(); + // set key + m.getTitle().set(item.getTitle()); + m.getSpecification().set(item.getSpecification()); + return m; + } + + private void addIn(HashMap map, List list) { + for (ContractItem item : list) { + String key = makeKey(item); + ContractItemComposeViewModel model = map.computeIfAbsent(key, k -> createNew(k, item)); + model.getUnit().set(item.getUnit()); + model.getIn().add(item); + } + } + + private void addOut(List list, HashMap map) { + for (ContractItem item : list) { + String key = makeKey(item); + ContractItemComposeViewModel model = map.computeIfAbsent(key, k -> createNew(k, item)); + model.getUnit().set(item.getUnit()); + model.getOut().add(item); + } + } + + @Override + public void initializeTab() { + super.initializeTab(); + + itemTable_idColumn.setCellValueFactory(param -> param.getValue().getId()); + itemTable_titleColumn.setCellValueFactory(param -> param.getValue().getTitle()); + itemTable_specificationColumn.setCellValueFactory(param -> param.getValue().getSpecification()); + itemTable_unitColumn.setCellValueFactory(param -> param.getValue().getUnit()); + itemTable_unitColumn.setCellFactory(col -> new UnitTableCell()); + NumberCellFactory v1 = new NumberCellFactory("%,.1f", null); + NumberCellFactory v2 = new NumberCellFactory("%,.2f", null); + NumberCellFactory v3 = new NumberCellFactory("%,.2f", "custom-cell"); + + + // 进项 + + itemTable_exclusiveTaxPriceColumn1.setCellValueFactory(param -> param.getValue().getIn().getExclusiveTaxPrice().asObject()); + itemTable_exclusiveTaxPriceColumn1.setCellFactory(v2); + + itemTable_taxRateColumn1.setCellValueFactory(param -> param.getValue().getIn().getTaxRate().asObject()); + itemTable_taxRateColumn1.setCellFactory(v1); + + itemTable_taxPriceColumn1.setCellValueFactory(param -> param.getValue().getIn().getTaxPrice().asObject()); + itemTable_taxPriceColumn1.setCellFactory(v2); + + itemTable_quantityColumn1.setCellValueFactory(param -> param.getValue().getIn().getQuantity().asObject()); + itemTable_quantityColumn1.setCellFactory(v1); + + itemTable_taxAmountColumn1.setCellValueFactory(param -> param.getValue().getIn().getTaxAmount().asObject()); + itemTable_taxAmountColumn1.setCellFactory(v3); + + itemTable_exclusiveTaxAmountColumn1.setCellValueFactory(param -> param.getValue().getIn().getExclusiveTaxAmount().asObject()); + itemTable_exclusiveTaxAmountColumn1.setCellFactory(v3); + + + // 出项 + + itemTable_exclusiveTaxPriceColumn2.setCellValueFactory(param -> param.getValue().getOut().getExclusiveTaxPrice().asObject()); + itemTable_exclusiveTaxPriceColumn2.setCellFactory(v2); + + + itemTable_taxRateColumn2.setCellValueFactory(param -> param.getValue().getOut().getTaxRate().asObject()); + itemTable_taxRateColumn2.setCellFactory(v1); + + itemTable_taxPriceColumn2.setCellValueFactory(param -> param.getValue().getOut().getTaxPrice().asObject()); + itemTable_taxPriceColumn2.setCellFactory(v2); + + itemTable_quantityColumn2.setCellValueFactory(param -> param.getValue().getOut().getQuantity().asObject()); + itemTable_quantityColumn2.setCellFactory(v1); + + itemTable_taxAmountColumn2.setCellValueFactory(param -> param.getValue().getOut().getTaxAmount().asObject()); + itemTable_taxAmountColumn2.setCellFactory(v3); + + itemTable_exclusiveTaxAmountColumn2.setCellValueFactory(param -> param.getValue().getOut().getExclusiveTaxAmount().asObject()); + itemTable_exclusiveTaxAmountColumn2.setCellFactory(v3); + + itemTable_remarkColumn.setCellValueFactory(param -> param.getValue().getRemark()); + } + + @Override + protected void createContextMenu(ContextMenu contextMenu) { + MenuItem item2 = new MenuItem("刷新"); + item2.setOnAction(this::onTableRefreshAction); + + MenuItem item3 = new MenuItem("删除"); + item3.setOnAction(this::onTableDeleteAction); + + contextMenu.getItems().addAll(item2, item3); + } + + + + public ContractItemService getItemService() { + if (itemService == null) { + itemService = SpringApp.getBean(ContractItemService.class); + } + return itemService; + } + + private void sum(ContractItemComposeViewModel model, Contract contract, HashMap map) { + double inQuantity = map.values().stream().map(ContractItemComposeViewModel::getIn).mapToDouble(v -> v.getQuantity().get()).sum(); + Platform.runLater(() -> model.getIn().getQuantity().set(inQuantity)); + + double inTaxAmount = map.values().stream().map(ContractItemComposeViewModel::getIn).mapToDouble(v -> v.getTaxAmount().get()).sum(); + Platform.runLater(() -> model.getIn().getTaxAmount().set(inTaxAmount)); + + double inExclusiveTaxAmount = map.values().stream().map(ContractItemComposeViewModel::getIn).mapToDouble(v -> v.getExclusiveTaxAmount().get()).sum(); + Platform.runLater(() -> model.getIn().getExclusiveTaxAmount().set(inExclusiveTaxAmount)); + + double outQuantity = map.values().stream().map(ContractItemComposeViewModel::getOut).mapToDouble(v -> v.getQuantity().get()).sum(); + Platform.runLater(() -> model.getOut().getQuantity().set(outQuantity)); + + double outTaxAmount = map.values().stream().map(ContractItemComposeViewModel::getOut).mapToDouble(v -> v.getTaxAmount().get()).sum(); + Platform.runLater(() -> model.getOut().getTaxAmount().set(outTaxAmount)); + + double outExclusiveTaxAmount = map.values().stream().map(ContractItemComposeViewModel::getOut).mapToDouble(v -> v.getExclusiveTaxAmount().get()).sum(); + Platform.runLater(() -> model.getOut().getExclusiveTaxAmount().set(outExclusiveTaxAmount)); + } + + String makeKey(ContractItem item) { + return item.getTitle() + "-" + item.getSpecification(); + } + + static class NumberTableCell extends TableCell { + // "%.1f" + private final String format; + + public NumberTableCell(String format, String styleClass) { + this.format = format; + if (StringUtils.hasText(styleClass)) { + getStyleClass().add(styleClass); + } + } + + @Override + protected void updateItem(Double item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setText(null); + return; + } + ContractItemComposeViewModel model = getTableRow().getItem(); + + String title = model.getTitle().get(); + + setText(String.format(format, item)); + + String tooltipText = null; + switch (getTableColumn().getId()) { + case "itemTable_taxAmountColumn1": { + if ("合计".equals(title)) { + setTooltip((String) null); + return; + } + SimpleListProperty items = model.getIn().getItems(); + ObservableList list = items.get(); + if (list == null) { + setText(null); + setTooltip(""); + return; + } + setTooltip(list, ContractItem::getTaxPrice); + break; + } + case "itemTable_exclusiveTaxAmountColumn1": { + if ("合计".equals(title)) { + setTooltip((String) null); + return; + } + SimpleListProperty items = model.getIn().getItems(); + ObservableList list = items.get(); + if (list == null) { + setText(null); + setTooltip(""); + return; + } + setTooltip(list, ContractItem::getExclusiveTaxPrice); + } + break; + case "itemTable_taxAmountColumn2": { + if ("合计".equals(title)) { + setTooltip((String) null); + return; + } + SimpleListProperty items = model.getOut().getItems(); + ObservableList list = items.get(); + if (list == null) { + setText(null); + setTooltip(""); + return; + } + setTooltip(list, ContractItem::getTaxPrice); + } + break; + case "itemTable_exclusiveTaxAmountColumn2": { + if ("合计".equals(title)) { + setTooltip((String) null); + return; + } + SimpleListProperty items = model.getOut().getItems(); + ObservableList list = items.get(); + if (list == null) { + setText(null); + setTooltip(""); + return; + } + setTooltip(list, ContractItem::getExclusiveTaxPrice); + } + break; + default: + if ("合计".equals(title)) { + setTooltip((String) null); + setText(null); + return; + } + setTooltip(""); + return; + } + } + + private void setTooltip(ObservableList list, Function getPrice) { + String text = list.stream().map(v -> { + Contract contract = v.getContract(); + if (!Hibernate.isInitialized(contract)) { + contract = SpringApp.getBean(ContractService.class).findById(contract.getId()); + } + Double price = getPrice.apply(v); + double quantity = v.getQuantity(); + return contract.getCode() + " " + String.format(format, price) + " x " + quantity + " = " + String.format(format, (price * quantity)); + }).collect(Collectors.joining("\n")); + setTooltip(text); + } + + private void setTooltip(String text) { + Tooltip tooltip = getTooltip(); + if (StringUtils.hasText(text)) { + if (tooltip == null) { + tooltip = new Tooltip(); + setTooltip(tooltip); + } + tooltip.setShowDelay(Duration.ZERO); + tooltip.setHideDelay(Duration.ZERO); + tooltip.setText(text); + } else { + if (tooltip != null) { + tooltip.setText(null); + } + } + } + } + + static class NumberCellFactory implements Callback, TableCell> { + private final String format; + private final String styleClass; + + public NumberCellFactory(String format, String styleClass) { + this.format = format; + this.styleClass = styleClass; + } + + @Override + public TableCell call(TableColumn param) { + NumberTableCell cell = new NumberTableCell(format, styleClass); + cell.setAlignment(Pos.CENTER_RIGHT); + return cell; + } + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinItemsV2.java b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinItemsV2.java new file mode 100644 index 0000000..5ff3030 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinItemsV2.java @@ -0,0 +1,192 @@ +package com.ecep.contract.manager.ds.contract.controller; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.contract.model.ContractItem; +import com.ecep.contract.manager.ds.contract.service.ContractItemService; +import com.ecep.contract.manager.ds.contract.vo.ContractItemComposeViewModel; +import com.ecep.contract.manager.ds.contract.vo.ContractItemViewModel; +import com.ecep.contract.manager.ds.contract.vo.ProjectCostItemViewModel; +import com.ecep.contract.manager.ds.other.EntityStringConverter; +import com.ecep.contract.manager.ds.other.controller.employee.EmployeeTableCell; +import com.ecep.contract.manager.ds.other.controller.inventory.InventoryWindowController; +import com.ecep.contract.manager.ds.other.model.Employee; +import com.ecep.contract.manager.ds.other.model.Inventory; +import com.ecep.contract.manager.ds.other.service.InventoryService; +import com.ecep.contract.manager.ds.other.vo.InventoryViewModel; +import com.ecep.contract.manager.ui.EditableEntityTableTabSkin; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.TabSkin; +import com.ecep.contract.manager.ui.util.LocalDateTimeTableCell; +import javafx.application.Platform; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.Tab; +import javafx.scene.control.TableColumn; +import javafx.scene.control.cell.TextFieldTableCell; +import javafx.util.converter.CurrencyStringConverter; +import javafx.util.converter.NumberStringConverter; +import lombok.Setter; +import org.hibernate.Hibernate; + +import java.text.NumberFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.HashMap; + +@FxmlPath("/ui/contract/contract-tab-item-v2.fxml") +public class ContractTabSkinItemsV2 + extends AbstContractTableTabSkin + implements TabSkin, EditableEntityTableTabSkin { + + + @Setter + ContractItemService itemService; + @Setter + InventoryService inventoryService; + + /* 以下是合同内容 */ + public TableColumn idColumn; + public TableColumn titleColumn; + public TableColumn specificationColumn; + public TableColumn unitColumn; + + public TableColumn inventoryColumn; + public TableColumn refIdColumn; + public TableColumn codeColumn; + public TableColumn creatorColumn; + public TableColumn createDateColumn; + public TableColumn updaterColumn; + public TableColumn updateDateColumn; + + public TableColumn startDateColumn; + public TableColumn endDateColumn; + + public TableColumn exclusiveTaxPriceColumn; + public TableColumn taxRateColumn; + public TableColumn taxPriceColumn; + public TableColumn quantityColumn; + public TableColumn exclusiveTaxAmountColumn; + public TableColumn taxAmountColumn; + + public TableColumn remarkColumn; + + public ContractTabSkinItemsV2(ContractWindowController controller) { + super(controller); + } + + @Override + public Tab getTab() { + return controller.itemTab; + } + + @Override + protected ContractItemService getViewModelService() { + return getItemService(); + } + + @Override + public void initializeTable() { + super.initializeTable(); + NumberFormat numberInstance = NumberFormat.getNumberInstance(getLocale()); + numberInstance.setMaximumFractionDigits(2); + numberInstance.setMinimumFractionDigits(2); + CurrencyStringConverter currencyStringConverter = new CurrencyStringConverter(numberInstance); + + idColumn.setCellValueFactory(param -> param.getValue().getId()); + titleColumn.setCellValueFactory(param -> param.getValue().getTitle()); + specificationColumn.setCellValueFactory(param -> param.getValue().getSpecification()); + unitColumn.setCellValueFactory(param -> param.getValue().getUnit()); + + inventoryColumn.setCellValueFactory(param -> param.getValue().getInventory()); + EntityStringConverter converter = new EntityStringConverter<>(); + converter.setInitialized(v -> getInventoryService().findById(v.getId())); + converter.setFormater(Inventory::getCode); + inventoryColumn.setCellFactory(TextFieldTableCell.forTableColumn(converter)); + + + exclusiveTaxPriceColumn.setCellValueFactory(param -> param.getValue().getExclusiveTaxPrice()); + exclusiveTaxPriceColumn.setCellFactory(TextFieldTableCell.forTableColumn(currencyStringConverter)); + + taxRateColumn.setCellValueFactory(param -> param.getValue().getTaxRate()); + taxRateColumn.setCellFactory(TextFieldTableCell.forTableColumn(currencyStringConverter)); + + taxPriceColumn.setCellValueFactory(param -> param.getValue().getTaxPrice()); + taxPriceColumn.setCellFactory(TextFieldTableCell.forTableColumn(currencyStringConverter)); + + quantityColumn.setCellValueFactory(param -> param.getValue().getQuantity()); + quantityColumn.setCellFactory(TextFieldTableCell.forTableColumn(new NumberStringConverter(getLocale()))); + + taxAmountColumn.setCellValueFactory(param -> param.getValue().getTaxAmount()); + taxAmountColumn.setCellFactory(TextFieldTableCell.forTableColumn(currencyStringConverter)); + + exclusiveTaxAmountColumn.setCellValueFactory(param -> param.getValue().getExclusiveTaxAmount()); + exclusiveTaxAmountColumn.setCellFactory(TextFieldTableCell.forTableColumn(currencyStringConverter)); + + refIdColumn.setCellValueFactory(param -> param.getValue().getRefId()); + codeColumn.setCellValueFactory(param -> param.getValue().getCode()); + + startDateColumn.setCellValueFactory(param -> param.getValue().getStartDate()); + endDateColumn.setCellValueFactory(param -> param.getValue().getEndDate()); + + creatorColumn.setCellValueFactory(param -> param.getValue().getCreator()); + creatorColumn.setCellFactory(cell -> new EmployeeTableCell<>(getEmployeeService())); + updaterColumn.setCellValueFactory(param -> param.getValue().getUpdater()); + updaterColumn.setCellFactory(cell -> new EmployeeTableCell<>(getEmployeeService())); + createDateColumn.setCellValueFactory(param -> param.getValue().getCreateDate()); + createDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); + updateDateColumn.setCellValueFactory(param -> param.getValue().getUpdateDate()); + updateDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); + + remarkColumn.setCellValueFactory(param -> param.getValue().getRemark()); + } + + public ContractItemService getItemService() { + if (itemService == null) { + itemService = getBean(ContractItemService.class); + } + return itemService; + } + InventoryService getInventoryService() { + if (inventoryService == null) { + inventoryService = getBean(InventoryService.class); + } + return inventoryService; + } + private void sum(ContractItemComposeViewModel model, Contract contract, HashMap map) { + double inQuantity = map.values().stream().map(ContractItemComposeViewModel::getIn).mapToDouble(v -> v.getQuantity().get()).sum(); + Platform.runLater(() -> model.getIn().getQuantity().set(inQuantity)); + + double inTaxAmount = map.values().stream().map(ContractItemComposeViewModel::getIn).mapToDouble(v -> v.getTaxAmount().get()).sum(); + Platform.runLater(() -> model.getIn().getTaxAmount().set(inTaxAmount)); + + double inExclusiveTaxAmount = map.values().stream().map(ContractItemComposeViewModel::getIn).mapToDouble(v -> v.getExclusiveTaxAmount().get()).sum(); + Platform.runLater(() -> model.getIn().getExclusiveTaxAmount().set(inExclusiveTaxAmount)); + + double outQuantity = map.values().stream().map(ContractItemComposeViewModel::getOut).mapToDouble(v -> v.getQuantity().get()).sum(); + Platform.runLater(() -> model.getOut().getQuantity().set(outQuantity)); + + double outTaxAmount = map.values().stream().map(ContractItemComposeViewModel::getOut).mapToDouble(v -> v.getTaxAmount().get()).sum(); + Platform.runLater(() -> model.getOut().getTaxAmount().set(outTaxAmount)); + + double outExclusiveTaxAmount = map.values().stream().map(ContractItemComposeViewModel::getOut).mapToDouble(v -> v.getExclusiveTaxAmount().get()).sum(); + Platform.runLater(() -> model.getOut().getExclusiveTaxAmount().set(outExclusiveTaxAmount)); + } + + @Override + protected void createContextMenu(ContextMenu contextMenu) { + super.createContextMenu(contextMenu); + MenuItem showInventory = new MenuItem("查看存货"); + showInventory.setOnAction(event -> { + ContractItemViewModel selectedItem = getTableView().getSelectionModel().getSelectedItem(); + Inventory inventory = selectedItem.getInventory().get(); + if (inventory == null) { + return; + } + if (!Hibernate.isInitialized(inventory)) { + inventory = getInventoryService().findById(inventory.getId()); + } + showInOwner(InventoryWindowController.class, InventoryViewModel.from(inventory)); + }); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinPayPlan.java b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinPayPlan.java new file mode 100644 index 0000000..73e3ba2 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinPayPlan.java @@ -0,0 +1,84 @@ +package com.ecep.contract.manager.ds.contract.controller; + +import com.ecep.contract.manager.ds.contract.model.ContractPayPlan; +import com.ecep.contract.manager.ds.contract.service.ContractPayPlanService; +import com.ecep.contract.manager.ds.contract.vo.ContractPayPlanViewModel; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.ViewModelService; +import com.ecep.contract.manager.ui.util.LocalDateTimeTableCell; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.Tab; +import javafx.scene.control.TableColumn; +import javafx.scene.control.cell.TextFieldTableCell; +import javafx.util.converter.CurrencyStringConverter; +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") +public class ContractTabSkinPayPlan extends AbstContractTableTabSkin { + + public TableColumn idColumn; + public TableColumn payDateColumn; + public TableColumn payRatioColumn; + public TableColumn payCurrencyColumn; + public TableColumn payTermColumn; + public TableColumn refIdColumn; + public TableColumn updateDateColumn; + + ContractPayPlanService payPlanService; + + public ContractTabSkinPayPlan(ContractWindowController controller) { + super(controller); + } + + ContractPayPlanService getPayPlanService() { + if (payPlanService == null) { + payPlanService = getBean(ContractPayPlanService.class); + } + return payPlanService; + } + + @Override + public Tab getTab() { + return controller.payPlanTab; + } + + @Override + protected ViewModelService getViewModelService() { + return getPayPlanService(); + } + + @Override + public void initializeTable() { + super.initializeTable(); + + NumberFormat numberInstance = NumberFormat.getNumberInstance(getLocale()); + numberInstance.setMaximumFractionDigits(2); + numberInstance.setMinimumFractionDigits(2); + CurrencyStringConverter currencyStringConverter = new CurrencyStringConverter(numberInstance); + + idColumn.setCellValueFactory(param -> param.getValue().getId()); + payDateColumn.setCellValueFactory(param -> param.getValue().getPayDate()); + + payRatioColumn.setCellValueFactory(param -> param.getValue().getPayRatio()); + payRatioColumn.setCellFactory(TextFieldTableCell.forTableColumn(new NumberStringConverter())); + + payCurrencyColumn.setCellValueFactory(param -> param.getValue().getPayCurrency()); + payCurrencyColumn.setCellFactory(TextFieldTableCell.forTableColumn(currencyStringConverter)); + + payTermColumn.setCellValueFactory(param -> param.getValue().getPayTerm()); + + refIdColumn.setCellValueFactory(param -> param.getValue().getRefId()); + + updateDateColumn.setCellValueFactory(param -> param.getValue().getUpdateDate()); + updateDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); + } + + @Override + protected void createContextMenu(ContextMenu contextMenu) { + super.createContextMenu(contextMenu); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinPurchaseOrders.java b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinPurchaseOrders.java new file mode 100644 index 0000000..59f61b0 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinPurchaseOrders.java @@ -0,0 +1,105 @@ +package com.ecep.contract.manager.ds.contract.controller; + +import com.ecep.contract.manager.ds.contract.ContractPayWay; +import com.ecep.contract.manager.ds.contract.controller.purchase_order.PurchaseOrderWindowController; +import com.ecep.contract.manager.ds.contract.model.PurchaseOrder; +import com.ecep.contract.manager.ds.contract.service.PurchaseOrdersService; +import com.ecep.contract.manager.ds.contract.vo.PurchaseOrderViewModel; +import com.ecep.contract.manager.ds.other.EmployeeStringConverter; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.TabSkin; +import com.ecep.contract.manager.ui.util.LocalDateTimeTableCell; +import javafx.scene.control.*; +import javafx.scene.input.KeyCode; + +import java.time.LocalDateTime; + +/** + * 子合同 + */ +@FxmlPath("/ui/contract/contract-tab-purchase-orders.fxml") +public class ContractTabSkinPurchaseOrders + extends AbstContractTableTabSkin + implements TabSkin { + + + private PurchaseOrdersService purchaseOrdersService; + private EmployeeStringConverter employeeStringConverter; + + public TableColumn idColumn; + public TableColumn codeColumn; + public TableColumn table_makerColumn; + public TableColumn table_makerDateColumn; + public TableColumn table_verifierColumn; + public TableColumn table_verifierDateColumn; + public TableColumn table_closerColumn; + public TableColumn table_closerDateColumn; + public TableColumn table_descriptionColumn; + + public MenuItem table_menu_refresh; + + private Tab tab; + + public ContractTabSkinPurchaseOrders(ContractWindowController controller, Tab tab) { + super(controller); + this.tab = tab; + } + + @Override + protected PurchaseOrdersService getViewModelService() { + return getPurchaseOrdersService(); + } + + @Override + public void initializeUIComponents() { + super.initializeUIComponents(); + // 合同是收时,有关联子合同 + getTab().disableProperty().bind(viewModel.getPayWay().isEqualTo(ContractPayWay.PAY).not()); + } + + @Override + public Tab getTab() { + return tab; + } + + @Override + public void initializeTab() { + table_menu_refresh.setOnAction(this::onTableRefreshAction); + + idColumn.setCellValueFactory(param -> param.getValue().getId()); + codeColumn.setCellValueFactory(param -> param.getValue().getCode()); + + EmployeeStringConverter converter = getEmployeeStringConverter(); + table_makerColumn.setCellValueFactory(param -> param.getValue().getMaker().map(converter::toString)); + table_makerDateColumn.setCellValueFactory(param -> param.getValue().getMakerDate()); + table_makerDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); + table_verifierColumn.setCellValueFactory(param -> param.getValue().getMaker().map(converter::toString)); + table_verifierDateColumn.setCellValueFactory(param -> param.getValue().getVerifierDate()); + table_verifierDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); + table_closerColumn.setCellValueFactory(param -> param.getValue().getMaker().map(converter::toString)); + table_closerDateColumn.setCellValueFactory(param -> param.getValue().getCloserDate()); + table_closerDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>()); + + table_descriptionColumn.setCellValueFactory(param -> param.getValue().getDescription()); + super.initializeTab(); + } + + @Override + protected void onTableRowDoubleClickedAction(PurchaseOrderViewModel item) { + showInOwner(PurchaseOrderWindowController.class, item); + } + + PurchaseOrdersService getPurchaseOrdersService() { + if (purchaseOrdersService == null) { + purchaseOrdersService = getBean(PurchaseOrdersService.class); + } + return purchaseOrdersService; + } + + EmployeeStringConverter getEmployeeStringConverter() { + if (employeeStringConverter == null) { + employeeStringConverter = getBean(EmployeeStringConverter.class); + } + return employeeStringConverter; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinSaleOrders.java b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinSaleOrders.java new file mode 100644 index 0000000..33646d6 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinSaleOrders.java @@ -0,0 +1,115 @@ +package com.ecep.contract.manager.ds.contract.controller; + +import com.ecep.contract.manager.ds.contract.ContractPayWay; +import com.ecep.contract.manager.ds.contract.controller.sale_order.SalesOrderWindowController; +import com.ecep.contract.manager.ds.contract.model.SalesOrder; +import com.ecep.contract.manager.ds.contract.service.SaleOrdersService; +import com.ecep.contract.manager.ds.contract.vo.SalesOrderViewModel; +import com.ecep.contract.manager.ds.other.EmployeeStringConverter; +import com.ecep.contract.manager.ds.other.model.Employee; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.TabSkin; +import com.ecep.contract.manager.ui.util.LocalDateFieldTableCell; +import javafx.scene.control.*; +import javafx.scene.control.cell.TextFieldTableCell; +import javafx.scene.input.KeyCode; +import lombok.Setter; + +import java.time.LocalDate; + +/** + * 子合同 + */ +@FxmlPath("/ui/contract/contract-tab-sale-orders.fxml") +public class ContractTabSkinSaleOrders + extends AbstContractTableTabSkin + implements TabSkin { + + @Setter + private SaleOrdersService saleOrdersService; + @Setter + private EmployeeStringConverter employeeStringConverter; + + public TableColumn idColumn; + public TableColumn codeColumn; + public TableColumn employeeColumn; + public TableColumn makerColumn; + public TableColumn makerDateColumn; + public TableColumn verifierColumn; + public TableColumn verifierDateColumn; + public TableColumn descriptionColumn; + public MenuItem subContractTable_menu_refresh; + public TextField contractSearchKeyField; + public Button searchBtn; + private Tab tab; + + public ContractTabSkinSaleOrders(ContractWindowController controller, Tab tab) { + super(controller); + this.tab = tab; + } + + @Override + public void initializeUIComponents() { + super.initializeUIComponents(); + // 合同是收时,有关联子合同 + getTab().disableProperty().bind(viewModel.getPayWay().isEqualTo(ContractPayWay.RECEIVE).not()); + } + + @Override + public Tab getTab() { + return tab; + } + + @Override + protected SaleOrdersService getViewModelService() { + return getSaleOrdersService(); + } + + @Override + public void initializeTab() { + contractSearchKeyField.setOnKeyReleased(event -> { + if (event.getCode() == KeyCode.ENTER) { + searchBtn.fire(); + } + }); + + searchBtn.setOnAction(this::onTableRefreshAction); + subContractTable_menu_refresh.setOnAction(this::onTableRefreshAction); + + idColumn.setCellValueFactory(param -> param.getValue().getId()); + codeColumn.setCellValueFactory(param -> param.getValue().getCode()); + employeeColumn.setCellValueFactory(param -> param.getValue().getEmployee()); + employeeColumn.setCellFactory(TextFieldTableCell.forTableColumn(getEmployeeStringConverter())); + makerColumn.setCellValueFactory(param -> param.getValue().getMaker()); + makerColumn.setCellFactory(TextFieldTableCell.forTableColumn(getEmployeeStringConverter())); + makerDateColumn.setCellValueFactory(param -> param.getValue().getMakerDate()); + makerDateColumn.setCellFactory(LocalDateFieldTableCell.forTableColumn()); + + verifierColumn.setCellValueFactory(param -> param.getValue().getVerifier()); + verifierColumn.setCellFactory(TextFieldTableCell.forTableColumn(getEmployeeStringConverter())); + verifierDateColumn.setCellValueFactory(param -> param.getValue().getVerifierDate()); + verifierDateColumn.setCellFactory(LocalDateFieldTableCell.forTableColumn()); + + descriptionColumn.setCellValueFactory(param -> param.getValue().getDescription()); + super.initializeTab(); + } + + @Override + protected void onTableRowDoubleClickedAction(SalesOrderViewModel item) { + SalesOrderWindowController.show(item, getTableView().getScene().getWindow()); + } + + private EmployeeStringConverter getEmployeeStringConverter() { + if (employeeStringConverter == null) { + employeeStringConverter = getBean(EmployeeStringConverter.class); + } + return employeeStringConverter; + } + + SaleOrdersService getSaleOrdersService() { + if (saleOrdersService == null) { + saleOrdersService = getBean(SaleOrdersService.class); + } + return saleOrdersService; + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinSubContract.java b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinSubContract.java new file mode 100644 index 0000000..1d9c647 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinSubContract.java @@ -0,0 +1,99 @@ +package com.ecep.contract.manager.ds.contract.controller; + +import com.ecep.contract.manager.ds.contract.ContractPayWay; +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ds.contract.vo.ContractViewModel; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.TabSkin; +import com.ecep.contract.manager.ui.ViewModelService; +import com.ecep.contract.manager.util.SpecificationUtils; +import javafx.application.Platform; +import javafx.scene.control.*; +import javafx.scene.input.KeyCode; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.util.StringUtils; + +import java.time.LocalDate; +import java.util.List; + +/** + * 子合同 + */ +@FxmlPath("/ui/contract/contract-tab-sub-contract.fxml") +public class ContractTabSkinSubContract + extends AbstContractTableTabSkin + implements TabSkin { + + public TableColumn subContractTable_idColumn; + public TableColumn subContractTable_nameColumn; + public TableColumn subContractTable_codeColumn; + public TableColumn subContractTable_setupDateColumn; + public TableColumn subContractTable_inureDateColumn; + public TableColumn subContractTable_orderDateColumn; + public TableColumn subContractTable_varyDateColumn; + public MenuItem subContractTable_menu_refresh; + public TextField contractSearchKeyField; + public Button contractSearchBtn; + + public ContractTabSkinSubContract(ContractWindowController controller) { + super(controller); + } + + @Override + public void initializeUIComponents() { + super.initializeUIComponents(); + // 合同是收时,有关联子合同 + getTab().disableProperty().bind(viewModel.getPayWay().isEqualTo(ContractPayWay.RECEIVE).not()); + } + + @Override + public Tab getTab() { + return controller.contractTab; + } + + @Override + protected ContractService getViewModelService() { + return getContractService(); + } + + + @Override + public Specification getSpecification(Contract parent) { + return SpecificationUtils.and(getSpecification(), (root, query, builder) -> { + return builder.equal(root.get("parentCode"), parent.getCode()); + }); + } + + @Override + public void initializeTab() { + super.initializeTab(); + contractSearchKeyField.setOnKeyReleased(event -> { + if (event.getCode() == KeyCode.ENTER) { + contractSearchBtn.fire(); + } + }); + + contractSearchBtn.setOnAction(this::onTableRefreshAction); + subContractTable_menu_refresh.setOnAction(this::onTableRefreshAction); + + subContractTable_idColumn.setCellValueFactory(param -> param.getValue().getId()); + subContractTable_nameColumn.setCellValueFactory(param -> param.getValue().getName()); + subContractTable_codeColumn.setCellValueFactory(param -> param.getValue().getCode()); + + subContractTable_setupDateColumn.setCellValueFactory(param -> param.getValue().getSetupDate()); + subContractTable_inureDateColumn.setCellValueFactory(param -> param.getValue().getInureDate()); + subContractTable_orderDateColumn.setCellValueFactory(param -> param.getValue().getOrderDate()); + subContractTable_varyDateColumn.setCellValueFactory(param -> param.getValue().getVaryDate()); + + Platform.runLater(() -> { + getTableView().getSortOrder().add(subContractTable_codeColumn); + }); + } + + @Override + protected void onTableRowDoubleClickedAction(ContractViewModel item) { + ContractWindowController.show(item, getTableView().getScene().getWindow()); + } + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinVendorBid.java b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinVendorBid.java new file mode 100644 index 0000000..5267cfe --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTabSkinVendorBid.java @@ -0,0 +1,193 @@ +package com.ecep.contract.manager.ds.contract.controller; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.ds.company.model.Company; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.company.vo.CompanyViewModel; +import com.ecep.contract.manager.ds.contract.ContractPayWay; +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.contract.model.ContractBidVendor; +import com.ecep.contract.manager.ds.contract.model.ContractFile; +import com.ecep.contract.manager.ds.contract.service.ContractBidVendorService; +import com.ecep.contract.manager.ds.contract.service.ContractFileService; +import com.ecep.contract.manager.ds.contract.vo.ContractBidVendorViewModel; +import com.ecep.contract.manager.ui.EditableEntityTableTabSkin; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.TabSkin; +import com.ecep.contract.manager.ui.ViewModelService; +import javafx.event.ActionEvent; +import javafx.scene.control.*; +import javafx.util.StringConverter; +import lombok.Setter; +import org.controlsfx.control.textfield.TextFields; +import org.hibernate.Hibernate; +import org.springframework.data.jpa.domain.Specification; + +import java.util.List; + +/** + * 以下是比价供应商 + */ +@FxmlPath("/ui/contract/contract-tab-bid.fxml") +public class ContractTabSkinVendorBid + extends AbstContractTableTabSkin + implements TabSkin, EditableEntityTableTabSkin { + @Setter + private ContractBidVendorService service; + public TableColumn bidVendorTable_idColumn; + public TableColumn bidVendorTable_companyColumn; + public TableColumn bidVendorTable_quotationSheetColumn; + + public Button bidVendorCreateBtn; + public MenuItem bidVendorTable_menu_refresh; + public MenuItem bidVendorTable_menu_delete; + public MenuItem bidVendorTable_menu_chose_sheet; + + public ContractTabSkinVendorBid(ContractWindowController controller) { + super(controller); + } + + @Override + protected ContractBidVendorService getViewModelService() { + return getService(); + } + + @Override + public void initializeUIComponents() { + super.initializeUIComponents(); + // 合同是收时,有关联子合同 + getTab().disableProperty().bind(viewModel.getPayWay().isEqualTo(ContractPayWay.PAY).not()); + } + + @Override + public Tab getTab() { + return controller.bidVendorTab; + } + + static class QuotationSheetColumnTableCell extends TableCell { + private ContractFileService contractFileService; + + public ContractFileService getContractFileService() { + if (contractFileService == null) { + contractFileService = SpringApp.getBean(ContractFileService.class); + } + return contractFileService; + } + + @Override + protected void updateItem(ContractFile item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setText(null); + } else { + if (!Hibernate.isInitialized(item)) { + item = getContractFileService().findById(item.getId()); + ContractBidVendorViewModel viewModel = getTableRow().getItem(); + viewModel.getQuotationSheet().set(item); + } + setText(item.getFileName()); + } + } + } + + @Override + public void initializeTab() { + bidVendorCreateBtn.setOnAction(this::onBidVendorTableCreateAction); + bidVendorTable_menu_refresh.setOnAction(this::onTableRefreshAction); + bidVendorTable_menu_delete.setOnAction(this::onTableDeleteAction); + bidVendorTable_menu_chose_sheet.setOnAction(this::onBidVendorTableChoseQuotationSheetAction); + + bidVendorTable_idColumn.setCellValueFactory(param -> param.getValue().getId()); + bidVendorTable_companyColumn.setCellValueFactory(param -> param.getValue().getCompany().map(c -> { + if (c == null) { + return null; + } else { + return c.getName(); + } + })); + bidVendorTable_quotationSheetColumn.setCellValueFactory(param -> param.getValue().getQuotationSheet()); + bidVendorTable_quotationSheetColumn.setCellFactory(param -> new QuotationSheetColumnTableCell()); + + super.initializeTab(); + } + + @Override + protected void onTableRowDoubleClickedAction(ContractBidVendorViewModel item) { + showInOwner(VendorBidWindowController.class, item); + } + + public void onBidVendorTableCreateAction(ActionEvent event) { + Contract contract = getParent(); + + + ChoiceDialog dialog = new ChoiceDialog<>(); + dialog.setTitle("添加比价供应商"); + dialog.setHeaderText("请输入比价的供应商公司名称,支持模糊搜索功能"); + dialog.setWidth(400); + ComboBox comboBox = (ComboBox) dialog.getDialogPane().getContent().lookup("ComboBox"); + TextFields.bindAutoCompletion(comboBox.getEditor(), p -> { + if (p.isCancelled()) { + return null; + } + return getCompanyService().search(p.getUserText()).stream().map(CompanyViewModel::from).toList(); + }, new StringConverter<>() { + @Override + public String toString(CompanyViewModel viewModel) { + return viewModel.getName().get(); + } + + @Override + public CompanyViewModel fromString(String string) { + return null; + } + }); + + comboBox.setEditable(true); + dialog.showAndWait().ifPresent(selectedItem -> { + + Company company = getCompanyService().findAllByName(selectedItem).getFirst(); + + List list = getService().findByContractAndCompany(contract, company); + if (list == null || list.isEmpty()) { + ContractBidVendor bidVendor = new ContractBidVendor(); + bidVendor.setContract(contract); + bidVendor.setCompany(company); + getService().save(bidVendor); + loadTableDataSet(); + } else { + Alert alert = new Alert(Alert.AlertType.INFORMATION); + alert.setTitle("添加比价供应商"); + alert.setHeaderText("添加的供应商:" + company.getName() + " 已经存在。"); + alert.showAndWait(); + } + }); + } + + + /** + * 匹配报价表 + * + * @param event event + */ + public void onBidVendorTableChoseQuotationSheetAction(ActionEvent event) { + ContractBidVendorViewModel selectedItem = getSelectedItem(); + if (selectedItem == null) { + return; + } + + // TODO xx + } + + private ContractBidVendorService getService() { + if (service == null) { + service = SpringApp.getBean(ContractBidVendorService.class); + } + return service; + } + + private CompanyService getCompanyService() { + return controller.companyService; + } + + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTypeTableCell.java b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTypeTableCell.java new file mode 100644 index 0000000..42cade9 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractTypeTableCell.java @@ -0,0 +1,40 @@ +package com.ecep.contract.manager.ds.contract.controller; + +import com.ecep.contract.manager.ds.contract.model.ContractType; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ds.contract.vo.ContractViewModel; +import javafx.scene.control.TableCell; +import org.hibernate.Hibernate; + +import static com.ecep.contract.manager.SpringApp.getBean; + +public class ContractTypeTableCell extends TableCell { + private ContractService contractService; + + public ContractTypeTableCell() { + } + + public ContractTypeTableCell(ContractService contractService) { + this.contractService = contractService; + } + + ContractService getContractService() { + if (contractService == null) { + contractService = getBean(ContractService.class); + } + return contractService; + } + + @Override + protected void updateItem(ContractType item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setText(""); + return; + } + if (!Hibernate.isInitialized(item)) { + item = getContractService().findTypeById(item.getId()); + } + setText(item.getName()); + } +} diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractUpdater.java b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractUpdater.java new file mode 100644 index 0000000..ecbe312 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractUpdater.java @@ -0,0 +1,195 @@ +package com.ecep.contract.manager.ds.contract.controller; + +import com.ecep.contract.manager.SpringApp; +import com.ecep.contract.manager.cloud.u8.YongYouU8Service; +import com.ecep.contract.manager.ds.company.service.CompanyService; +import com.ecep.contract.manager.ds.contract.model.ContractGroup; +import com.ecep.contract.manager.ds.contract.model.ContractKind; +import com.ecep.contract.manager.ds.contract.model.ContractType; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ds.customer.service.CompanyCustomerService; +import com.ecep.contract.manager.ds.other.model.Employee; +import com.ecep.contract.manager.ds.other.service.EmployeeService; +import com.ecep.contract.manager.ds.vendor.service.CompanyVendorService; +import com.ecep.contract.manager.ui.MessageHolder; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Data; +import lombok.Getter; +import org.springframework.beans.BeansException; + +import java.util.Map; +import java.util.logging.Level; + +@Data +public class ContractUpdater implements MessageHolder { + + @lombok.Data + static class Data { + + + @Getter + private boolean changed = false; + + private String guid; + private String code; + private String name; + private String parentCode; + private String description; + private String typeCode; + private String groupCode; + private String kindName; + private String way; + private String unit; + private java.sql.Date orderDate; + private java.sql.Date startDate; + private java.sql.Date endDate; + private String setupPerson; + private java.sql.Date setupDate; + private String inurePerson; + private java.sql.Date inureDate; + private String varyPerson; + private java.sql.Date varyDate; + private Double totalCurrency; + private String personId; + + private void setValues(ContractUpdater updater, Map rs) { + guid = (String) rs.get("GUID"); + code = (String) rs.get("strContractID"); + name = (String) rs.get("strContractName"); + parentCode = (String) rs.get("strParentID"); + description = (String) rs.get("strContractDesc"); + typeCode = (String) rs.get("strContractType"); + groupCode = (String) rs.get("strContractGrp"); + kindName = (String) rs.get("strContractKind"); + way = (String) rs.get("strWay"); + unit = (String) rs.get("strBisectionUnit"); + orderDate = updater.convertValue(rs.get("strContractOrderDate"), java.sql.Date.class); + startDate = updater.convertValue(rs.get("strContractStartDate"), java.sql.Date.class); + endDate = updater.convertValue(rs.get("strContractEndDate"), java.sql.Date.class); + setupPerson = (String) rs.get("strSetupPerson"); + setupDate = updater.convertValue(rs.get("strSetupDate"), java.sql.Date.class); + inurePerson = (String) rs.get("strInurePerson"); + inureDate = updater.convertValue(rs.get("strInureDate"), java.sql.Date.class); + varyPerson = (String) rs.get("strVaryPerson"); + varyDate = updater.convertValue(rs.get("dtVaryDate"), java.sql.Date.class); + totalCurrency = (Double) rs.get("dblTotalCurrency"); + personId = (String) rs.get("strPersonID"); + } + + private void changed() { + changed = true; + } + } + + public static ContractUpdater with(MessageHolder holder) { + ContractUpdater updater = new ContractUpdater(); + updater.holder = holder; + return updater; + } + + + private ObjectMapper objectMapper; + private MessageHolder holder; + private ContractService contractService; + private EmployeeService employeeService; + private CompanyService companyService; + private CompanyVendorService vendorService; + private CompanyCustomerService customerService; + private YongYouU8Service u8Service; + + + public T convertValue(Object fromValue, Class toValueType) throws IllegalArgumentException { + return getObjectMapper().convertValue(fromValue, toValueType); + } + + @Override + public void addMessage(Level level, String message) { + holder.addMessage(level, message); + } + + + public void updateMessage(String message) { + MessageHolder consumer = getHolder(); + if (consumer != null) { + consumer.info(message); + } + } + + T getBean(Class requiredType) throws BeansException { + return SpringApp.getBean(requiredType); + } + + private ObjectMapper getObjectMapper() { + if (objectMapper == null) { + objectMapper = getBean(ObjectMapper.class); + } + return objectMapper; + } + + private ContractService getContractService() { + if (contractService == null) { + contractService = getBean(ContractService.class); + } + return contractService; + } + + private EmployeeService getEmployeeService() { + if (employeeService == null) { + employeeService = getBean(EmployeeService.class); + } + return employeeService; + } + + private ContractType getTypeByCode(String typeCode) { + return getContractService().findTypeByCode(typeCode); + } + + private ContractGroup getGroupByCode(String groupCode) { + return getContractService().findGroupByCode(groupCode); + } + + private ContractKind getKindByName(String kindName) { + return getContractService().findKindByName(kindName); + } + + private Employee findEmployeeByCode(String personCode) { + return getEmployeeService().findByCode(personCode); + } + + private Employee findEmployeeByName(String personName) { + if (personName == null) { + return null; + } + return getEmployeeService().findByName(personName); + } + + private CompanyVendorService getVendorService() { + if (vendorService == null) { + vendorService = SpringApp.getBean(CompanyVendorService.class); + } + return vendorService; + } + + private CompanyCustomerService getCustomerService() { + if (customerService == null) { + customerService = SpringApp.getBean(CompanyCustomerService.class); + } + return customerService; + } + + private CompanyService getCompanyService() { + if (companyService == null) { + companyService = SpringApp.getBean(CompanyService.class); + } + return companyService; + } + + private YongYouU8Service getU8Service() { + if (u8Service == null) { + u8Service = SpringApp.getBean(YongYouU8Service.class); + } + return u8Service; + } + + +} diff --git a/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractVerifyWindowController.java b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractVerifyWindowController.java new file mode 100644 index 0000000..0ce5ec2 --- /dev/null +++ b/src/main/java/com/ecep/contract/manager/ds/contract/controller/ContractVerifyWindowController.java @@ -0,0 +1,397 @@ +package com.ecep.contract.manager.ds.contract.controller; + +import com.ecep.contract.manager.ds.contract.ContractPayWay; +import com.ecep.contract.manager.ds.contract.model.Contract; +import com.ecep.contract.manager.ds.contract.service.ContractService; +import com.ecep.contract.manager.ds.contract.tasker.ContractVerifyComm; +import com.ecep.contract.manager.ds.contract.tasker.ContractVerifyResultExportAsExcelFile; +import com.ecep.contract.manager.ui.BaseController; +import com.ecep.contract.manager.ds.other.model.Employee; +import com.ecep.contract.manager.ds.other.service.EmployeeService; +import com.ecep.contract.manager.ds.project.service.SaleTypeService; +import com.ecep.contract.manager.ds.vendor.service.VendorGroupService; +import com.ecep.contract.manager.ui.FxmlPath; +import com.ecep.contract.manager.ui.Message; +import com.ecep.contract.manager.ui.MessageHolder; +import com.ecep.contract.manager.util.UITools; +import javafx.application.Platform; +import javafx.beans.property.SimpleListProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.*; +import javafx.scene.layout.HBox; +import javafx.stage.*; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.Hibernate; +import org.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.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import java.io.File; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; + +import static com.ecep.contract.manager.util.TableViewUtils.bindDoubleClicked; +import static com.ecep.contract.manager.util.TableViewUtils.bindEnterPressed; +import static java.util.concurrent.CompletableFuture.runAsync; + +@Lazy +@Scope("prototype") +@Component +@FxmlPath("/ui/contract/contract-verify.fxml") +public class ContractVerifyWindowController extends BaseController { + private static final Logger logger = LoggerFactory.getLogger(ContractVerifyWindowController.class); + + @Override + public Stage show(FXMLLoader loader, Window owner, Modality modality) { + return super.show(loader, owner, modality); + } + + @Setter + @Getter + public static class MessageExt extends Message { + private String prefix = null; + + public MessageExt(Level level, String message) { + super(level, message); + } + } + + + @Data + public static class Model implements MessageHolder { + private SimpleStringProperty code = new SimpleStringProperty(); + private SimpleStringProperty name = new SimpleStringProperty(); + private SimpleObjectProperty employee = new SimpleObjectProperty<>(); + private SimpleObjectProperty setupDate = new SimpleObjectProperty<>(); + private SimpleListProperty messages = new SimpleListProperty<>(FXCollections.observableArrayList()); + + @Override + public void addMessage(Level level, String message) { + MessageExt msg = new MessageExt(level, message); + messages.add(msg); + } + + @Override + public MessageHolder sub(String prefix) { + return (level, message) -> { + MessageExt msg = new MessageExt(level, message); + msg.setPrefix(prefix); + msg.setMessage(message); + messages.add(msg); + }; + } + } + + + class EmployeeTableCell extends TableCell { + @Override + protected void updateItem(Employee item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setText(null); + return; + } + if (!Hibernate.isInitialized(item)) { + item = employeeService.findById(item.getId()); + Model model = getTableRow().getItem(); + if (model != null) { + model.getEmployee().set(item); + } + } + setText(item.toPrettyString()); + } + } + + static class StateTableCell extends TableCell> { + Label message2Label(MessageExt message) { + Label label = new Label(message.getMessage()); + label.getStyleClass().add("msg-text"); + label.getStyleClass().add(message.getLevel().getName().toLowerCase()); + return label; + } + + @Override + protected void updateItem(ObservableList item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setText(null); + setGraphic(null); + return; + } + if (item.isEmpty()) { + setText(""); + setGraphic(null); + return; + } + try { + HBox box = new HBox(); + box.getStyleClass().add("message-column-ctx"); + + // 使用绑定方式监听列表变化,避免重复创建节点 + List