Compare commits
127 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 643338f4b0 | |||
| 880671a5a9 | |||
| 4e738bea3c | |||
| 3cf3a717be | |||
| e8c8305f40 | |||
| be63ff62a4 | |||
| 18057a657e | |||
| db07befffe | |||
| 94030a5a39 | |||
| 7e59f2c17e | |||
| c8b0d57f22 | |||
| 6eebdb1744 | |||
| 9dc90507cb | |||
| 0c26d329c6 | |||
| e3661630fe | |||
| 5d6fb961b6 | |||
| 72edb07798 | |||
| 330418cfd6 | |||
| c10bd369c0 | |||
| f0e85c5a18 | |||
| a784438e97 | |||
| 02afa189f8 | |||
| 87290f15b0 | |||
| e761990ebf | |||
| 1cb0edbd07 | |||
| dd49c3927a | |||
| 7d4961dae4 | |||
| 235269f86f | |||
| 0b45f6eef2 | |||
| 22ab2c7bdf | |||
| eea4d93ae1 | |||
| 71a358fa77 | |||
| cf0a7e18ea | |||
| d2e0dc4555 | |||
| 1c1ff678a5 | |||
| 5b3ab3ed00 | |||
| 59d78619da | |||
| 86e18632aa | |||
| ddd9dad945 | |||
| 333feb0d5a | |||
| 553feac0a4 | |||
| bda92193d4 | |||
| 0dcc9236a8 | |||
| e49952a63c | |||
| 35e8fba805 | |||
| c4eec0a9dd | |||
| 51b8c16798 | |||
| 49413ad473 | |||
| 64471b46f8 | |||
| 1f354853b0 | |||
| b03b5385a5 | |||
| df6188db40 | |||
| 510952d72e | |||
| a4db8a1644 | |||
| 42a8f9ab30 | |||
| 045a1e9eed | |||
| 97a2586c21 | |||
| 45f7b611c5 | |||
| bf90117116 | |||
| 928d10c681 | |||
| fa6920806d | |||
| c21269975d | |||
| ad42a49858 | |||
| 2057c3ca67 | |||
| dc764e6ed8 | |||
| 09b0da498b | |||
| 45eed8281f | |||
| 7b023fd07b | |||
| 9561ad99e6 | |||
| 9c3306eea3 | |||
| 71d3ecab52 | |||
| 5919636c04 | |||
| 57fbae90c5 | |||
| 4b8c1d4038 | |||
| 543311c676 | |||
| 73cbb4e19e | |||
| 515b255567 | |||
| 386b6d01b4 | |||
| 39dbce013f | |||
| b84e011857 | |||
| 866e08224a | |||
| 8aac509e51 | |||
| 35a15f4702 | |||
| 3c3003fdf3 | |||
| 35b33d401b | |||
| 039d753bab | |||
| 07c3f39a95 | |||
| f113cd8c48 | |||
| 2752828094 | |||
| d0645c33f1 | |||
| 588779d611 | |||
| 9ffaac39cb | |||
| 9ef90f98c1 | |||
| c0e4916474 | |||
| 7560250036 | |||
| c42ff7501d | |||
| 30deb0a280 | |||
| ada539bebf | |||
| 5edb44f619 | |||
| 98e48c520f | |||
| 422994efcd | |||
| fc263288e4 | |||
| a1b87de7c0 | |||
| 375de610ef | |||
| 23e1f98ae5 | |||
| 3b90db0450 | |||
| effd7b103c | |||
| 0e444508ff | |||
| acb63116d5 | |||
| 72f8e2e209 | |||
| b927f9d572 | |||
| a2f5e4864b | |||
| 08cc2c29a5 | |||
| 7d684a5006 | |||
| 8139a45f06 | |||
| cca51c6fcc | |||
| 1514cb0f9f | |||
| cf73769ef2 | |||
| c793c0925e | |||
| c69d3f1af2 | |||
| f810532824 | |||
| fb28bac53a | |||
| 9ff84ebe8a | |||
| 6711657663 | |||
| 17e326b35c | |||
|
|
fa25130c9f | ||
|
|
b0b67b5d7f |
1
.env
Normal file
1
.env
Normal file
@@ -0,0 +1 @@
|
||||
PLAYWRIGHT_MCP_EXTENSION_TOKEN=TB7T39NhEbruyS7L-E7RXIGYk39PVK7eu1h-WP8M1Cg
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -35,4 +35,6 @@ build/
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
/config.properties
|
||||
/config.properties
|
||||
node_modules
|
||||
node_modules
|
||||
|
||||
223
.trae/rules/client_controller_rules.md
Normal file
223
.trae/rules/client_controller_rules.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# 客户端 Controller 类规则
|
||||
|
||||
## 1. 目录结构
|
||||
|
||||
客户端控制器位于`client/src/main/java/com/ecep/contract/controller/`目录下,按业务模块组织:
|
||||
|
||||
- **根目录**:包含基础控制器、抽象控制器和主窗口控制器
|
||||
- **业务子包**:按业务领域组织,如`company/`、`project/`、`contract/`、`vendor/`等
|
||||
- **tab/子包**:包含所有Tab皮肤控制器和相关接口、抽象类
|
||||
- **table/子包**:包含表格相关控制器和单元格实现
|
||||
|
||||
## 2. 命名规范
|
||||
|
||||
- **基础控制器**:`BaseController.java`
|
||||
- **抽象控制器**:以`Abst`开头,如`AbstEntityController.java`、`AbstManagerWindowController.java`
|
||||
- **窗口控制器**:以`WindowController`结尾,如`HomeWindowController.java`
|
||||
- **管理窗口控制器**:以`ManagerWindowController`结尾,如`CompanyManagerWindowController.java`
|
||||
- **Tab皮肤控制器**:以`TabSkin`结尾,如`CompanyTabSkinBase.java`
|
||||
- **管理器皮肤**:以`ManagerSkin`结尾,如`CompanyManagerSkin.java`
|
||||
- **组件控制器**:如`OkHttpLoginController.java`
|
||||
|
||||
## 3. 继承关系
|
||||
|
||||
控制器类遵循以下继承层次结构:
|
||||
|
||||
- `BaseController`:所有控制器的基础类,提供窗口显示、异常处理等通用功能
|
||||
- `AbstEntityBasedController`:基于实体的控制器抽象类
|
||||
- `AbstManagerWindowController`:管理窗口控制器的抽象类,包含搜索、分页等功能
|
||||
- `AbstEntityController<T extends IdentityEntity, TV extends IdentityViewModel<T>>`:实体详情窗口控制器的抽象类
|
||||
- 具体业务窗口控制器,如`CompanyWindowController`、`ProjectWindowController`等
|
||||
|
||||
Tab相关控制器继承关系:
|
||||
|
||||
- `Skin`:皮肤接口基础
|
||||
- `TabSkin`:Tab皮肤接口
|
||||
- `AbstGenericTabSkin<C extends BaseController>`:通用Tab皮肤抽象类
|
||||
- `AbstEntityBasedTabSkin<C extends AbstEntityController<T, V>, T extends IdentityEntity, V extends IdentityViewModel<T>>`:基于实体的Tab皮肤抽象类
|
||||
- 具体业务Tab皮肤控制器,如`CompanyTabSkinBase.java`、`ContractTabSkinFiles.java`等
|
||||
|
||||
## 4. 注解使用
|
||||
|
||||
客户端控制器类应使用以下注解:
|
||||
|
||||
- `@Component`:声明为Spring组件,使其可被Spring容器管理
|
||||
- `@Scope("prototype")`:设置为原型作用域,确保每次请求创建新实例
|
||||
- `@Lazy`:延迟加载,提高应用启动性能
|
||||
- `@FxmlPath("/ui/[业务模块]/[文件名].fxml")`:指定对应的FXML文件路径
|
||||
- `@Autowired`:自动注入依赖的服务层组件
|
||||
|
||||
## 5. FXML文件规范
|
||||
|
||||
- **文件位置**:FXML文件通常位于`/client/src/main/resources/ui/`目录下,按业务模块组织子目录
|
||||
- **命名规范**:使用小写字母和下划线,如`home.fxml`、`company/company.fxml`
|
||||
- **关联方式**:通过`@FxmlPath`注解指定控制器对应的FXML文件路径
|
||||
- **组件ID**:FXML文件中组件的id应与控制器中对应的字段名保持一致(驼峰命名法)
|
||||
|
||||
## 6. 控制器方法规范
|
||||
|
||||
### 6.1 初始化方法
|
||||
|
||||
- `initialize()`:控制器初始化方法,用于设置UI组件初始状态、绑定事件监听器等
|
||||
- 此方法会在FXML加载完成后自动调用
|
||||
|
||||
### 6.2 窗口生命周期方法
|
||||
|
||||
- `onShown(WindowEvent windowEvent)`:窗口显示时触发的方法
|
||||
- 用于执行窗口显示后的初始化操作,如数据加载、组件状态更新等
|
||||
- 通常需要调用`super.onShown(windowEvent)`确保父类逻辑执行
|
||||
|
||||
### 6.3 窗口显示方法
|
||||
|
||||
- `public static void show()`:静态方法,用于便捷地显示控制器对应的窗口
|
||||
- 通常有多个重载版本,支持不同参数(如viewModel、owner窗口等)
|
||||
- 内部调用`BaseController.show()`方法实现窗口显示逻辑
|
||||
|
||||
### 6.4 Tab相关方法
|
||||
|
||||
Tab皮肤控制器包含以下核心方法:
|
||||
|
||||
- `install()`:安装Tab皮肤,加载FXML并初始化UI组件
|
||||
- `initializeUIComponents()`:初始化UI组件,设置数据绑定和事件监听
|
||||
- `getTab()`:获取对应的Tab对象
|
||||
- `save()`:保存Tab中的数据变更
|
||||
- `dispose()`:释放资源,清理监听器等
|
||||
|
||||
## 7. 字段规范
|
||||
|
||||
- **UI组件字段**:与FXML文件中的组件id对应,使用`public`访问修饰符
|
||||
- **服务依赖字段**:使用`@Autowired`注解注入,通常使用`private`访问修饰符
|
||||
- **ViewModel字段**:通常作为控制器的核心数据模型,提供`getter/setter`方法
|
||||
- **命名规范**:使用驼峰命名法,如`nameField`、`tabPane`、`saveBtn`等
|
||||
|
||||
## 8. 数据绑定与更新
|
||||
|
||||
- **ViewModel绑定**:控制器通常通过ViewModel与实体数据进行绑定
|
||||
- **异步数据加载**:使用`CompletableFuture`进行异步数据加载,避免阻塞UI线程
|
||||
- **UI更新**:在JavaFX应用线程中更新UI组件,可使用`Platform.runLater()`确保线程安全
|
||||
|
||||
## 9. 事件处理
|
||||
|
||||
- **按钮点击事件**:通过`setOnAction()`方法绑定事件处理器
|
||||
- **表单字段变更**:可通过JavaFX的属性绑定机制监听字段变更
|
||||
- **Tab切换事件**:实现`onTabShown()`方法处理Tab显示事件
|
||||
|
||||
## 10. 异常处理
|
||||
|
||||
- 使用`handleException()`方法统一处理异常
|
||||
- 异常信息通常会显示在状态栏并记录日志
|
||||
- 异步操作中的异常应通过`exceptionally()`或`handle()`方法捕获处理
|
||||
|
||||
## 11. 最佳实践
|
||||
|
||||
- **职责分离**:控制器应专注于UI交互和数据展示,业务逻辑应委托给服务层
|
||||
- **避免重复代码**:利用抽象类和接口提取通用逻辑
|
||||
- **资源管理**:及时释放资源,避免内存泄漏
|
||||
- **线程安全**:确保在JavaFX应用线程中更新UI
|
||||
- **代码可读性**:添加适当的注释,遵循命名规范
|
||||
|
||||
## 12. 示例代码结构
|
||||
|
||||
```java
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
@FxmlPath("/ui/[模块名]/[文件名].fxml")
|
||||
public class [业务]WindowController extends AbstEntityController<[Vo类型], [ViewModel类型]> {
|
||||
private static final Logger logger = LoggerFactory.getLogger([业务]WindowController.class);
|
||||
|
||||
@Autowired
|
||||
private [业务]Service [业务]Service;
|
||||
|
||||
// UI组件字段
|
||||
public BorderPane root;
|
||||
public TabPane tabPane;
|
||||
public TextField nameField;
|
||||
// ...其他UI组件
|
||||
|
||||
public static void show([Vo类型] entity, Window window) {
|
||||
show([ViewModel类型].from(entity), window);
|
||||
}
|
||||
|
||||
public static void show([ViewModel类型] viewModel, Window window) {
|
||||
BaseController.show([业务]WindowController.class, viewModel, window);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
// 初始化UI组件和事件监听
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShown(WindowEvent windowEvent) {
|
||||
super.onShown(windowEvent);
|
||||
// 窗口显示后的逻辑
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 13. TableCell 工厂模式规范
|
||||
|
||||
### 13.1 工厂方法命名
|
||||
|
||||
- TableCell类应提供静态工厂方法`forTableColumn`,用于创建单元格工厂
|
||||
- 方法命名必须为`forTableColumn`,保持一致性
|
||||
|
||||
### 13.2 工厂方法参数
|
||||
|
||||
- 工厂方法应接收服务层参数,如`BankService`
|
||||
- 参数类型应与TableCell构造函数所需的服务类型一致
|
||||
|
||||
### 13.3 工厂方法返回类型
|
||||
|
||||
- 返回类型应为`Callback<javafx.scene.control.TableColumn<V, T>, javafx.scene.control.TableCell<V, T>>`
|
||||
- 其中`V`为ViewModel类型,`T`为单元格值类型
|
||||
|
||||
### 13.4 工厂方法实现
|
||||
|
||||
- 在工厂方法内部,创建并返回TableCell实例
|
||||
- 使用泛型参数确保类型安全
|
||||
|
||||
### 13.5 使用方式
|
||||
|
||||
- 在设置表格列的单元格工厂时,应调用TableCell的静态工厂方法
|
||||
- 避免直接使用`new TableCell<>(service)`的方式创建实例
|
||||
|
||||
### 13.6 示例代码
|
||||
|
||||
```java
|
||||
/**
|
||||
* 银行单元格
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
public class BankTableCell<T> extends AsyncUpdateTableCell<T, Integer, BankVo> {
|
||||
/**
|
||||
* 创建单元格工厂
|
||||
*
|
||||
* @param bankService 银行服务
|
||||
* @return 单元格工厂
|
||||
*/
|
||||
public static <V> Callback<javafx.scene.control.TableColumn<V, Integer>, javafx.scene.control.TableCell<V, Integer>> forTableColumn(
|
||||
BankService bankService) {
|
||||
return param -> new BankTableCell<V>(bankService);
|
||||
}
|
||||
|
||||
public BankTableCell(BankService service) {
|
||||
setService(service);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BankService getServiceBean() {
|
||||
return getBean(BankService.class);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 13.7 使用示例
|
||||
|
||||
```java
|
||||
// 推荐方式:使用工厂方法
|
||||
column.setCellFactory(BankTableCell.forTableColumn(getBankService()));
|
||||
|
||||
// 不推荐方式:直接实例化
|
||||
// column.setCellFactory(param -> new BankTableCell<>(getBankService()));
|
||||
239
.trae/rules/client_converter_rules.md
Normal file
239
.trae/rules/client_converter_rules.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# 客户端转换器类规则
|
||||
|
||||
## 1. 目录结构
|
||||
- 所有类型转换器类必须位于 `com.ecep.contract.converter` 包下
|
||||
- 按照实体类型组织,无需进一步分包
|
||||
|
||||
## 2. 命名规范
|
||||
- 类名格式:`[EntityName]StringConverter`
|
||||
- 例如:`ContractStringConverter`、`CompanyStringConverter`、`VendorStringConverter`
|
||||
|
||||
## 3. 继承关系
|
||||
### 3.1 基础转换器类型
|
||||
- 直接继承 `javafx.util.StringConverter<T>` 接口
|
||||
- 适用于简单的转换场景
|
||||
- 例如:
|
||||
```java
|
||||
public class ContractStringConverter extends StringConverter<ContractVo> {
|
||||
// 实现代码
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 增强转换器类型
|
||||
- 继承自定义的 `EntityStringConverter<T>` 抽象类
|
||||
- 适用于需要更多功能(如延迟初始化、搜索建议等)的场景
|
||||
- 大多数业务实体转换器应使用此类型
|
||||
- 例如:
|
||||
```java
|
||||
public class CompanyStringConverter extends EntityStringConverter<CompanyVo> {
|
||||
// 实现代码
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 枚举转换器类型
|
||||
- 继承 `EnumEntityStringConverter<E extends Enum<?>, T extends BaseEnumEntity<E>>`
|
||||
- 专门用于处理枚举类型实体
|
||||
- 例如:
|
||||
```java
|
||||
public class EnumEntityStringConverter<E extends Enum<?>, T extends BaseEnumEntity<E>> extends StringConverter<T> {
|
||||
// 实现代码
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 核心接口实现
|
||||
### 4.1 StringConverter<T> 接口
|
||||
- 所有转换器必须实现 `toString(T object)` 方法
|
||||
- 所有转换器必须实现 `fromString(String string)` 方法
|
||||
|
||||
## 5. Spring框架集成
|
||||
### 5.1 组件注解
|
||||
- 增强转换器类型通常使用 `@Component` 注解标记为Spring组件
|
||||
- 结合 `@Lazy` 注解实现延迟加载
|
||||
- 例如:
|
||||
```java
|
||||
@Lazy
|
||||
@Component
|
||||
public class CompanyStringConverter extends EntityStringConverter<CompanyVo> {
|
||||
// 实现代码
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 依赖注入
|
||||
- 使用 `@Autowired` 注解注入对应的Service实例
|
||||
- 通常也需要为注入的Service添加 `@Lazy` 注解
|
||||
- 例如:
|
||||
```java
|
||||
@Lazy
|
||||
@Autowired
|
||||
private CompanyService service;
|
||||
```
|
||||
|
||||
## 6. 初始化机制
|
||||
### 6.1 构造函数
|
||||
- 基础转换器通常使用构造函数接收Service实例
|
||||
- 例如:
|
||||
```java
|
||||
public ContractStringConverter(ContractService service) {
|
||||
this.service = service;
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 初始化方法
|
||||
- 增强转换器使用 `@PostConstruct` 注解标记初始化方法
|
||||
- 在初始化方法中配置转换器的各种功能
|
||||
- 例如:
|
||||
```java
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
setInitialized(project -> service.findById(project.getId()));
|
||||
setSuggestion(service::search);
|
||||
setFromString(service::findByName);
|
||||
}
|
||||
```
|
||||
|
||||
## 7. EntityStringConverter 特性配置
|
||||
继承自 EntityStringConverter 的增强转换器可以配置以下特性:
|
||||
|
||||
### 7.1 对象初始化
|
||||
- 使用 `setInitialized(Function<T, T> initialized)` 配置对象延迟初始化逻辑
|
||||
- 通常调用Service的findById方法
|
||||
- 例如:`setInitialized(project -> service.findById(project.getId()))`
|
||||
|
||||
### 7.2 搜索建议
|
||||
- 使用 `setSuggestion(Function<String, List<T>> suggestion)` 配置自动补全数据提供方法
|
||||
- 通常调用Service的search方法
|
||||
- 例如:`setSuggestion(service::search)`
|
||||
|
||||
### 7.3 字符串转换
|
||||
- 使用 `setFromString(Function<String, T> fromString)` 配置从字符串到对象的转换逻辑
|
||||
- 通常调用Service的findByName方法
|
||||
- 例如:`setFromString(service::findByName)`
|
||||
|
||||
### 7.4 格式化器
|
||||
- 使用 `setFormater(Function<T, String> formater)` 配置自定义格式化逻辑
|
||||
- 用于自定义对象的字符串表示
|
||||
- 例如:`setFormater(contract -> contract.getCode() + " " + contract.getName())`
|
||||
|
||||
## 8. 核心方法实现规范
|
||||
### 8.1 toString(T object) 方法
|
||||
- 将对象转换为可读的字符串表示
|
||||
- 必须处理null情况
|
||||
- 通常返回对象的名称、编码或其他关键标识
|
||||
- 对于基础转换器:
|
||||
```java
|
||||
@Override
|
||||
public String toString(ContractVo cc) {
|
||||
return cc.getCode() + " " + cc.getName();
|
||||
}
|
||||
```
|
||||
- 对于枚举转换器:
|
||||
```java
|
||||
@Override
|
||||
public String toString(T object) {
|
||||
return object == null ? "" : object.getValue();
|
||||
}
|
||||
```
|
||||
|
||||
### 8.2 fromString(String string) 方法
|
||||
- 从字符串还原对象实例
|
||||
- 通常调用Service的findByName或findByCode方法
|
||||
- 例如:
|
||||
```java
|
||||
@Override
|
||||
public ContractVo fromString(String string) {
|
||||
return service.findByCode(string);
|
||||
}
|
||||
```
|
||||
- 对于某些场景(如枚举转换器),可能返回null
|
||||
|
||||
## 9. 与Service类的关联
|
||||
### 9.1 Service中的转换器创建
|
||||
- 在Service类中通常会创建并持有对应的StringConverter实例
|
||||
- 实现 `getStringConverter()` 方法返回转换器实例
|
||||
- 例如:
|
||||
```java
|
||||
@Service
|
||||
@CacheConfig(cacheNames = "contract-cache")
|
||||
public class ContractService extends QueryService<ContractVo, ContractViewModel> {
|
||||
|
||||
private final ContractStringConverter stringConverter = new ContractStringConverter(this);
|
||||
|
||||
@Override
|
||||
public StringConverter<ContractVo> getStringConverter() {
|
||||
return stringConverter;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 9.2 Service方法支持
|
||||
- Service需提供 `findByName`、`findById`、`search` 等方法供转换器使用
|
||||
- 这些方法通常需要添加缓存注解以提高性能
|
||||
|
||||
## 10. 使用场景
|
||||
StringConverter主要用于以下场景:
|
||||
- ComboBox、ChoiceBox等选择组件的数据绑定和显示
|
||||
- 表单数据的绑定和转换
|
||||
- 支持用户输入的自动补全功能
|
||||
- 在表格、列表等UI组件中显示对象的可读表示
|
||||
|
||||
## 11. 最佳实践
|
||||
- 对于大多数业务实体,优先使用继承 `EntityStringConverter<T>` 的增强转换器
|
||||
- 使用Spring的依赖注入和组件管理功能
|
||||
- 正确处理null值和空字符串
|
||||
- 为频繁调用的Service方法添加缓存支持
|
||||
- 实现合理的toString逻辑,提供有意义的对象表示
|
||||
|
||||
## 12. 示例代码结构
|
||||
### 12.1 基础转换器示例
|
||||
```java
|
||||
package com.ecep.contract.converter;
|
||||
|
||||
import com.ecep.contract.service.ContractService;
|
||||
import com.ecep.contract.vo.ContractVo;
|
||||
import javafx.util.StringConverter;
|
||||
|
||||
public class ContractStringConverter extends StringConverter<ContractVo> {
|
||||
private final ContractService service;
|
||||
|
||||
public ContractStringConverter(ContractService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(ContractVo cc) {
|
||||
return cc == null ? "" : cc.getCode() + " " + cc.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContractVo fromString(String string) {
|
||||
return string == null || string.isEmpty() ? null : service.findByCode(string);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 12.2 增强转换器示例
|
||||
```java
|
||||
package com.ecep.contract.converter;
|
||||
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
public class CompanyStringConverter extends EntityStringConverter<CompanyVo> {
|
||||
@Lazy
|
||||
@Autowired
|
||||
private CompanyService service;
|
||||
|
||||
@PostConstruct
|
||||
private void init() {
|
||||
setInitialized(company -> service.findById(company.getId()));
|
||||
setSuggestion(service::search);
|
||||
setFromString(service::findByName);
|
||||
}
|
||||
}
|
||||
```
|
||||
508
.trae/rules/client_service_rules.md
Normal file
508
.trae/rules/client_service_rules.md
Normal file
@@ -0,0 +1,508 @@
|
||||
# Client Service 实现编写指南
|
||||
|
||||
## 📋 概述
|
||||
|
||||
本指南总结 Client 模块 Service 层的实现经验,用于指导后续 Service 的编写。本指南基于 Contract-Manager 项目中已实现的 Service 模式整理。
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ 基础架构
|
||||
|
||||
### 1. 继承层次结构
|
||||
|
||||
```java
|
||||
// Service 接口定义
|
||||
public interface IEntityService<T> {
|
||||
T findById(Integer id);
|
||||
T save(T entity);
|
||||
void delete(T entity);
|
||||
List<T> findAll();
|
||||
Page<T> findAll(Map<String, Object> params, Pageable pageable);
|
||||
StringConverter<T> getStringConverter();
|
||||
}
|
||||
|
||||
// 基础 Service 实现
|
||||
public abstract class QueryService<T extends IdentityEntity, TV extends IdentityViewModel<T>>
|
||||
implements ViewModelService<T, TV> {
|
||||
// 核心实现
|
||||
}
|
||||
|
||||
// 具体业务 Service
|
||||
@Service
|
||||
@CacheConfig(cacheNames = "business")
|
||||
public class XxxService extends QueryService<XxxVo, XxxViewModel> {
|
||||
// 业务特定实现
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 核心特性
|
||||
|
||||
- **泛型支持**:`QueryService` 使用泛型处理不同类型的 Vo 和 ViewModel
|
||||
- **WebSocket 通信**:通过 `WebSocketClientService` 与 Server 端通信
|
||||
- **异步处理**:使用 `CompletableFuture` 实现异步操作
|
||||
- **缓存机制**:集成 Spring Cache 支持多级缓存
|
||||
- **错误处理**:统一的异常处理和日志记录
|
||||
|
||||
---
|
||||
|
||||
## 📝 Service 编写规范
|
||||
|
||||
### 1. 类声明和注解
|
||||
|
||||
```java
|
||||
@Service // Spring 组件注解
|
||||
@CacheConfig(cacheNames = "xxx") // 缓存配置,xxx为业务域名称
|
||||
public class XxxService extends QueryService<XxxVo, XxxViewModel> {
|
||||
// Service 实现
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 缓存策略
|
||||
|
||||
#### 缓存注解使用
|
||||
```java
|
||||
@Cacheable(key = "#p0") // 按ID缓存
|
||||
@Cacheable(key = "'code-'+#p0") // 按代码缓存
|
||||
@Cacheable(key = "'name-'+#p0") // 按名称缓存
|
||||
|
||||
@CacheEvict(key = "#p0.id") // 删除时清除ID缓存
|
||||
@CacheEvict(key = "'code-'+#p0.code") // 清除代码缓存
|
||||
|
||||
@Caching(evict = {
|
||||
@CacheEvict(key = "#p0.id"),
|
||||
@CacheEvict(key = "'code-'+#p0.code")
|
||||
}) // 批量清除缓存
|
||||
```
|
||||
|
||||
#### 缓存键设计原则
|
||||
- **ID 缓存**:`#p0`(第一个参数,通常是ID)
|
||||
- **代码缓存**:`'code-'+#p0`(业务代码的缓存)
|
||||
- **名称缓存**:`'name-'+#p0`(业务名称的缓存)
|
||||
- **关联缓存**:`'company-'+#p0.id`(关联实体的缓存)
|
||||
|
||||
### 3. 核心方法实现
|
||||
|
||||
#### findById 方法
|
||||
```java
|
||||
@Cacheable(key = "#p0")
|
||||
@Override
|
||||
public XxxVo findById(Integer id) {
|
||||
return super.findById(id); // 调用父类方法
|
||||
}
|
||||
```
|
||||
|
||||
#### save 方法(带缓存清除)
|
||||
```java
|
||||
@Caching(evict = {
|
||||
@CacheEvict(key = "#p0.id"),
|
||||
@CacheEvict(key = "'code-'+#p0.code")
|
||||
})
|
||||
@Override
|
||||
public XxxVo save(XxxVo entity) {
|
||||
return super.save(entity);
|
||||
}
|
||||
```
|
||||
|
||||
#### delete 方法(带缓存清除)
|
||||
```java
|
||||
@Caching(evict = {
|
||||
@CacheEvict(key = "#p0.id"),
|
||||
@CacheEvict(key = "'code-'+#p0.code")
|
||||
})
|
||||
@Override
|
||||
public void delete(XxxVo entity) {
|
||||
super.delete(entity);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 异步通信模式
|
||||
|
||||
### 1. 基本异步调用
|
||||
|
||||
```java
|
||||
// 异步调用示例
|
||||
public XxxVo findByCode(String code) {
|
||||
try {
|
||||
return async("findByCode", code, String.class).handle((response, ex) -> {
|
||||
if (ex != null) {
|
||||
throw new RuntimeException("远程方法 findByCode 调用失败", ex);
|
||||
}
|
||||
if (response != null) {
|
||||
return updateValue(createNewEntity(), response);
|
||||
}
|
||||
return null;
|
||||
}).get();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("查找实体失败: " + code, e);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 复杂对象处理
|
||||
|
||||
```java
|
||||
public List<XxxDetailVo> findDetailsByXxxId(Integer xxxId) {
|
||||
try {
|
||||
return async("findDetailsByXxxId", List.of(xxxId), List.of(Integer.class))
|
||||
.handle((response, ex) -> {
|
||||
if (ex != null) {
|
||||
throw new RuntimeException("远程方法调用失败", ex);
|
||||
}
|
||||
if (response != null) {
|
||||
try {
|
||||
List<XxxDetailVo> content = new ArrayList<>();
|
||||
for (JsonNode node : response) {
|
||||
XxxDetailVo newEntity = new XxxDetailVo();
|
||||
objectMapper.updateValue(newEntity, node);
|
||||
content.add(newEntity);
|
||||
}
|
||||
return content;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(response.toString(), e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}).get();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 💼 业务逻辑模式
|
||||
|
||||
### 1. 文件系统集成
|
||||
|
||||
#### 路径管理
|
||||
```java
|
||||
@Autowired
|
||||
private SysConfService confService;
|
||||
|
||||
private File basePath;
|
||||
|
||||
public File getBasePath() {
|
||||
if (basePath == null) {
|
||||
basePath = new File(confService.getString(Constant.KEY_BASE_PATH));
|
||||
}
|
||||
return basePath;
|
||||
}
|
||||
|
||||
// 验证路径是否在基础目录内
|
||||
public boolean checkXxxPathInBasePath(XxxVo xxx) {
|
||||
if (!existsXxxPath(xxx)) {
|
||||
return false;
|
||||
}
|
||||
File basePath = getBasePath();
|
||||
if (basePath == null || !basePath.exists()) {
|
||||
throw new IllegalArgumentException("基础目录不存在");
|
||||
}
|
||||
File path = new File(xxx.getPath());
|
||||
return path.getAbsolutePath().startsWith(basePath.getAbsolutePath());
|
||||
}
|
||||
|
||||
// 检查路径是否存在
|
||||
public boolean existsXxxPath(XxxVo xxx) {
|
||||
if (!StringUtils.hasText(xxx.getPath())) {
|
||||
return false;
|
||||
}
|
||||
File path = new File(xxx.getPath());
|
||||
return path.exists();
|
||||
}
|
||||
```
|
||||
|
||||
#### 目录创建
|
||||
```java
|
||||
public File makePath(XxxVo xxx) {
|
||||
File basePath = getBasePath();
|
||||
if (!basePath.exists()) {
|
||||
holder.error("存储目录不存在:" + basePath.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
|
||||
// 构建目录路径逻辑
|
||||
String fileName = FileUtils.escapeFileName(xxx.getName());
|
||||
File dir = new File(basePath, fileName);
|
||||
|
||||
if (!dir.exists()) {
|
||||
if (!dir.mkdir()) {
|
||||
holder.error("创建目录失败:" + dir.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 业务验证模式
|
||||
|
||||
#### 数据完整性验证
|
||||
```java
|
||||
public void verifyXxx(XxxVo xxx, LocalDate verifyDate, MessageHolder holder) {
|
||||
// 检查关键字段
|
||||
if (!StringUtils.hasText(xxx.getCode())) {
|
||||
holder.error("编号异常:未设置");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查状态字段
|
||||
String status = xxx.getStatus();
|
||||
if (StringUtils.hasText(status) && status.contains("无效")) {
|
||||
LocalDate end = xxx.getEndDate();
|
||||
LocalDate begin = xxx.getBeginDate();
|
||||
if (begin == null || end == null) {
|
||||
holder.error("状态异常:" + status);
|
||||
} else {
|
||||
if (!MyDateTimeUtils.dateValidFilter(verifyDate, begin, end, 0)) {
|
||||
holder.error("状态异常:" + status);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
holder.error("状态异常:未设置");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 关联实体验证
|
||||
```java
|
||||
public boolean verifyAsXxxType(XxxVo xxx, LocalDate verifyDate, MessageHolder holder) {
|
||||
boolean valid = false;
|
||||
|
||||
// 检查关联实体
|
||||
RelatedVo related = relatedService.findById(xxx.getRelatedId());
|
||||
if (related == null) {
|
||||
holder.error("关联实体不存在");
|
||||
valid = true;
|
||||
}
|
||||
|
||||
// 检查关联数据
|
||||
if (!StringUtils.hasText(xxx.getRelatedField())) {
|
||||
holder.error("关联字段未设置");
|
||||
valid = true;
|
||||
}
|
||||
|
||||
// 检查业务规则
|
||||
if (xxx.getStatus() == XxxStatus.INACTIVE) {
|
||||
holder.error("业务状态异常:已停用");
|
||||
valid = true;
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 复杂业务查询
|
||||
|
||||
#### 分页查询
|
||||
```java
|
||||
public List<XxxVo> findAllByXxxCondition(XxxCondition condition, LocalDate beginDate, LocalDate endDate) {
|
||||
return findAll(ParamUtils.builder()
|
||||
.equals("field1", condition.getField1())
|
||||
.between("createDate", beginDate, endDate)
|
||||
.equals("status", "ACTIVE")
|
||||
.build(), Pageable.unpaged()).getContent();
|
||||
}
|
||||
```
|
||||
|
||||
#### 组合条件查询
|
||||
```java
|
||||
public Page<XxxVo> findAllWithComplexCondition(XxxQueryParam param) {
|
||||
ParamUtils.ParamBuilder builder = ParamUtils.builder()
|
||||
.equals("category", param.getCategory());
|
||||
|
||||
if (StringUtils.hasText(param.getName())) {
|
||||
builder.like("name", "%" + param.getName() + "%");
|
||||
}
|
||||
|
||||
if (param.getDateRange() != null) {
|
||||
builder.between("createDate", param.getDateRange().getStart(),
|
||||
param.getDateRange().getEnd());
|
||||
}
|
||||
|
||||
return findAll(builder.build(), Pageable.ofSize(param.getPageSize()));
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 工具类和依赖注入
|
||||
|
||||
### 1. 常用工具类依赖
|
||||
|
||||
```java
|
||||
// 常用注入
|
||||
@Autowired
|
||||
private SysConfService confService; // 系统配置服务
|
||||
@Autowired
|
||||
private RelatedService relatedService; // 关联实体服务
|
||||
|
||||
// 静态工具类使用
|
||||
import com.ecep.contract.util.FileUtils; // 文件工具
|
||||
import com.ecep.contract.util.ParamUtils; // 参数工具
|
||||
import com.ecep.contract.util.MyStringUtils; // 字符串工具
|
||||
```
|
||||
|
||||
### 2. 工具类使用示例
|
||||
|
||||
#### ParamUtils 构建查询条件
|
||||
```java
|
||||
// 构建复杂查询条件
|
||||
Map<String, Object> params = ParamUtils.builder()
|
||||
.equals("field1", value1)
|
||||
.equals("field2", value2)
|
||||
.like("name", "%" + keyword + "%")
|
||||
.between("date", startDate, endDate)
|
||||
.in("status", List.of("ACTIVE", "PENDING"))
|
||||
.orderBy("createTime", "desc")
|
||||
.build();
|
||||
```
|
||||
|
||||
#### FileUtils 处理文件路径
|
||||
```java
|
||||
// 文件名转义
|
||||
String safeFileName = FileUtils.escapeFileName(companyName);
|
||||
|
||||
// 获取父级前缀
|
||||
String parentPrefix = FileUtils.getParentPrefixByDistrict(district);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 错误处理和日志
|
||||
|
||||
### 1. 异常处理模式
|
||||
|
||||
```java
|
||||
// 查询异常处理
|
||||
try {
|
||||
return async("findByCode", code, String.class).handle((response, ex) -> {
|
||||
if (ex != null) {
|
||||
throw new RuntimeException("远程方法 findByCode 调用失败", ex);
|
||||
}
|
||||
if (response != null) {
|
||||
return updateValue(createNewEntity(), response);
|
||||
}
|
||||
return null;
|
||||
}).get();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("查找实体失败: " + code, e);
|
||||
}
|
||||
|
||||
// 业务验证异常
|
||||
public boolean businessMethod(XxxVo xxx) {
|
||||
if (xxx == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!xxx.isValid()) {
|
||||
throw new IllegalArgumentException("实体数据无效");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 日志记录
|
||||
|
||||
```java
|
||||
// 在 QueryService 中已集成日志
|
||||
private static final Logger logger = LoggerFactory.getLogger(QueryService.class);
|
||||
|
||||
// 在业务方法中使用
|
||||
public void deleteXxx(XxxVo xxx) {
|
||||
try {
|
||||
super.delete(xxx);
|
||||
logger.info("删除实体成功 #{}", xxx.getId());
|
||||
} catch (Exception e) {
|
||||
logger.error("删除实体失败 #{}", xxx.getId(), e);
|
||||
throw new RuntimeException("删除实体失败", e);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试和验证
|
||||
|
||||
### 1. 方法验证检查点
|
||||
|
||||
- **输入参数验证**:检查空值、格式、范围
|
||||
- **业务逻辑验证**:检查状态、关联、权限
|
||||
- **文件系统验证**:检查路径、权限、空间
|
||||
- **数据库验证**:检查连接、事务、一致性
|
||||
|
||||
### 2. 常见测试场景
|
||||
|
||||
```java
|
||||
// 测试用例示例
|
||||
@Test
|
||||
public void testFindById() {
|
||||
// 正常情况
|
||||
XxxVo result = xxxService.findById(1);
|
||||
assertNotNull(result);
|
||||
|
||||
// 异常情况
|
||||
assertThrows(RuntimeException.class, () -> {
|
||||
xxxService.findById(-1);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSaveWithCache() {
|
||||
XxxVo xxx = createTestXxx();
|
||||
xxx.setCode("TEST001");
|
||||
|
||||
XxxVo saved = xxxService.save(xxx);
|
||||
assertNotNull(saved.getId());
|
||||
|
||||
// 验证缓存
|
||||
XxxVo cached = xxxService.findById(saved.getId());
|
||||
assertEquals(saved.getId(), cached.getId());
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 最佳实践
|
||||
|
||||
### 1. 代码组织原则
|
||||
|
||||
- **单一职责**:每个 Service 专注特定的业务域
|
||||
- **依赖注入**:合理使用 `@Autowired` 注入依赖
|
||||
- **缓存策略**:为高频查询字段配置缓存
|
||||
- **异常处理**:统一处理业务异常和系统异常
|
||||
|
||||
### 2. 性能优化建议
|
||||
|
||||
- **缓存配置**:合理设置缓存过期时间
|
||||
- **分页查询**:避免大数据量一次性查询
|
||||
- **异步处理**:使用异步调用提升响应速度
|
||||
- **批量操作**:考虑批量处理减少网络开销
|
||||
|
||||
### 3. 可维护性提升
|
||||
|
||||
- **命名规范**:遵循项目统一的命名约定
|
||||
- **注释文档**:为复杂业务逻辑添加注释
|
||||
- **代码复用**:提取公共逻辑到工具类
|
||||
- **版本兼容**:考虑前后端版本兼容性
|
||||
|
||||
---
|
||||
|
||||
## 📚 相关规范
|
||||
|
||||
- **Controller 规范**:[client_controller_rules.md](.trae/rules/client_controller_rules.md)
|
||||
- **转换器规范**:[client_converter_rules.md](.trae/rules/client_converter_rules.md)
|
||||
- **Task 规范**:[client_task_rules.md](.trae/rules/client_task_rules.md)
|
||||
- **Entity 规范**:[entity_rules.md](.trae/rules/entity_rules.md)
|
||||
- **VO 规范**:[vo_rules.md](.trae/rules/vo_rules.md)
|
||||
|
||||
---
|
||||
|
||||
*本文档基于 Contract-Manager 项目现有 Service 实现模式总结,遵循项目既定的技术架构和编程规范。*
|
||||
|
||||
**文档版本**: v1.0.0
|
||||
**最后更新**: 2024-12-19
|
||||
**维护团队**: Contract Manager Development Team
|
||||
419
.trae/rules/client_task_rules.md
Normal file
419
.trae/rules/client_task_rules.md
Normal file
@@ -0,0 +1,419 @@
|
||||
# 客户端Tasker实现WebSocketClientTasker接口规范
|
||||
|
||||
## 概述
|
||||
|
||||
本文档基于 `ContractRepairAllTasker` 实现 `WebSocketClientTasker` 接口的经验,总结了客户端Tasker类升级为支持WebSocket通信的最佳实践和规范。
|
||||
|
||||
## WebSocketClientTasker接口介绍
|
||||
|
||||
`WebSocketClientTasker` 接口定义了通过WebSocket与服务器通信的任务的通用方法,包括任务名称、消息更新、进度更新等核心功能。
|
||||
|
||||
### 核心方法
|
||||
|
||||
1. **getTaskName()** - 获取任务名称,用于在WebSocket通信中标识任务
|
||||
2. **updateMessage(Level, String)** - 更新任务执行过程中的消息
|
||||
3. **updateTitle(String)** - 更新任务标题
|
||||
4. **updateProgress(long, long)** - 更新任务进度
|
||||
5. **cancelTask()** - 取消任务执行(默认实现为空)
|
||||
6. **callRemoteTask(MessageHolder, Locale, Object...)** - 调用远程WebSocket任务
|
||||
7. **callRemoteTaskAsync(MessageHolder, Locale, Object...)** - 异步调用远程WebSocket任务
|
||||
8. **generateTaskId()** - 生成唯一的任务ID
|
||||
|
||||
### 典型实现模式概览
|
||||
|
||||
通过分析项目中的17个实现类,我们发现了以下典型实现模式:
|
||||
|
||||
1. **标准实现**:继承Tasker<Object>并实现WebSocketClientTasker
|
||||
2. **属性注入**:使用@Setter注解或手动设置属性传递任务参数
|
||||
3. **Spring Bean获取**:通过SpringApp.getBean()获取服务实例
|
||||
4. **消息更新**:简洁的消息更新方式
|
||||
5. **参数传递**:通过callRemoteTask的可变参数传递任务所需数据
|
||||
|
||||
## Tasker实现WebSocketClientTasker最佳实践
|
||||
|
||||
### 1. 类定义和继承
|
||||
|
||||
```java
|
||||
/**
|
||||
* 任务类描述
|
||||
* 用于通过WebSocket与服务器通信执行具体操作
|
||||
*/
|
||||
public class 任务类名 extends Tasker<Object> implements WebSocketClientTasker {
|
||||
private static final Logger logger = LoggerFactory.getLogger(任务类名.class);
|
||||
|
||||
// 实现方法
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 参数传递模式
|
||||
|
||||
#### 2.1 使用@Setter注解注入参数
|
||||
|
||||
```java
|
||||
/**
|
||||
* 更新供应商评价表任务
|
||||
*/
|
||||
public class CompanyVendorEvaluationFormUpdateTask extends Tasker<Object> implements WebSocketClientTasker {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CompanyVendorEvaluationFormUpdateTask.class);
|
||||
|
||||
@Setter
|
||||
private VendorVo vendor;
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
updateTitle("更新供应商评价表");
|
||||
return callRemoteTask(holder, getLocale(), vendor.getId());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 使用@Getter和@Setter注解
|
||||
|
||||
```java
|
||||
/**
|
||||
* 客户文件重建任务类
|
||||
*/
|
||||
public class CustomerRebuildFilesTasker extends Tasker<Object> implements WebSocketClientTasker {
|
||||
@Getter
|
||||
@Setter
|
||||
private CustomerVo companyCustomer;
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
updateTitle("重建客户文件");
|
||||
return callRemoteTask(holder, getLocale(), companyCustomer.getId());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 必要方法实现
|
||||
|
||||
#### 2.1 getTaskName()
|
||||
|
||||
```java
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
return "Task名称"; // 必须与服务器端对应Tasker类名匹配
|
||||
}
|
||||
```
|
||||
|
||||
**注意事项**:
|
||||
- 任务名称必须与服务器端对应的Tasker注册名(tasker_mapper.json中的key)保持一致
|
||||
- 名称应简洁明了,反映任务的核心功能
|
||||
|
||||
#### 2.2 updateProgress()
|
||||
updateProgress 方法重载为public,用于外部调用更新进度
|
||||
|
||||
```java
|
||||
@Override
|
||||
public void updateProgress(long current, long total) {
|
||||
super.updateProgress(current, total); // 调用父类方法更新进度
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.3 updateTitle()
|
||||
|
||||
```java
|
||||
@Override
|
||||
public void updateTitle(String title) {
|
||||
super.updateTitle(title); // 使用Tasker的updateTitle方法更新标题
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.4 execute() 方法重写
|
||||
|
||||
**标准实现**:
|
||||
```java
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
logger.info("开始执行任务描述");
|
||||
updateTitle("任务标题");
|
||||
|
||||
// 调用远程任务,可选传入参数
|
||||
Object result = callRemoteTask(holder, getLocale(), 可选参数...);
|
||||
|
||||
logger.info("任务执行完成");
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
**简洁实现**(适用于简单任务):
|
||||
```java
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
updateTitle("更新供应商评价表");
|
||||
return callRemoteTask(holder, getLocale(), vendor.getId());
|
||||
}
|
||||
```
|
||||
|
||||
**带消息更新的实现**:
|
||||
```java
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
// 设置任务标题
|
||||
updateTitle("全量库存同步任务");
|
||||
|
||||
// 更新任务消息
|
||||
updateMessage("开始执行全量库存同步...");
|
||||
|
||||
// 调用远程WebSocket任务
|
||||
return callRemoteTask(holder, getLocale());
|
||||
}
|
||||
```
|
||||
|
||||
**关键步骤**:
|
||||
1. 记录任务开始日志(可选)
|
||||
2. 设置任务标题
|
||||
3. 可选:添加任务开始消息
|
||||
4. 调用远程任务执行核心逻辑,传入必要参数
|
||||
5. 记录任务完成日志(可选)
|
||||
6. 返回执行结果
|
||||
|
||||
### 3. 日志记录
|
||||
|
||||
```java
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
// 在类中定义
|
||||
private static final Logger logger = LoggerFactory.getLogger(任务类名.class);
|
||||
|
||||
// 在关键位置使用日志
|
||||
logger.info("任务开始");
|
||||
logger.warn("警告信息");
|
||||
logger.error("错误信息", exception);
|
||||
```
|
||||
|
||||
**日志使用建议**:
|
||||
- 复杂任务建议记录详细日志
|
||||
- 简单任务可以简化或省略日志记录
|
||||
- 确保异常情况下有适当的错误日志记录
|
||||
|
||||
### 4. 异常处理
|
||||
|
||||
在`execute`方法中应妥善处理可能的异常,并通过MessageHolder通知用户:
|
||||
|
||||
```java
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
try {
|
||||
// 任务执行逻辑
|
||||
} catch (Exception e) {
|
||||
logger.error("任务执行失败", e);
|
||||
holder.addMessage(Level.SEVERE, "任务执行失败: " + e.getMessage());
|
||||
throw e; // 向上抛出异常,让框架处理
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**异常处理策略**:
|
||||
- 对于简单任务,可以依赖框架的异常处理机制
|
||||
- 对于复杂任务,建议添加自定义的异常处理逻辑
|
||||
- 确保异常信息对用户友好且具有足够的调试信息
|
||||
|
||||
## 完整实现示例
|
||||
|
||||
### 示例1:简单任务实现
|
||||
|
||||
```java
|
||||
package com.ecep.contract.task;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.WebSocketClientTasker;
|
||||
|
||||
/**
|
||||
* 合同修复任务类
|
||||
* 用于通过WebSocket与服务器通信执行合同数据修复操作
|
||||
*/
|
||||
public class ContractRepairAllTasker extends Tasker<Object> implements WebSocketClientTasker {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ContractRepairAllTasker.class);
|
||||
|
||||
@Override
|
||||
public void updateProgress(long current, long total) {
|
||||
super.updateProgress(current, total);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTitle(String title) {
|
||||
// 使用Tasker的updateTitle方法更新标题
|
||||
super.updateTitle(title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
return "ContractRepairAllTask"; // 与服务器端对应Tasker类名匹配
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
logger.info("开始执行合同修复任务");
|
||||
updateTitle("合同数据修复");
|
||||
Object result = callRemoteTask(holder, getLocale());
|
||||
logger.info("合同修复任务执行完成");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 示例2:带参数的任务实现
|
||||
|
||||
```java
|
||||
package com.ecep.contract.task;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.WebSocketClientTasker;
|
||||
import com.ecep.contract.vo.VendorVo;
|
||||
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 更新供应商评价表
|
||||
*/
|
||||
public class CompanyVendorEvaluationFormUpdateTask extends Tasker<Object> implements WebSocketClientTasker {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CompanyVendorEvaluationFormUpdateTask.class);
|
||||
@Setter
|
||||
private VendorVo vendor;
|
||||
|
||||
@Override
|
||||
public void updateProgress(long current, long total) {
|
||||
super.updateProgress(current, total);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
return "CompanyVendorEvaluationFormUpdateTask"; // 与服务器端对应Tasker类名匹配
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
updateTitle("更新供应商评价表");
|
||||
return callRemoteTask(holder, getLocale(), vendor.getId());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 示例3:使用Spring Bean的任务实现
|
||||
|
||||
```java
|
||||
package com.ecep.contract.task;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.SpringApp;
|
||||
import com.ecep.contract.WebSocketClientTasker;
|
||||
import com.ecep.contract.service.YongYouU8Service;
|
||||
|
||||
/**
|
||||
* 合同同步任务
|
||||
*/
|
||||
public class ContractSyncTask extends Tasker<Object> implements WebSocketClientTasker {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ContractSyncTask.class);
|
||||
|
||||
private YongYouU8Service yongYouU8Service;
|
||||
|
||||
private YongYouU8Service getYongYouU8Service() {
|
||||
if (yongYouU8Service == null) {
|
||||
yongYouU8Service = SpringApp.getBean(YongYouU8Service.class);
|
||||
}
|
||||
return yongYouU8Service;
|
||||
}
|
||||
|
||||
public String getTaskName() {
|
||||
return "ContractSyncTask"; // 与服务器端对应Tasker类名匹配
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(long current, long total) {
|
||||
super.updateProgress(current, total);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
updateTitle("用友U8系统-同步合同");
|
||||
return callRemoteTask(holder, getLocale());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项和最佳实践
|
||||
|
||||
### 1. 命名规范
|
||||
- 任务类名应采用驼峰命名法,以`Tasker`结尾或描述性名称如`Task`
|
||||
- getTaskName()返回的名称应与服务器端对应Tasker类名完全匹配
|
||||
- 类注释应清晰描述任务的功能和用途
|
||||
|
||||
### 2. 继承关系
|
||||
- 必须同时继承Tasker类并实现WebSocketClientTasker接口
|
||||
- Tasker泛型参数通常为Object
|
||||
- 确保正确导入所有必要的包
|
||||
|
||||
### 3. 参数处理
|
||||
- 对于需要参数的任务,使用@Setter注解简化属性设置
|
||||
- 对于需要在多处使用的参数,考虑添加@Getter注解
|
||||
- 确保参数验证(如果必要)
|
||||
|
||||
### 4. Spring Bean获取
|
||||
- 使用SpringApp.getBean()获取所需的服务实例
|
||||
- 考虑使用懒加载模式,避免不必要的Bean初始化
|
||||
|
||||
### 5. 消息和进度更新
|
||||
- 使用updateTitle()设置有意义的任务标题
|
||||
- 通过MessageHolder或updateMessage()记录详细的执行消息
|
||||
- 确保进度更新反映真实的执行进度
|
||||
|
||||
### 6. 异常处理
|
||||
- 在关键操作处添加try-catch块
|
||||
- 记录异常日志并通知用户
|
||||
- 适当向上抛出异常以确保框架能正确处理
|
||||
|
||||
### 7. 日志级别使用
|
||||
- INFO: 记录正常的操作流程
|
||||
- WARNING: 记录可能的问题,但不影响继续执行
|
||||
- ERROR: 记录严重错误,通常会终止执行
|
||||
|
||||
### 8. 远程调用参数
|
||||
- 确保传入的参数类型与服务器端Tasker期望的一致
|
||||
- 对于不需要参数的任务,可以不传入额外参数
|
||||
- 对于需要多个参数的任务,确保参数顺序正确
|
||||
|
||||
### 9. 代码风格
|
||||
- 保持代码简洁明了
|
||||
- 遵循项目的代码格式化规范
|
||||
- 添加必要的注释说明核心逻辑
|
||||
|
||||
### 10. 实现策略选择
|
||||
- 简单任务:使用简洁的实现方式,省略不必要的日志
|
||||
- 复杂任务:添加详细的日志记录和异常处理
|
||||
- 有特定需求的任务:根据需要重写接口中的其他方法
|
||||
|
||||
## 与服务器端交互流程
|
||||
|
||||
1. 客户端Tasker通过callRemoteTask()方法提交任务
|
||||
2. WebSocketClientService负责建立与服务器的连接并发送任务信息
|
||||
3. 服务器接收到任务后,创建对应的Tasker实例并执行
|
||||
4. 执行过程中的消息、进度等通过WebSocket实时返回给客户端
|
||||
5. 客户端Tasker通过updateMessage()、updateProgress()等方法更新UI
|
||||
|
||||
## 扩展和自定义
|
||||
|
||||
如需为特定任务提供自定义功能,可以:
|
||||
|
||||
1. 重写cancelTask()方法实现任务取消逻辑
|
||||
2. 根据需要添加额外的字段和方法
|
||||
3. 扩展execute()方法实现更复杂的任务流程
|
||||
|
||||
## 总结
|
||||
|
||||
通过分析项目中的17个WebSocketClientTasker实现类,我们总结了客户端Tasker实现的多种模式和最佳实践。这些实现从简单到复杂,涵盖了各种使用场景,为后续Tasker的编写提供了全面的参考。
|
||||
|
||||
客户端Tasker类实现WebSocketClientTasker接口是实现与服务器实时通信的关键步骤。通过遵循本文档中的规范和最佳实践,可以确保任务执行的可靠性、进度的实时更新和良好的用户体验。
|
||||
|
||||
在实际开发中,应根据任务的复杂度和具体需求,选择合适的实现模式和策略,同时保持代码的一致性和可维护性。
|
||||
74
.trae/rules/entity_rules.md
Normal file
74
.trae/rules/entity_rules.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# 实体类规则
|
||||
|
||||
## 1. 目录结构
|
||||
- **基础实体类和接口**:放置在 `common/src/main/java/com/ecep/contract/model/` 目录下
|
||||
- **业务领域实体类**:放置在 `server/src/main/java/com/ecep/contract/ds/{业务领域}/model/` 目录下,按业务领域组织
|
||||
- 例如:公司相关实体在 `ds/company/model/` 目录下
|
||||
- 合同相关实体在 `ds/contract/model/` 目录下
|
||||
- 客户相关实体在 `ds/customer/model/` 目录下
|
||||
- 项目相关实体在 `ds/project/model/` 目录下
|
||||
- 供应商相关实体在 `ds/vendor/model/` 目录下
|
||||
|
||||
## 2. 命名规范
|
||||
- 类名:使用驼峰命名法,首字母大写,如 `Contract.java`、`Company.java`
|
||||
- 表名:通常与类名对应,使用大写字母和下划线,如 `CONTRACT`、`COMPANY`
|
||||
- 字段名:使用小写字母和驼峰命名法,对应数据库中使用大写字母和下划线的列名
|
||||
|
||||
## 3. 继承与接口实现
|
||||
- 实体类通常实现以下接口:
|
||||
- `IdentityEntity`:提供主键ID的getter/setter及equals方法实现
|
||||
- `BasedEntity`:提供toPrettyString()方法
|
||||
- `NamedEntity`:提供name属性的getter/setter(适用于有名称的实体)
|
||||
- `Voable<T>`:提供toVo()方法,用于将实体转换为对应的VO对象
|
||||
- **特定领域接口**:放置在对应的业务领域目录下,如 `CompanyBasedEntity` 在 `ds/company/model/` 目录下,定义与某一业务领域相关的通用方法(如getter/setter)
|
||||
|
||||
## 4. 注解规范
|
||||
- **JPA注解**:
|
||||
- `@Entity`:标记为实体类
|
||||
- `@Table(name = "表名", schema = "supplier_ms")`:指定对应的数据库表,schema通常为"supplier_ms"
|
||||
- `@Id`:标记主键字段
|
||||
- `@GeneratedValue(strategy = GenerationType.IDENTITY)`:指定主键生成策略
|
||||
- `@Column(name = "列名", nullable = false, length = 长度)`:指定字段对应的数据库列属性
|
||||
- `@ManyToOne(fetch = FetchType.LAZY)`:指定多对一关系,通常使用LAZY加载
|
||||
- `@JoinColumn(name = "外键列名")`:指定外键列
|
||||
- `@Enumerated(EnumType.ORDINAL)`:指定枚举类型的存储方式
|
||||
- `@Lob`:指定大对象类型
|
||||
- `@Version`:指定乐观锁版本字段
|
||||
- **Lombok注解**:
|
||||
- `@Getter`:生成getter方法
|
||||
- `@Setter`:生成setter方法
|
||||
- `@ToString`:生成toString方法,复杂关联对象可使用`@ToString.Exclude`排除
|
||||
|
||||
## 5. 字段规范
|
||||
- 主键字段:
|
||||
- 类型:`Integer`
|
||||
- 命名:`id`
|
||||
- 注解:`@Id`、`@GeneratedValue(strategy = GenerationType.IDENTITY)`
|
||||
- 其他字段:
|
||||
- 基本类型:使用Java包装类型(如`Integer`、`Boolean`)而不是原始类型
|
||||
- 时间类型:使用`java.time.LocalDate`或`java.time.LocalDateTime`
|
||||
- 布尔类型:使用`boolean`或`Boolean`,对应数据库中的`BIT`或`BOOLEAN`类型
|
||||
- 关联关系:使用`@ManyToOne`、`@OneToMany`等注解定义
|
||||
|
||||
## 6. 方法规范
|
||||
- `equals`方法:通常使用`HibernateProxyUtils`工具类处理代理对象的比较
|
||||
- `hashCode`方法:通常使用`HibernateProxyUtils.hashCode(this)`实现
|
||||
- `toVo`方法:实现`Voable`接口,将实体转换为对应的VO对象
|
||||
- `toString`方法:通常使用Lombok的`@ToString`注解生成
|
||||
|
||||
## 7. 注释规范
|
||||
- 类注释:使用JavaDoc格式,描述实体类的用途
|
||||
- 字段注释:使用JavaDoc格式,描述字段的含义、数据来源、取值范围等
|
||||
- 对于特殊字段,可包含详细的说明表格或示例
|
||||
|
||||
## 8. 序列化规范
|
||||
- **重要**:实体类**不包含**`Serializable`接口和`serialVersionUID`字段
|
||||
- 序列化相关功能由对应的VO类实现
|
||||
|
||||
## 9. 最佳实践
|
||||
- 实体类应保持简洁,只包含数据和基本的数据访问方法
|
||||
- 业务逻辑应在服务层实现
|
||||
- 复杂的查询逻辑应在Repository层或通过Specification实现
|
||||
- 避免在实体类中使用复杂的计算逻辑
|
||||
- 对于多对多关系,建议使用中间表和两个一对多关系实现
|
||||
- 关联关系应使用懒加载(LAZY)以提高性能
|
||||
@@ -1,13 +1,284 @@
|
||||
Java 21
|
||||
Spring Boot 3.3.7
|
||||
Spring Data JPA 3.3.7
|
||||
JavaFX 21
|
||||
ControlsFX 11.1.2
|
||||
MySQL 8.0.33
|
||||
Lombok 1.18.32
|
||||
POI 5.2.5
|
||||
PDFBox 3.0.1
|
||||
# 项目规则
|
||||
|
||||
## 技术栈规范
|
||||
|
||||
### server 模块
|
||||
- Java 21
|
||||
- Spring Boot 3.3.7
|
||||
- Spring Data JPA 3.3.7
|
||||
- MySQL 8.0.33
|
||||
- Lombok 1.18.32
|
||||
- POI 5.2.5
|
||||
- PDFBox 3.0.1
|
||||
- Redis
|
||||
|
||||
### client 模块
|
||||
- Java 21
|
||||
- JavaFX 21
|
||||
- ControlsFX 11.1.2
|
||||
- Lombok 1.18.32
|
||||
- caffeine 3.1.8
|
||||
- .fxml 界面UI, 放置于 /client/src/main/resources/ui/ 目录下
|
||||
- websocket 与 server 模块通信
|
||||
- StringConverter 创建规则与实现逻辑 `docs/task/string_converter_implementation_guide.md`
|
||||
- TableCell 规则与逻辑 `docs/task/table_cell_implementation_guide.md`
|
||||
- Service 规则与逻辑 `docs/task/serservice_layer_rules.md`
|
||||
- 客户端 Tasker 至 服务器端 Tasker 通信规则与逻辑 `docs/task/client_server_tasker_communication_rules.md`
|
||||
|
||||
### common 模块
|
||||
- Java 21
|
||||
- Lombok 1.18.32
|
||||
|
||||
## 文件命名规范
|
||||
- Java类名:使用驼峰命名法,首字母大写,如 `ContractService.java`
|
||||
- 接口名:使用驼峰命名法,首字母大写,以I开头,如 `IContractService.java`
|
||||
- 控制器类名:以Controller结尾,如 `ContractController.java`
|
||||
- 服务类名:以Service结尾,如 `ContractService.java`
|
||||
- 实体类名:使用驼峰命名法,首字母大写,如 `Contract.java`
|
||||
- FXML文件:使用小写字母和下划线,如 `contract_view.fxml`
|
||||
- SQL文件:表名使用大写和下划线,如 `CONTRACT_TYPE_LOCAL.sql`
|
||||
|
||||
## 目录结构规范
|
||||
### 项目整体结构
|
||||
- `client/`: 客户端模块,基于JavaFX实现的桌面应用
|
||||
- `server/`: 服务端模块,基于Spring Boot实现的后端服务
|
||||
- `common/`: 公共模块,包含客户端和服务端共享的代码
|
||||
- `docs/`: 项目文档和数据库脚本目录
|
||||
- `.trae/`: Trae IDE相关配置和规则
|
||||
- `.mvn/`: Maven包装器配置
|
||||
- 根目录下包含Maven构建文件和配置文件
|
||||
|
||||
### server 模块目录结构
|
||||
- `src/main/java/com/ecep/contract/`: 主包路径
|
||||
- `api/`: API接口定义
|
||||
- `cloud/`: 云服务集成相关代码
|
||||
- `config/`: Spring Boot配置类
|
||||
- `controller/`: Web控制器
|
||||
- `ds/`: 数据访问层,按业务领域组织
|
||||
- `company/`: 公司相关业务
|
||||
- `contract/`: 合同相关业务
|
||||
- `customer/`: 客户相关业务
|
||||
- `project/`: 项目相关业务
|
||||
- `vendor/`: 供应商相关业务
|
||||
- 每个业务领域包含:`model/`(实体类)、`repository/`(数据访问接口)、`service/`(业务逻辑, 详细规范见 `.trae\rules\server_service_rules.md`)、`tasker/`(任务处理器, 详细规范见 `.trae\rules\server_tasker_rules.md`)、`controller/`(控制器, 详细规范见 `.trae\rules\server_controller_rules.md`)
|
||||
- `handler/`: WebSocket处理器
|
||||
- `service/`: 服务层,包含一些通用服务和任务处理器
|
||||
- `ui/`: UI相关组件
|
||||
- `util/`: 工具类
|
||||
- 核心服务接口和基类文件直接位于contract包下
|
||||
- `src/main/resources/`: 资源文件目录
|
||||
- `src/test/`: 测试代码目录
|
||||
|
||||
### client 模块目录结构
|
||||
- `src/main/java/com/ecep/contract/`: 主包路径
|
||||
- `controller/`: JavaFX控制器,按业务领域组织, 详细规范见 .trae\rules\client_controller_rules.md
|
||||
- 包含各种业务窗口控制器和Tab皮肤控制器
|
||||
- `converter/`: 类型转换器,详细规范见 .trae\rules\client_converter_rules.md
|
||||
- `serializer/`: 序列化相关类
|
||||
- `service/`: 客户端服务层,与服务端交互, 详细规范见 .trae\rules\client_service_rules.md
|
||||
- `task/`: 客户端任务类, 详细规范见 .trae\rules\client_task_rules.md
|
||||
- `util/`: 工具类
|
||||
- `vm/`: 视图模型
|
||||
- `src/main/resources/ui/`: FXML界面文件目录
|
||||
- `src/test/`: 测试代码目录
|
||||
|
||||
### common 模块目录结构
|
||||
- `src/main/java/ecep/contract/`: 包含客户端和服务端共享的代码
|
||||
- `constant/`: 常量类,按业务领域组织
|
||||
- `model/`: 实体类,按业务领域组织, 不包含Serializable接口和serialVersionUID字段, 详细规范见 .trae\rules\entity_rules.md
|
||||
- `vo/`: 视图对象,按业务领域组织, 包含Serializable接口和serialVersionUID字段, 详细规范见 .trae\rules\vo_rules.md
|
||||
- `util/`: 工具类
|
||||
|
||||
### 文档和资源目录
|
||||
- `docs/db/`: 数据库脚本文件
|
||||
- `docs/task/`: 任务相关文档和规范
|
||||
- `docs/model/`: 数据模型文档
|
||||
- 其他项目文档和资源
|
||||
|
||||
## 数据库规范
|
||||
- 表名使用大写字母和下划线,如 `COMPANY_VENDOR_FILE_TYPE_LOCAL`
|
||||
- 字段名使用大写字母和下划线,如 `CREATE_DATE`
|
||||
- 主键命名为 `ID`
|
||||
- 外键命名格式为 `[关联表名]_ID`
|
||||
- 唯一约束命名格式为 `UK_[表名缩写]_[字段名]`
|
||||
|
||||
## 代码规范
|
||||
- 使用Lombok注解简化代码,如 `@Data`、`@Slf4j` 等
|
||||
- 类和方法应有适当的JavaDoc注释
|
||||
- 变量命名应清晰表达其含义
|
||||
- 避免魔法数字,使用常量替代
|
||||
- 异常处理使用统一的异常处理机制
|
||||
|
||||
## 忽略文件
|
||||
ignore:
|
||||
- .idea
|
||||
- target
|
||||
- *.iml
|
||||
- .gitignore
|
||||
- .gitattributes
|
||||
- out/
|
||||
- *.log
|
||||
- build/
|
||||
- .DS_Store
|
||||
- *.class
|
||||
|
||||
|
||||
# 6A工作流执行规则
|
||||
## 阶段1: Align (对齐阶段)
|
||||
### 目标: 模糊需求 → 精确规范
|
||||
### 执行步骤
|
||||
1. **项目上下文分析**
|
||||
- 分析现有项目结构、技术栈、架构模式、依赖关系
|
||||
- 分析现有代码模式、现有文档和约定
|
||||
- 理解业务域和数据模型
|
||||
2. **需求理解确认**
|
||||
- 创建 `docs/任务名/ALIGNMENT_[任务名].md`
|
||||
- 包含项目和任务特性规范
|
||||
- 包含原始需求、边界确认(明确任务范围)、需求理解(对现有项目的理解)、疑问澄清(存在歧义的地方)
|
||||
3. **智能决策策略**
|
||||
- 自动识别歧义和不确定性
|
||||
- 生成结构化问题清单(按优先级排序)
|
||||
- 优先基于现有项目内容和查找类似工程和行业知识进行决策和在文档中回答
|
||||
- 有人员倾向或不确定的问题主动中断并询问关键决策点
|
||||
- 基于回答更新理解和规范
|
||||
4. **中断并询问关键决策点**
|
||||
- 主动中断询问,迭代执行智能决策策略
|
||||
5. **最终共识**
|
||||
- 生成 `docs/任务名/CONSENSUS_[任务名].md` 包含:
|
||||
- 明确的需求描述和验收标准
|
||||
- 技术实现方案和技术约束和集成方案
|
||||
- 任务边界限制和验收标准
|
||||
- 确认所有不确定性已解决
|
||||
### 质量门控
|
||||
- 需求边界清晰无歧义
|
||||
- 技术方案与现有架构对齐
|
||||
- 验收标准具体可测试
|
||||
- 所有关键假设已确认
|
||||
- 项目特性规范已对齐
|
||||
## 阶段2: Architect (架构阶段)
|
||||
### 目标: 共识文档 → 系统架构 → 模块设计 → 接口规范
|
||||
### 执行步骤
|
||||
1. **系统分层设计**
|
||||
- 基于CONSENSUS、ALIGNMENT文档设计架构
|
||||
- 生成 `docs/任务名/DESIGN_[任务名].md` 包含:
|
||||
- 整体架构图(mermaid绘制)
|
||||
- 分层设计和核心组件
|
||||
- 模块依赖关系图
|
||||
- 接口契约定义
|
||||
- 数据流向图
|
||||
- 异常处理策略
|
||||
2. **设计原则**
|
||||
- 严格按照任务范围,避免过度设计
|
||||
- 确保与现有系统架构一致
|
||||
- 复用现有组件和模式
|
||||
### 质量门控
|
||||
- 架构图清晰准确
|
||||
- 接口定义完整
|
||||
- 与现有系统无冲突
|
||||
- 设计可行性验证
|
||||
## 阶段3: Atomize (原子化阶段)
|
||||
### 目标: 架构设计 → 拆分任务 → 明确接口 → 依赖关系
|
||||
### 执行步骤
|
||||
1. **子任务拆分**
|
||||
- 基于DESIGN文档生成 `docs/任务名/TASK_[任务名].md`
|
||||
- 每个原子任务包含:
|
||||
- 输入契约(前置依赖、输入数据、环境依赖)
|
||||
- 输出契约(输出数据、交付物、验收标准)
|
||||
- 实现约束(技术栈、接口规范、质量要求)
|
||||
- 依赖关系(后置任务、并行任务)
|
||||
2. **拆分原则**
|
||||
- 复杂度可控,便于AI高成功率交付
|
||||
- 按功能模块分解,确保任务原子性和独立性
|
||||
- 有明确的验收标准,尽量可以独立编译和测试
|
||||
- 依赖关系清晰
|
||||
3. **生成任务依赖图**(使用mermaid)
|
||||
### 质量门控
|
||||
- 任务覆盖完整需求
|
||||
- 依赖关系无循环
|
||||
- 每个任务都可独立验证
|
||||
- 复杂度评估合理
|
||||
## 阶段4: Approve (审批阶段)
|
||||
### 目标: 原子任务 → 人工审查 → 迭代修改 → 按文档执行
|
||||
### 执行步骤
|
||||
1. **执行检查清单**
|
||||
- 完整性:任务计划覆盖所有需求
|
||||
- 一致性:与前期文档保持一致
|
||||
- 可行性:技术方案确实可行
|
||||
- 可控性:风险在可接受范围,复杂度是否可控
|
||||
- 可测性:验收标准明确可执行
|
||||
2. **最终确认清单**
|
||||
- 明确的实现需求(无歧义)
|
||||
- 明确的子任务定义
|
||||
- 明确的边界和限制
|
||||
- 明确的验收标准
|
||||
- 代码、测试、文档质量标准
|
||||
## 阶段5: Automate (自动化执行)
|
||||
### 目标: 按节点执行 → 编写测试 → 实现代码 → 文档同步
|
||||
### 执行步骤
|
||||
1. **逐步实施子任务**
|
||||
- 创建 `docs/任务名/ACCEPTANCE_[任务名].md` 记录完成情况
|
||||
2. **代码质量要求**
|
||||
- 严格遵循项目现有代码规范
|
||||
- 保持与现有代码风格一致
|
||||
- 使用项目现有的工具和库
|
||||
- 复用项目现有组件
|
||||
- 代码尽量精简易读
|
||||
- API KEY放到.env文件中并且不要提交git
|
||||
3. **异常处理**
|
||||
- 遇到不确定问题立刻中断执行
|
||||
- 在TASK文档中记录问题详细信息和位置
|
||||
- 寻求人工澄清后继续
|
||||
4. **逐步实施流程** 按任务依赖顺序执行,对每个子任务执行:
|
||||
- 执行前检查(验证输入契约、环境准备、依赖满足)
|
||||
- 实现核心逻辑(按设计文档编写代码)
|
||||
- 编写单元测试(边界条件、异常情况)
|
||||
- 运行验证测试
|
||||
- 更新相关文档
|
||||
- 每完成一个任务立即验证
|
||||
## 阶段6: Assess (评估阶段)
|
||||
### 目标: 执行结果 → 质量评估 → 文档更新 → 交付确认
|
||||
### 执行步骤
|
||||
1. **验证执行结果**
|
||||
- 更新 `docs/任务名/ACCEPTANCE_[任务名].md`
|
||||
- 整体验收检查:
|
||||
- 所有需求已实现
|
||||
- 验收标准全部满足
|
||||
- 项目编译通过
|
||||
- 所有测试通过
|
||||
- 功能完整性验证
|
||||
- 实现与设计文档一致
|
||||
2. **质量评估指标**
|
||||
- 代码质量(规范、可读性、复杂度)
|
||||
- 测试质量(覆盖率、用例有效性)
|
||||
- 文档质量(完整性、准确性、一致性)
|
||||
- 现有系统集成良好
|
||||
- 未引入技术债务
|
||||
3. **最终交付物**
|
||||
- 生成 `docs/任务名/FINAL_[任务名].md`(项目总结报告)
|
||||
- 生成 `docs/任务名/TODO_[任务名].md`(精简明确哪些待办的事宜和哪些缺少的配置等,我方便直接寻找支持)
|
||||
4. **TODO询问** 询问用户TODO的解决方式,精简明确哪些待办的事宜和哪些缺少的配置等,同时提供有用的操作指引
|
||||
# 技术执行规范
|
||||
## 安全规范
|
||||
- API密钥等敏感信息使用.env文件管理
|
||||
## 文档同步
|
||||
- 代码变更同时更新相关文档
|
||||
## 测试策略
|
||||
- 测试优先:先写测试,后写实现
|
||||
- 边界覆盖:覆盖正常流程、边界条件、异常情况
|
||||
## 交互体验优化
|
||||
### 进度反馈
|
||||
- 显示当前执行阶段
|
||||
- 提供详细的执行步骤
|
||||
- 标示完成情况
|
||||
- 突出需要关注的问题
|
||||
### 异常处理机制
|
||||
#### 中断条件
|
||||
- 遇到无法自主决策的问题
|
||||
- 觉得需要询问用户的问题
|
||||
- 技术实现出现阻塞
|
||||
- 文档不一致需要确认修正
|
||||
#### 恢复策略
|
||||
- 保存当前执行状态
|
||||
- 记录问题详细信息
|
||||
- 询问并等待人工干预
|
||||
- 从中断点任务继续执行
|
||||
374
.trae/rules/repository_comprehensive_analysis_report.md
Normal file
374
.trae/rules/repository_comprehensive_analysis_report.md
Normal file
@@ -0,0 +1,374 @@
|
||||
# Repository一致性分析 - 综合报告
|
||||
|
||||
## 📊 执行摘要
|
||||
|
||||
### 分析概览
|
||||
- **总Repository数量**: 22个
|
||||
- **涉及业务域**: contract, company, customer, project, vendor
|
||||
- **分析时间**: 2024年项目全面分析
|
||||
- **分析范围**: 覆盖server模块下所有5个业务域的Repository实现
|
||||
|
||||
### 目录覆盖情况
|
||||
✅ `server/src/main/java/com/ecep/contract/ds/contract/repository/` (13个)
|
||||
✅ `server/src/main/java/com/ecep/contract/ds/company/repository/` (3个)
|
||||
✅ `server/src/main/java/com/ecep/contract/ds/customer/repository/` (2个)
|
||||
✅ `server/src/main/java/com/ecep/contract/ds/project/repository/` (3个)
|
||||
✅ `server/src/main/java/com/ecep/contract/ds/vendor/repository/` (2个)
|
||||
|
||||
## 🔍 关键发现与评估
|
||||
|
||||
### 🔴 严重问题:继承层次不一致(5个)
|
||||
|
||||
#### 1. 直接继承JpaRepository(4个)
|
||||
- `ContractFileRepository` - 直接继承`JpaRepository`和`JpaSpecificationExecutor`
|
||||
- `ContractItemRepository` - 直接继承`JpaRepository`和`JpaSpecificationExecutor`
|
||||
- `VendorFileRepository` - 直接继承`JpaRepository`和`JpaSpecificationExecutor`
|
||||
- `ProjectQuotationRepository` - 直接继承`JpaRepository`和`JpaSpecificationExecutor`
|
||||
|
||||
#### 2. 继承多个冗余接口(1个)
|
||||
- `PurchaseOrderRepository` - 同时继承了4个接口:`CrudRepository`, `PagingAndSortingRepository`, `JpaRepository`, `JpaSpecificationExecutor`
|
||||
|
||||
### 🟡 中等问题(6个)
|
||||
|
||||
#### 注解缺失(2个)
|
||||
- `CompanyFileRepository` - 缺少`@Repository`注解
|
||||
- `ProjectFileRepository` - 缺少`@Repository`注解
|
||||
|
||||
#### 文档注释缺失(4个)
|
||||
- `CompanyFileRepository` - 缺少JavaDoc注释
|
||||
- `ProjectFileRepository` - 缺少JavaDoc注释
|
||||
- `VendorFileRepository` - 缺少JavaDoc注释
|
||||
- `ProjectQuotationRepository` - 缺少JavaDoc注释
|
||||
|
||||
### 📈 整体质量评估
|
||||
|
||||
#### 正确实现统计
|
||||
- ✅ **17个Repository正确继承MyRepository** (77%)
|
||||
- ❌ **5个Repository继承层次错误** (23%)
|
||||
- 🟡 **2个Repository缺少注解** (9%)
|
||||
- 🟡 **4个Repository文档不完整** (18%)
|
||||
|
||||
#### 质量分级分布
|
||||
- **A级(完全正确)**: 13个 (59%)
|
||||
- **B级(轻微问题)**: 6个 (27%)
|
||||
- **C级(严重错误)**: 3个 (14%)
|
||||
|
||||
---
|
||||
|
||||
# 详细技术分析
|
||||
|
||||
## 继承一致性详细分析
|
||||
|
||||
### ✅ 正确继承MyRepository的Repository(17个)
|
||||
|
||||
**contract业务域(7个):**
|
||||
- ContractRepository
|
||||
- ContractBalanceRepository
|
||||
- SalesOrderRepository
|
||||
- ContractTypeRepository
|
||||
- ContractKindRepository
|
||||
- ContractInvoiceRepository
|
||||
|
||||
**company业务域(3个):**
|
||||
- CompanyRepository
|
||||
- CompanyFileRepository (有注解问题)
|
||||
- CompanyCustomerRepository
|
||||
|
||||
**customer业务域(2个):**
|
||||
- CustomerCatalogRepository
|
||||
|
||||
**project业务域(2个):**
|
||||
- ProjectRepository
|
||||
- ProjectFileRepository (有注解问题)
|
||||
|
||||
**vendor业务域(1个):**
|
||||
- VendorRepository
|
||||
|
||||
### ❌ 继承层次错误的Repository(5个)
|
||||
|
||||
#### Contract业务域(3个)
|
||||
1. **ContractFileRepository**
|
||||
2. **ContractItemRepository**
|
||||
3. **PurchaseOrderRepository**
|
||||
|
||||
#### Project业务域(1个)
|
||||
4. **ProjectQuotationRepository**
|
||||
|
||||
#### Vendor业务域(1个)
|
||||
5. **VendorFileRepository**
|
||||
|
||||
## 问题影响分析
|
||||
|
||||
### 技术影响
|
||||
- ❌ 违反了统一的架构设计原则
|
||||
- ❌ 失去了MyRepository提供的统一功能增强
|
||||
- ❌ 代码风格不一致,影响可维护性
|
||||
- ❌ 开发团队需要维护多种不同的实现模式
|
||||
- ❌ 增加了代码维护成本和技术债务
|
||||
|
||||
### 业务影响
|
||||
- ❌ 代码可维护性降低
|
||||
- ❌ 新团队成员学习成本增加
|
||||
- ❌ 代码审查复杂度提高
|
||||
- ❌ 系统稳定性潜在风险增加
|
||||
|
||||
## 详细修复方案
|
||||
|
||||
### 🔴 优先级1:修复继承层次错误
|
||||
|
||||
#### 1. ContractFileRepository
|
||||
```java
|
||||
// ❌ 当前错误实现
|
||||
public interface ContractFileRepository
|
||||
extends JpaRepository<ContractFile, Integer>, JpaSpecificationExecutor<ContractFile> {
|
||||
|
||||
// ✅ 正确修复
|
||||
@Repository
|
||||
public interface ContractFileRepository extends MyRepository<ContractFile, Integer> {
|
||||
// 自动获得MyRepository提供的所有功能
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. ContractItemRepository
|
||||
```java
|
||||
// ❌ 当前错误实现
|
||||
public interface ContractItemRepository
|
||||
extends JpaRepository<ContractItem, Integer>, JpaSpecificationExecutor<ContractItem> {
|
||||
|
||||
// ✅ 正确修复
|
||||
@Repository
|
||||
public interface ContractItemRepository extends MyRepository<ContractItem, Integer> {
|
||||
// 统一继承结构,简化维护
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. VendorFileRepository
|
||||
```java
|
||||
// ❌ 当前错误实现
|
||||
@Repository
|
||||
public interface VendorFileRepository
|
||||
extends JpaRepository<VendorFile, Integer>, JpaSpecificationExecutor<VendorFile> {
|
||||
|
||||
// ✅ 正确修复
|
||||
@Repository
|
||||
public interface VendorFileRepository extends MyRepository<VendorFile, Integer> {
|
||||
// 保持注解的同时修正继承结构
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. ProjectQuotationRepository
|
||||
```java
|
||||
// ❌ 当前错误实现
|
||||
@Repository
|
||||
public interface ProjectQuotationRepository
|
||||
extends JpaRepository<ProjectQuotation, Integer>, JpaSpecificationExecutor<ProjectQuotation> {
|
||||
|
||||
// ✅ 正确修复
|
||||
@Repository
|
||||
public interface ProjectQuotationRepository extends MyRepository<ProjectQuotation, Integer> {
|
||||
// 获得MyRepository的所有增强功能
|
||||
}
|
||||
```
|
||||
|
||||
#### 5. PurchaseOrderRepository
|
||||
```java
|
||||
// ❌ 当前错误实现(继承4个冗余接口)
|
||||
public interface PurchaseOrderRepository extends
|
||||
CrudRepository<PurchaseOrder, Integer>,
|
||||
PagingAndSortingRepository<PurchaseOrder, Integer>,
|
||||
JpaRepository<PurchaseOrder, Integer>,
|
||||
JpaSpecificationExecutor<PurchaseOrder> {
|
||||
|
||||
// ✅ 正确修复
|
||||
@Repository
|
||||
public interface PurchaseOrderRepository extends MyRepository<PurchaseOrder, Integer> {
|
||||
// 单一继承,清晰简洁
|
||||
}
|
||||
```
|
||||
|
||||
### 🟡 优先级2:修复注解缺失
|
||||
|
||||
#### CompanyFileRepository
|
||||
```java
|
||||
// ❌ 当前缺少注解
|
||||
public interface CompanyFileRepository extends MyRepository<CompanyFile, Integer> {
|
||||
|
||||
// ✅ 添加注解
|
||||
@Repository
|
||||
public interface CompanyFileRepository extends MyRepository<CompanyFile, Integer> {
|
||||
// 添加完整的JavaDoc注释
|
||||
/**
|
||||
* 公司文件数据访问接口
|
||||
* 提供公司相关文件的CRUD操作和业务查询功能
|
||||
*/
|
||||
}
|
||||
```
|
||||
|
||||
#### ProjectFileRepository
|
||||
```java
|
||||
// ❌ 当前缺少注解
|
||||
public interface ProjectFileRepository extends MyRepository<ProjectFile, Integer> {
|
||||
|
||||
// ✅ 添加注解
|
||||
@Repository
|
||||
public interface ProjectFileRepository extends MyRepository<ProjectFile, Integer> {
|
||||
/**
|
||||
* 项目文件数据访问接口
|
||||
* 提供项目文件的管理和查询功能
|
||||
*/
|
||||
}
|
||||
```
|
||||
|
||||
### 🟡 优先级3:完善文档注释
|
||||
|
||||
为以下Repository补充完整的JavaDoc注释:
|
||||
- CompanyFileRepository
|
||||
- ProjectFileRepository
|
||||
- VendorFileRepository
|
||||
- ProjectQuotationRepository
|
||||
|
||||
标准JavaDoc格式示例:
|
||||
```java
|
||||
/**
|
||||
* [功能描述]数据访问接口
|
||||
*
|
||||
* 提供[业务描述]的CRUD操作和业务查询功能
|
||||
*
|
||||
* @author [作者]
|
||||
* @since [版本]
|
||||
*/
|
||||
```
|
||||
|
||||
## 实施计划
|
||||
|
||||
### 阶段1:立即修复(1-2天)
|
||||
**目标**: 解决所有严重的继承层次错误
|
||||
|
||||
**执行步骤**:
|
||||
1. 备份当前的Repository实现
|
||||
2. 逐一修复5个继承层次错误的Repository
|
||||
3. 添加缺失的@Repository注解
|
||||
4. 运行编译测试确保无破坏性变更
|
||||
5. 执行基本功能验证测试
|
||||
|
||||
**验收标准**:
|
||||
- 所有Repository都继承MyRepository
|
||||
- 编译通过,无语法错误
|
||||
- 基本CRUD功能正常
|
||||
|
||||
### 阶段2:文档完善(1天)
|
||||
**目标**: 统一JavaDoc注释规范
|
||||
|
||||
**执行步骤**:
|
||||
1. 为4个Repository补充JavaDoc注释
|
||||
2. 检查并更新相关单元测试
|
||||
3. 验证文档格式规范性
|
||||
4. 代码审查确认
|
||||
|
||||
**验收标准**:
|
||||
- 所有Repository都有完整的JavaDoc注释
|
||||
- 测试用例覆盖主要功能
|
||||
- 代码审查通过
|
||||
|
||||
### 阶段3:质量保证(半天)
|
||||
**目标**: 确保修改不影响系统稳定性
|
||||
|
||||
**执行步骤**:
|
||||
1. 全面编译测试
|
||||
2. 运行相关单元测试套件
|
||||
3. 执行集成测试验证
|
||||
4. 更新相关文档
|
||||
5. 最终验收确认
|
||||
|
||||
**验收标准**:
|
||||
- 所有测试通过
|
||||
- 系统功能完整
|
||||
- 性能无明显影响
|
||||
|
||||
## 对规范文档的影响
|
||||
|
||||
### ✅ 已更新的规范文档
|
||||
- **`server_repository_rules.md`** - 已包含错误示例和正确实现指南
|
||||
- 新增"2.2 重要错误示例"章节
|
||||
- 新增"2.3 当前项目错误统计"章节
|
||||
- 新增"2.5 MyRepository基类能力"章节
|
||||
|
||||
### 📋 需要更新的检查清单
|
||||
1. **继承层次检查** - 确保所有Repository继承MyRepository
|
||||
2. **注解完整性检查** - 验证所有Repository都有@Repository注解
|
||||
3. **文档规范性检查** - 确认JavaDoc注释完整性
|
||||
4. **代码风格一致性检查** - 验证命名规范和代码格式
|
||||
|
||||
## 🎯 预期收益
|
||||
|
||||
### 技术收益
|
||||
- ✅ **统一架构设计** - 所有Repository遵循一致的设计模式
|
||||
- ✅ **代码一致性提升** - 消除实现差异,提高代码质量
|
||||
- ✅ **维护成本降低** - 单一继承结构,简化维护工作
|
||||
- ✅ **团队开发效率提升** - 统一规范,降低学习成本
|
||||
|
||||
### 质量收益
|
||||
- ✅ **减少代码冗余** - 消除重复的接口继承
|
||||
- ✅ **提高代码可读性** - 统一模式,易于理解
|
||||
- ✅ **降低技术债务** - 减少不一致实现的技术负担
|
||||
- ✅ **增强系统稳定性** - 统一模式,减少潜在风险
|
||||
|
||||
## 📅 后续行动
|
||||
|
||||
### 立即行动(本周)
|
||||
1. 🔧 修复5个继承层次错误的Repository
|
||||
2. 🔧 添加缺失的@Repository注解
|
||||
3. 🔧 编译测试验证功能完整性
|
||||
|
||||
### 短期完善(下周)
|
||||
1. 📝 补充JavaDoc文档注释
|
||||
2. 🧪 运行完整的单元测试套件
|
||||
3. 👥 代码审查确认实现质量
|
||||
|
||||
### 长期维护
|
||||
1. 📋 建立Repository开发规范检查清单
|
||||
2. 🔍 定期进行Repository实现一致性检查
|
||||
3. 🎓 团队培训和规范宣贯
|
||||
4. 🤖 建立CI/CD流程中的自动检查机制
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
如需技术支持或有任何疑问,请参考:
|
||||
|
||||
### 相关文档
|
||||
- **详细技术分析**: `repository_analysis_report.md`
|
||||
- **实施规范指导**: `server_repository_rules.md`
|
||||
- **项目整体规范**: `.trae/rules/` 目录
|
||||
|
||||
### 联系方式
|
||||
- 技术问题: 参考详细分析报告中的修复代码示例
|
||||
- 规范疑问: 查看server_repository_rules.md文档
|
||||
- 实施支持: 遵循本报告的分阶段实施计划
|
||||
|
||||
---
|
||||
|
||||
## 结论
|
||||
|
||||
通过对项目中22个Repository的全面分析,我们发现了明显的实现不一致性问题,特别是23%的Repository存在继承层次错误。这些问题严重影响了代码的可维护性和架构的统一性。
|
||||
|
||||
**关键统计数据**:
|
||||
- 17个Repository正确实现 (77%)
|
||||
- 5个继承层次错误 (23%)
|
||||
- 2个注解缺失 (9%)
|
||||
- 4个文档不完整 (18%)
|
||||
|
||||
**修复优先级**:
|
||||
1. **高优先级**: 修复5个继承层次错误(1-2天)
|
||||
2. **中优先级**: 补充2个缺失注解(半天)
|
||||
3. **低优先级**: 完善4个文档注释(1天)
|
||||
|
||||
建议按照本报告提出的分阶段实施计划,逐步改进所有Repository实现,确保项目达到统一的架构设计标准。通过这些改进,将显著提升代码质量、降低维护成本,并为团队的长期发展奠定坚实基础。
|
||||
|
||||
**分析状态**: ✅ 完成
|
||||
**下一步**: 执行分阶段修复计划
|
||||
**预计完成时间**: 3-4个工作日
|
||||
|
||||
---
|
||||
**报告生成时间**: 2024年
|
||||
**分析深度**: 全面技术分析
|
||||
**实施可行性**: 高(详细修复方案已提供)
|
||||
460
.trae/rules/server_repository_rules.md
Normal file
460
.trae/rules/server_repository_rules.md
Normal file
@@ -0,0 +1,460 @@
|
||||
# Server模块 Repository 实现经验总结
|
||||
|
||||
## 1. Repository 基本架构
|
||||
|
||||
### 1.1 接口设计原则
|
||||
- Repository接口必须继承`MyRepository<T, ID>`基类
|
||||
- 使用`@Repository`注解标记为Spring组件
|
||||
- 遵循接口隔离原则,按业务功能组织方法
|
||||
|
||||
### 1.2 包结构规范
|
||||
```
|
||||
com.ecep.contract.ds.{business}.repository.{EntityName}Repository.java
|
||||
```
|
||||
示例:`ContractBalanceRepository`位于`com.ecep.contract.ds.contract.repository`
|
||||
|
||||
## 2. 接口继承层次
|
||||
|
||||
### 2.1 基础接口结构
|
||||
```java
|
||||
@Repository
|
||||
public interface ContractBalanceRepository extends MyRepository<ContractBalance, Integer> {
|
||||
// 自定义查询方法
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 ⚠️ 重要错误示例 - 避免这些实现方式
|
||||
|
||||
**❌ 错误示例1:直接继承JpaRepository**
|
||||
```java
|
||||
// ContractFileRepository, ContractItemRepository, VendorFileRepository, ProjectQuotationRepository
|
||||
@Repository
|
||||
public interface ContractFileRepository
|
||||
extends JpaRepository<ContractFile, Integer>, JpaSpecificationExecutor<ContractFile> {
|
||||
// 错误:不应该直接继承JpaRepository
|
||||
List<ContractFile> findByContractId(Integer contractId);
|
||||
}
|
||||
```
|
||||
|
||||
**❌ 错误示例2:继承多个冗余接口**
|
||||
```java
|
||||
// PurchaseOrderRepository
|
||||
public interface PurchaseOrderRepository extends
|
||||
CrudRepository<PurchaseOrder, Integer>,
|
||||
PagingAndSortingRepository<PurchaseOrder, Integer>,
|
||||
JpaRepository<PurchaseOrder, Integer>,
|
||||
JpaSpecificationExecutor<PurchaseOrder> {
|
||||
// 错误:冗余接口继承,应该只继承MyRepository
|
||||
}
|
||||
```
|
||||
|
||||
**❌ 错误示例3:缺少@Repository注解**
|
||||
```java
|
||||
// CompanyFileRepository, ProjectFileRepository
|
||||
public interface CompanyFileRepository extends MyRepository<CompanyFile, Integer> {
|
||||
// 错误:缺少@Repository注解
|
||||
List<CompanyFile> findByCompany(Company company);
|
||||
}
|
||||
```
|
||||
|
||||
**❌ 错误示例4:缺少JavaDoc注释**
|
||||
```java
|
||||
// 多个Repository存在此问题
|
||||
public interface VendorFileRepository extends MyRepository<VendorFile, Integer> {
|
||||
// 错误:缺少JavaDoc注释说明用途和方法
|
||||
List<VendorFile> findAllByVendorId(int vendorId);
|
||||
}
|
||||
```
|
||||
|
||||
**✅ 正确实现示例**
|
||||
```java
|
||||
/**
|
||||
* 合同Repository - 提供合同相关的数据库访问操作
|
||||
*/
|
||||
@Repository
|
||||
public interface ContractRepository extends MyRepository<Contract, Integer> {
|
||||
// 正确:统一继承MyRepository,有完整的JavaDoc
|
||||
|
||||
/**
|
||||
* 根据合同代码查询合同
|
||||
* @param code 合同代码
|
||||
* @return 合同 Optional
|
||||
*/
|
||||
Optional<Contract> findByCode(String code);
|
||||
|
||||
/**
|
||||
* 根据状态查询合同列表
|
||||
* @param status 合同状态
|
||||
* @return 合同列表
|
||||
*/
|
||||
List<Contract> findByStatus(ContractStatus status);
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 当前项目错误统计
|
||||
|
||||
基于对全项目22个Repository的全面分析,发现以下问题分布:
|
||||
|
||||
**🔴 继承层次错误(5个):**
|
||||
- `ContractFileRepository` - 直接继承JpaRepository
|
||||
- `ContractItemRepository` - 直接继承JpaRepository
|
||||
- `VendorFileRepository` - 直接继承JpaRepository
|
||||
- `ProjectQuotationRepository` - 直接继承JpaRepository
|
||||
- `PurchaseOrderRepository` - 继承4个冗余接口
|
||||
|
||||
**🟡 注解缺失(2个):**
|
||||
- `CompanyFileRepository` - 缺少@Repository注解
|
||||
- `ProjectFileRepository` - 缺少@Repository注解
|
||||
|
||||
**🟡 文档注释不完整(4个):**
|
||||
- `CompanyFileRepository` - 缺少JavaDoc注释
|
||||
- `ProjectFileRepository` - 缺少JavaDoc注释
|
||||
- `VendorFileRepository` - 缺少JavaDoc注释
|
||||
- `ProjectQuotationRepository` - 缺少JavaDoc注释
|
||||
|
||||
**✅ 正确实现(17个):**
|
||||
- contract: ContractRepository, ContractBalanceRepository, SalesOrderRepository, ContractTypeRepository, ContractKindRepository, ContractInvoiceRepository
|
||||
- company: CompanyRepository
|
||||
- customer: CompanyCustomerRepository, CustomerCatalogRepository
|
||||
- project: ProjectRepository
|
||||
- vendor: VendorRepository
|
||||
|
||||
### 2.5 MyRepository基类能力
|
||||
|
||||
`MyRepository`接口继承结构:
|
||||
- `JpaRepository`提供CRUD操作
|
||||
- 继承`JpaSpecificationExecutor`提供条件查询能力
|
||||
- 继承`QueryByExampleExecutor`提供示例查询能力
|
||||
|
||||
## 3. 查询方法设计
|
||||
|
||||
### 3.1 方法命名规范
|
||||
使用Spring Data JPA的查询方法命名约定:
|
||||
|
||||
#### 基本查询方法
|
||||
- `findBy{属性名}` - 根据属性查找
|
||||
- `findBy{属性名}And{属性名}` - 多条件AND查询
|
||||
- `findBy{属性名}Or{属性名}` - 多条件OR查询
|
||||
- `findBy{属性名}OrderBy{排序属性}` - 带排序查询
|
||||
|
||||
#### 关联对象查询
|
||||
- `findBy{关联对象属性名}.{关联对象属性}` - 嵌套属性查询
|
||||
- 支持多级嵌套,如:`findByContractCompanyName`
|
||||
|
||||
#### 特殊查询类型
|
||||
```java
|
||||
// 根据业务ID查找
|
||||
ContractBalance findByGuid(UUID guid);
|
||||
|
||||
// 根据合同ID查找(带分页支持)
|
||||
Page<ContractBalance> findByContractId(Integer contractId, Pageable pageable);
|
||||
|
||||
// 根据多个条件组合查询
|
||||
List<ContractBalance> findByContractIdAndRefId(Integer contractId, String refId);
|
||||
```
|
||||
|
||||
### 3.2 自定义查询实现
|
||||
当方法命名无法满足需求时,使用`@Query`注解:
|
||||
|
||||
```java
|
||||
@Query("SELECT cb FROM ContractBalance cb WHERE cb.contract.id = :contractId " +
|
||||
"AND cb.guid = :guid AND cb.setupDate >= :startDate")
|
||||
Page<ContractBalance> findByContractAndGuidAndDateRange(
|
||||
@Param("contractId") Integer contractId,
|
||||
@Param("guid") UUID guid,
|
||||
@Param("startDate") LocalDateTime startDate,
|
||||
Pageable pageable
|
||||
);
|
||||
```
|
||||
|
||||
## 4. 分页和排序支持
|
||||
|
||||
### 4.1 分页查询
|
||||
```java
|
||||
// 在Repository接口中定义
|
||||
Page<ContractBalance> findByContractId(Integer contractId, Pageable pageable);
|
||||
|
||||
// 在Service中调用
|
||||
Page<ContractBalance> result = repository.findByContractId(contractId,
|
||||
PageRequest.of(page, size, Sort.by("setupDate").descending()));
|
||||
```
|
||||
|
||||
### 4.2 排序规范
|
||||
- 默认按主键ID降序排列
|
||||
- 业务相关字段按创建时间降序
|
||||
- 支持多字段排序:`Sort.by("field1").ascending().and(Sort.by("field2").descending())`
|
||||
|
||||
## 5. 统计和聚合查询
|
||||
|
||||
### 5.1 基础统计
|
||||
```java
|
||||
@Query("SELECT COUNT(cb) FROM ContractBalance cb WHERE cb.contract.id = :contractId")
|
||||
Long countByContractId(@Param("contractId") Integer contractId);
|
||||
|
||||
@Query("SELECT SUM(cb.balanceAmount) FROM ContractBalance cb WHERE cb.contract.id = :contractId")
|
||||
BigDecimal sumBalanceByContractId(@Param("contractId") Integer contractId);
|
||||
```
|
||||
|
||||
### 5.2 复杂聚合
|
||||
```java
|
||||
@Query("SELECT new com.ecep.contract.vo.ContractBalanceStatsVO(" +
|
||||
"cb.contract.id, COUNT(cb), SUM(cb.balanceAmount)) " +
|
||||
"FROM ContractBalance cb GROUP BY cb.contract.id")
|
||||
List<ContractBalanceStatsVO> getBalanceStatsByContract();
|
||||
```
|
||||
|
||||
## 6. 缓存策略
|
||||
|
||||
### 6.1 缓存注解使用
|
||||
虽然Repository本身不直接使用缓存,但需要考虑Service层的缓存需求:
|
||||
|
||||
```java
|
||||
// 在Service层配合使用
|
||||
@Cacheable(key = "#p0")
|
||||
ContractBalance findById(Integer id);
|
||||
|
||||
@CacheEvict(key = "#p0.id")
|
||||
ContractBalance save(ContractBalance contractBalance);
|
||||
```
|
||||
|
||||
### 6.2 缓存键设计原则
|
||||
- 单一记录:使用主键作为键,如`"balance-" + id`
|
||||
- 按业务维度缓存:使用业务标识,如`"contract-" + contractId`
|
||||
- 避免缓存键冲突
|
||||
|
||||
## 7. 事务管理
|
||||
|
||||
### 7.1 事务传播级别
|
||||
- 查询方法:默认`REQUIRED`
|
||||
- 写操作:明确指定`@Transactional(propagation = Propagation.REQUIRED)`
|
||||
|
||||
### 7.2 异常处理
|
||||
```java
|
||||
@Transactional
|
||||
public ContractBalance saveWithRetry(ContractBalance balance) {
|
||||
try {
|
||||
return repository.save(balance);
|
||||
} catch (DataIntegrityViolationException e) {
|
||||
// 处理数据完整性异常
|
||||
throw new BusinessException("数据重复或违反约束", e);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 8. 性能优化
|
||||
|
||||
### 8.1 懒加载优化
|
||||
```java
|
||||
// 在查询时指定抓取策略
|
||||
@Query("SELECT cb FROM ContractBalance cb LEFT JOIN FETCH cb.contract " +
|
||||
"LEFT JOIN FETCH cb.employee WHERE cb.id = :id")
|
||||
Optional<ContractBalance> findByIdWithRelations(@Param("id") Integer id);
|
||||
```
|
||||
|
||||
### 8.2 批量操作优化
|
||||
```java
|
||||
// 批量插入
|
||||
@Modifying
|
||||
@Query("DELETE FROM ContractBalance cb WHERE cb.contract.id IN :contractIds")
|
||||
void deleteByContractIds(@Param("contractIds") List<Integer> contractIds);
|
||||
```
|
||||
|
||||
## 9. 错误处理和调试
|
||||
|
||||
### 9.1 常见异常类型
|
||||
- `DataAccessException` - 数据访问异常
|
||||
- `DataIntegrityViolationException` - 数据完整性异常
|
||||
- `EmptyResultDataAccessException` - 空结果异常
|
||||
|
||||
### 9.2 日志记录
|
||||
```java
|
||||
@Repository
|
||||
@Slf4j
|
||||
public class ContractBalanceRepository {
|
||||
|
||||
@Query("...")
|
||||
public List<ContractBalance> findComplexQuery(...) {
|
||||
log.debug("执行复杂查询: {}", jpql);
|
||||
// 执行查询
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 10. 测试策略
|
||||
|
||||
### 10.1 Repository测试
|
||||
```java
|
||||
@DataJpaTest
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class ContractBalanceRepositoryTest {
|
||||
|
||||
@Autowired
|
||||
private TestEntityManager em;
|
||||
|
||||
@Autowired
|
||||
private ContractBalanceRepository repository;
|
||||
|
||||
@Test
|
||||
void shouldFindByContractId() {
|
||||
// 测试数据准备
|
||||
ContractBalance balance = createTestBalance();
|
||||
|
||||
// 执行测试
|
||||
Page<ContractBalance> result = repository.findByContractId(
|
||||
balance.getContract().getId(),
|
||||
PageRequest.of(0, 10)
|
||||
);
|
||||
|
||||
// 断言验证
|
||||
assertThat(result.getContent()).hasSize(1);
|
||||
assertThat(result.getContent().get(0)).isEqualTo(balance);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 10.2 性能测试
|
||||
```java
|
||||
@Test
|
||||
@RepeatedTest(100)
|
||||
@Timeout(value = 5, unit = TimeUnit.SECONDS)
|
||||
void performanceTest() {
|
||||
// 性能测试实现
|
||||
}
|
||||
```
|
||||
|
||||
## 11. 最佳实践总结
|
||||
|
||||
### 11.1 设计原则
|
||||
1. **单一职责**:每个Repository只负责一个实体类型
|
||||
2. **接口隔离**:根据客户端需求设计专用查询方法
|
||||
3. **命名清晰**:方法名应能直观表达查询意图
|
||||
4. **性能优先**:合理使用索引和查询优化
|
||||
|
||||
### 11.2 代码规范
|
||||
1. **注解完整**:正确使用`@Repository`、`@Query`、`@Param`
|
||||
2. **参数校验**:对传入参数进行null和有效性检查
|
||||
3. **异常处理**:捕获并处理数据库访问异常
|
||||
4. **日志记录**:记录关键查询操作和性能指标
|
||||
|
||||
### 11.3 维护性
|
||||
1. **版本兼容**:考虑数据库模式变更的影响
|
||||
2. **向后兼容**:新增方法不影响现有功能
|
||||
3. **文档完整**:复杂查询添加JavaDoc说明
|
||||
4. **测试覆盖**:确保关键查询逻辑有测试覆盖
|
||||
|
||||
## 12. 常见问题和解决方案
|
||||
|
||||
### 12.1 N+1查询问题
|
||||
**问题**:查询列表时触发N+1次关联查询
|
||||
**解决**:使用`JOIN FETCH`或`@EntityGraph`
|
||||
|
||||
### 12.2 性能瓶颈
|
||||
**问题**:复杂查询导致响应慢
|
||||
**解决**:
|
||||
1. 添加数据库索引
|
||||
2. 优化查询语句
|
||||
3. 使用分页限制结果集
|
||||
4. 考虑使用缓存
|
||||
|
||||
### 12.3 事务死锁
|
||||
**问题**:并发操作导致事务死锁
|
||||
**解决**:
|
||||
1. 合理设计事务边界
|
||||
2. 调整事务隔离级别
|
||||
3. 实现重试机制
|
||||
|
||||
## 13. 扩展建议
|
||||
|
||||
### 13.1 动态查询支持
|
||||
考虑使用`Querydsl`或`Specification`支持动态查询构建。
|
||||
|
||||
### 13.2 读写分离
|
||||
实现主从数据库分离,提升查询性能。
|
||||
|
||||
### 13.3 分布式缓存
|
||||
结合Redis等分布式缓存,提升查询性能。
|
||||
|
||||
---
|
||||
|
||||
## 14. 实施检查清单
|
||||
|
||||
### 14.1 Repository实现后验证步骤
|
||||
在每个Repository实现完成后,请按以下清单进行验证:
|
||||
|
||||
#### ✅ 基础架构检查
|
||||
- [ ] Repository接口继承了 `MyRepository<T, ID>`
|
||||
- [ ] 使用了 `@Repository` 注解
|
||||
- [ ] 包路径符合规范:`com.ecep.contract.ds.{business}.repository`
|
||||
- [ ] 类名符合命名规范:`{EntityName}Repository`
|
||||
|
||||
#### ✅ 方法设计检查
|
||||
- [ ] 遵循Spring Data JPA命名约定
|
||||
- [ ] 包含必要的业务查询方法
|
||||
- [ ] 复杂查询使用 `@Query` 注解
|
||||
- [ ] 分页方法支持 `Pageable` 参数
|
||||
|
||||
#### ✅ 文档和注释检查
|
||||
- [ ] 类级别JavaDoc注释完整
|
||||
- [ ] 关键方法有JavaDoc说明
|
||||
- [ ] 参数和返回值有明确说明
|
||||
|
||||
#### ✅ 性能和质量检查
|
||||
- [ ] 避免N+1查询问题
|
||||
- [ ] 适当使用索引提示
|
||||
- [ ] 包含必要的单元测试
|
||||
- [ ] 异常处理考虑周全
|
||||
|
||||
### 14.2 常见错误检查
|
||||
在提交代码前,特别检查是否犯了以下常见错误:
|
||||
|
||||
#### ❌ 继承层次错误
|
||||
```java
|
||||
// 错误示例 - 不要这样做
|
||||
public interface SomeRepository extends JpaRepository<Entity, Integer>
|
||||
public interface AnotherRepository extends CrudRepository<Entity, Integer>
|
||||
|
||||
// 正确做法 - 统一使用MyRepository
|
||||
public interface SomeRepository extends MyRepository<Entity, Integer>
|
||||
```
|
||||
|
||||
#### ❌ 方法命名不规范
|
||||
```java
|
||||
// 错误示例
|
||||
List<Entity> getDataByCondition(String condition) // 使用get前缀而非find
|
||||
|
||||
// 正确做法
|
||||
List<Entity> findByCondition(String condition) // 使用find前缀
|
||||
```
|
||||
|
||||
#### ❌ 缺少必要文档
|
||||
```java
|
||||
// 错误示例 - 无注释
|
||||
List<Entity> findByStatus(String status);
|
||||
|
||||
// 正确做法 - 完整注释
|
||||
/**
|
||||
* 根据状态查询实体列表
|
||||
*
|
||||
* @param status 状态值
|
||||
* @return 符合状态的实体列表
|
||||
*/
|
||||
List<Entity> findByStatus(String status);
|
||||
```
|
||||
|
||||
### 14.3 架构一致性验证
|
||||
确保新实现的Repository与项目中其他Repository保持一致:
|
||||
|
||||
1. **继承模式统一**:所有Repository都必须继承MyRepository
|
||||
2. **注解使用统一**:统一使用@Repository注解
|
||||
3. **命名约定统一**:遵循Spring Data JPA命名规范
|
||||
4. **文档风格统一**:保持JavaDoc注释风格一致
|
||||
|
||||
### 14.4 后续维护注意事项
|
||||
- 新增查询方法时遵循现有命名规范
|
||||
- 修改现有方法时保持向后兼容性
|
||||
- 定期审查Repository方法的性能和合理性
|
||||
- 及时更新相关文档和测试用例
|
||||
|
||||
---
|
||||
|
||||
*本文档基于ContractBalanceRepository实现经验总结,结合项目实际情况分析,其他Repository实现应参考此文档规范。特别注意避免文档中提到的常见错误。*
|
||||
435
.trae/rules/server_service_rules.md
Normal file
435
.trae/rules/server_service_rules.md
Normal file
@@ -0,0 +1,435 @@
|
||||
# 服务器端Service设计规范
|
||||
|
||||
## 目录结构
|
||||
|
||||
每个业务域下的service目录结构示例:
|
||||
```
|
||||
ds/
|
||||
├── company/service/
|
||||
│ ├── CompanyService.java # 主业务服务
|
||||
│ ├── CompanyContactService.java # 联系人服务
|
||||
│ ├── CompanyFileService.java # 文件管理服务
|
||||
│ ├── CompanyOldNameService.java # 曾用名服务
|
||||
│ └── ...
|
||||
├── contract/service/
|
||||
│ ├── ContractService.java # 主业务服务
|
||||
│ ├── ContractCatalogService.java # 分类目录服务
|
||||
│ └── ...
|
||||
├── customer/service/
|
||||
│ ├── CustomerService.java # 主业务服务(继承CompanyBasicService)
|
||||
│ └── ...
|
||||
├── project/service/
|
||||
│ ├── ProjectService.java # 主业务服务
|
||||
│ ├── ProjectFileService.java # 文件管理服务
|
||||
│ └── ...
|
||||
└── vendor/service/
|
||||
├── VendorService.java # 主业务服务(继承CompanyBasicService)
|
||||
└── ...
|
||||
```
|
||||
|
||||
## 核心基类和接口体系
|
||||
|
||||
### 主要基类
|
||||
- **EntityService<M, Vo, ID>**: 通用实体服务基类,提供CRUD操作的标准实现
|
||||
- **CompanyBasicService**: 专门处理公司相关业务的基础服务类,支持公司关联查询
|
||||
|
||||
### 核心接口
|
||||
- **IEntityService<T>**: 实体基本操作接口
|
||||
- **QueryService<Vo>**: 查询服务接口
|
||||
- **VoableService<M, Vo>**: 实体与视图对象转换服务接口
|
||||
|
||||
## 注解使用规范
|
||||
|
||||
### 类级别注解
|
||||
```java
|
||||
@Lazy // 延迟加载,避免循环依赖
|
||||
@Service // Spring服务组件
|
||||
@CacheConfig(cacheNames = "业务缓存名称") // 缓存配置
|
||||
public class CompanyService extends EntityService<Company, CompanyVo, Integer>
|
||||
implements IEntityService<Company>, QueryService<CompanyVo>, VoableService<Company, CompanyVo> {
|
||||
// 实现代码
|
||||
}
|
||||
```
|
||||
|
||||
### 方法级别注解
|
||||
```java
|
||||
// 查询方法缓存 - 使用参数作为缓存键
|
||||
@Cacheable(key = "#p0") // ID查询
|
||||
public CompanyVo findById(Integer id) {
|
||||
return repository.findById(id).map(Company::toVo).orElse(null);
|
||||
}
|
||||
|
||||
@Cacheable(key = "'name-'+#p0") // 名称查询,带前缀
|
||||
public CompanyVo findByName(String name) {
|
||||
return repository.findFirstByName(name).map(Company::toVo).orElse(null);
|
||||
}
|
||||
|
||||
// 修改方法缓存清理 - 清理所有相关缓存
|
||||
@Caching(evict = {
|
||||
@CacheEvict(key = "#p0.id"),
|
||||
@CacheEvict(key = "'name-'+#p0.name"),
|
||||
@CacheEvict(key = "'code-'+#p0.code")
|
||||
})
|
||||
public Contract save(Contract contract) {
|
||||
return contractRepository.save(contract);
|
||||
}
|
||||
```
|
||||
|
||||
## 依赖注入规范
|
||||
|
||||
### Repository注入
|
||||
```java
|
||||
@Lazy
|
||||
@Autowired
|
||||
private CompanyRepository repository;
|
||||
```
|
||||
|
||||
### Service间依赖注入
|
||||
```java
|
||||
@Lazy
|
||||
@Autowired
|
||||
private ContractService contractService;
|
||||
|
||||
@Lazy
|
||||
@Autowired
|
||||
private VendorService vendorService;
|
||||
|
||||
@Lazy
|
||||
@Autowired
|
||||
private CompanyContactService companyContactService;
|
||||
```
|
||||
|
||||
### 外部服务依赖注入
|
||||
```java
|
||||
@Lazy
|
||||
@Autowired
|
||||
private CloudRkService cloudRkService;
|
||||
|
||||
@Lazy
|
||||
@Autowired
|
||||
private CloudTycService cloudTycService;
|
||||
|
||||
@Autowired(required = false) // 可选依赖
|
||||
private YongYouU8Service yongYouU8Service;
|
||||
```
|
||||
|
||||
## 查询实现模式
|
||||
|
||||
### 标准查询实现
|
||||
```java
|
||||
@Override
|
||||
public Page<CompanyVo> findAll(JsonNode paramsNode, Pageable pageable) {
|
||||
Specification<Company> spec = null;
|
||||
|
||||
// 搜索文本查询
|
||||
if (paramsNode.has("searchText")) {
|
||||
spec = getSpecification(paramsNode.get("searchText").asText());
|
||||
}
|
||||
|
||||
// 字段等值查询
|
||||
spec = SpecificationUtils.andFieldEqualParam(spec, paramsNode, "name", "uniscid", "abbName");
|
||||
|
||||
// 分页查询并转换为VO
|
||||
return findAll(spec, pageable).map(Company::toVo);
|
||||
}
|
||||
```
|
||||
|
||||
### 复杂查询条件构建
|
||||
```java
|
||||
@Override
|
||||
public Specification<Company> getSpecification(String searchText) {
|
||||
if (!StringUtils.hasText(searchText)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (root, query, builder) -> {
|
||||
return builder.or(
|
||||
builder.like(root.get("name"), "%" + searchText + "%"),
|
||||
builder.like(root.get("code"), "%" + searchText + "%"),
|
||||
builder.like(root.get("description"), "%" + searchText + "%"));
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### CompanyBasicService继承模式
|
||||
继承CompanyBasicService的Service(如CustomerService、VendorService)提供公司关联查询:
|
||||
|
||||
```java
|
||||
@Override
|
||||
public Specification<Vendor> getSpecification(String searchText) {
|
||||
if (!StringUtils.hasText(searchText)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 使用公司关联查询
|
||||
Specification<Vendor> nameSpec = (root, query, builder) -> {
|
||||
Path<Company> company = root.get("company");
|
||||
return companyService.buildSearchPredicate(searchText, company, query, builder);
|
||||
};
|
||||
|
||||
// 数字ID查询
|
||||
if (MyStringUtils.isAllDigit(searchText)) {
|
||||
try {
|
||||
int id = Integer.parseInt(searchText);
|
||||
nameSpec = SpecificationUtils.or(nameSpec, (root, query, builder) -> {
|
||||
return builder.equal(root.get("id"), id);
|
||||
});
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
// 实体搜索
|
||||
List<VendorEntity> searched = vendorEntityService.search(searchText);
|
||||
if (!searched.isEmpty()) {
|
||||
nameSpec = SpecificationUtils.or(nameSpec, (root, query, builder) -> {
|
||||
return builder.in(root.get("id")).value(searched.stream()
|
||||
.map(VendorEntity::getVendor)
|
||||
.filter(Objects::nonNull)
|
||||
.map(Vendor::getId)
|
||||
.collect(Collectors.toSet()));
|
||||
});
|
||||
}
|
||||
|
||||
return nameSpec;
|
||||
}
|
||||
```
|
||||
|
||||
## 业务逻辑实现模式
|
||||
|
||||
### 1. 主业务Service(继承EntityService)
|
||||
```java
|
||||
@Lazy
|
||||
@Service
|
||||
@CacheConfig(cacheNames = "contract")
|
||||
public class ContractService extends EntityService<Contract, ContractVo, Integer>
|
||||
implements IEntityService<Contract>, QueryService<ContractVo>, VoableService<Contract, ContractVo> {
|
||||
|
||||
@Override
|
||||
protected ContractRepository getRepository() {
|
||||
return contractRepository;
|
||||
}
|
||||
|
||||
@Cacheable(key = "#p0")
|
||||
public ContractVo findById(Integer id) {
|
||||
return getRepository().findById(id).map(Contract::toVo).orElse(null);
|
||||
}
|
||||
|
||||
// 业务特定方法
|
||||
public List<Contract> findAllByCompany(Company company) {
|
||||
return contractRepository.findAllByCompanyId(company.getId());
|
||||
}
|
||||
|
||||
public File getContractCatalogPath(ContractCatalogVo catalog, ContractVo contract) {
|
||||
// 文件路径处理逻辑
|
||||
String parent = catalog.getParent();
|
||||
File dir = getBasePath();
|
||||
// ... 路径构建逻辑
|
||||
return dir;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 继承CompanyBasicService的Service
|
||||
```java
|
||||
@Lazy
|
||||
@Service
|
||||
@CacheConfig(cacheNames = "company-customer")
|
||||
public class CustomerService extends CompanyBasicService
|
||||
implements IEntityService<CompanyCustomer>, QueryService<CustomerVo>,
|
||||
VoableService<CompanyCustomer, CustomerVo> {
|
||||
|
||||
// 提供公司关联查询
|
||||
public CompanyCustomer findByCompany(Company company) {
|
||||
return repository.findByCompany(company).orElse(null);
|
||||
}
|
||||
|
||||
public CustomerVo findByCompany(CompanyVo company) {
|
||||
return repository.findByCompanyId(company.getId()).map(CompanyCustomer::toVo).orElse(null);
|
||||
}
|
||||
|
||||
// 文件重建业务逻辑
|
||||
public boolean reBuildingFiles(CompanyCustomer companyCustomer, MessageHolder holder) {
|
||||
List<CompanyCustomerFile> dbFiles = companyCustomerFileService.findAllByCustomer(companyCustomer);
|
||||
Map<String, CompanyCustomerFile> map = new HashMap<>();
|
||||
|
||||
boolean modified = fetchDbFiles(dbFiles, map, holder::info);
|
||||
|
||||
List<File> needMoveToCompanyPath = new ArrayList<>();
|
||||
fetchFiles(companyCustomer.getPath(), needMoveToCompanyPath, retrieveFiles, map, holder::info);
|
||||
|
||||
moveFileToCompany(companyCustomer.getCompany(), needMoveToCompanyPath);
|
||||
|
||||
holder.info("导入 " + retrieveFiles.size() + " 个文件");
|
||||
|
||||
if (!retrieveFiles.isEmpty()) {
|
||||
retrieveFiles.forEach(v -> v.setCustomer(companyCustomer));
|
||||
companyCustomerFileService.saveAll(retrieveFiles);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 文件管理Service实现模式
|
||||
|
||||
### 文件路径管理
|
||||
```java
|
||||
public File getBasePath() {
|
||||
return new File(confService.getString(ContractConstant.KEY_BASE_PATH));
|
||||
}
|
||||
|
||||
public File getContractCatalogPath(ContractCatalogVo catalog, ContractVo contract) {
|
||||
String parent = catalog.getParent();
|
||||
File dir = getBasePath();
|
||||
|
||||
if (StringUtils.hasText(parent)) {
|
||||
dir = new File(dir, parent);
|
||||
if (!dir.exists() && !dir.mkdir()) {
|
||||
System.out.println("unable make directory = " + dir.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
dir = new File(dir, catalog.getPath());
|
||||
if (!dir.exists() && !dir.mkdir()) {
|
||||
System.out.println("unable make directory = " + dir.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (catalog.isUseYear()) {
|
||||
String code = contract.getCode();
|
||||
String catalogCode = catalog.getCode();
|
||||
int length = catalogCode.length();
|
||||
String yearCode = code.substring(length, length + 2);
|
||||
dir = new File(dir, "20" + yearCode);
|
||||
if (!dir.exists() && !dir.mkdir()) {
|
||||
System.out.println("unable make directory = " + dir.getAbsolutePath());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return dir;
|
||||
}
|
||||
```
|
||||
|
||||
## 缓存策略最佳实践
|
||||
|
||||
### 缓存键设计原则
|
||||
- **ID查询**: `@Cacheable(key = "#p0")`
|
||||
- **字符串查询**: `@Cacheable(key = "'name-'+#p0")`
|
||||
- **复合键查询**: `@Cacheable(key = "'code-year-'+#p0+'-'+#p1")`
|
||||
|
||||
### 缓存清理策略
|
||||
```java
|
||||
@Caching(evict = {
|
||||
@CacheEvict(key = "#p0.id"), // 主键缓存
|
||||
@CacheEvict(key = "'name-'+#p0.name"), // 名称缓存
|
||||
@CacheEvict(key = "'code-'+#p0.code"), // 编码缓存
|
||||
@CacheEvict(key = "'guid-'+#p0.guid") // GUID缓存
|
||||
})
|
||||
public Contract save(Contract contract) {
|
||||
return contractRepository.save(contract);
|
||||
}
|
||||
```
|
||||
|
||||
## 性能优化建议
|
||||
|
||||
### 1. 延迟加载
|
||||
所有Service依赖都应使用@Lazy注解,避免循环依赖和启动时的性能问题。
|
||||
|
||||
### 2. 缓存粒度
|
||||
- 单条查询使用细粒度缓存键
|
||||
- 避免缓存大量数据的列表查询
|
||||
- 合理设置缓存过期策略
|
||||
|
||||
### 3. 分页查询
|
||||
使用Page<T>进行分页查询,避免一次性加载大量数据:
|
||||
```java
|
||||
public Page<Project> findAll(Specification<Project> spec, Pageable pageable) {
|
||||
return projectRepository.findAll(spec, pageable);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 批量操作
|
||||
对于大量数据操作,考虑使用批量处理:
|
||||
```java
|
||||
public void saveAll(List<CompanyCustomerFile> files) {
|
||||
companyCustomerFileService.saveAll(retrieveFiles);
|
||||
}
|
||||
```
|
||||
|
||||
## 异常处理规范
|
||||
|
||||
### 参数校验
|
||||
```java
|
||||
public void updateByVo(CustomerCatalog model, CustomerCatalogVo vo) {
|
||||
if (model == null) {
|
||||
throw new ServiceException("实体对象不能为空");
|
||||
}
|
||||
if (vo == null) {
|
||||
throw new ServiceException("VO对象不能为空");
|
||||
}
|
||||
// ... 业务逻辑
|
||||
}
|
||||
```
|
||||
|
||||
### 业务异常处理
|
||||
```java
|
||||
public Company findAndRemoveDuplicateCompanyByUniscid(String uniscid) {
|
||||
List<Company> companies = repository.findAllByUniscid(uniscid);
|
||||
if (companies.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (companies.size() == 1) {
|
||||
return companies.getFirst();
|
||||
} else {
|
||||
List<Company> result = removeDuplicatesByUniscid(companies);
|
||||
if (!result.isEmpty()) {
|
||||
return result.getFirst();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
## 工具类使用规范
|
||||
|
||||
### SpecificationUtils使用
|
||||
```java
|
||||
// 字段等值查询
|
||||
spec = SpecificationUtils.andFieldEqualParam(spec, paramsNode, "name", "code", "status");
|
||||
|
||||
// 参数关联查询
|
||||
spec = SpecificationUtils.andParam(spec, paramsNode, "company", "catalog", "type");
|
||||
|
||||
// 搜索文本组合
|
||||
spec = SpecificationUtils.andWith(searchText, this::buildSearchSpecification);
|
||||
```
|
||||
|
||||
### 字符串工具使用CompanyBasicService
|
||||
```java
|
||||
// 全数字判断
|
||||
if (MyStringUtils.isAllDigit(searchText)) {
|
||||
// 数字处理逻辑
|
||||
}
|
||||
|
||||
// 空值检查
|
||||
if (!StringUtils.hasText(searchText)) {
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
## 最佳实践总结
|
||||
|
||||
1. **接口实现完整性**: 所有Service应实现三个核心接口,确保功能一致性
|
||||
2. **缓存策略一致性**: 遵循统一的缓存键设计和清理策略
|
||||
3. **依赖注入规范**: 使用@Lazy避免循环依赖,清晰管理Service间依赖关系
|
||||
4. **查询性能优化**: 合理使用SpecificationUtils构建查询条件,支持分页查询
|
||||
5. **异常处理统一**: 统一的异常处理方式,提高代码健壮性
|
||||
6. **代码复用**: 继承合适的基类,复用通用逻辑
|
||||
7. **文档注释**: 关键方法和复杂业务逻辑应有清晰的JavaDoc注释
|
||||
8. **性能监控**: 关注缓存命中率,适时调整缓存策略
|
||||
|
||||
这套规范确保了Service层的代码质量、性能和可维护性,为整个系统的稳定运行提供了坚实基础。
|
||||
460
.trae/rules/server_task_rules.md
Normal file
460
.trae/rules/server_task_rules.md
Normal file
@@ -0,0 +1,460 @@
|
||||
# WebSocketServerTasker 接口实现规范
|
||||
|
||||
## 1. 概述
|
||||
|
||||
本文档总结了实现 `WebSocketServerTasker` 接口的标准做法和最佳实践,基于对项目中已有实现类的分析。该接口用于服务器端实现支持WebSocket通信的任务处理器,实现异步任务的执行和状态监控。
|
||||
|
||||
## 2. WebSocketServerTasker 接口介绍
|
||||
|
||||
`WebSocketServerTasker` 接口位于 `com.ecep.contract.service.tasker` 包下,继承自 `java.util.concurrent.Callable<Object>`,定义了以下核心方法:
|
||||
|
||||
```java
|
||||
public interface WebSocketServerTasker extends Callable<Object> {
|
||||
// 初始化任务,处理传入的参数
|
||||
void init(JsonNode argsNode);
|
||||
|
||||
// 设置会话信息(默认实现为空)
|
||||
default void setSession(SessionInfo session) {}
|
||||
|
||||
// 设置消息处理函数
|
||||
void setMessageHandler(Predicate<Message> messageHandler);
|
||||
|
||||
// 设置标题处理函数
|
||||
void setTitleHandler(Predicate<String> titleHandler);
|
||||
|
||||
// 设置属性处理函数
|
||||
void setPropertyHandler(BiConsumer<String, Object> propertyHandler);
|
||||
|
||||
// 设置进度处理函数
|
||||
void setProgressHandler(BiConsumer<Long, Long> progressHandler);
|
||||
}
|
||||
```
|
||||
|
||||
## 3. 基础实现模式
|
||||
|
||||
### 3.1 推荐实现方式
|
||||
|
||||
通过分析项目中的实现类,推荐以下标准实现模式:
|
||||
|
||||
1. **继承 Tasker<Object> 并实现 WebSocketServerTasker 接口**
|
||||
|
||||
```java
|
||||
public class YourTasker extends Tasker<Object> implements WebSocketServerTasker {
|
||||
// 实现代码
|
||||
}
|
||||
```
|
||||
|
||||
2. **在 Tasker 基类中已实现的方法**
|
||||
|
||||
`Tasker` 类已经提供了 `WebSocketServerTasker` 接口中大部分方法的实现,包括:
|
||||
- `setMessageHandler`
|
||||
- `setTitleHandler`
|
||||
- `setPropertyHandler`
|
||||
- `setProgressHandler`
|
||||
- `call()` 方法(实现了 `Callable` 接口)
|
||||
|
||||
这使得实现类只需要关注特定业务逻辑的实现。
|
||||
|
||||
## 3.2 WebSocketServerTasker 注册配置
|
||||
|
||||
要使WebSocketServerTasker能够被客户端通过WebSocket调用,必须在`tasker_mapper.json`配置文件中进行注册。
|
||||
|
||||
### 3.2.1 注册配置文件
|
||||
|
||||
配置文件位置:`server/src/main/resources/tasker_mapper.json`
|
||||
|
||||
### 3.2.2 注册格式
|
||||
|
||||
注册格式为JSON对象,包含一个`tasks`字段,该字段是一个键值对映射,其中:
|
||||
- **键**:任务名称(客户端通过此名称调用任务)
|
||||
- **值**:任务类的完全限定名
|
||||
|
||||
```json
|
||||
{
|
||||
"tasks": {
|
||||
"任务名称": "任务类的完全限定名",
|
||||
"ContractVerifyTasker": "com.ecep.contract.service.tasker.ContractVerifyTasker"
|
||||
},
|
||||
"descriptions": "任务注册信息, 客户端的任务可以通过 WebSocket 调用"
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2.3 注册示例
|
||||
|
||||
假设我们创建了一个名为`CustomTasker`的新任务类,其完全限定名为`com.ecep.contract.service.tasker.CustomTasker`,则注册方式如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"tasks": {
|
||||
"CustomTasker": "com.ecep.contract.service.tasker.CustomTasker",
|
||||
"OtherTasker": "..."
|
||||
},
|
||||
"descriptions": "任务注册信息, 客户端的任务可以通过 WebSocket 调用"
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2.4 注册机制说明
|
||||
|
||||
WebSocketServerTaskManager类在启动时会读取`tasker_mapper.json`文件,初始化任务类映射表。当客户端请求执行任务时,系统会:
|
||||
|
||||
1. 根据客户端提供的任务名称从映射表中查找对应的任务类
|
||||
2. 使用反射机制实例化任务对象
|
||||
3. 设置WebSocket会话和各类处理器
|
||||
4. 执行任务并通过WebSocket返回结果
|
||||
|
||||
## 4. ContractRepairAllTasker 升级经验
|
||||
|
||||
通过分析 `ContractRepairAllTasker` 的实现,我们总结了以下升级经验:
|
||||
|
||||
### 4.1 升级步骤
|
||||
|
||||
1. **继承适当的基础类**:根据业务需求选择继承 `Tasker<Object>` 或其他特定的抽象类(如 `AbstContractRepairTasker`)
|
||||
|
||||
2. **实现 WebSocketServerTasker 接口**:声明实现该接口,自动继承接口定义的方法
|
||||
|
||||
3. **实现 init 方法**:处理任务初始化和参数解析
|
||||
|
||||
```java
|
||||
@Override
|
||||
public void init(JsonNode argsNode) {
|
||||
// 解析参数或初始化任务状态
|
||||
// 如果 Client 没有传递参数,就不做处理
|
||||
// do nothing
|
||||
|
||||
// 如果有参数,正确做法:检查参数有效性并安全解析
|
||||
if (argsNode != null && argsNode.size() > 0) {
|
||||
ContractService contractService = getCachedBean(ContractService.class);
|
||||
int contractId = argsNode.get(0).asInt();
|
||||
this.contract = contractService.findById(contractId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. **实现或重写 execute 方法**:提供具体的业务逻辑实现
|
||||
|
||||
```java
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
// 调用父类初始化
|
||||
super.execute(holder);
|
||||
updateTitle("同步修复所有合同");
|
||||
// 执行具体业务逻辑
|
||||
repair(holder);
|
||||
return null; // 通常返回 null 或处理结果
|
||||
}
|
||||
```
|
||||
|
||||
5. **使用更新方法提供状态反馈**:在执行过程中使用 `updateTitle`、`updateProgress` 等方法提供实时反馈
|
||||
|
||||
```java
|
||||
updateProgress(counter.incrementAndGet(), total);
|
||||
```
|
||||
|
||||
6. **支持任务取消**:定期检查 `isCancelled()` 方法,支持任务的取消操作
|
||||
|
||||
```java
|
||||
if (isCancelled()) {
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 关键经验
|
||||
|
||||
1. **消息处理**:通过 `MessageHolder` 处理和记录执行过程中的消息,方便调试和错误追踪
|
||||
|
||||
2. **进度更新**:对于批量操作,使用计数器和总数定期更新进度,提供良好的用户体验
|
||||
|
||||
3. **异常处理**:在循环处理中捕获异常并记录,确保单个项目的失败不会导致整个任务失败
|
||||
|
||||
4. **分页处理**:对于大量数据的处理,使用分页查询避免内存溢出
|
||||
|
||||
## 5. 共同模式和最佳实践
|
||||
|
||||
通过分析项目中的17个实现类,我们总结了以下共同模式和最佳实践:
|
||||
|
||||
### 5.1 共同模式
|
||||
|
||||
1. **继承结构**:所有实现类都继承 `Tasker<Object>` 并实现 `WebSocketServerTasker` 接口
|
||||
|
||||
2. **核心方法实现**:
|
||||
- 实现 `init(JsonNode argsNode)` 方法处理参数
|
||||
- 实现或重写 `execute(MessageHolder holder)` 方法实现业务逻辑
|
||||
|
||||
3. **状态更新**:使用 `updateTitle`、`updateMessage`、`updateProgress` 等方法更新任务状态
|
||||
|
||||
4. **Bean获取**:使用 `getCachedBean` 方法获取Spring管理的Bean
|
||||
|
||||
```java
|
||||
ContractService contractService = getCachedBean(ContractService.class);
|
||||
```
|
||||
|
||||
### 5.2 最佳实践
|
||||
|
||||
1. **参数验证**:在 `init` 方法中验证和转换输入参数
|
||||
|
||||
2. **进度反馈**:对于长时间运行的任务,提供合理的进度更新
|
||||
|
||||
3. **异常处理**:捕获并处理特定异常,提供有意义的错误信息
|
||||
|
||||
4. **取消支持**:实现任务取消机制,定期检查 `isCancelled()` 状态
|
||||
|
||||
5. **资源清理**:在任务结束或取消时清理资源
|
||||
|
||||
6. **日志记录**:使用 `slf4j` 记录关键操作和异常信息
|
||||
|
||||
7. **属性传递**:使用 `updateProperty` 方法传递任务执行结果或状态
|
||||
|
||||
```java
|
||||
updateProperty("passed", passed);
|
||||
```
|
||||
|
||||
## 6. 完整实现示例
|
||||
|
||||
### 6.1 简单任务实现示例
|
||||
|
||||
```java
|
||||
package com.ecep.contract.service.tasker;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.ds.contract.service.ContractService;
|
||||
import com.ecep.contract.ui.Tasker;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class SimpleTasker extends Tasker<Object> implements WebSocketServerTasker {
|
||||
@Getter
|
||||
@Setter
|
||||
private int entityId;
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean success = false;
|
||||
|
||||
@Override
|
||||
public void init(JsonNode argsNode) {
|
||||
// 解析参数
|
||||
this.entityId = argsNode.get(0).asInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
// 更新标题
|
||||
updateTitle("执行简单任务:" + entityId);
|
||||
|
||||
try {
|
||||
// 更新进度
|
||||
updateProgress(1, 3);
|
||||
|
||||
// 执行业务逻辑
|
||||
ContractService service = getCachedBean(ContractService.class);
|
||||
updateProgress(2, 3);
|
||||
|
||||
// 处理结果
|
||||
success = true;
|
||||
holder.info("任务执行成功");
|
||||
|
||||
// 更新最终进度
|
||||
updateProgress(3, 3);
|
||||
|
||||
// 更新结果属性
|
||||
updateProperty("success", success);
|
||||
} catch (Exception e) {
|
||||
holder.error("任务执行失败: " + e.getMessage());
|
||||
success = false;
|
||||
updateProperty("success", success);
|
||||
throw e;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 批量处理任务实现示例
|
||||
|
||||
```java
|
||||
package com.ecep.contract.service.tasker;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.ds.contract.service.ContractService;
|
||||
import com.ecep.contract.ds.contract.model.Contract;
|
||||
import com.ecep.contract.ui.Tasker;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
public class BatchProcessTasker extends Tasker<Object> implements WebSocketServerTasker {
|
||||
private ContractService contractService;
|
||||
private List<Integer> entityIds;
|
||||
|
||||
@Override
|
||||
public void init(JsonNode argsNode) {
|
||||
// 初始化服务引用
|
||||
this.contractService = getCachedBean(ContractService.class);
|
||||
|
||||
// 解析实体ID列表
|
||||
entityIds = new ArrayList<>();
|
||||
for (JsonNode node : argsNode) {
|
||||
entityIds.add(node.asInt());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
updateTitle("批量处理实体任务");
|
||||
|
||||
AtomicInteger counter = new AtomicInteger(0);
|
||||
long total = entityIds.size();
|
||||
|
||||
for (Integer id : entityIds) {
|
||||
// 检查是否取消
|
||||
if (isCancelled()) {
|
||||
holder.info("任务已取消");
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
MessageHolder subHolder = holder.sub("处理 ID: " + id + " > ");
|
||||
|
||||
// 执行业务逻辑
|
||||
Contract entity = contractService.getById(id);
|
||||
// 处理实体...
|
||||
|
||||
subHolder.info("处理成功");
|
||||
} catch (Exception e) {
|
||||
holder.error("处理 ID: " + id + " 失败: " + e.getMessage());
|
||||
// 记录异常但继续处理下一个
|
||||
}
|
||||
|
||||
// 更新进度
|
||||
updateProgress(counter.incrementAndGet(), total);
|
||||
}
|
||||
|
||||
updateTitle("批量处理完成");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 带有依赖的任务实现示例
|
||||
|
||||
```java
|
||||
package com.ecep.contract.service.tasker;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.ds.contract.service.ContractService;
|
||||
import com.ecep.contract.ds.customer.service.CustomerService;
|
||||
import com.ecep.contract.ui.Tasker;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class DependentTasker extends Tasker<Object> implements WebSocketServerTasker {
|
||||
@Getter
|
||||
@Setter
|
||||
private int contractId;
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean processed = false;
|
||||
|
||||
private ContractService contractService;
|
||||
private CustomerService customerService;
|
||||
|
||||
@Override
|
||||
public void init(JsonNode argsNode) {
|
||||
// 解析参数
|
||||
this.contractId = argsNode.get(0).asInt();
|
||||
|
||||
// 获取服务依赖
|
||||
this.contractService = getCachedBean(ContractService.class);
|
||||
this.customerService = getCachedBean(CustomerService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
updateTitle("执行带有服务依赖的任务");
|
||||
|
||||
updateProgress(1, 5);
|
||||
|
||||
// 使用多个服务协同完成任务
|
||||
try {
|
||||
// 步骤1: 获取合同信息
|
||||
var contract = contractService.getById(contractId);
|
||||
updateProgress(2, 5);
|
||||
|
||||
// 步骤2: 获取相关客户信息
|
||||
var customer = customerService.getById(contract.getCustomerId());
|
||||
updateProgress(3, 5);
|
||||
|
||||
// 步骤3: 执行业务逻辑
|
||||
// ...
|
||||
updateProgress(4, 5);
|
||||
|
||||
processed = true;
|
||||
holder.info("任务处理成功");
|
||||
|
||||
// 更新结果属性
|
||||
updateProperty("processed", processed);
|
||||
} catch (Exception e) {
|
||||
holder.error("任务处理失败: " + e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
|
||||
updateProgress(5, 5);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 7. 最佳实践总结
|
||||
|
||||
1. **继承 Tasker<Object> 并实现 WebSocketServerTasker 接口**:利用已有的基础实现
|
||||
|
||||
2. **合理实现 init 方法**:处理参数初始化,避免在 execute 中重复解析
|
||||
|
||||
3. **实现清晰的 execute 逻辑**:
|
||||
- 开始时设置任务标题
|
||||
- 过程中定期更新进度
|
||||
- 提供详细的消息反馈
|
||||
- 结束时更新最终状态
|
||||
|
||||
4. **使用 Spring Bean**:通过 `getCachedBean` 获取需要的服务依赖
|
||||
|
||||
5. **支持任务取消**:在关键循环中检查 `isCancelled()` 状态
|
||||
|
||||
6. **异常处理**:捕获异常并记录,提供有意义的错误信息
|
||||
|
||||
7. **进度反馈**:对于长时间运行的任务,提供合理的进度更新频率
|
||||
|
||||
8. **属性传递**:使用 `updateProperty` 方法传递任务结果给调用方
|
||||
|
||||
9. **日志记录**:使用 slf4j 记录关键操作和异常
|
||||
|
||||
10. **资源管理**:确保在任务完成或取消时释放相关资源
|
||||
|
||||
## 8. 注意事项
|
||||
|
||||
1. **不要在构造函数中获取 Spring Bean**:使用 `init` 方法或懒加载方式获取
|
||||
|
||||
2. **避免阻塞操作**:长时间阻塞的操作应考虑异步处理
|
||||
|
||||
3. **线程安全**:注意多线程环境下的并发访问问题
|
||||
|
||||
4. **内存管理**:对于处理大量数据的任务,注意内存使用,避免OOM
|
||||
|
||||
5. **超时处理**:考虑设置合理的超时机制
|
||||
|
||||
6. **事务管理**:根据需要考虑事务的范围和传播行为
|
||||
|
||||
7. **任务注册**:创建新的WebSocketServerTasker后,必须在`tasker_mapper.json`文件中注册,否则客户端将无法调用
|
||||
|
||||
8. **任务名称唯一性**:确保任务名称在`tasker_mapper.json`中唯一,避免名称冲突
|
||||
|
||||
9. **类路径正确**:注册时确保使用正确的类完全限定名,否则会导致实例化失败
|
||||
|
||||
10. **重启服务**:修改`tasker_mapper.json`后,需要重启服务才能使新的注册生效
|
||||
|
||||
通过遵循这些规范和最佳实践,可以确保实现的 `WebSocketServerTasker` 类具有良好的可维护性、可扩展性和用户体验。
|
||||
182
.trae/rules/tasker_implementation_guide.md
Normal file
182
.trae/rules/tasker_implementation_guide.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# Tasker实现规范指南
|
||||
|
||||
## 1. Tasker架构概述
|
||||
|
||||
Tasker是系统中用于执行异步任务的核心框架,主要用于处理耗时操作并提供实时进度反馈。Tasker架构包括:
|
||||
|
||||
- **Tasker基类**:提供任务执行的基础框架和通信功能
|
||||
- **WebSocketServerTasker接口**:定义WebSocket通信任务的规范
|
||||
- **MessageHolder**:用于在任务执行过程中传递消息和更新进度
|
||||
|
||||
## 2. 服务器端Tasker实现规范
|
||||
|
||||
### 2.1 基本结构
|
||||
|
||||
服务器端Tasker实现应遵循以下结构:
|
||||
|
||||
```java
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class YourTaskNameTask extends Tasker<Object> implements WebSocketServerTasker {
|
||||
// 服务依赖
|
||||
@Setter
|
||||
private YourService service;
|
||||
|
||||
@Override
|
||||
public void init(JsonNode argsNode) {
|
||||
// 初始化任务标题
|
||||
updateTitle("任务标题");
|
||||
// 初始化参数
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
// 实现业务逻辑
|
||||
// 使用holder.info()、holder.error()等方法反馈消息
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 关键注意事项
|
||||
|
||||
1. **继承与实现**:
|
||||
- 必须继承`Tasker<Object>`基类
|
||||
- 必须实现`WebSocketServerTasker`接口
|
||||
|
||||
2. **方法实现**:
|
||||
- **不要重复实现**Tasker基类已提供的handler设置方法:`setMessageHandler`、`setTitleHandler`、`setPropertyHandler`、`setProgressHandler`
|
||||
- 必须实现`init`方法,设置任务标题和初始化参数
|
||||
- 必须实现`execute`方法,实现具体业务逻辑
|
||||
|
||||
3. **进度和消息反馈**:
|
||||
- 使用`updateProgress`方法提供进度更新
|
||||
- 使用`MessageHolder`的`info`、`error`、`debug`等方法提供消息反馈
|
||||
|
||||
4. **任务取消**:
|
||||
- 在适当的地方检查`isCancelled()`状态,支持任务取消
|
||||
|
||||
5. **注册要求**:
|
||||
- 任务类必须在`tasker_mapper.json`中注册
|
||||
|
||||
## 3. 客户端Tasker实现规范
|
||||
|
||||
客户端Tasker实现应遵循:
|
||||
|
||||
```java
|
||||
public class YourTaskNameTask extends Tasker<Object> {
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
// 客户端任务逻辑
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 通信规范
|
||||
|
||||
### 4.1 命名规范
|
||||
|
||||
- 客户端和服务器端对应的Tasker类应使用**相同的类名**
|
||||
- 示例:客户端的`ContractSyncAllTask`对应服务器端的`ContractSyncAllTask`
|
||||
|
||||
### 4.2 消息传递
|
||||
|
||||
- 使用`MessageHolder`进行消息传递
|
||||
- 支持不同级别的消息:INFO、ERROR、DEBUG
|
||||
|
||||
## 5. 最佳实践
|
||||
|
||||
1. **任务拆分**:
|
||||
- 将大型任务拆分为多个小任务,提高可维护性
|
||||
|
||||
2. **异常处理**:
|
||||
- 捕获并正确处理异常,使用`holder.error()`提供详细错误信息
|
||||
|
||||
3. **资源管理**:
|
||||
- 确保及时释放资源,特别是在任务取消的情况下
|
||||
|
||||
4. **日志记录**:
|
||||
- 使用`holder.debug()`记录调试信息
|
||||
- 使用`holder.info()`记录进度信息
|
||||
|
||||
5. **状态检查**:
|
||||
- 定期检查`isCancelled()`状态,确保任务可以被及时取消
|
||||
|
||||
## 6. 代码示例
|
||||
|
||||
### 6.1 服务器端Tasker示例
|
||||
|
||||
```java
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ExampleSyncTask extends Tasker<Object> implements WebSocketServerTasker {
|
||||
@Setter
|
||||
private ExampleService exampleService;
|
||||
|
||||
@Override
|
||||
public void init(JsonNode argsNode) {
|
||||
updateTitle("示例同步任务");
|
||||
// 从argsNode初始化参数
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
try {
|
||||
// 获取总记录数
|
||||
long total = exampleService.getTotalCount();
|
||||
holder.info("开始同步数据,共" + total + "条记录");
|
||||
|
||||
// 分批处理
|
||||
long processed = 0;
|
||||
List<ExampleEntity> entities = exampleService.getAllEntities();
|
||||
|
||||
for (ExampleEntity entity : entities) {
|
||||
// 检查是否取消
|
||||
if (isCancelled()) {
|
||||
holder.info("任务已取消");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 处理单个实体
|
||||
exampleService.processEntity(entity);
|
||||
processed++;
|
||||
|
||||
// 更新进度
|
||||
updateProgress(processed, total);
|
||||
if (processed % 100 == 0) {
|
||||
holder.info("已处理" + processed + "/" + total + "条记录");
|
||||
}
|
||||
}
|
||||
|
||||
holder.info("同步完成,共处理" + processed + "条记录");
|
||||
return processed;
|
||||
} catch (Exception e) {
|
||||
holder.error("同步失败: " + e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 7. 常见错误和避免方法
|
||||
|
||||
1. **重复实现handler方法**:
|
||||
- 错误:在子类中重复实现setMessageHandler等方法
|
||||
- 解决:删除子类中的这些方法,直接使用Tasker基类提供的实现
|
||||
|
||||
2. **缺少任务注册**:
|
||||
- 错误:任务类没有在tasker_mapper.json中注册
|
||||
- 解决:确保所有服务器端Tasker类都在配置文件中正确注册
|
||||
|
||||
3. **类名不匹配**:
|
||||
- 错误:客户端和服务器端Tasker类名不一致
|
||||
- 解决:确保两端使用相同的类名
|
||||
|
||||
4. **进度更新不正确**:
|
||||
- 错误:进度计算错误或没有更新
|
||||
- 解决:正确计算和更新进度,提高用户体验
|
||||
|
||||
5. **资源泄漏**:
|
||||
- 错误:未及时关闭数据库连接等资源
|
||||
- 解决:使用try-with-resources或在finally块中关闭资源
|
||||
58
.trae/rules/vo_rules.md
Normal file
58
.trae/rules/vo_rules.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# VO类规则
|
||||
|
||||
## 1. 目录结构
|
||||
- VO类统一放置在 `common/src/main/java/com/ecep/contract/vo/` 目录下
|
||||
- 按业务领域组织,不使用子目录
|
||||
|
||||
## 2. 命名规范
|
||||
- 类名:使用驼峰命名法,首字母大写,以Vo结尾,如 `ContractVo.java`、`CompanyVo.java`
|
||||
|
||||
## 3. 继承与接口实现
|
||||
- **必须实现** `java.io.Serializable` 接口,用于对象序列化
|
||||
- **通常实现** `IdentityEntity` 接口,提供主键ID的getter/setter方法
|
||||
- **可能实现** `NamedEntity` 接口,提供name属性的getter/setter方法(适用于有名称的VO)
|
||||
- **可能实现**特定领域接口,如:
|
||||
- `CompanyBasedVo`:提供companyId属性的getter/setter方法
|
||||
- `ProjectBasedVo`:提供project属性的getter/setter方法
|
||||
- `ContractBasedVo`:提供contractId属性的getter/setter方法
|
||||
- 其他类似的基于特定实体的接口
|
||||
|
||||
## 4. 注解规范
|
||||
- 通常使用Lombok的 `@Data` 注解,自动生成getter、setter、equals、hashCode和toString方法
|
||||
|
||||
## 5. 序列化规范
|
||||
- **必须包含** `private static final long serialVersionUID = 1L;` 字段,用于版本控制
|
||||
- 所有字段类型必须是可序列化的
|
||||
|
||||
## 6. 字段规范
|
||||
- 字段类型:
|
||||
- 整数类型:可使用 `Integer` 包装类型或 `int` 原始类型
|
||||
- 布尔类型:可使用 `Boolean` 包装类型或 `boolean` 原始类型
|
||||
- 时间类型:使用 `java.time.LocalDate`(日期)或 `java.time.LocalDateTime`(日期时间)
|
||||
- 字符串类型:使用 `String`
|
||||
- 浮点数类型:使用 `double` 或 `Double`
|
||||
- 关联关系:
|
||||
- 通常只包含关联实体的ID字段,如 `companyId`、`project`、`contractId` 等
|
||||
- 不包含实体对象的直接引用
|
||||
- 默认值:
|
||||
- 布尔类型字段通常有默认值(如 `false`)
|
||||
- 其他类型根据业务需求设置默认值
|
||||
|
||||
## 7. 注释规范
|
||||
- 类注释:使用JavaDoc格式,描述VO类的用途,如 `/** 项目报价视图对象 */`
|
||||
- 字段注释:使用JavaDoc格式,描述字段的含义、用途或业务规则
|
||||
- 关键字段(如外键、状态字段)应提供清晰的注释说明
|
||||
|
||||
## 8. 最佳实践
|
||||
- VO类应保持简洁,只包含数据和基本的数据访问方法
|
||||
- 避免在VO类中包含复杂的业务逻辑
|
||||
- 字段命名应与对应的实体类保持一致或有明确的映射关系
|
||||
- 确保所有字段都是可序列化的
|
||||
- 对于有默认值的字段,应在声明时初始化
|
||||
|
||||
## 9. 与实体类的区别
|
||||
- **序列化**:VO类必须实现 `Serializable` 接口并包含 `serialVersionUID` 字段,而实体类不包含
|
||||
- **用途**:VO类主要用于数据传输和展示,实体类主要用于持久化
|
||||
- **关联关系**:VO类通常只包含关联实体的ID,实体类包含实体对象的引用
|
||||
- **注解**:VO类主要使用Lombok注解,实体类使用JPA注解和Lombok注解
|
||||
- **方法**:VO类通常只有getter/setter方法,实体类可能包含更多业务相关方法
|
||||
231
README.md
231
README.md
@@ -1,33 +1,198 @@
|
||||
## 项目改进与优化分析报告
|
||||
### 1. 同步任务与调度优化
|
||||
- 调度策略优化 :各同步任务(如 ContractSyncTask 、 VendorSyncTask )使用不同的调度间隔和策略,建议统一调度配置,避免重复执行和资源浪费
|
||||
- 增量同步实现 :多数同步任务缺乏断点续传机制,建议添加基于最后同步ID或时间戳的增量同步功能
|
||||
- 任务依赖管理 :部分任务存在隐式依赖关系(如 OldVersionSyncVendorTask 中的多步骤执行),建议引入任务依赖管理机制
|
||||
### 2. 线程池与异步处理
|
||||
- 线程池配置 :当前项目未明确配置线程池参数(核心线程数、最大线程数等),建议在 SpringApp 中添加显式线程池配置
|
||||
- 线程池隔离 :不同类型的任务(IO密集型、CPU密集型)应使用不同线程池,避免资源竞争
|
||||
- 异步任务监控 :添加异步任务执行状态监控和超时处理机制,避免任务长时间阻塞
|
||||
### 3. 缓存机制优化
|
||||
- 缓存策略细化 :当前缓存配置较为简单( CaffeineCacheManager ),建议为不同类型数据配置差异化缓存策略(过期时间、最大容量等)
|
||||
- 缓存一致性 :修复 CloudRkService 中缓存更新问题(注释中提到的"这个可以无法更新缓存")
|
||||
- 缓存预热 :添加关键数据缓存预热机制,提高系统启动后响应速度
|
||||
### 4. 依赖注入与懒加载
|
||||
- 依赖注入规范化 :减少 SpringApp.getBean() 手动获取bean的方式,推广构造函数注入或字段注入
|
||||
- 懒加载优化 :充分利用 @Lazy 注解和 BootstrapMode.LAZY 配置,优化应用启动性能
|
||||
- 服务解耦 :继续推进服务拆分工作(如之前的 ContractService 拆分为专用服务),减少服务间耦合
|
||||
### 5. 异常处理与重试机制
|
||||
- 异常处理统一 :规范异常处理策略,确保所有异常都被适当记录和处理
|
||||
- 重试机制添加 :为网络请求和数据库操作添加重试机制(如使用 Spring Retry )
|
||||
- 熔断降级 :对外部系统调用(如用友U8接口)添加熔断降级机制,提高系统稳定性
|
||||
### 6. 数据库操作优化
|
||||
- 批量操作引入 :对大量数据的CRUD操作,建议使用批量处理API提高性能
|
||||
- 查询优化 :添加适当索引,优化复杂查询,避免全表扫描
|
||||
- 连接池配置 :优化 HikariDataSource 配置参数(如连接池大小、超时时间等)
|
||||
### 7. 代码质量与维护性
|
||||
- 重复代码消除 :提取同步任务中的共同逻辑(如进度更新、异常处理)为抽象基类
|
||||
- 注释完善 :补充关键类和方法的文档注释,特别是复杂业务逻辑和优化点
|
||||
- 技术债务清理 :解决代码中的TODO项(如 CloudRkService 中的缓存更新问题)
|
||||
### 8. 配置管理优化
|
||||
- 配置集中管理 :将分散在代码中的配置项(如同步间隔、批处理大小)集中到配置文件
|
||||
- 动态配置支持 :添加动态配置更新机制,避免重启应用
|
||||
以上优化点已按优先级和影响范围排序,建议逐步实施。实施过程中应注意性能测试和兼容性验证,确保优化不会引入新问题。
|
||||
# Contract-Manager 项目报告
|
||||
|
||||
## 项目概述
|
||||
Contract-Manager是一个企业级合同管理系统,提供完整的合同生命周期管理,包括合同的创建、审批、执行、归档等功能,同时集成了供应商管理、客户管理、项目管理等相关业务模块。
|
||||
|
||||
## 项目架构
|
||||
|
||||
### 模块划分
|
||||
项目采用模块化设计,分为三个主要模块:
|
||||
|
||||
- **common**:公共模块,包含实体类、枚举、异常、工具类等共享组件
|
||||
- **server**:服务端模块,基于Spring Boot开发,提供HTTP服务和WebSocket通信
|
||||
- **client**:客户端模块,基于JavaFX开发,提供图形用户界面
|
||||
|
||||
### 技术栈
|
||||
|
||||
#### 服务端技术栈
|
||||
- Java 21
|
||||
- Spring Boot 3.3.7
|
||||
- Spring Data JPA 3.3.7
|
||||
- MySQL 8.0.33
|
||||
- Lombok 1.18.32
|
||||
- POI 5.2.5 (Office文档处理)
|
||||
- PDFBox 3.0.1 (PDF文档处理)
|
||||
- Redis (缓存)
|
||||
- WebSocket (实时通信)
|
||||
|
||||
#### 客户端技术栈
|
||||
- Java 21
|
||||
- JavaFX 21
|
||||
- ControlsFX 11.1.2 (UI控件增强)
|
||||
- Lombok 1.18.32
|
||||
- Caffeine 3.1.8 (本地缓存)
|
||||
- WebSocket (与服务端通信)
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
Contract-Manager/
|
||||
├── common/ # 公共模块
|
||||
│ ├── src/main/java/com/ecep/contract/
|
||||
│ │ ├── model/ # 实体类
|
||||
│ │ ├── vo/ # 值对象
|
||||
│ │ ├── util/ # 工具类
|
||||
│ │ ├── constant/ # 常量定义
|
||||
│ │ └── msg/ # 消息相关
|
||||
├── server/ # 服务端模块
|
||||
│ ├── src/main/java/com/ecep/contract/
|
||||
│ │ ├── api/ # API接口
|
||||
│ │ ├── controller/ # 控制器
|
||||
│ │ ├── service/ # 服务层
|
||||
│ │ ├── ds/ # 数据访问层
|
||||
│ │ ├── config/ # 配置类
|
||||
│ │ ├── util/ # 工具类
|
||||
│ │ ├── handler/ # WebSocket处理器
|
||||
│ │ ├── cloud/ # 云端服务集成
|
||||
│ │ └── ui/ # 服务端UI组件
|
||||
├── client/ # 客户端模块
|
||||
│ ├── src/main/java/com/ecep/contract/
|
||||
│ │ ├── controller/ # 控制器
|
||||
│ │ ├── service/ # 服务层
|
||||
│ │ ├── vm/ # 视图模型
|
||||
│ │ ├── util/ # 工具类
|
||||
│ │ ├── task/ # 任务处理
|
||||
│ │ ├── converter/ # 类型转换器
|
||||
│ │ └── serializer/ # 序列化器
|
||||
│ └── src/main/resources/ui/ # FXML界面文件
|
||||
└── docs/ # 文档目录
|
||||
└── db/ # 数据库相关文件
|
||||
└── task/ # 任务
|
||||
```
|
||||
|
||||
## 核心功能模块
|
||||
|
||||
### 1. 公司管理
|
||||
- 公司基本信息管理
|
||||
- 公司联系方式管理
|
||||
- 公司银行账户管理
|
||||
- 公司文件管理
|
||||
- 公司黑名单管理
|
||||
|
||||
### 2. 供应商管理
|
||||
- 供应商信息管理
|
||||
- 供应商资质审核
|
||||
- 供应商分类管理
|
||||
- 供应商文件管理
|
||||
- 供应商评估
|
||||
|
||||
### 3. 客户管理
|
||||
- 客户信息管理
|
||||
- 客户分类管理
|
||||
- 客户满意度调查
|
||||
- 客户文件管理
|
||||
|
||||
### 4. 合同管理
|
||||
- 合同创建与编辑
|
||||
- 合同审批流程
|
||||
- 合同执行跟踪
|
||||
- 合同付款计划
|
||||
- 合同文件管理
|
||||
- 合同分类与归档
|
||||
|
||||
### 5. 项目管理
|
||||
- 项目信息管理
|
||||
- 项目成本管理
|
||||
- 项目报价管理
|
||||
- 项目文件管理
|
||||
- 项目资金计划
|
||||
|
||||
### 6. 库存管理
|
||||
- 库存物品管理
|
||||
- 库存价格历史
|
||||
- 库存分类管理
|
||||
|
||||
### 7. 员工与权限管理
|
||||
- 员工信息管理
|
||||
- 角色权限管理
|
||||
- 登录历史记录
|
||||
|
||||
### 8. 云端服务集成
|
||||
- 天眼查企业信息集成
|
||||
- 用友U8系统集成
|
||||
- 其他云端服务集成
|
||||
|
||||
## 数据模型设计
|
||||
|
||||
系统采用JPA注解式实体类设计,主要实体类包括:
|
||||
|
||||
- **基础实体类**:BasedEntity, IdentityEntity, NamedEntity等
|
||||
- **公司相关**:Company, CompanyContact, CompanyBankAccount等
|
||||
- **供应商相关**:Vendor, VendorApproved, VendorFile等
|
||||
- **客户相关**:CompanyCustomer, CustomerSatisfactionSurvey等
|
||||
- **合同相关**:Contract, ContractItem, ContractPayPlan等
|
||||
- **项目相关**:Project, ProjectCost, ProjectFundPlan等
|
||||
- **枚举实体类**:各种类型的本地枚举实体,如VendorTypeLocal, ContractFileTypeLocal等
|
||||
|
||||
## 通信机制
|
||||
|
||||
系统采用WebSocket进行客户端与服务端的实时通信,主要组件包括:
|
||||
|
||||
- **服务端**:WebSocketController, WebSocketServerHandler
|
||||
- **客户端**:WebSocketClientService, WebSocketClientSession
|
||||
|
||||
## 任务处理机制
|
||||
|
||||
系统实现了异步任务处理机制,支持长时间运行的任务监控和管理:
|
||||
|
||||
- **TaskMonitorCenter**:任务监控中心
|
||||
- **MonitoredTask**:可监控的任务基类
|
||||
- **TaskHistory**:任务历史记录
|
||||
|
||||
## 扩展性设计
|
||||
|
||||
系统采用良好的分层架构和接口设计,具有较强的扩展性:
|
||||
|
||||
1. **服务接口标准化**:通过IEntityService等接口统一服务访问方式
|
||||
2. **模块化设计**:各功能模块相对独立,便于扩展和维护
|
||||
3. **配置管理**:支持动态配置和属性绑定
|
||||
4. **国际化支持**:通过messages.properties实现多语言支持
|
||||
|
||||
## 安全机制
|
||||
|
||||
系统实现了基本的安全机制:
|
||||
|
||||
- **用户认证与授权**:基于角色的权限控制
|
||||
- **登录历史记录**:记录用户登录信息
|
||||
- **异常处理**:全局异常处理机制
|
||||
|
||||
## 部署说明
|
||||
|
||||
### 服务端部署
|
||||
1. 配置application.properties中的数据库连接和Redis连接信息
|
||||
2. 打包为jar文件:`mvn clean package`
|
||||
3. 运行jar文件:`java -jar server.jar`
|
||||
|
||||
### 客户端部署
|
||||
1. 确保安装Java 21运行环境
|
||||
2. 配置连接到服务端的WebSocket地址
|
||||
3. 打包为可执行jar或使用jpackage创建安装包
|
||||
|
||||
## Tasker实现规范指南
|
||||
|
||||
有关Tasker框架的详细实现规范,请参阅 [Tasker实现规范指南](docs/task/tasker_implementation_guide.md)。
|
||||
|
||||
## 开发环境要求
|
||||
- JDK 21
|
||||
- Maven 3.6+
|
||||
- MySQL 8.0+
|
||||
- Redis 6.0+
|
||||
- IntelliJ IDEA或Eclipse(推荐使用IDEA)
|
||||
|
||||
## 后续优化方向
|
||||
|
||||
1. 完善单元测试和集成测试
|
||||
2. 优化数据库查询性能
|
||||
3. 增强系统安全机制
|
||||
4. 改进用户界面体验
|
||||
5. 扩展更多云端服务集成
|
||||
143
client/pom.xml
Normal file
143
client/pom.xml
Normal file
@@ -0,0 +1,143 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.ecep.contract</groupId>
|
||||
<artifactId>Contract-Manager</artifactId>
|
||||
<version>0.0.135-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.ecep.contract</groupId>
|
||||
<artifactId>client</artifactId>
|
||||
<version>0.0.135-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.ecep.contract</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>0.0.135-SNAPSHOT</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- JavaFX -->
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-web</artifactId>
|
||||
<version>${javafx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.controlsfx</groupId>
|
||||
<artifactId>controlsfx</artifactId>
|
||||
<version>11.2.0</version>
|
||||
</dependency>
|
||||
<!-- Caffeine Cache -->
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
<version>3.1.8</version>
|
||||
</dependency>
|
||||
<!-- Spring Context Support for CaffeineCacheManager -->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context-support</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-commons</artifactId>
|
||||
<version>3.3.7</version>
|
||||
</dependency>
|
||||
<!-- OkHttp -->
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>4.12.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Logback 日志实现 -->
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.4.14</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-maven-plugin</artifactId>
|
||||
<version>0.0.8</version>
|
||||
<configuration>
|
||||
<mainClass>com.ecep.contract.ClientV2</mainClass>
|
||||
<launcher>app</launcher>
|
||||
<jlinkZipName>app-jlink</jlinkZipName>
|
||||
<jlinkImageName>app-jlink-image</jlinkImageName>
|
||||
<noManPages>true</noManPages>
|
||||
<stripDebug>true</stripDebug>
|
||||
<compress>2</compress>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>versions-maven-plugin</artifactId>
|
||||
<version>2.16.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<inherited>true</inherited>
|
||||
<id>increment-version</id>
|
||||
<phase>install</phase>
|
||||
<goals>
|
||||
<goal>set</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<generateBackupPoms>false</generateBackupPoms>
|
||||
<processAllModules>true</processAllModules>
|
||||
<!-- <newVersion>${project.version}</newVersion> -->
|
||||
<nextSnapshot>true</nextSnapshot>
|
||||
<nextSnapshotIndexToIncrement>3</nextSnapshotIndexToIncrement>
|
||||
<!-- <removeSnapshot>true</removeSnapshot>-->
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>3.1.2</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-dependencies</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/relativePath/client/lib</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
17
client/src/main/java/com/ecep/contract/ClientV2.java
Normal file
17
client/src/main/java/com/ecep/contract/ClientV2.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.ecep.contract;
|
||||
|
||||
import javafx.application.Application;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Created by Administrator on 2017/4/16.
|
||||
*/
|
||||
public class ClientV2 {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("当前目录 = " + new File(".").getAbsolutePath());
|
||||
Application.launch(Desktop.class, args);
|
||||
}
|
||||
|
||||
public static final String DEFAULT_HOST = "10.84.209.154";
|
||||
}
|
||||
@@ -1,27 +1,4 @@
|
||||
package com.ecep.contract.manager;
|
||||
|
||||
import com.ecep.contract.manager.ds.other.controller.LoginWidowController;
|
||||
import com.ecep.contract.manager.ui.BaseController;
|
||||
import com.ecep.contract.manager.ui.MessageHolder;
|
||||
import com.ecep.contract.manager.ui.task.TaskMonitorCenter;
|
||||
import com.ecep.contract.manager.util.UITools;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import lombok.Getter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
package com.ecep.contract;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
@@ -29,8 +6,42 @@ import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.FutureTask;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
|
||||
import com.ecep.contract.controller.BaseController;
|
||||
import com.ecep.contract.controller.HomeWindowController;
|
||||
import com.ecep.contract.controller.OkHttpLoginController;
|
||||
import com.ecep.contract.task.TaskMonitorCenter;
|
||||
import com.ecep.contract.util.HibernateProxyUtils;
|
||||
import com.ecep.contract.util.TextMessageHolder;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.CurrentEmployee;
|
||||
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import lombok.Getter;
|
||||
import okhttp3.Cookie;
|
||||
import okhttp3.CookieJar;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
/**
|
||||
* JavaFx 应用程序
|
||||
@@ -58,9 +69,11 @@ public class Desktop extends Application {
|
||||
private ScheduledExecutorService scheduledExecutorService = null;
|
||||
private final TaskMonitorCenter taskMonitorCenter = new TaskMonitorCenter();
|
||||
|
||||
private final SimpleIntegerProperty sessionId = new SimpleIntegerProperty(0);
|
||||
private final SimpleStringProperty sessionId = new SimpleStringProperty("");
|
||||
@Getter
|
||||
private final CurrentEmployee activeEmployee = new CurrentEmployee();
|
||||
@Getter
|
||||
private OkHttpClient httpClient;
|
||||
|
||||
public void setActiveEmployeeId(int activeEmployeeId) {
|
||||
activeEmployee.getId().set(activeEmployeeId);
|
||||
@@ -70,11 +83,11 @@ public class Desktop extends Application {
|
||||
return activeEmployee.getId().get();
|
||||
}
|
||||
|
||||
public int getSessionId() {
|
||||
public String getSessionId() {
|
||||
return sessionId.get();
|
||||
}
|
||||
|
||||
public void setSessionId(int sessionId) {
|
||||
public void setSessionId(String sessionId) {
|
||||
this.sessionId.set(sessionId);
|
||||
}
|
||||
|
||||
@@ -122,8 +135,12 @@ public class Desktop extends Application {
|
||||
}
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> runAsync(Runnable runnable) {
|
||||
return CompletableFuture.runAsync(runnable, getExecutorService());
|
||||
}
|
||||
|
||||
private void startSpringApp(Stage primaryStage, Parent root, FXMLLoader loader) {
|
||||
System.out.println("Desktop.startSpringApp");
|
||||
logger.debug("startSpringApp");
|
||||
// 更新窗口标题
|
||||
Node titleNode = root.lookup("#title");
|
||||
if (titleNode != null) {
|
||||
@@ -138,34 +155,26 @@ public class Desktop extends Application {
|
||||
ScrollPane logPane = (ScrollPane) root.lookup("#logPane");
|
||||
|
||||
logBox.getChildren().clear();
|
||||
MessageHolder holder = (level, message) -> {
|
||||
Text text = new Text(message);
|
||||
if (Level.WARNING == level) { // warning
|
||||
text.setFill(Color.YELLOW);
|
||||
} else if (Level.SEVERE == level) {// error
|
||||
text.setFill(Color.RED);
|
||||
} else if (Level.FINE == level) { // debug
|
||||
text.setFill(Color.GRAY);
|
||||
} else {
|
||||
text.setFill(Color.WHITE);
|
||||
TextMessageHolder holder = new TextMessageHolder() {
|
||||
@Override
|
||||
public void addTextMessage(Text text) {
|
||||
Platform.runLater(() -> {
|
||||
logBox.getChildren().add(text);
|
||||
logPane.layout();
|
||||
logPane.setVvalue(1.0);
|
||||
});
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
logBox.getChildren().add(text);
|
||||
logPane.layout();
|
||||
logPane.setVvalue(1.0);
|
||||
});
|
||||
};
|
||||
|
||||
holder.info("启动中,请稍后...");
|
||||
|
||||
CompletableFuture.runAsync(() -> {
|
||||
runAsync(() -> {
|
||||
try {
|
||||
//
|
||||
holder.info("读取配置文件...");
|
||||
Properties properties = new Properties();
|
||||
File configFile = new File("config.properties");
|
||||
if (configFile.exists()) {
|
||||
holder.debug("读取配置文件 " + configFile.getName() + "...");
|
||||
holder.info("读取配置文件 " + configFile.getAbsolutePath() + "...");
|
||||
try (FileInputStream input = new FileInputStream(configFile)) {
|
||||
properties.load(input);
|
||||
holder.info("配置文件读取成功.");
|
||||
@@ -174,9 +183,12 @@ public class Desktop extends Application {
|
||||
logger.error(e.getMessage(), e);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
logger.warn("配置文件{}不存在", configFile.getAbsolutePath());
|
||||
}
|
||||
HibernateProxyUtils.useProxy(false);
|
||||
|
||||
CompletableFuture.runAsync(() -> {
|
||||
runAsync(() -> {
|
||||
SpringApp.launch(properties, holder);
|
||||
ConfigurableListableBeanFactory beanFactory = SpringApp.context.getBeanFactory();
|
||||
|
||||
@@ -185,16 +197,37 @@ public class Desktop extends Application {
|
||||
});
|
||||
|
||||
try {
|
||||
LoginWidowController controller = new LoginWidowController();
|
||||
initHttpClient();
|
||||
|
||||
OkHttpLoginController controller = new OkHttpLoginController();
|
||||
controller.setHttpClient(this.httpClient);
|
||||
controller.setHolder(holder);
|
||||
controller.setPrimaryStage(primaryStage);
|
||||
controller.setProperties(properties);
|
||||
MyProperties myProperties = new MyProperties();
|
||||
myProperties.loadFromProperties(properties);
|
||||
controller.setProperties(myProperties);
|
||||
while (true) {
|
||||
controller.tryLogin();
|
||||
break;
|
||||
}
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("login in");
|
||||
try {
|
||||
controller.tryLogin().get();
|
||||
holder.info("登录成功");
|
||||
|
||||
try {
|
||||
while (!SpringApp.isRunning()) {
|
||||
System.out.println("等待启动");
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
|
||||
// 必须要等待启动成功后才能关闭主场景,否则进程结束程序退出
|
||||
HomeWindowController.show().thenRun(() -> Platform.runLater(primaryStage::close));
|
||||
|
||||
break;
|
||||
} catch (Exception ex) {
|
||||
holder.error(ex.getMessage());
|
||||
Thread.sleep(3000);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
holder.error("登录失败:" + e.getMessage());
|
||||
@@ -206,6 +239,27 @@ public class Desktop extends Application {
|
||||
}
|
||||
});
|
||||
System.out.println("Desktop.startSpringApp.");
|
||||
|
||||
}
|
||||
|
||||
private void initHttpClient() {
|
||||
this.httpClient = new OkHttpClient().newBuilder().cookieJar(new CookieJar() {
|
||||
private final List<Cookie> cookies = new java.util.ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
|
||||
// 保存服务器返回的Cookie(如JSESSIONID)
|
||||
this.cookies.addAll(cookies);
|
||||
System.out.println("保存Cookie: " + cookies);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Cookie> loadForRequest(HttpUrl url) {
|
||||
// 请求时自动携带Cookie
|
||||
return cookies;
|
||||
}
|
||||
|
||||
}).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -253,5 +307,4 @@ public class Desktop extends Application {
|
||||
}
|
||||
scheduledExecutorService.close();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.ecep.contract.manager.util;
|
||||
package com.ecep.contract;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -6,8 +6,6 @@ import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.ecep.contract.manager.Desktop;
|
||||
|
||||
public class DesktopUtils {
|
||||
|
||||
/**
|
||||
236
client/src/main/java/com/ecep/contract/MyProperties.java
Normal file
236
client/src/main/java/com/ecep/contract/MyProperties.java
Normal file
@@ -0,0 +1,236 @@
|
||||
package com.ecep.contract;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 应用程序配置类,用于管理系统配置信息
|
||||
*/
|
||||
@Component
|
||||
public class MyProperties implements InitializingBean {
|
||||
private static final Logger logger = LoggerFactory.getLogger(MyProperties.class);
|
||||
private static final String FILE_NAME = "config.properties";
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private String downloadsPath;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private String serverHost;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private String serverPort;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private String userName;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private String password;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean rememberPassword;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
// 初始化时加载配置文件
|
||||
try {
|
||||
loadFromFile();
|
||||
} catch (Exception e) {
|
||||
logger.warn("初始化配置文件失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件加载配置
|
||||
*/
|
||||
public void loadFromFile() {
|
||||
File configFile = new File(FILE_NAME);
|
||||
if (!configFile.exists()) {
|
||||
logger.debug("配置文件不存在: {}", configFile.getPath());
|
||||
return;
|
||||
}
|
||||
|
||||
try (FileInputStream input = new FileInputStream(configFile)) {
|
||||
Properties properties = new Properties();
|
||||
properties.load(input);
|
||||
loadFromProperties(properties);
|
||||
logger.debug("成功从配置文件加载配置: {}", configFile.getPath());
|
||||
} catch (Exception e) {
|
||||
logger.error("加载配置文件失败: {}", configFile.getPath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Properties对象并加载配置
|
||||
* 用于从外部设置配置项
|
||||
*
|
||||
* @param properties 配置对象
|
||||
*/
|
||||
public void setProperties(Properties properties) {
|
||||
this.loadFromProperties(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Properties对象加载配置
|
||||
* 用于从config.properties文件中读取配置项
|
||||
*
|
||||
* @param properties 配置对象
|
||||
*/
|
||||
public void loadFromProperties(Properties properties) {
|
||||
if (properties == null) {
|
||||
logger.warn("Properties对象为空,无法加载配置");
|
||||
return;
|
||||
}
|
||||
|
||||
// 加载下载路径配置
|
||||
String downloadsPath = properties.getProperty("my.downloadsPath");
|
||||
if (StringUtils.hasText(downloadsPath)) {
|
||||
this.setDownloadsPath(downloadsPath);
|
||||
logger.debug("从配置文件加载下载路径: {}", downloadsPath);
|
||||
}
|
||||
|
||||
// 加载服务器配置
|
||||
String serverHost = properties.getProperty("server.host", "127.0.0.1");
|
||||
if (StringUtils.hasText(serverHost)) {
|
||||
this.setServerHost(serverHost);
|
||||
logger.debug("从配置文件加载服务器地址: {}", serverHost);
|
||||
}
|
||||
|
||||
String serverPort = properties.getProperty("server.port", "8080");
|
||||
if (StringUtils.hasText(serverPort)) {
|
||||
this.setServerPort(serverPort);
|
||||
logger.debug("从配置文件加载服务器端口: {}", serverPort);
|
||||
}
|
||||
|
||||
// 加载用户凭证配置
|
||||
String userName = properties.getProperty("user.name");
|
||||
if (StringUtils.hasText(userName)) {
|
||||
this.setUserName(userName);
|
||||
logger.debug("从配置文件加载用户名");
|
||||
}
|
||||
|
||||
// 只有在记住密码的情况下才加载密码
|
||||
String rememberPasswordStr = properties.getProperty("user.rememberPassword");
|
||||
boolean rememberPassword = "true".equals(rememberPasswordStr);
|
||||
this.setRememberPassword(rememberPassword);
|
||||
|
||||
if (rememberPassword) {
|
||||
String password = properties.getProperty("user.password");
|
||||
if (StringUtils.hasText(password)) {
|
||||
this.setPassword(password);
|
||||
logger.debug("从配置文件加载密码");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将配置保存到Properties对象
|
||||
* 用于将配置项保存到config.properties文件
|
||||
*
|
||||
* @param properties 配置对象
|
||||
*/
|
||||
public void saveToProperties(Properties properties) {
|
||||
if (properties == null) {
|
||||
logger.warn("Properties对象为空,无法保存配置");
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存下载路径配置
|
||||
if (StringUtils.hasText(getDownloadsPath())) {
|
||||
properties.setProperty("my.downloadsPath", getDownloadsPath());
|
||||
logger.debug("保存下载路径到配置文件: {}", getDownloadsPath());
|
||||
}
|
||||
|
||||
// 保存服务器配置
|
||||
if (StringUtils.hasText(getServerHost())) {
|
||||
properties.setProperty("server.host", getServerHost());
|
||||
logger.debug("保存服务器地址到配置文件: {}", getServerHost());
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(getServerPort())) {
|
||||
properties.setProperty("server.port", getServerPort());
|
||||
logger.debug("保存服务器端口到配置文件: {}", getServerPort());
|
||||
}
|
||||
|
||||
// 保存用户凭证配置
|
||||
if (StringUtils.hasText(getUserName())) {
|
||||
properties.setProperty("user.name", getUserName());
|
||||
logger.debug("保存用户名为配置文件");
|
||||
}
|
||||
|
||||
properties.setProperty("user.rememberPassword", String.valueOf(isRememberPassword()));
|
||||
|
||||
// 只有在记住密码的情况下才保存密码
|
||||
if (isRememberPassword() && StringUtils.hasText(getPassword())) {
|
||||
properties.setProperty("user.password", getPassword());
|
||||
logger.debug("保存密码到配置文件");
|
||||
} else if (properties.containsKey("user.password")) {
|
||||
// 如果不记住密码,删除已存在的密码配置
|
||||
properties.remove("user.password");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试返回当前用户的下载文件夹
|
||||
*/
|
||||
public File getDownloadDirectory() {
|
||||
String downloadsPath = getDownloadsPath();
|
||||
if (StringUtils.hasText(downloadsPath)) {
|
||||
// 确保目录存在
|
||||
File dir = new File(downloadsPath);
|
||||
if (!dir.exists()) {
|
||||
boolean created = dir.mkdirs();
|
||||
logger.debug("创建下载目录: {}, 结果: {}", downloadsPath, created);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
// 没有配置下载目录时,尝试使用默认设置
|
||||
String home = System.getProperty("user.home");
|
||||
Path path = Paths.get(home, "Downloads");
|
||||
return path.toFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存配置到文件
|
||||
*/
|
||||
public void save() {
|
||||
File configFile = new File(FILE_NAME);
|
||||
try (FileOutputStream output = new FileOutputStream(configFile)) {
|
||||
Properties properties = new Properties();
|
||||
|
||||
// 如果文件已存在,先读取现有配置,避免覆盖其他配置
|
||||
if (configFile.exists()) {
|
||||
try (FileInputStream input = new FileInputStream(configFile)) {
|
||||
properties.load(input);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存当前配置
|
||||
saveToProperties(properties);
|
||||
properties.store(output, "Contract Manager 应用程序配置");
|
||||
logger.debug("成功保存配置到文件: {}", configFile.getPath());
|
||||
} catch (Exception e) {
|
||||
logger.error("保存配置到文件失败: {}", configFile.getPath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.ecep.contract.manager;
|
||||
package com.ecep.contract;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
@@ -25,7 +25,6 @@ import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cache.caffeine.CaffeineCacheManager;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.event.ContextClosedEvent;
|
||||
import org.springframework.context.event.EventListener;
|
||||
@@ -36,11 +35,7 @@ import org.springframework.core.metrics.StartupStep;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
import com.ecep.contract.manager.cloud.CloudRepositoriesConfig;
|
||||
import com.ecep.contract.manager.ds.DsRepositoriesConfig;
|
||||
import com.ecep.contract.manager.ui.MessageHolder;
|
||||
import com.ecep.contract.manager.util.MyDateTimeUtils;
|
||||
import com.ecep.contract.manager.util.UITools;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||
@@ -50,15 +45,20 @@ import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
|
||||
|
||||
@SpringBootApplication(exclude = { org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration.class })
|
||||
@SpringBootApplication(exclude = {
|
||||
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration.class,
|
||||
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration.class,
|
||||
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration.class,
|
||||
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration.class
|
||||
})
|
||||
@EnableScheduling
|
||||
@EnableAsync
|
||||
@EnableCaching
|
||||
public class SpringApp {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SpringApp.class);
|
||||
|
||||
static SpringApplication application;
|
||||
static ConfigurableApplicationContext context;
|
||||
public static SpringApplication application;
|
||||
public static ConfigurableApplicationContext context;
|
||||
|
||||
public static <T> T getBean(Class<T> requiredType) throws BeansException {
|
||||
return context.getBean(requiredType);
|
||||
@@ -92,23 +92,29 @@ public class SpringApp {
|
||||
environment.getPropertySources().addLast(new PropertiesPropertySource("dynamicProperties", properties));
|
||||
// app.getBeanFactory().registerSingleton("dataSource", dataSource());
|
||||
logger.debug("app = {}", app);
|
||||
|
||||
if (app instanceof AnnotationConfigApplicationContext ctx) {
|
||||
ctx.register(DsRepositoriesConfig.class);
|
||||
ctx.register(CloudRepositoriesConfig.class);
|
||||
}
|
||||
});
|
||||
|
||||
startup.start("");
|
||||
context = application.run();
|
||||
logger.debug("SpringApp.launch application.run().");
|
||||
Duration between = Duration.between(startup.getBufferedTimeline().getStartTime(), Instant.now());
|
||||
|
||||
// 初始化MyProperties,从properties加载配置
|
||||
try {
|
||||
MyProperties myProperties = context.getBean(MyProperties.class);
|
||||
myProperties.loadFromProperties(properties);
|
||||
holder.info("MyProperties配置加载完成");
|
||||
} catch (Exception e) {
|
||||
logger.error("加载MyProperties配置失败", e);
|
||||
holder.error("加载MyProperties配置失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
holder.info("应用程序环境加载完成... " + between);
|
||||
});
|
||||
CompletableFuture.runAsync(() -> {
|
||||
// 在这里调用 startup 性能分析
|
||||
analyzeStartupPerformance(startup);
|
||||
});
|
||||
}, Desktop.instance.getExecutorService());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,14 +248,18 @@ public class SpringApp {
|
||||
public ObjectMapper objectMapper() {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
// LocalDate
|
||||
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE));
|
||||
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(
|
||||
DateTimeFormatter.ofPattern(MyDateTimeUtils.DEFAULT_DATETIME_FORMAT_PATTERN)));
|
||||
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
|
||||
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE));
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(
|
||||
DateTimeFormatter.ofPattern(MyDateTimeUtils.DEFAULT_DATETIME_FORMAT_PATTERN)));
|
||||
// LocalTime
|
||||
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ISO_LOCAL_TIME));
|
||||
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ISO_LOCAL_TIME));
|
||||
// LocalDateTime
|
||||
javaTimeModule.addSerializer(LocalDateTime.class,
|
||||
new LocalDateTimeSerializer(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class,
|
||||
new LocalDateTimeDeserializer(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
|
||||
|
||||
objectMapper.registerModule(javaTimeModule);
|
||||
return objectMapper;
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
package com.ecep.contract;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.ecep.contract.constant.WebSocketConstant;
|
||||
import com.ecep.contract.controller.OkHttpLoginController;
|
||||
import com.ecep.contract.msg.SimpleMessage;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.WebSocket;
|
||||
import okhttp3.WebSocketListener;
|
||||
import okio.ByteString;
|
||||
|
||||
/**
|
||||
* WebSocket消息服务
|
||||
* 提供向服务器端发送WebSocket消息的功能
|
||||
*/
|
||||
@Service
|
||||
public class WebSocketClientService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(WebSocketClientService.class);
|
||||
@Getter
|
||||
@Setter
|
||||
private WebSocket webSocket;
|
||||
@Getter
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
private static final int RECONNECT_DELAY_MS = 5000; // 重连延迟时间(毫秒)
|
||||
private static final int HEARTBEAT_INTERVAL_MS = 30000; // 心跳间隔时间(毫秒)
|
||||
@Getter
|
||||
@Setter
|
||||
private long readTimeout = 30000;
|
||||
private boolean isActive = false; // 标记连接是否活跃
|
||||
private ScheduledFuture<?> heartbeatTask; // 心跳任务
|
||||
private ScheduledFuture<?> reconnectFuture; // 修改类型为CompletableFuture<Void>
|
||||
private SimpleBooleanProperty online = new SimpleBooleanProperty(false);
|
||||
private SimpleStringProperty message = new SimpleStringProperty("");
|
||||
|
||||
// 存储所有活跃的WebSocket会话
|
||||
private final Map<String, WebSocketClientSession> sessions = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
// 存储所有活跃的WebSocket会话
|
||||
private final Map<String, CompletableFuture<JsonNode>> callbacks = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
WebSocketListener listener = new WebSocketListener() {
|
||||
@Override
|
||||
public void onOpen(WebSocket webSocket, Response response) {
|
||||
Platform.runLater(() -> {
|
||||
online.setValue(true);
|
||||
message.setValue("已连接");
|
||||
});
|
||||
startHeartbeat(); // 启动心跳
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, String text) {
|
||||
// 处理收到的文本消息
|
||||
logger.debug("收到WebSocket消息: {}", text);
|
||||
// 这里可以根据需要处理从服务器接收的数据
|
||||
if ("pong".equals(text)) {
|
||||
// 收到pong响应,说明连接正常
|
||||
logger.debug("收到心跳响应");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
JsonNode node = objectMapper.readTree(text);
|
||||
if (node.has(WebSocketConstant.MESSAGE_ID_FIELD_NAME)) {
|
||||
String messageId = node.get(WebSocketConstant.MESSAGE_ID_FIELD_NAME).asText();
|
||||
CompletableFuture<JsonNode> future = callbacks.remove(messageId);
|
||||
if (future != null) {
|
||||
onCallbackMessage(future, node);
|
||||
} else {
|
||||
logger.error("未找到对应的回调future: {}", messageId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.has(WebSocketConstant.SESSION_ID_FIELD_NAME)) {
|
||||
String sessionId = node.get(WebSocketConstant.SESSION_ID_FIELD_NAME).asText();
|
||||
WebSocketClientSession session = sessions.get(sessionId);
|
||||
if (session != null) {
|
||||
try {
|
||||
session.onMessage(node);
|
||||
} catch (Exception e) {
|
||||
session.updateMessage(java.util.logging.Level.SEVERE, "会话异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.has(WebSocketConstant.ERROR_CODE_FIELD_NAME)) {
|
||||
int errorCode = node.get(WebSocketConstant.ERROR_CODE_FIELD_NAME).asInt();
|
||||
String errorMsg = node.get(WebSocketConstant.MESSAGE_FIELD_NAME).asText();
|
||||
logger.error("收到错误消息: 错误码={}, 错误信息={}", errorCode, errorMsg);
|
||||
if (errorCode == WebSocketConstant.ERROR_CODE_UNAUTHORIZED) {
|
||||
|
||||
// 调用所有的 callbacks 和 session 失败并且移除
|
||||
callbacks.keySet().stream().toList()
|
||||
.forEach(key -> callbacks.remove(key).completeExceptionally(new Exception("未授权")));
|
||||
sessions.values().stream().toList().forEach(session -> {
|
||||
session.updateMessage(java.util.logging.Level.SEVERE, "未授权");
|
||||
session.close();
|
||||
});
|
||||
isActive = false;
|
||||
webSocket.close(1000, "");
|
||||
WebSocketClientService.this.webSocket = null;
|
||||
|
||||
// 处理未授权错误,重新登录
|
||||
OkHttpLoginController controller = new OkHttpLoginController();
|
||||
controller.setHttpClient(Desktop.instance.getHttpClient());
|
||||
controller.setProperties(SpringApp.getBean(MyProperties.class));
|
||||
controller.tryLogin().get();
|
||||
// 需要把窗口顶置
|
||||
initWebSocket();
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("处理WebSocket消息失败: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, ByteString bytes) {
|
||||
// 处理收到的二进制消息
|
||||
logger.debug("收到二进制WebSocket消息,长度: " + bytes.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosing(WebSocket webSocket, int code, String reason) {
|
||||
logger.debug("WebSocket连接正在关闭: 代码=" + code + ", 原因=" + reason);
|
||||
stopHeartbeat(); // 停止心跳
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed(WebSocket webSocket, int code, String reason) {
|
||||
logger.debug("WebSocket连接已关闭: 代码=" + code + ", 原因=" + reason);
|
||||
stopHeartbeat(); // 停止心跳
|
||||
// 处理重连逻辑
|
||||
scheduleReconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
||||
logger.error("WebSocket连接失败: " + t.getMessage() + ", response=" + response, t);
|
||||
Platform.runLater(() -> {
|
||||
online.setValue(false);
|
||||
message.set("连接失败: " + t.getMessage());
|
||||
});
|
||||
stopHeartbeat(); // 停止心跳
|
||||
// 处理重连逻辑
|
||||
scheduleReconnect();
|
||||
}
|
||||
};
|
||||
|
||||
private void onCallbackMessage(CompletableFuture<JsonNode> future, JsonNode node) {
|
||||
if (node.has(WebSocketConstant.SUCCESS_FIELD_NAME)) {
|
||||
if (!node.get(WebSocketConstant.SUCCESS_FIELD_NAME).asBoolean()) {
|
||||
future.completeExceptionally(
|
||||
new RuntimeException(
|
||||
"请求失败:来自服务器的消息=" + node.get(WebSocketConstant.MESSAGE_FIELD_NAME).asText()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 使用具体类型后,这里不会再出现类型不匹配的错误
|
||||
if (node.has("data")) {
|
||||
future.complete(node.get("data"));
|
||||
} else {
|
||||
future.complete(node);
|
||||
}
|
||||
}
|
||||
|
||||
public void send(String string) {
|
||||
if (webSocket != null && webSocket.send(string)) {
|
||||
logger.debug("send message success:{}", string);
|
||||
} else if (webSocket == null) {
|
||||
logger.warn("Failed to send message: WebSocket is not initialized");
|
||||
}
|
||||
}
|
||||
|
||||
public void send(Object message) throws JsonProcessingException {
|
||||
send(objectMapper.writeValueAsString(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocketServerCallbackManage#onMessage 负责接收处理
|
||||
*
|
||||
* @param msg
|
||||
* @return
|
||||
*/
|
||||
public CompletableFuture<JsonNode> send(SimpleMessage msg) {
|
||||
CompletableFuture<JsonNode> future = new CompletableFuture<>();
|
||||
try {
|
||||
if (webSocket == null) {
|
||||
throw new IllegalStateException("WebSocket is not initialized");
|
||||
}
|
||||
if (!online.get()) {
|
||||
throw new IllegalStateException("WebSocket is not online");
|
||||
}
|
||||
String json = objectMapper.writeValueAsString(msg);
|
||||
callbacks.put(msg.getMessageId(), future);
|
||||
if (webSocket.send(json)) {
|
||||
logger.debug("send json success:{}", json);
|
||||
} else {
|
||||
if (isActive) {
|
||||
future.completeExceptionally(new RuntimeException("Failed to send WebSocket message"));
|
||||
} else {
|
||||
future.completeExceptionally(new RuntimeException("WebSocket is not active"));
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to send WebSocket message: {}", e.getMessage());
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
return future;
|
||||
}
|
||||
|
||||
public CompletableFuture<JsonNode> invoke(String service, String method, Object... params) {
|
||||
SimpleMessage msg = new SimpleMessage();
|
||||
msg.setService(service);
|
||||
msg.setMethod(method);
|
||||
msg.setArguments(params);
|
||||
return send(msg).orTimeout(getReadTimeout(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public void initWebSocket() {
|
||||
isActive = true;
|
||||
OkHttpClient httpClient = Desktop.instance.getHttpClient();
|
||||
|
||||
try {
|
||||
// 构建WebSocket请求,包含认证信息
|
||||
var myProperties = SpringApp.getBean(MyProperties.class);
|
||||
String webSocketUrl = "ws://" + myProperties.getServerHost() + ":" + myProperties.getServerPort() + "/ws";
|
||||
Request request = new Request.Builder()
|
||||
.url(webSocketUrl)
|
||||
.build();
|
||||
webSocket = httpClient.newWebSocket(request, listener);
|
||||
} catch (Exception e) {
|
||||
logger.error("建立WebSocket连接失败: " + e.getMessage());
|
||||
Platform.runLater(() -> {
|
||||
online.setValue(false);
|
||||
message.set("连接失败: " + e.getMessage());
|
||||
});
|
||||
// 处理重连逻辑
|
||||
scheduleReconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动心跳任务,定期发送ping消息保持连接
|
||||
*/
|
||||
private void startHeartbeat() {
|
||||
// 先停止可能存在的心跳任务
|
||||
stopHeartbeat();
|
||||
ScheduledExecutorService executorService = Desktop.instance.getExecutorService();
|
||||
heartbeatTask = executorService.scheduleAtFixedRate(this::heartbeat, RECONNECT_DELAY_MS, HEARTBEAT_INTERVAL_MS,
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
void heartbeat() {
|
||||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (webSocket != null) {
|
||||
logger.debug("发送心跳 ping");
|
||||
webSocket.send("ping");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("发送心跳失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止心跳任务
|
||||
*/
|
||||
private void stopHeartbeat() {
|
||||
if (heartbeatTask != null && !heartbeatTask.isCancelled()) {
|
||||
heartbeatTask.cancel(true);
|
||||
heartbeatTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安排重连任务
|
||||
*/
|
||||
private void scheduleReconnect() {
|
||||
if (!isActive) {
|
||||
return; // 如果连接已被主动关闭,则不再重连
|
||||
}
|
||||
|
||||
// 取消之前可能存在的重连任务
|
||||
if (reconnectFuture != null && !reconnectFuture.isDone()) {
|
||||
reconnectFuture.cancel(true);
|
||||
}
|
||||
// 创建新的重连任务s
|
||||
logger.info("计划在 {} 毫秒后尝试重连WebSocket", RECONNECT_DELAY_MS);
|
||||
|
||||
reconnectFuture = Desktop.instance.getExecutorService().schedule(() -> {
|
||||
if (isActive) {
|
||||
logger.info("尝试重新连接WebSocket");
|
||||
Platform.runLater(() -> {
|
||||
online.setValue(false);
|
||||
message.set("正在重新连接WebSocket...");
|
||||
});
|
||||
initWebSocket();
|
||||
}
|
||||
}, RECONNECT_DELAY_MS, TimeUnit.MILLISECONDS);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭WebSocket连接
|
||||
*/
|
||||
public void closeWebSocket() {
|
||||
isActive = false;
|
||||
stopHeartbeat();
|
||||
|
||||
if (reconnectFuture != null && !reconnectFuture.isDone()) {
|
||||
reconnectFuture.cancel(false);
|
||||
reconnectFuture = null;
|
||||
}
|
||||
if (webSocket != null) {
|
||||
webSocket.close(1000, "主动关闭连接");
|
||||
webSocket = null;
|
||||
}
|
||||
}
|
||||
|
||||
public StringProperty getMessageProperty() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public BooleanProperty getOnlineProperty() {
|
||||
return online;
|
||||
}
|
||||
|
||||
|
||||
public void closeSession(WebSocketClientSession session) {
|
||||
if (session != null) {
|
||||
sessions.remove(session.getSessionId());
|
||||
// session.close();
|
||||
}
|
||||
}
|
||||
|
||||
public WebSocketClientSession createSession() {
|
||||
WebSocketClientSession session = new WebSocketClientSession(this);
|
||||
sessions.put(session.getSessionId(), session);
|
||||
return session;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.ecep.contract;
|
||||
|
||||
import com.ecep.contract.constant.WebSocketConstant;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.Getter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class WebSocketClientSession {
|
||||
private static final Logger logger = LoggerFactory.getLogger(WebSocketClientSession.class);
|
||||
/**
|
||||
* 会话ID由客户端创建,服务器不保存会话只回传会话ID
|
||||
*/
|
||||
@Getter
|
||||
private final String sessionId = UUID.randomUUID().toString();
|
||||
@Getter
|
||||
private boolean done = false;
|
||||
|
||||
private WebSocketClientTasker tasker;
|
||||
|
||||
private final WebSocketClientService webSocketService;
|
||||
|
||||
public WebSocketClientSession(WebSocketClientService webSocketService) {
|
||||
this.webSocketService = webSocketService;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
webSocketService.closeSession(this);
|
||||
}
|
||||
|
||||
public void submitTask(WebSocketClientTasker tasker, Locale locale, Object... args) throws JsonProcessingException {
|
||||
this.tasker = tasker;
|
||||
send("createTask", tasker.getTaskName(), locale.toLanguageTag(), args);
|
||||
}
|
||||
|
||||
public void send(String type, Object... args) throws JsonProcessingException {
|
||||
Map<String, Object> arguments = Map.of(
|
||||
WebSocketConstant.SESSION_ID_FIELD_NAME, getSessionId(),
|
||||
"type", type,
|
||||
WebSocketConstant.ARGUMENTS_FIELD_NAME, args);
|
||||
webSocketService.send(arguments);
|
||||
}
|
||||
|
||||
public void onMessage(JsonNode node) {
|
||||
if (node.has("type")) {
|
||||
String type = node.get("type").asText();
|
||||
JsonNode args = node.get(WebSocketConstant.ARGUMENTS_FIELD_NAME);
|
||||
if (type.equals("message")) {
|
||||
handleAsMessage(args);
|
||||
} else if (type.equals("title")) {
|
||||
handleAsTitle(args);
|
||||
} else if (type.equals("property")) {
|
||||
handleAsProperty(args);
|
||||
} else if (type.equals("progress")) {
|
||||
handleAsProgress(args);
|
||||
} else if (type.equals("start")) {
|
||||
handleAsStart(args);
|
||||
} else if (type.equals("done")) {
|
||||
handleAsDone(args);
|
||||
done = true;
|
||||
close();
|
||||
} else {
|
||||
tasker.updateMessage(java.util.logging.Level.INFO, "未知的消息类型: " + node.toString());
|
||||
}
|
||||
} else {
|
||||
tasker.updateMessage(java.util.logging.Level.INFO, "未知的消息: " + node.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleAsStart(JsonNode args) {
|
||||
tasker.updateMessage(java.util.logging.Level.INFO, "任务开始");
|
||||
}
|
||||
|
||||
private void handleAsDone(JsonNode args) {
|
||||
tasker.updateMessage(java.util.logging.Level.INFO, "任务完成");
|
||||
}
|
||||
|
||||
private void handleAsProgress(JsonNode args) {
|
||||
long current = args.get(0).asLong();
|
||||
long total = args.get(1).asLong();
|
||||
tasker.updateProgress(current, total);
|
||||
}
|
||||
|
||||
private void handleAsProperty(JsonNode args) {
|
||||
String name = args.get(0).asText();
|
||||
Object value = args.get(1);
|
||||
try {
|
||||
PropertyDescriptor descriptor = BeanUtils.getPropertyDescriptor(tasker.getClass(), name);
|
||||
if (descriptor == null) {
|
||||
tasker.updateMessage(java.util.logging.Level.SEVERE, "属性 " + name + " 不存在");
|
||||
return;
|
||||
}
|
||||
Object object = webSocketService.getObjectMapper().convertValue(value, descriptor.getPropertyType());
|
||||
if (descriptor.getWriteMethod() == null) {
|
||||
tasker.updateMessage(java.util.logging.Level.SEVERE, "属性 " + name + " 不可写");
|
||||
} else {
|
||||
descriptor.getWriteMethod().invoke(tasker, object);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
tasker.updateMessage(java.util.logging.Level.SEVERE, "属性设置失败: " + name + " = " + value);
|
||||
logger.error("set {} = {}", name, value, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleAsTitle(JsonNode args) {
|
||||
String message = args.get(0).asText();
|
||||
tasker.updateTitle(message);
|
||||
}
|
||||
|
||||
private void handleAsMessage(JsonNode args) {
|
||||
String level = args.get(0).asText();
|
||||
String message = args.get(1).asText();
|
||||
updateMessage(java.util.logging.Level.parse(level), "[R] " + message);
|
||||
}
|
||||
|
||||
public void updateMessage(Level level, String message) {
|
||||
tasker.updateMessage(level, message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package com.ecep.contract;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* WebSocket客户端任务接口
|
||||
* 定义了所有通过WebSocket与服务器通信的任务的通用方法
|
||||
* 包括任务名称、更新消息、更新标题、更新进度等操作
|
||||
* <p>
|
||||
* 所有通过WebSocket与服务器通信的任务类都应实现此接口, 文档参考 .trace/rules/client_task_rules.md
|
||||
*/
|
||||
public interface WebSocketClientTasker {
|
||||
/**
|
||||
* s
|
||||
* 获取任务名称
|
||||
* 任务名称用于唯一标识任务, 服务器端会根据任务名称来调用对应的任务处理函数
|
||||
*
|
||||
* @return 任务名称
|
||||
*/
|
||||
String getTaskName();
|
||||
|
||||
/**
|
||||
* 更新任务执行过程中的消息
|
||||
* 客户端可以通过此方法向用户展示任务执行过程中的重要信息或错误提示
|
||||
*
|
||||
* @param level 消息级别, 用于区分不同类型的消息, 如INFO, WARNING, SEVERE等
|
||||
* @param message 消息内容, 可以是任意字符串, 用于展示给用户
|
||||
*/
|
||||
void updateMessage(Level level, String message);
|
||||
|
||||
/**
|
||||
* 更新任务标题
|
||||
* 客户端可以通过此方法向用户展示任务的当前执行状态或重要信息
|
||||
*
|
||||
* @param title 任务标题
|
||||
*/
|
||||
void updateTitle(String title);
|
||||
|
||||
/**
|
||||
* 更新任务进度
|
||||
* 客户端可以通过此方法向用户展示任务的执行进度, 如文件上传进度、数据库操作进度等
|
||||
*
|
||||
* @param current 当前进度
|
||||
* @param total 总进度
|
||||
*/
|
||||
void updateProgress(long current, long total);
|
||||
|
||||
/**
|
||||
* 取消任务执行
|
||||
* 默认实现为空,可由具体任务重写以提供取消逻辑
|
||||
*/
|
||||
default void cancelTask() {
|
||||
// 默认实现为空,由具体任务重写
|
||||
// 需要获取到 session
|
||||
// 发送 cancel 指令
|
||||
// 关闭 session.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用远程WebSocket任务
|
||||
* 客户端可以通过此方法向服务器提交任务, 并等待服务器返回任务执行结果
|
||||
*
|
||||
* @param holder 消息持有者,用于记录任务执行过程中的消息
|
||||
* @param locale 语言环境
|
||||
* @param args 任务参数
|
||||
* @return 任务执行结果
|
||||
*/
|
||||
default Object callRemoteTask(MessageHolder holder, Locale locale, Object... args) {
|
||||
WebSocketClientService webSocketService = SpringApp.getBean(WebSocketClientService.class);
|
||||
|
||||
// 检查WebSocket连接是否可用
|
||||
if (!webSocketService.getOnlineProperty().get()) {
|
||||
String errorMsg = "WebSocket连接不可用,请检查网络连接或服务器状态";
|
||||
holder.addMessage(Level.SEVERE, errorMsg);
|
||||
return null;
|
||||
}
|
||||
|
||||
WebSocketClientSession session = webSocketService.createSession();
|
||||
try {
|
||||
session.submitTask(this, locale, args);
|
||||
holder.info("已提交任务到服务器: " + getTaskName());
|
||||
} catch (JsonProcessingException e) {
|
||||
String errorMsg = "任务提交失败: " + e.getMessage();
|
||||
holder.warn(errorMsg);
|
||||
throw new RuntimeException("任务提交失败: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
// while (!session.isDone()) {
|
||||
// // 使用TimeUnit
|
||||
// try {
|
||||
// TimeUnit.SECONDS.sleep(1);
|
||||
// } catch (InterruptedException e) {
|
||||
// Thread.currentThread().interrupt();
|
||||
// }
|
||||
// }
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步调用远程WebSocket任务
|
||||
*
|
||||
* @param holder 消息持有者,用于记录任务执行过程中的消息
|
||||
* @param locale 语言环境
|
||||
* @param args 任务参数
|
||||
* @return 包含任务执行结果的CompletableFuture
|
||||
*/
|
||||
default CompletableFuture<Object> callRemoteTaskAsync(MessageHolder holder, Locale locale, Object... args) {
|
||||
CompletableFuture<Object> future = new CompletableFuture<>();
|
||||
|
||||
try {
|
||||
// 立即执行callRemoteTask并返回结果
|
||||
Object result = callRemoteTask(holder, locale, args);
|
||||
future.complete(result);
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成唯一的任务ID
|
||||
*
|
||||
* @return 唯一的任务ID
|
||||
*/
|
||||
default String generateTaskId() {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.ecep.contract.manager.ui;
|
||||
package com.ecep.contract.controller;
|
||||
|
||||
import com.ecep.contract.manager.ds.other.model.IdentityEntity;
|
||||
import com.ecep.contract.manager.ds.other.vo.IdentityViewModel;
|
||||
import org.apache.poi.ss.formula.functions.T;
|
||||
|
||||
import com.ecep.contract.model.IdentityEntity;
|
||||
import com.ecep.contract.service.ViewModelService;
|
||||
import com.ecep.contract.vm.IdentityViewModel;
|
||||
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.stage.WindowEvent;
|
||||
@@ -1,11 +1,21 @@
|
||||
package com.ecep.contract.manager.ui;
|
||||
package com.ecep.contract.controller;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
|
||||
import com.ecep.contract.controller.tab.RefreshableSkin;
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.model.IdentityEntity;
|
||||
import com.ecep.contract.service.QueryService;
|
||||
import com.ecep.contract.service.ViewModelService;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.BaseViewModel;
|
||||
import com.ecep.contract.vm.IdentityViewModel;
|
||||
|
||||
import com.ecep.contract.manager.ds.other.model.IdentityEntity;
|
||||
import com.ecep.contract.manager.ds.other.vo.BaseViewModel;
|
||||
import com.ecep.contract.manager.ds.other.vo.IdentityViewModel;
|
||||
import com.ecep.contract.manager.ui.tab.AbstEntityBasedTabSkin;
|
||||
import com.ecep.contract.manager.ui.tab.TabSkin;
|
||||
import com.ecep.contract.manager.util.UITools;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
@@ -18,12 +28,6 @@ import javafx.stage.WindowEvent;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
|
||||
public abstract class AbstEntityController<T extends IdentityEntity, TV extends IdentityViewModel<T>>
|
||||
extends BaseController {
|
||||
|
||||
@@ -41,38 +45,55 @@ public abstract class AbstEntityController<T extends IdentityEntity, TV extends
|
||||
protected CompletableFuture<T> loadedFuture;
|
||||
private final ObservableList<TabSkin> tabSkins = FXCollections.observableArrayList();
|
||||
|
||||
|
||||
@Override
|
||||
public void onShown(WindowEvent windowEvent) {
|
||||
Class<?> aClass = getClass();
|
||||
super.onShown(windowEvent);
|
||||
loadedFuture = CompletableFuture.supplyAsync(() -> {
|
||||
T entity = loadEntity();
|
||||
if (entity == null) {
|
||||
// fixed, bind change if new view model create
|
||||
if (viewModel != null) {
|
||||
viewModel.bindListener();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
setStatus();
|
||||
viewModel.update(entity);
|
||||
});
|
||||
viewModel.bindListener();
|
||||
return entity;
|
||||
});
|
||||
|
||||
// 载数据
|
||||
initializeData();
|
||||
// 注册 skin
|
||||
registerTabSkins();
|
||||
// 初始化保存按钮
|
||||
initializeSaveBtn();
|
||||
// 安装 skin
|
||||
installTabSkins();
|
||||
}
|
||||
|
||||
protected void initializeSaveBtn() {
|
||||
if (saveBtn != null) {
|
||||
saveBtn.disableProperty().bind(createTabSkinChangedBindings().not());
|
||||
saveBtn.setOnAction(event -> saveTabSkins());
|
||||
}
|
||||
|
||||
installTabSkins();
|
||||
}
|
||||
|
||||
protected T loadEntity() {
|
||||
return getViewModelService().findById(viewModel.getId().get());
|
||||
protected void initializeData() {
|
||||
ViewModelService<T, TV> service = getViewModelService();
|
||||
|
||||
if (service instanceof QueryService<T, TV> queryService) {
|
||||
setStatus("读取...");
|
||||
loadedFuture = queryService.asyncFindById(viewModel.getId().get());
|
||||
} else {
|
||||
loadedFuture = CompletableFuture.supplyAsync(() -> {
|
||||
return getViewModelService().findById(viewModel.getId().get());
|
||||
});
|
||||
}
|
||||
|
||||
loadedFuture.thenAccept(entity -> {
|
||||
// fixed, bind change if new view model create
|
||||
if (viewModel != null) {
|
||||
updateViewModel(entity);
|
||||
viewModel.bindListener();
|
||||
}
|
||||
setStatus();
|
||||
});
|
||||
loadedFuture.exceptionally(ex -> {
|
||||
handleException("载入失败,#" + viewModel.getId().get(), ex);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
protected void updateViewModel(T entity) {
|
||||
BaseViewModel.updateInFxApplicationThread(entity, viewModel);
|
||||
}
|
||||
|
||||
public T getEntity() {
|
||||
@@ -94,6 +115,19 @@ public abstract class AbstEntityController<T extends IdentityEntity, TV extends
|
||||
return saved;
|
||||
}
|
||||
|
||||
public void save() {
|
||||
T entity = getEntity();
|
||||
if (entity == null) {
|
||||
return;
|
||||
}
|
||||
if (viewModel.copyTo(entity)) {
|
||||
save(entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册 skin
|
||||
*/
|
||||
protected void registerTabSkins() {
|
||||
}
|
||||
|
||||
@@ -105,6 +139,9 @@ public abstract class AbstEntityController<T extends IdentityEntity, TV extends
|
||||
return f;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装 skin
|
||||
*/
|
||||
protected void installTabSkins() {
|
||||
for (TabSkin tabSkin : tabSkins) {
|
||||
try {
|
||||
@@ -190,35 +227,42 @@ public abstract class AbstEntityController<T extends IdentityEntity, TV extends
|
||||
|
||||
public CompletableFuture<Void> refresh() {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
T entity = loadEntity();
|
||||
|
||||
Platform.runLater(() -> {
|
||||
ViewModelService<T, TV> service = getViewModelService();
|
||||
if (service instanceof QueryService<T, TV> queryService) {
|
||||
loadedFuture = queryService.asyncFindById(viewModel.getId().get());
|
||||
loadedFuture.whenComplete((entity, ex) -> {
|
||||
if (ex != null) {
|
||||
future.completeExceptionally(ex);
|
||||
return;
|
||||
}
|
||||
BaseViewModel.updateInFxApplicationThread(entity, viewModel);
|
||||
});
|
||||
} else {
|
||||
T entity = service.findById(viewModel.getId().get());
|
||||
setEntity(entity);
|
||||
}
|
||||
|
||||
List<RefreshableSkin> list = tabSkins.stream()
|
||||
.filter(v -> v instanceof RefreshableSkin)
|
||||
.map(v -> ((RefreshableSkin) v)).toList();
|
||||
List<RefreshableSkin> list = tabSkins.stream()
|
||||
.filter(v -> v instanceof RefreshableSkin)
|
||||
.map(v -> ((RefreshableSkin) v)).toList();
|
||||
|
||||
if (list.isEmpty()) {
|
||||
future.complete(null);
|
||||
return;
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
future.complete(null);
|
||||
return future;
|
||||
}
|
||||
|
||||
CompletableFuture.allOf(list
|
||||
.stream()
|
||||
.map(RefreshableSkin::refresh)
|
||||
.filter(Objects::nonNull)
|
||||
.toArray(CompletableFuture<?>[]::new))
|
||||
.whenComplete((v, ex) -> {
|
||||
if (ex != null) {
|
||||
future.completeExceptionally(ex);
|
||||
} else {
|
||||
future.complete(null);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
return future;
|
||||
return CompletableFuture.allOf(list
|
||||
.stream()
|
||||
.map(RefreshableSkin::refresh)
|
||||
.filter(Objects::nonNull)
|
||||
.toArray(CompletableFuture<?>[]::new))
|
||||
.whenComplete((v, ex) -> {
|
||||
if (ex != null) {
|
||||
future.completeExceptionally(ex);
|
||||
} else {
|
||||
future.complete(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public abstract ViewModelService<T, TV> getViewModelService();
|
||||
@@ -1,27 +1,31 @@
|
||||
package com.ecep.contract.manager.ui;
|
||||
package com.ecep.contract.controller;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.ecep.contract.manager.ui.table.EditableEntityTableTabSkin;
|
||||
import com.ecep.contract.manager.ui.table.TableTabSkin;
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
import com.ecep.contract.manager.ds.other.model.IdentityEntity;
|
||||
import com.ecep.contract.manager.ds.other.vo.IdentityViewModel;
|
||||
import com.ecep.contract.manager.util.TableViewUtils;
|
||||
import com.ecep.contract.manager.util.UITools;
|
||||
import com.ecep.contract.controller.table.EditableEntityTableTabSkin;
|
||||
import com.ecep.contract.controller.table.TableTabSkin;
|
||||
import com.ecep.contract.model.IdentityEntity;
|
||||
import com.ecep.contract.service.QueryService;
|
||||
import com.ecep.contract.service.ViewModelService;
|
||||
import com.ecep.contract.util.TableViewUtils;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.IdentityViewModel;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.Property;
|
||||
@@ -43,12 +47,15 @@ import javafx.scene.input.KeyEvent;
|
||||
import javafx.util.converter.NumberStringConverter;
|
||||
|
||||
/**
|
||||
* 实体管理器皮肤
|
||||
* 提供了实体管理器的基本功能,如查询、新增、删除、修改、分页等
|
||||
*
|
||||
* @param <T> Entity 的类型
|
||||
* @param <TV> Entity 对应的ViewModel
|
||||
* @param <Skin> Skin 的类型
|
||||
* @param <SKIN> Skin 的类型
|
||||
* @param <C>
|
||||
*/
|
||||
public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends IdentityViewModel<T>, Skin extends ManagerSkin, C extends AbstManagerWindowController<T, TV, Skin>>
|
||||
public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends IdentityViewModel<T>, SKIN extends ManagerSkin, C extends AbstManagerWindowController<T, TV, SKIN>>
|
||||
implements ManagerSkin, TableTabSkin<T, TV>, EditableEntityTableTabSkin<T, TV> {
|
||||
private static final Logger logger = LoggerFactory.getLogger(AbstEntityManagerSkin.class);
|
||||
/**
|
||||
@@ -61,11 +68,20 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
|
||||
|
||||
protected PageRequest currentPageable = PageRequest.ofSize(25);
|
||||
protected final SimpleIntegerProperty currentPageNumber = new SimpleIntegerProperty();
|
||||
// 是否允许调整表格高度
|
||||
private boolean allowResize = true;
|
||||
// 记录延时任务信息
|
||||
private ScheduledFuture<?> loadTableDataSetFuture;
|
||||
|
||||
public AbstEntityManagerSkin(C controller) {
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <TT> TT getBean(Class<TT> requiredType) throws BeansException {
|
||||
return controller.getCachedBean(requiredType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableView<TV> getTableView() {
|
||||
return controller.table;
|
||||
@@ -199,6 +215,9 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
|
||||
* 根据表格高度重新计算分页的页大小
|
||||
*/
|
||||
private void resizeTable(Object observable, Bounds old, Bounds newBounds) {
|
||||
if (!allowResize) {
|
||||
return;
|
||||
}
|
||||
double tableHeight = newBounds.getHeight();
|
||||
if (tableHeight <= 0) {
|
||||
return;
|
||||
@@ -208,8 +227,13 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
|
||||
if (lookup != null) {
|
||||
double rowHeight = lookup.prefHeight(-1);
|
||||
int rows = (int) Math.round(table.getHeight() / rowHeight) - 1;
|
||||
int pageNumber = (int) Math.abs(currentPageable.getOffset() / rows);
|
||||
// 只有当行数变化超过一定阈值时才重新加载数据
|
||||
int currentRows = currentPageable.getPageSize();
|
||||
if (Math.abs(rows - currentRows) <= 2) {
|
||||
return; // 避免微小变化导致频繁刷新
|
||||
}
|
||||
|
||||
int pageNumber = (int) Math.abs(currentPageable.getOffset() / rows);
|
||||
if (currentPageable.getPageNumber() == pageNumber && currentPageable.getPageSize() == rows) {
|
||||
return;
|
||||
}
|
||||
@@ -259,9 +283,17 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
|
||||
}
|
||||
}
|
||||
|
||||
protected <K> void acceptCellEditEvent(TableColumn.CellEditEvent<TV, K> event, Function<TV, Property<K>> function) {
|
||||
/**
|
||||
* 处理单元格编辑事件
|
||||
*
|
||||
* @param <K>
|
||||
* @param event
|
||||
* @param propGetter
|
||||
*/
|
||||
protected <K> void acceptCellEditEvent(TableColumn.CellEditEvent<TV, K> event,
|
||||
Function<TV, Property<K>> propGetter) {
|
||||
TV row = event.getRowValue();
|
||||
Property<K> property = function.apply(row);
|
||||
Property<K> property = propGetter.apply(row);
|
||||
property.setValue(event.getNewValue());
|
||||
try {
|
||||
saveRowData(row);
|
||||
@@ -317,21 +349,47 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
|
||||
row.saveInFxApplicationThread(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载行数据
|
||||
*
|
||||
* @param row
|
||||
* @return
|
||||
*/
|
||||
public T loadRowData(TV row) {
|
||||
return getViewModelService().findById(row.getId().get());
|
||||
if (row.getId() == null) {
|
||||
return null;
|
||||
}
|
||||
T entity = getViewModelService().findById(row.getId().get());
|
||||
return entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除行数据
|
||||
*
|
||||
* @param entity
|
||||
*/
|
||||
public void deleteRowData(T entity) {
|
||||
if (entity == null) {
|
||||
return;
|
||||
}
|
||||
ViewModelService<T, TV> service = getViewModelService();
|
||||
getViewModelService().delete(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存行数据
|
||||
*
|
||||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
public T saveRowData(T entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
ViewModelService<T, TV> service = getViewModelService();
|
||||
return getViewModelService().save(entity);
|
||||
}
|
||||
|
||||
// 记录延时任务信息
|
||||
private ScheduledFuture<?> loadTableDataSetFuture;
|
||||
|
||||
@Override
|
||||
public void loadTableDataSet() {
|
||||
loadTableDataSet(false);
|
||||
@@ -359,18 +417,18 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
|
||||
private CompletableFuture<Void> _reloadTableData() {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
Platform.runLater(() -> {
|
||||
allowResize = false; // 禁用调整
|
||||
dataSet.clear();
|
||||
runAsync(() -> {
|
||||
controller.setStatus("载入中...");
|
||||
// 异步加载数据
|
||||
if (getViewModelService() instanceof QueryService<T, TV> queryService) {
|
||||
asyncLoadTableData(queryService, future);
|
||||
return;
|
||||
}
|
||||
// 同步加载方法
|
||||
List<TV> models = loadTableData();
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
updateTableDataSet(models);
|
||||
future.complete(null);
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
_updateModels(models, future);
|
||||
}).exceptionally(ex -> {
|
||||
future.completeExceptionally(ex);
|
||||
return null;
|
||||
@@ -379,20 +437,60 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
|
||||
return future;
|
||||
}
|
||||
|
||||
|
||||
private void _updateModels(List<TV> models, CompletableFuture<Void> future) {
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
updateTableDataSet(models);
|
||||
allowResize = true; // 恢复调整
|
||||
future.complete(null);
|
||||
} catch (Exception e) {
|
||||
allowResize = true; // 恢复调整
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新表格数据
|
||||
*
|
||||
* @param models
|
||||
*/
|
||||
protected void updateTableDataSet(List<TV> models) {
|
||||
long timeMillis = System.currentTimeMillis();
|
||||
dataSet.setAll(models);
|
||||
// 清除所有选择状态,避免选择状态混乱
|
||||
if (getTableView() != null && getTableView().getSelectionModel() != null) {
|
||||
getTableView().getSelectionModel().clearSelection();
|
||||
}
|
||||
|
||||
// 先清空再设置新数据,避免数据叠加
|
||||
dataSet.clear();
|
||||
if (models != null) {
|
||||
dataSet.addAll(models);
|
||||
}
|
||||
|
||||
// 强制刷新表格布局
|
||||
if (getTableView() != null) {
|
||||
getTableView().requestLayout();
|
||||
getTableView().refresh();
|
||||
}
|
||||
|
||||
long timeCost = System.currentTimeMillis() - timeMillis;
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("update table dataSet cost: {} ms", timeCost);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载表格数据
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected List<TV> loadTableData() {
|
||||
Specification<T> spec = getSpecification();
|
||||
ParamUtils.Builder params = getSpecification();
|
||||
ViewModelService<T, TV> service = getViewModelService();
|
||||
long timeMillis = System.currentTimeMillis();
|
||||
Page<T> page = service.findAll(spec, getPageable());
|
||||
Page<T> page = service.findAll(params == null ? null : params.build(), getPageable());
|
||||
long timeCost = System.currentTimeMillis() - timeMillis;
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("load table data cost: {} ms", timeCost);
|
||||
@@ -404,17 +502,45 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
|
||||
return page.map(service::from).toList();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 异步加载表格数据
|
||||
*
|
||||
* @param queryService
|
||||
* @param future
|
||||
*/
|
||||
private void asyncLoadTableData(QueryService<T, TV> queryService, CompletableFuture<Void> future) {
|
||||
ParamUtils.Builder params = getSpecification();
|
||||
queryService.asyncFindAll(params == null ? null : params.build(), getPageable()).whenComplete((result, ex) -> {
|
||||
if (ex != null) {
|
||||
future.completeExceptionally(ex);
|
||||
return;
|
||||
}
|
||||
updateFooter(result);
|
||||
List<TV> models = result.map(getViewModelService()::from).toList();
|
||||
_updateModels(models, future);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取ViewModelService
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
protected ViewModelService<T, TV> getViewModelService() {
|
||||
ViewModelService<T, TV> service = controller.getViewModelService();
|
||||
if (service == null) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("ViewModelService is null");
|
||||
}
|
||||
throw new IllegalArgumentException("ViewModelService is null");
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
public Specification<T> getSpecification() {
|
||||
/**
|
||||
* 获取查询条件
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ParamUtils.Builder getSpecification() {
|
||||
TextField field = controller.searchKeyField;
|
||||
if (field != null) {
|
||||
return getViewModelService().getSpecification(field.getText());
|
||||
@@ -422,8 +548,14 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
|
||||
return null;
|
||||
}
|
||||
|
||||
protected Specification<T> getSpecification(String searchText) {
|
||||
return controller.getViewModelService().getSpecification(searchText);
|
||||
/**
|
||||
* 获取查询条件
|
||||
*
|
||||
* @param searchText
|
||||
* @return
|
||||
*/
|
||||
protected ParamUtils.Builder getSpecification(String searchText) {
|
||||
return getViewModelService().getSpecification(searchText);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -434,6 +566,11 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
|
||||
protected void onTableRowDoubleClickedAction(TV item) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新页脚
|
||||
*
|
||||
* @param page
|
||||
*/
|
||||
protected void updateFooter(Page<T> page) {
|
||||
Platform.runLater(() -> {
|
||||
controller.previousPageBtn.setDisable(!page.hasPrevious());
|
||||
@@ -444,19 +581,44 @@ public abstract class AbstEntityManagerSkin<T extends IdentityEntity, TV extends
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表格排序
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<Sort.Order> getTableOrders() {
|
||||
return TableViewUtils.getOrders(getTableView());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表格排序
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Sort getSortByTable() {
|
||||
if (getTableView() == null) {
|
||||
return Sort.unsorted();
|
||||
}
|
||||
return Sort.by(getTableOrders());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分页参数
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Pageable getPageable() {
|
||||
Sort sort = getSortByTable();
|
||||
return currentPageable.withSort(sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示在当前窗口为父窗口的新窗口
|
||||
*
|
||||
* @param <Controller> 控制器类型
|
||||
* @param clz 控制器类
|
||||
* @param model 数据
|
||||
*/
|
||||
protected <Controller extends AbstEntityController<T, TV>> void showInOwner(Class<Controller> clz, TV model) {
|
||||
BaseController.show(clz, model, getTableView().getScene().getWindow());
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.ecep.contract.manager.ui;
|
||||
package com.ecep.contract.controller;
|
||||
|
||||
import com.ecep.contract.manager.ds.other.model.IdentityEntity;
|
||||
import com.ecep.contract.manager.ds.other.vo.IdentityViewModel;
|
||||
import com.ecep.contract.model.IdentityEntity;
|
||||
import com.ecep.contract.vm.IdentityViewModel;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -41,6 +41,4 @@ public abstract class AbstManagerWindowController<T extends IdentityEntity, TV e
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,14 +1,35 @@
|
||||
package com.ecep.contract.manager.ui;
|
||||
package com.ecep.contract.controller;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.ecep.contract.util.BeanContext;
|
||||
import com.ecep.contract.util.DefaultBeanContext;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.util.FxmlUtils;
|
||||
import com.ecep.contract.vo.EmployeeVo;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
|
||||
import com.ecep.contract.Desktop;
|
||||
import com.ecep.contract.SpringApp;
|
||||
import com.ecep.contract.model.Employee;
|
||||
import com.ecep.contract.model.IdentityEntity;
|
||||
import com.ecep.contract.service.EmployeeService;
|
||||
import com.ecep.contract.service.SysConfService;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.CurrentEmployee;
|
||||
import com.ecep.contract.vm.IdentityViewModel;
|
||||
|
||||
import com.ecep.contract.manager.Desktop;
|
||||
import com.ecep.contract.manager.SpringApp;
|
||||
import com.ecep.contract.manager.ds.other.model.Employee;
|
||||
import com.ecep.contract.manager.ds.other.model.IdentityEntity;
|
||||
import com.ecep.contract.manager.ds.other.service.EmployeeService;
|
||||
import com.ecep.contract.manager.ds.other.service.SysConfService;
|
||||
import com.ecep.contract.manager.ds.other.vo.IdentityViewModel;
|
||||
import com.ecep.contract.manager.util.FxmlUtils;
|
||||
import com.ecep.contract.manager.util.UITools;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
@@ -21,23 +42,8 @@ import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import javafx.stage.WindowEvent;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class BaseController {
|
||||
public class BaseController implements BeanContext {
|
||||
private static final Logger logger = LoggerFactory.getLogger(BaseController.class);
|
||||
public static HashMap<String, Stage> stages = new HashMap<>();
|
||||
|
||||
@@ -45,7 +51,8 @@ public class BaseController {
|
||||
return show(clz, owner, null);
|
||||
}
|
||||
|
||||
public static <T extends BaseController> CompletableFuture<Void> show(Class<T> clz, Window owner, Consumer<T> consumer) {
|
||||
public static <T extends BaseController> CompletableFuture<Void> show(Class<T> clz, Window owner,
|
||||
Consumer<T> consumer) {
|
||||
String key = clz.getName();
|
||||
if (toFront(key)) {
|
||||
return null;
|
||||
@@ -63,10 +70,10 @@ public class BaseController {
|
||||
controller.show(loader, owner, Modality.NONE, key);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <K extends IdentityEntity, M extends IdentityViewModel<K>, T extends BaseController>
|
||||
void show(Class<T> clz, M viewModel, Window owner) {
|
||||
public static <K extends IdentityEntity, M extends IdentityViewModel<K>, T extends BaseController> void show(
|
||||
Class<T> clz, M viewModel, Window owner) {
|
||||
String key = getKey(clz, viewModel);
|
||||
if (toFront(key)) {
|
||||
return;
|
||||
@@ -76,7 +83,6 @@ public class BaseController {
|
||||
throw new RuntimeException("@FxmlPath is required");
|
||||
}
|
||||
|
||||
|
||||
FxmlUtils.newLoaderAsyncWithRunLater(annotation.value(), null, loader -> {
|
||||
T controller = loader.getController();
|
||||
if (controller instanceof AbstEntityController<?, ?>) {
|
||||
@@ -114,7 +120,6 @@ public class BaseController {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static boolean toFront(String key) {
|
||||
Stage stage = stages.get(key);
|
||||
if (stage != null) {
|
||||
@@ -161,6 +166,11 @@ public class BaseController {
|
||||
return future;
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> runAsync(Runnable runnable) {
|
||||
return CompletableFuture.runAsync(runnable, Desktop.instance.getExecutorService())
|
||||
.exceptionally(this::handleException);
|
||||
}
|
||||
|
||||
/**
|
||||
* 窗口标题
|
||||
*/
|
||||
@@ -177,42 +187,44 @@ public class BaseController {
|
||||
private String stageKey;
|
||||
public Label leftStatusLabel;
|
||||
public Label rightStatusLabel;
|
||||
private EmployeeVo currentUser;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private Locale locale = Locale.getDefault();
|
||||
private BeanContext beanContext = new DefaultBeanContext();
|
||||
public <T> T getBean(Class<T> requiredType) throws BeansException {
|
||||
return beanContext.getBean(requiredType);
|
||||
}
|
||||
|
||||
@Setter
|
||||
private SysConfService confService;
|
||||
@Setter
|
||||
private EmployeeService employeeService;
|
||||
private Employee currentUser;
|
||||
|
||||
protected <T> T getBean(Class<T> requiredType) throws BeansException {
|
||||
return SpringApp.getBean(requiredType);
|
||||
public <T> T getCachedBean(Class<T> requiredType) throws BeansException {
|
||||
return beanContext.getCachedBean(requiredType);
|
||||
}
|
||||
|
||||
public SysConfService getConfService() {
|
||||
if (confService == null) {
|
||||
confService = getBean(SysConfService.class);
|
||||
}
|
||||
return confService;
|
||||
return getCachedBean(SysConfService.class);
|
||||
}
|
||||
|
||||
public EmployeeService getEmployeeService() {
|
||||
if (employeeService == null) {
|
||||
employeeService = getBean(EmployeeService.class);
|
||||
}
|
||||
return employeeService;
|
||||
return getCachedBean(EmployeeService.class);
|
||||
}
|
||||
|
||||
public Employee getCurrentUser() {
|
||||
public EmployeeVo getCurrentUser() {
|
||||
if (currentUser == null) {
|
||||
currentUser = getEmployeeService().findById(Desktop.instance.getActiveEmployeeId());
|
||||
}
|
||||
return currentUser;
|
||||
}
|
||||
|
||||
public CurrentEmployee getCurrentEmployee() {
|
||||
return Desktop.instance.getActiveEmployee();
|
||||
}
|
||||
|
||||
public Locale getLocale() {
|
||||
CurrentEmployee currentEmployee = getCurrentEmployee();
|
||||
if (currentEmployee == null) {
|
||||
return Locale.getDefault();
|
||||
}
|
||||
return currentEmployee.localeProperty().get();
|
||||
}
|
||||
|
||||
public String getMessage(String code, Object... args) {
|
||||
return SpringApp.getMessage(code, args, getLocale());
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.ecep.contract.manager;
|
||||
package com.ecep.contract.controller;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
import com.ecep.contract.vm.CurrentEmployee;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
|
||||
@Getter
|
||||
public class CurrentEmployeeInitialedEvent extends ApplicationEvent {
|
||||
@@ -1,11 +1,16 @@
|
||||
package com.ecep.contract.manager.ds.other.controller;
|
||||
package com.ecep.contract.controller;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.ecep.contract.controller.cloud.rk.CloudRkManagerWindowController;
|
||||
import com.ecep.contract.controller.cloud.tyc.CloudTycManagerWindowController;
|
||||
import com.ecep.contract.controller.cloud.u8.YongYouU8ManagerWindowController;
|
||||
import org.controlsfx.control.TaskProgressView;
|
||||
import org.controlsfx.glyphfont.Glyph;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeansException;
|
||||
@@ -14,32 +19,26 @@ import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.manager.CurrentEmployee;
|
||||
import com.ecep.contract.manager.CurrentEmployeeInitialedEvent;
|
||||
import com.ecep.contract.manager.Desktop;
|
||||
import com.ecep.contract.manager.cloud.old.OldVersionService;
|
||||
import com.ecep.contract.manager.cloud.rk.CloudRkManagerWindowController;
|
||||
import com.ecep.contract.manager.cloud.rk.CloudRkService;
|
||||
import com.ecep.contract.manager.cloud.tyc.CloudTycManagerWindowController;
|
||||
import com.ecep.contract.manager.cloud.u8.ContractSyncTask;
|
||||
import com.ecep.contract.manager.cloud.u8.YongYouU8ManagerWindowController;
|
||||
import com.ecep.contract.manager.cloud.u8.YongYouU8Service;
|
||||
import com.ecep.contract.manager.ds.company.controller.CompanyManagerWindowController;
|
||||
import com.ecep.contract.manager.ds.contract.controller.ContractManagerWindowController;
|
||||
import com.ecep.contract.manager.ds.customer.controller.CompanyCustomerManagerWindowController;
|
||||
import com.ecep.contract.manager.ds.other.controller.bank.BankManagerWindowController;
|
||||
import com.ecep.contract.manager.ds.other.controller.department.DepartmentManagerWindowController;
|
||||
import com.ecep.contract.manager.ds.other.controller.employee.EmployeeManagerWindowController;
|
||||
import com.ecep.contract.manager.ds.other.controller.inventory.InventoryManagerWindowController;
|
||||
import com.ecep.contract.manager.ds.other.controller.permission.EmployeeFunctionsManagerWindowController;
|
||||
import com.ecep.contract.manager.ds.other.controller.permission.EmployeeRoleManagerWindowController;
|
||||
import com.ecep.contract.manager.ds.project.controller.ProjectManagerWindowController;
|
||||
import com.ecep.contract.manager.ds.vendor.controller.CompanyVendorManagerWindowController;
|
||||
import com.ecep.contract.manager.ui.BaseController;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.ui.task.TaskMonitorViewController;
|
||||
import com.ecep.contract.manager.util.DesktopUtils;
|
||||
import com.ecep.contract.manager.util.FxmlUtils;
|
||||
import com.ecep.contract.Desktop;
|
||||
import com.ecep.contract.DesktopUtils;
|
||||
import com.ecep.contract.WebSocketClientService;
|
||||
import com.ecep.contract.controller.bank.BankManagerWindowController;
|
||||
import com.ecep.contract.controller.company.CompanyManagerWindowController;
|
||||
import com.ecep.contract.controller.contract.ContractManagerWindowController;
|
||||
import com.ecep.contract.controller.customer.CompanyCustomerManagerWindowController;
|
||||
import com.ecep.contract.controller.department.DepartmentManagerWindowController;
|
||||
import com.ecep.contract.controller.employee.EmployeeManagerWindowController;
|
||||
import com.ecep.contract.controller.inventory.InventoryManagerWindowController;
|
||||
import com.ecep.contract.controller.permission.EmployeeFunctionsManagerWindowController;
|
||||
import com.ecep.contract.controller.permission.EmployeeRoleManagerWindowController;
|
||||
import com.ecep.contract.controller.project.ProjectManagerWindowController;
|
||||
import com.ecep.contract.controller.vendor.VendorManagerWindowController;
|
||||
import com.ecep.contract.service.CloudRkService;
|
||||
import com.ecep.contract.service.YongYouU8Service;
|
||||
import com.ecep.contract.task.ContractSyncTask;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.util.FxmlUtils;
|
||||
import com.ecep.contract.vm.CurrentEmployee;
|
||||
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.event.ActionEvent;
|
||||
@@ -75,13 +74,15 @@ public class HomeWindowController extends BaseController {
|
||||
public Button openCustomManagerWindow;
|
||||
public TaskProgressView<Task<?>> taskProgressView;
|
||||
public Label taskMonitorLabel;
|
||||
public Label webSocketMonitorLabel;
|
||||
public Glyph webSocketMonitorIcon;
|
||||
public Label employeeStatusLabel;
|
||||
|
||||
public void initialize() {
|
||||
openCompanyManagerWindow.setOnAction(event -> showInOwner(CompanyManagerWindowController.class));
|
||||
openProjectManagerWindow.setOnAction(event -> showInOwner(ProjectManagerWindowController.class));
|
||||
openContractManagerWindow.setOnAction(event -> showInOwner(ContractManagerWindowController.class));
|
||||
openVendorManagerWindow.setOnAction(event -> showInOwner(CompanyVendorManagerWindowController.class));
|
||||
openVendorManagerWindow.setOnAction(event -> showInOwner(VendorManagerWindowController.class));
|
||||
openCustomManagerWindow.setOnAction(event -> showInOwner(CompanyCustomerManagerWindowController.class));
|
||||
}
|
||||
|
||||
@@ -102,20 +103,26 @@ public class HomeWindowController extends BaseController {
|
||||
employeeStatusLabel.textProperty().bind(Desktop.instance.getActiveEmployee().getName());
|
||||
Desktop.instance.getTaskMonitorCenter().bindStatusLabel(taskMonitorLabel);
|
||||
Desktop.instance.getActiveEmployee().initialize();
|
||||
|
||||
WebSocketClientService webSocketService = getBean(WebSocketClientService.class);
|
||||
webSocketMonitorIcon.iconProperty()
|
||||
.bind(webSocketService.getOnlineProperty().map(b -> b ? "CHAIN" : "CHAIN_BROKEN"));
|
||||
webSocketMonitorLabel.textProperty().bind(webSocketService.getMessageProperty());
|
||||
webSocketMonitorLabel.setOnMouseClicked(event -> {
|
||||
webSocketService.send("webSocketUrl - " + LocalDateTime.now().toString());
|
||||
});
|
||||
webSocketService.initWebSocket();
|
||||
}
|
||||
|
||||
@EventListener
|
||||
public void onCurrentEmployeeInitialed(CurrentEmployeeInitialedEvent event) {
|
||||
System.out.println("event = " + event);
|
||||
CurrentEmployee currentEmployee = event.getEmployee();
|
||||
if (currentEmployee.isSystemAdministrator()) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("You are administrator, try schedule sync tasks.");
|
||||
}
|
||||
Desktop.instance.getExecutorService().schedule(() -> {
|
||||
try {
|
||||
getBean(OldVersionService.class).scheduledTasks(taskProgressView);
|
||||
} catch (BeansException ignored) {
|
||||
}
|
||||
try {
|
||||
getBean(YongYouU8Service.class).scheduledTasks(taskProgressView);
|
||||
} catch (BeansException ignored) {
|
||||
@@ -140,14 +147,6 @@ public class HomeWindowController extends BaseController {
|
||||
// scheduledExecutorService.shutdownNow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHidden(WindowEvent windowEvent) {
|
||||
System.out.println("windowEvent = " + windowEvent);
|
||||
super.onHidden(windowEvent);
|
||||
|
||||
// Platform.exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开 配置 窗口
|
||||
*/
|
||||
@@ -221,12 +220,11 @@ public class HomeWindowController extends BaseController {
|
||||
showInOwner(TaskMonitorViewController.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行任务监控演示
|
||||
*/
|
||||
public void onRunTaskMonitorDemo(ActionEvent event) {
|
||||
com.ecep.contract.manager.ui.task.DemoTask.runDemo();
|
||||
// 自动打开任务监控窗口
|
||||
showInOwner(TaskMonitorViewController.class);
|
||||
@Override
|
||||
public void onHidden(WindowEvent windowEvent) {
|
||||
System.out.println("windowEvent = " + windowEvent);
|
||||
WebSocketClientService webSocketService = getBean(WebSocketClientService.class);
|
||||
webSocketService.closeWebSocket(); // 在窗口隐藏时关闭WebSocket连接
|
||||
super.onHidden(windowEvent);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
package com.ecep.contract.manager.ui;
|
||||
package com.ecep.contract.controller;
|
||||
|
||||
import com.ecep.contract.controller.tab.Skin;
|
||||
|
||||
public interface ManagerSkin extends Skin {
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
package com.ecep.contract.controller;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.ecep.contract.Desktop;
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.MyProperties;
|
||||
import com.ecep.contract.SpringApp;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import javafx.stage.Stage;
|
||||
import lombok.Setter;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.Cookie;
|
||||
import okhttp3.CookieJar;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import okhttp3.WebSocket;
|
||||
|
||||
public class OkHttpLoginController implements MessageHolder {
|
||||
private static final Logger logger = LoggerFactory.getLogger(OkHttpLoginController.class);
|
||||
public static final MediaType JSON = MediaType.get("application/json; charset=utf-8");
|
||||
|
||||
@Setter
|
||||
private MessageHolder holder;
|
||||
@Setter
|
||||
private Stage primaryStage;
|
||||
@Setter
|
||||
private MyProperties properties;
|
||||
@Setter
|
||||
private OkHttpClient httpClient;
|
||||
private SimpleStringProperty serverUrl = new SimpleStringProperty();
|
||||
private String webSocketUrl;
|
||||
|
||||
public OkHttpLoginController() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addMessage(Level level, String message) {
|
||||
if (holder != null) {
|
||||
holder.addMessage(level, message);
|
||||
}
|
||||
}
|
||||
|
||||
private void initServerUrls() {
|
||||
String host = properties.getServerHost();
|
||||
String port = properties.getServerPort();
|
||||
serverUrl.set("http://" + host + ":" + port);
|
||||
this.webSocketUrl = "ws://" + host + ":" + port + "/ws";
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> tryLogin() {
|
||||
initServerUrls();
|
||||
|
||||
// 检查配置文件中是否保存用户名和密码
|
||||
String userName = getUserName();
|
||||
String password = getPassword();
|
||||
|
||||
if (StringUtils.hasText(userName) && StringUtils.hasText(password)) {
|
||||
return login(userName, password);
|
||||
} else {
|
||||
CompletableFuture<Void> loginFuture = new CompletableFuture<>();
|
||||
Platform.runLater(() -> {
|
||||
showLoginDialog();
|
||||
loginFuture.complete(null);
|
||||
});
|
||||
return loginFuture;
|
||||
}
|
||||
}
|
||||
|
||||
private String getUserName() {
|
||||
return properties.getUserName();
|
||||
}
|
||||
|
||||
private String getPassword() {
|
||||
return properties.getPassword();
|
||||
}
|
||||
|
||||
private void showLoginDialog() {
|
||||
Stage stage = new Stage();
|
||||
stage.initOwner(primaryStage);
|
||||
stage.setTitle("系统登录");
|
||||
|
||||
// 创建 BorderPane 并设置边距
|
||||
BorderPane borderPane = new BorderPane();
|
||||
borderPane.setPadding(new Insets(20));
|
||||
|
||||
// 创建布局
|
||||
GridPane grid = new GridPane();
|
||||
grid.setHgap(10);
|
||||
grid.setVgap(15);
|
||||
|
||||
Label hostLabel = new Label("服务器:");
|
||||
TextField hostField = new TextField();
|
||||
{
|
||||
hostField.textProperty().bindBidirectional(serverUrl);
|
||||
grid.add(hostLabel, 0, 0);
|
||||
grid.add(hostField, 1, 0);
|
||||
}
|
||||
|
||||
// 账户输入框
|
||||
Label userLabel = new Label("用户名:");
|
||||
TextField userField = new TextField();
|
||||
{
|
||||
String username = getUserName();
|
||||
if (StringUtils.hasText(username)) {
|
||||
userField.setText(username);
|
||||
}
|
||||
grid.add(userLabel, 0, 1);
|
||||
grid.add(userField, 1, 1);
|
||||
}
|
||||
|
||||
// 密码输入框
|
||||
Label passwordLabel = new Label("密码:");
|
||||
PasswordField passwordField = new PasswordField();
|
||||
{
|
||||
String password = getPassword();
|
||||
if (StringUtils.hasText(password)) {
|
||||
passwordField.setText(password);
|
||||
}
|
||||
grid.add(passwordLabel, 0, 2);
|
||||
grid.add(passwordField, 1, 2);
|
||||
}
|
||||
|
||||
// 记住密码复选框
|
||||
CheckBox rememberCheckBox = new CheckBox("记住密码");
|
||||
{
|
||||
boolean remember = properties.isRememberPassword();
|
||||
if (remember) {
|
||||
rememberCheckBox.setSelected(true);
|
||||
}
|
||||
}
|
||||
grid.add(rememberCheckBox, 1, 3);
|
||||
|
||||
// 错误消息提示
|
||||
Label errorLabel = new Label();
|
||||
errorLabel.setStyle("-fx-text-fill: red;");
|
||||
errorLabel.setVisible(false);
|
||||
grid.add(errorLabel, 0, 4);
|
||||
GridPane.setColumnSpan(errorLabel, 2);
|
||||
|
||||
borderPane.setCenter(grid);
|
||||
|
||||
// 登录按钮
|
||||
Button loginButton = new Button("登录");
|
||||
loginButton.setDefaultButton(true);
|
||||
loginButton.setPrefWidth(100);
|
||||
borderPane.setBottom(loginButton);
|
||||
BorderPane.setAlignment(loginButton, javafx.geometry.Pos.CENTER);
|
||||
BorderPane.setMargin(loginButton, new Insets(20, 0, 0, 0));
|
||||
|
||||
// 登录按钮点击事件
|
||||
loginButton.setOnAction(event -> {
|
||||
String username = userField.getText();
|
||||
String password = passwordField.getText();
|
||||
boolean remember = rememberCheckBox.isSelected();
|
||||
|
||||
if (!StringUtils.hasText(username) || !StringUtils.hasText(password)) {
|
||||
errorLabel.setText("用户名和密码不能为空");
|
||||
errorLabel.setVisible(true);
|
||||
return;
|
||||
}
|
||||
|
||||
errorLabel.setVisible(false);
|
||||
|
||||
// 保存配置
|
||||
if (remember) {
|
||||
properties.setUserName(username);
|
||||
properties.setPassword(password);
|
||||
properties.setRememberPassword(true);
|
||||
} else {
|
||||
properties.setUserName(username);
|
||||
properties.setPassword("");
|
||||
properties.setRememberPassword(false);
|
||||
}
|
||||
properties.save();
|
||||
|
||||
// 执行登录
|
||||
login(username, password).whenComplete((v, e) -> {
|
||||
if (e != null) {
|
||||
Platform.runLater(() -> {
|
||||
errorLabel.setText(e.getMessage());
|
||||
errorLabel.setVisible(true);
|
||||
// showError("登录失败", e.getMessage());
|
||||
});
|
||||
return;
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
stage.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 创建场景并设置到窗口
|
||||
Scene scene = new Scene(borderPane, 400, 280);
|
||||
stage.setScene(scene);
|
||||
stage.setResizable(false);
|
||||
stage.showAndWait();
|
||||
|
||||
System.out.println("登录窗口关闭");
|
||||
}
|
||||
|
||||
private CompletableFuture<Void> login(String username, String password) {
|
||||
// 添加详细日志,记录服务器URL和请求准备情况
|
||||
info("正在连接服务器: " + serverUrl.get());
|
||||
logger.debug("用户名: {}", username);
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
|
||||
try {
|
||||
while (!SpringApp.isRunning()) {
|
||||
holder.info("环境准备中,请稍后...");
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
|
||||
ObjectMapper objectMapper = SpringApp.getBean(ObjectMapper.class);
|
||||
ObjectNode objectNode = objectMapper.createObjectNode();
|
||||
objectNode.put("username", username);
|
||||
objectNode.put("password", password);
|
||||
objectNode.put("type", "client");
|
||||
|
||||
// 将MacIP列表转换为Map<String, String>格式(MAC地址->IP地址)
|
||||
List<MacIP> macIpList = getMacAndIP().join();
|
||||
ObjectNode signNode = objectMapper.createObjectNode();
|
||||
for (MacIP macIp : macIpList) {
|
||||
signNode.put(macIp.mac, macIp.ip);
|
||||
}
|
||||
objectNode.set("sign", signNode);
|
||||
|
||||
// 构建JSON格式的登录请求
|
||||
RequestBody body = RequestBody.create(objectNode.toString(), JSON);
|
||||
|
||||
// 构建并记录完整的请求URL
|
||||
String loginUrl = serverUrl.get() + "/api/login";
|
||||
logger.debug("构建登录请求URL: " + loginUrl);
|
||||
Request request = new Request.Builder()
|
||||
.url(loginUrl)
|
||||
.post(body)
|
||||
.build();
|
||||
|
||||
httpClient.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e) {
|
||||
future.completeExceptionally(
|
||||
new IOException("登录失败: 无法连接到服务器,请检查网络连接或服务器配置 - " + e.getMessage(), e));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) throws IOException {
|
||||
try {
|
||||
if (!response.isSuccessful()) {
|
||||
future.completeExceptionally(
|
||||
new IOException("登录失败: 服务器返回错误码 - " + response.toString()));
|
||||
return;
|
||||
}
|
||||
ResponseBody body = response.body();
|
||||
System.out.println("contentType = " + body.contentType());
|
||||
JsonNode jsonNode = objectMapper.readTree(body.string());
|
||||
|
||||
boolean success = jsonNode.get("success").asBoolean(false);
|
||||
if (!success) {
|
||||
future.completeExceptionally(
|
||||
new IOException("登录失败: 服务器返回错误 - " + jsonNode.get("error").asText()));
|
||||
return;
|
||||
}
|
||||
System.out.println("登录成功: " + jsonNode.toString());
|
||||
// 登录成功后,调用新的API端点获取用户信息
|
||||
|
||||
Desktop.instance.setActiveEmployeeId(jsonNode.get("employeeId").asInt());
|
||||
Desktop.instance.setSessionId(jsonNode.get("sessionId").asText());
|
||||
future.complete(null);
|
||||
|
||||
} finally {
|
||||
// 确保主响应体被关闭
|
||||
response.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(new IOException("登录过程中发生错误: " + e.getMessage(), e));
|
||||
}
|
||||
return future;
|
||||
}
|
||||
|
||||
CompletableFuture<List<MacIP>> getMacAndIP() {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
List<MacIP> list = new ArrayList<>();
|
||||
try {
|
||||
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
|
||||
while (interfaces.hasMoreElements()) {
|
||||
NetworkInterface anInterface = interfaces.nextElement();
|
||||
if (anInterface.isLoopback() || !anInterface.isUp()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
byte[] hardwareAddress = anInterface.getHardwareAddress();
|
||||
if (hardwareAddress == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Enumeration<InetAddress> inetAddresses = anInterface.getInetAddresses();
|
||||
if (!inetAddresses.hasMoreElements()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// -分割16进制表示法
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < hardwareAddress.length; i++) {
|
||||
sb.append(
|
||||
String.format("%02X%s", hardwareAddress[i], i < hardwareAddress.length - 1 ? "-" : ""));
|
||||
}
|
||||
|
||||
String mac = sb.toString();
|
||||
while (inetAddresses.hasMoreElements()) {
|
||||
InetAddress inetAddress = inetAddresses.nextElement();
|
||||
if (inetAddress.getHostAddress().contains(":")) {
|
||||
continue; // 跳过IPv6地址
|
||||
}
|
||||
list.add(new MacIP(mac, inetAddress.getHostAddress()));
|
||||
}
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return list;
|
||||
});
|
||||
}
|
||||
|
||||
static class MacIP {
|
||||
String mac;
|
||||
String ip;
|
||||
|
||||
public MacIP(String mac, String ip) {
|
||||
this.mac = mac;
|
||||
this.ip = ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.ecep.contract.controller;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.constant.CompanyCustomerConstant;
|
||||
import com.ecep.contract.constant.CompanyVendorConstant;
|
||||
import com.ecep.contract.constant.ContractConstant;
|
||||
import com.ecep.contract.util.StringConfig;
|
||||
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import javafx.stage.FileChooser;
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
public class SysConfWindowController extends BaseController {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SysConfWindowController.class);
|
||||
|
||||
public Label companyContractPathLabel;
|
||||
|
||||
public TextField u8DataBaseServerHostField;
|
||||
public TextField u8DataBaseCatalogField;
|
||||
public TextField u8DataBaseUserNameField;
|
||||
public TextField u8DataBasePasswordField;
|
||||
|
||||
public Label vendorPathLabel;
|
||||
public Label vendorEvaluationFormTemplateLabel;
|
||||
|
||||
public Label customerPathLabel;
|
||||
public Label customerEvaluationFormTemplateLabel;
|
||||
public Label customerSaleBookPathLabel;
|
||||
|
||||
StringConfig contractPathConfig = new StringConfig(ContractConstant.KEY_BASE_PATH);
|
||||
StringConfig vendorPathConfig = new StringConfig(CompanyVendorConstant.KEY_BASE_PATH);
|
||||
StringConfig vendorEvaluationFormTemplateConfig = new StringConfig(
|
||||
CompanyVendorConstant.KEY_EVALUATION_FORM_TEMPLATE);
|
||||
StringConfig customerPathConfig = new StringConfig(CompanyCustomerConstant.KEY_BASE_PATH);
|
||||
StringConfig customerEvaluationFormTemplateConfig = new StringConfig(
|
||||
CompanyCustomerConstant.KEY_EVALUATION_FORM_TEMPLATE);
|
||||
StringConfig customerSaleBookPathConfig = new StringConfig(CompanyCustomerConstant.KEY_SALEBOOK_PATH);
|
||||
|
||||
public void initialize() {
|
||||
contractPathConfig.setControl(companyContractPathLabel);
|
||||
contractPathConfig.initialize();
|
||||
|
||||
vendorPathConfig.setControl(vendorPathLabel);
|
||||
vendorPathConfig.initialize();
|
||||
vendorEvaluationFormTemplateConfig.setControl(vendorEvaluationFormTemplateLabel);
|
||||
vendorEvaluationFormTemplateConfig.initialize();
|
||||
|
||||
customerPathConfig.setControl(customerPathLabel);
|
||||
customerPathConfig.initialize();
|
||||
customerEvaluationFormTemplateConfig.setControl(customerEvaluationFormTemplateLabel);
|
||||
customerEvaluationFormTemplateConfig.initialize();
|
||||
customerSaleBookPathConfig.setControl(customerSaleBookPathLabel);
|
||||
customerSaleBookPathConfig.initialize();
|
||||
|
||||
logger.debug("#initialize()");
|
||||
}
|
||||
|
||||
private void directoryChoose(StringConfig config, String title, String key, ActionEvent event) {
|
||||
DirectoryChooser chooser = new DirectoryChooser();
|
||||
chooser.setTitle(title);
|
||||
File value = new File(config.getProperty().getValue());
|
||||
chooser.setInitialDirectory(value);
|
||||
|
||||
Node node = (Node) event.getSource();
|
||||
File selected = chooser.showDialog(node.getScene().getWindow());
|
||||
if (selected != null) {
|
||||
config.getProperty().setValue(selected.getAbsolutePath());
|
||||
config.save();
|
||||
}
|
||||
}
|
||||
|
||||
public void changeCompanyContractPath(ActionEvent actionEvent) {
|
||||
directoryChoose(contractPathConfig, "请选择合同目录", ContractConstant.KEY_BASE_PATH, actionEvent);
|
||||
}
|
||||
|
||||
public void changeVendorPath(ActionEvent actionEvent) {
|
||||
directoryChoose(vendorPathConfig, "请选择供应商目录", CompanyVendorConstant.KEY_BASE_PATH, actionEvent);
|
||||
}
|
||||
|
||||
public void changeCustomerPath(ActionEvent actionEvent) {
|
||||
directoryChoose(customerPathConfig, "请选择客户目录", CompanyCustomerConstant.KEY_BASE_PATH, actionEvent);
|
||||
}
|
||||
|
||||
public void changeCustomerSaleBookPath(ActionEvent actionEvent) {
|
||||
directoryChoose(customerSaleBookPathConfig, "请选择销售台账目录", CompanyCustomerConstant.KEY_SALEBOOK_PATH, actionEvent);
|
||||
}
|
||||
|
||||
// 模拟销毁方法
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
|
||||
}
|
||||
|
||||
private void fileChoose(StringConfig config, String title, String key, ActionEvent event) {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle(title);
|
||||
File value = new File(config.getProperty().getValue());
|
||||
chooser.setInitialDirectory(value.getParentFile());
|
||||
chooser.setInitialFileName(value.getName());
|
||||
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(" 模板文件(*.xlsx)", "*.xlsx"));
|
||||
Node node = (Node) event.getSource();
|
||||
File selected = chooser.showOpenDialog(node.getScene().getWindow());
|
||||
if (selected != null) {
|
||||
config.getProperty().setValue(selected.getAbsolutePath());
|
||||
config.save();
|
||||
}
|
||||
}
|
||||
|
||||
public void changeCustomerEvaluationFormTemplate(ActionEvent actionEvent) {
|
||||
fileChoose(customerEvaluationFormTemplateConfig, "请选择客户资信评估表模板",
|
||||
CompanyCustomerConstant.KEY_EVALUATION_FORM_TEMPLATE, actionEvent);
|
||||
}
|
||||
|
||||
public void changeVendorEvaluationFormTemplate(ActionEvent actionEvent) {
|
||||
fileChoose(vendorEvaluationFormTemplateConfig, "请选择供方调查评价表模板",
|
||||
CompanyVendorConstant.KEY_EVALUATION_FORM_TEMPLATE,
|
||||
actionEvent);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,25 @@
|
||||
package com.ecep.contract.manager.ui.task;
|
||||
package com.ecep.contract.controller;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.manager.Desktop;
|
||||
import com.ecep.contract.manager.ui.BaseController;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.ui.Message;
|
||||
import com.ecep.contract.manager.util.MyDateTimeUtils;
|
||||
import com.ecep.contract.Desktop;
|
||||
import com.ecep.contract.Message;
|
||||
import com.ecep.contract.MyDateTimeUtils;
|
||||
import com.ecep.contract.task.MonitoredTask;
|
||||
import com.ecep.contract.task.TaskHistory;
|
||||
import com.ecep.contract.task.TaskMonitorCenter;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
@@ -69,10 +76,47 @@ public class TaskMonitorViewController extends BaseController {
|
||||
private Button cancelTaskButton;
|
||||
@FXML
|
||||
private Button clearHistoryButton;
|
||||
@FXML
|
||||
private Button refreshExecutorInfoButton;
|
||||
@FXML
|
||||
private TableView<ExecutorInfo> executorInfoTable;
|
||||
@FXML
|
||||
private TableColumn<ExecutorInfo, String> executorFieldColumn;
|
||||
@FXML
|
||||
private TableColumn<ExecutorInfo, String> executorValueColumn;
|
||||
@FXML
|
||||
private TableColumn<ExecutorInfo, String> executorDescriptionColumn;
|
||||
|
||||
public TaskMonitorViewController() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于存储ExecutorService信息的内部类
|
||||
*/
|
||||
public static class ExecutorInfo {
|
||||
private final String field;
|
||||
private final String value;
|
||||
private final String description;
|
||||
|
||||
public ExecutorInfo(String field, String value, String description) {
|
||||
this.field = field;
|
||||
this.value = value;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShown(WindowEvent windowEvent) {
|
||||
super.onShown(windowEvent);
|
||||
@@ -144,6 +188,94 @@ public class TaskMonitorViewController extends BaseController {
|
||||
});
|
||||
|
||||
cancelTaskButton.disableProperty().bind(activeTasksTable.getSelectionModel().selectedItemProperty().isNull());
|
||||
// 初始化Executor信息表格
|
||||
initializeExecutorInfoTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化Executor信息表格
|
||||
*/
|
||||
private void initializeExecutorInfoTable() {
|
||||
executorFieldColumn.setCellValueFactory(param -> new SimpleStringProperty(param.getValue().getField()));
|
||||
executorValueColumn.setCellValueFactory(param -> new SimpleStringProperty(param.getValue().getValue()));
|
||||
executorDescriptionColumn
|
||||
.setCellValueFactory(param -> new SimpleStringProperty(param.getValue().getDescription()));
|
||||
|
||||
// 初始加载Executor信息
|
||||
refreshExecutorInfo();
|
||||
// 绑定刷新按钮事件
|
||||
refreshExecutorInfoButton.setOnAction(event -> refreshExecutorInfo());
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新Executor信息
|
||||
*/
|
||||
@FXML
|
||||
private void refreshExecutorInfo() {
|
||||
executorInfoTable.getItems().clear();
|
||||
|
||||
try {
|
||||
ExecutorService executorService = Desktop.instance.getExecutorService();
|
||||
if (executorService instanceof ThreadPoolExecutor threadPoolExecutor) {
|
||||
executorInfoTable.getItems().add(new ExecutorInfo(
|
||||
"PoolSize",
|
||||
String.valueOf(threadPoolExecutor.getPoolSize()),
|
||||
"PoolSize"));
|
||||
executorInfoTable.getItems().add(new ExecutorInfo(
|
||||
"活动线程数",
|
||||
String.valueOf(threadPoolExecutor.getActiveCount()),
|
||||
"当前正在执行任务的线程数"));
|
||||
// 添加线程池信息
|
||||
executorInfoTable.getItems().add(new ExecutorInfo(
|
||||
"核心线程数",
|
||||
String.valueOf(threadPoolExecutor.getCorePoolSize()),
|
||||
"线程池维护的最小线程数"));
|
||||
executorInfoTable.getItems().add(new ExecutorInfo(
|
||||
"最大线程数",
|
||||
String.valueOf(threadPoolExecutor.getMaximumPoolSize()),
|
||||
"线程池允许的最大线程数"));
|
||||
executorInfoTable.getItems().add(new ExecutorInfo(
|
||||
"KeepAliveTime",
|
||||
String.valueOf(threadPoolExecutor.getKeepAliveTime(TimeUnit.SECONDS)),
|
||||
"the thread keep-alive time, which is the amount of time that threads may remain idle before being terminated. Threads that wait this amount of time without processing a task will be terminated if there are more than the core number of threads currently in the pool, or if this pool allows core thread timeout."));
|
||||
|
||||
executorInfoTable.getItems().add(new ExecutorInfo(
|
||||
"任务队列大小",
|
||||
String.valueOf(threadPoolExecutor.getQueue().size()),
|
||||
"等待执行的任务数量"));
|
||||
executorInfoTable.getItems().add(new ExecutorInfo(
|
||||
"已完成任务数",
|
||||
String.valueOf(threadPoolExecutor.getCompletedTaskCount()),
|
||||
"已完成执行的任务总数"));
|
||||
executorInfoTable.getItems().add(new ExecutorInfo(
|
||||
"LargestPoolSize",
|
||||
String.valueOf(threadPoolExecutor.getLargestPoolSize()),
|
||||
"线程池当前状态"));
|
||||
|
||||
// 如果是ScheduledExecutorService,添加额外信息
|
||||
if (executorService instanceof ScheduledExecutorService) {
|
||||
executorInfoTable.getItems().add(new ExecutorInfo(
|
||||
"线程池类型",
|
||||
"ScheduledExecutorService",
|
||||
"可调度的线程池"));
|
||||
} else {
|
||||
executorInfoTable.getItems().add(new ExecutorInfo(
|
||||
"线程池类型",
|
||||
"ThreadPoolExecutor",
|
||||
"普通线程池"));
|
||||
}
|
||||
} else {
|
||||
executorInfoTable.getItems().add(new ExecutorInfo(
|
||||
"错误",
|
||||
"未知的ExecutorService类型",
|
||||
"无法获取线程池详细信息"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
executorInfoTable.getItems().add(new ExecutorInfo(
|
||||
"异常",
|
||||
e.getMessage(),
|
||||
"获取ExecutorService信息时发生错误"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,7 +337,7 @@ public class TaskMonitorViewController extends BaseController {
|
||||
text.setFill(Color.ORANGE);
|
||||
} else if (msg.getLevel() == Level.SEVERE) {
|
||||
text.setFill(Color.RED);
|
||||
}else if (msg.getLevel()== Level.CONFIG) {
|
||||
} else if (msg.getLevel() == Level.CONFIG) {
|
||||
text.setFill(Color.GRAY);
|
||||
}
|
||||
vBox.getChildren().add(text);
|
||||
@@ -218,4 +350,11 @@ public class TaskMonitorViewController extends BaseController {
|
||||
alert.getDialogPane().setContent(scrollPane);
|
||||
alert.showAndWait();
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行任务监控演示
|
||||
*/
|
||||
public void onRunTaskMonitorDemo(ActionEvent event) {
|
||||
com.ecep.contract.task.DemoTask.runDemo();
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,18 @@
|
||||
package com.ecep.contract.manager.ds.other.controller.bank;
|
||||
package com.ecep.contract.controller.bank;
|
||||
|
||||
import com.ecep.contract.controller.AbstEntityManagerSkin;
|
||||
import com.ecep.contract.controller.ManagerSkin;
|
||||
import com.ecep.contract.controller.table.EditableEntityTableTabSkin;
|
||||
import com.ecep.contract.vm.BankViewModel;
|
||||
import com.ecep.contract.vo.BankVo;
|
||||
|
||||
import com.ecep.contract.manager.ds.other.model.Bank;
|
||||
import com.ecep.contract.manager.ds.other.vo.BankViewModel;
|
||||
import com.ecep.contract.manager.ui.AbstEntityManagerSkin;
|
||||
import com.ecep.contract.manager.ui.table.EditableEntityTableTabSkin;
|
||||
import com.ecep.contract.manager.ui.ManagerSkin;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.cell.TextFieldTableCell;
|
||||
|
||||
|
||||
public class BankManagerSkin
|
||||
extends AbstEntityManagerSkin<Bank, BankViewModel, BankManagerSkin, BankManagerWindowController>
|
||||
implements ManagerSkin, EditableEntityTableTabSkin<Bank, BankViewModel> {
|
||||
extends AbstEntityManagerSkin<BankVo, BankViewModel, BankManagerSkin, BankManagerWindowController>
|
||||
implements ManagerSkin, EditableEntityTableTabSkin<BankVo, BankViewModel> {
|
||||
|
||||
public BankManagerSkin(BankManagerWindowController controller) {
|
||||
super(controller);
|
||||
@@ -1,23 +1,26 @@
|
||||
package com.ecep.contract.manager.ds.other.controller.bank;
|
||||
package com.ecep.contract.controller.bank;
|
||||
|
||||
import com.ecep.contract.manager.ds.other.model.Bank;
|
||||
import com.ecep.contract.manager.ds.other.service.BankService;
|
||||
import com.ecep.contract.manager.ds.other.vo.BankViewModel;
|
||||
import com.ecep.contract.manager.ui.AbstManagerWindowController;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import org.controlsfx.control.SearchableComboBox;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.controller.AbstManagerWindowController;
|
||||
import com.ecep.contract.service.BankService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.vm.BankViewModel;
|
||||
import com.ecep.contract.vo.BankVo;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.TableColumn;
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
@FxmlPath("/ui/bank-manager.fxml")
|
||||
public class BankManagerWindowController
|
||||
extends AbstManagerWindowController<Bank, BankViewModel, BankManagerSkin> {
|
||||
extends AbstManagerWindowController<BankVo, BankViewModel, BankManagerSkin> {
|
||||
|
||||
@Autowired
|
||||
private BankService bankService;
|
||||
@@ -32,7 +35,6 @@ public class BankManagerWindowController
|
||||
}
|
||||
|
||||
public void onCreateNewAction(ActionEvent event) {
|
||||
|
||||
}
|
||||
|
||||
public void onReBuildFilesAction(ActionEvent event) {
|
||||
@@ -1,14 +1,15 @@
|
||||
package com.ecep.contract.manager.cloud.rk;
|
||||
package com.ecep.contract.controller.cloud.rk;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
|
||||
import com.ecep.contract.manager.SpringApp;
|
||||
import com.ecep.contract.manager.ui.table.cell.CompanyTableCell;
|
||||
import com.ecep.contract.manager.ds.company.controller.CompanyWindowController;
|
||||
import com.ecep.contract.manager.ds.company.model.Company;
|
||||
import com.ecep.contract.manager.ds.company.service.CompanyService;
|
||||
import com.ecep.contract.manager.ui.AbstEntityManagerSkin;
|
||||
import com.ecep.contract.manager.ui.table.cell.LocalDateTimeTableCell;
|
||||
import com.ecep.contract.SpringApp;
|
||||
import com.ecep.contract.controller.AbstEntityManagerSkin;
|
||||
import com.ecep.contract.controller.company.CompanyWindowController;
|
||||
import com.ecep.contract.controller.table.cell.CompanyTableCell;
|
||||
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
|
||||
import com.ecep.contract.service.CloudRkService;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.vm.CloudRkViewModel;
|
||||
import com.ecep.contract.vo.CloudRkVo;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
@@ -19,7 +20,7 @@ import javafx.scene.control.cell.CheckBoxTableCell;
|
||||
import lombok.Setter;
|
||||
|
||||
public class CloudRkManagerSkin
|
||||
extends AbstEntityManagerSkin<CloudRk, CloudRkInfoViewModel, CloudRkManagerSkin, CloudRkManagerWindowController> {
|
||||
extends AbstEntityManagerSkin<CloudRkVo, CloudRkViewModel, CloudRkManagerSkin, CloudRkManagerWindowController> {
|
||||
@Setter
|
||||
private CompanyService companyService;
|
||||
|
||||
@@ -47,13 +48,14 @@ public class CloudRkManagerSkin
|
||||
|
||||
controller.cloudIdColumn.setCellValueFactory(param -> param.getValue().getCloudId());
|
||||
|
||||
controller.latestUpdateColumn.setCellValueFactory(param -> param.getValue().getLatest());
|
||||
controller.latestUpdateColumn.setCellValueFactory(param -> param.getValue().getLatestUpdate());
|
||||
controller.latestUpdateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
|
||||
|
||||
controller.cloudLatestColumn.setCellValueFactory(param -> param.getValue().getCloudLatest());
|
||||
controller.cloudLatestColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
|
||||
|
||||
controller.cloudBlackListUpdatedColumn.setCellValueFactory(param -> param.getValue().getCloudBlackListUpdated());
|
||||
controller.cloudBlackListUpdatedColumn
|
||||
.setCellValueFactory(param -> param.getValue().getCloudBlackListUpdated());
|
||||
controller.cloudBlackListUpdatedColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
|
||||
|
||||
controller.cloudEntUpdateColumn.setCellValueFactory(param -> param.getValue().getCloudEntUpdate());
|
||||
@@ -65,13 +67,12 @@ public class CloudRkManagerSkin
|
||||
controller.descriptionColumn.setCellValueFactory(param -> param.getValue().getDescription());
|
||||
}
|
||||
|
||||
private void onAutoUpdateColumnEditCommit(TableColumn.CellEditEvent<CloudRkInfoViewModel, Boolean> event) {
|
||||
CloudRkInfoViewModel row = event.getRowValue();
|
||||
private void onAutoUpdateColumnEditCommit(TableColumn.CellEditEvent<CloudRkViewModel, Boolean> event) {
|
||||
CloudRkViewModel row = event.getRowValue();
|
||||
row.getAutoUpdate().set(event.getNewValue());
|
||||
saveRowData(row);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void createContextMenu(ContextMenu contextMenu) {
|
||||
MenuItem item2 = new MenuItem("刷新");
|
||||
@@ -89,23 +90,21 @@ public class CloudRkManagerSkin
|
||||
* @param event event
|
||||
*/
|
||||
public void onTableClearDescriptionAction(ActionEvent event) {
|
||||
ObservableList<CloudRkInfoViewModel> selectedItems = getTableView().getSelectionModel().getSelectedItems();
|
||||
ObservableList<CloudRkViewModel> selectedItems = getTableView().getSelectionModel().getSelectedItems();
|
||||
if (selectedItems.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
CloudRkService service = getCloudRkService();
|
||||
for (CloudRkInfoViewModel selectedItem : selectedItems) {
|
||||
for (CloudRkViewModel selectedItem : selectedItems) {
|
||||
selectedItem.getDescription().set("");
|
||||
selectedItem.saveInFxApplicationThread(service);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTableRowDoubleClickedAction(CloudRkInfoViewModel item) {
|
||||
Company company = item.getCompany().get();
|
||||
if (!Hibernate.isInitialized(item)) {
|
||||
company = getCompanyService().findById(company.getId());
|
||||
}
|
||||
protected void onTableRowDoubleClickedAction(CloudRkViewModel item) {
|
||||
Integer companyId = item.getCompany().get();
|
||||
CompanyVo company = getCompanyService().findById(companyId);
|
||||
CompanyWindowController.show(company, getTableView().getScene().getWindow());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.ecep.contract.controller.cloud.rk;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.ecep.contract.controller.AbstManagerWindowController;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.Message;
|
||||
import com.ecep.contract.SpringApp;
|
||||
import com.ecep.contract.service.CloudRkService;
|
||||
import com.ecep.contract.task.CloudRkSyncTask;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.CloudRkViewModel;
|
||||
import com.ecep.contract.vo.CloudRkVo;
|
||||
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.stage.WindowEvent;
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
@FxmlPath("/ui/cloud/rk_manager.fxml")
|
||||
public class CloudRkManagerWindowController
|
||||
extends AbstManagerWindowController<CloudRkVo, CloudRkViewModel, CloudRkManagerSkin> {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CloudRkManagerWindowController.class);
|
||||
|
||||
public static void show() {
|
||||
show(CloudRkManagerWindowController.class, null);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private CloudRkService cloudRkService;
|
||||
|
||||
public TableColumn<CloudRkViewModel, Number> idColumn;
|
||||
public TableColumn<CloudRkViewModel, LocalDateTime> latestUpdateColumn;
|
||||
/**
|
||||
* 集团相关方, Company
|
||||
*/
|
||||
public TableColumn<CloudRkViewModel, Integer> companyColumn;
|
||||
public TableColumn<CloudRkViewModel, String> cloudIdColumn;
|
||||
public TableColumn<CloudRkViewModel, LocalDateTime> cloudLatestColumn;
|
||||
public TableColumn<CloudRkViewModel, LocalDateTime> cloudBlackListUpdatedColumn;
|
||||
public TableColumn<CloudRkViewModel, LocalDateTime> cloudEntUpdateColumn;
|
||||
public TableColumn<CloudRkViewModel, Boolean> autoUpdateColumn;
|
||||
public TableColumn<CloudRkViewModel, String> descriptionColumn;
|
||||
|
||||
@Override
|
||||
protected CloudRkManagerSkin createDefaultSkin() {
|
||||
return new CloudRkManagerSkin(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShown(WindowEvent windowEvent) {
|
||||
super.onShown(windowEvent);
|
||||
getTitle().set("数据源:集团相关方");
|
||||
}
|
||||
|
||||
public void onDataRepairAction(ActionEvent event) {
|
||||
}
|
||||
|
||||
public void onSyncAction(ActionEvent event) {
|
||||
CloudRkSyncTask task = new CloudRkSyncTask();
|
||||
UITools.showTaskDialogAndWait("同步数据", task, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudRkService getViewModelService() {
|
||||
return cloudRkService;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,16 @@
|
||||
package com.ecep.contract.manager.cloud.tyc;
|
||||
package com.ecep.contract.controller.cloud.tyc;
|
||||
|
||||
import java.util.List;
|
||||
import com.ecep.contract.SpringApp;
|
||||
import com.ecep.contract.controller.AbstEntityManagerSkin;
|
||||
import com.ecep.contract.controller.company.CompanyWindowController;
|
||||
import com.ecep.contract.controller.table.cell.CompanyTableCell;
|
||||
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
|
||||
import com.ecep.contract.service.CloudTycService;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.vm.CloudTycInfoViewModel;
|
||||
import com.ecep.contract.vo.CloudTycVo;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.ecep.contract.manager.SpringApp;
|
||||
import com.ecep.contract.manager.ui.table.cell.CompanyTableCell;
|
||||
import com.ecep.contract.manager.ds.company.controller.CompanyWindowController;
|
||||
import com.ecep.contract.manager.ds.company.model.Company;
|
||||
import com.ecep.contract.manager.ds.company.service.CompanyService;
|
||||
import com.ecep.contract.manager.ui.AbstEntityManagerSkin;
|
||||
import com.ecep.contract.manager.ui.table.cell.LocalDateTimeTableCell;
|
||||
|
||||
import jakarta.persistence.criteria.Path;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
@@ -24,7 +19,7 @@ import lombok.Setter;
|
||||
|
||||
public class CloudTycManagerSkin
|
||||
extends
|
||||
AbstEntityManagerSkin<CloudTyc, CloudTycInfoViewModel, CloudTycManagerSkin, CloudTycManagerWindowController> {
|
||||
AbstEntityManagerSkin<CloudTycVo, CloudTycInfoViewModel, CloudTycManagerSkin, CloudTycManagerWindowController> {
|
||||
@Setter
|
||||
private CloudTycService cloudTycService;
|
||||
|
||||
@@ -49,31 +44,6 @@ public class CloudTycManagerSkin
|
||||
return companyService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<CloudTycInfoViewModel> loadTableData() {
|
||||
String searchText = controller.searchKeyField.getText();
|
||||
Specification<CloudTyc> spec = null;
|
||||
if (StringUtils.hasText(searchText)) {
|
||||
|
||||
Specification<CloudTyc> companySpec = (root, query, builder) -> {
|
||||
Path<Object> company = root.get("company");
|
||||
return builder.or(
|
||||
builder.like(company.get("name"), "%" + searchText + "%"),
|
||||
builder.like(company.get("shortName"), "%" + searchText + "%"));
|
||||
};
|
||||
|
||||
Specification<CloudTyc> cloudIdSpec = (root, query, builder) -> {
|
||||
return builder.like(root.get("cloudId"), "%" + searchText + "%");
|
||||
};
|
||||
|
||||
spec = Specification.anyOf(companySpec, cloudIdSpec);
|
||||
}
|
||||
|
||||
Page<CloudTyc> page = getCloudTycService().findAll(spec, getPageable());
|
||||
updateFooter(page);
|
||||
return page.map(CloudTycInfoViewModel::from).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTable() {
|
||||
controller.idColumn.setCellValueFactory(param -> param.getValue().getId());
|
||||
@@ -82,7 +52,7 @@ public class CloudTycManagerSkin
|
||||
|
||||
controller.cloudIdColumn.setCellValueFactory(param -> param.getValue().getCloudId());
|
||||
|
||||
controller.latestUpdateColumn.setCellValueFactory(param -> param.getValue().getLatest());
|
||||
controller.latestUpdateColumn.setCellValueFactory(param -> param.getValue().getLatestUpdate());
|
||||
controller.latestUpdateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
|
||||
|
||||
controller.cloudLatestColumn.setCellValueFactory(param -> param.getValue().getCloudLatest());
|
||||
@@ -114,21 +84,20 @@ public class CloudTycManagerSkin
|
||||
return;
|
||||
}
|
||||
for (CloudTycInfoViewModel selectedItem : selectedItems) {
|
||||
CloudTyc cloudTyc = getCloudTycService().findById(selectedItem.getId().get());
|
||||
CloudTycVo cloudTyc = getCloudTycService().findById(selectedItem.getId().get());
|
||||
// selectedItem.getDescription().set("");
|
||||
if (selectedItem.copyTo(cloudTyc)) {
|
||||
CloudTyc saved = getCloudTycService().save(cloudTyc);
|
||||
CloudTycVo saved = getCloudTycService().save(cloudTyc);
|
||||
selectedItem.update(saved);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@Override
|
||||
protected void onTableRowDoubleClickedAction(CloudTycInfoViewModel item) {
|
||||
Company company = item.getCompany().get();
|
||||
if (!Hibernate.isInitialized(item)) {
|
||||
company = getCompanyService().findById(company.getId());
|
||||
}
|
||||
Integer companyId = item.getCompany().get();
|
||||
CompanyVo company = getCompanyService().findById(companyId);
|
||||
CompanyWindowController.show(company, getTableView().getScene().getWindow());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.ecep.contract.controller.cloud.tyc;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import com.ecep.contract.controller.AbstManagerWindowController;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.service.CloudTycService;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.vm.CloudTycInfoViewModel;
|
||||
import com.ecep.contract.vo.CloudTycVo;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
/**
|
||||
* 天眼查信息管理窗口控制器
|
||||
*/
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
@FxmlPath("/ui/cloud/tyc_manager.fxml")
|
||||
public class CloudTycManagerWindowController
|
||||
extends AbstManagerWindowController<CloudTycVo, CloudTycInfoViewModel, CloudTycManagerSkin> {
|
||||
|
||||
public static void show() {
|
||||
show(CloudTycManagerWindowController.class, null);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private CloudTycService cloudTycService;
|
||||
|
||||
@Autowired
|
||||
private CompanyService companyService;
|
||||
|
||||
@FXML
|
||||
public TableColumn<CloudTycInfoViewModel, Number> idColumn;
|
||||
@FXML
|
||||
public TableColumn<CloudTycInfoViewModel, LocalDateTime> latestUpdateColumn;
|
||||
|
||||
/**
|
||||
* 公司, Company
|
||||
*/
|
||||
@FXML
|
||||
public TableColumn<CloudTycInfoViewModel, Integer> companyColumn;
|
||||
@FXML
|
||||
public TableColumn<CloudTycInfoViewModel, String> cloudIdColumn;
|
||||
@FXML
|
||||
public TableColumn<CloudTycInfoViewModel, LocalDateTime> cloudLatestColumn;
|
||||
@FXML
|
||||
public TableColumn<CloudTycInfoViewModel, Number> scoreColumn;
|
||||
|
||||
@FXML
|
||||
public TableColumn<CloudTycInfoViewModel, String> descriptionColumn;
|
||||
|
||||
@Override
|
||||
public CloudTycService getViewModelService() {
|
||||
return cloudTycService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CloudTycManagerSkin createDefaultSkin() {
|
||||
return new CloudTycManagerSkin(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show(Stage stage) {
|
||||
super.show(stage);
|
||||
getTitle().set("数据源:天眼查");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.ecep.contract.controller.cloud.u8;
|
||||
|
||||
import com.ecep.contract.controller.BaseController;
|
||||
import org.controlsfx.control.ToggleSwitch;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.constant.CloudYuConstant;
|
||||
import com.ecep.contract.util.BooleanConfig;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.util.LocalDateConfig;
|
||||
import com.ecep.contract.util.LocalDateTimeConfig;
|
||||
import com.ecep.contract.util.StringConfig;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.DatePicker;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.stage.WindowEvent;
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
@FxmlPath("/ui/cloud/u8_config.fxml")
|
||||
public class YongYouU8ConfigWindowController extends BaseController {
|
||||
@FXML
|
||||
private DatePicker auto_create_company_after;
|
||||
|
||||
@FXML
|
||||
private TextField contract_latest_date;
|
||||
|
||||
@FXML
|
||||
private TextField contract_latest_id;
|
||||
|
||||
@FXML
|
||||
private TextField sync_elapse;
|
||||
@FXML
|
||||
private ToggleSwitch use_latest_id;
|
||||
|
||||
LocalDateConfig config1 = new LocalDateConfig(CloudYuConstant.KEY_AUTO_CREATE_COMPANY_AFTER);
|
||||
LocalDateTimeConfig config2 = new LocalDateTimeConfig(CloudYuConstant.KEY_SYNC_BY_LATEST_DATE);
|
||||
StringConfig config3 = new StringConfig(CloudYuConstant.KEY_SYNC_BY_LATEST_ID);
|
||||
StringConfig config4 = new StringConfig(CloudYuConstant.KEY_SYNC_ELAPSE);
|
||||
BooleanConfig config5 = new BooleanConfig(CloudYuConstant.KEY_SYNC_USE_LATEST_ID);
|
||||
|
||||
@Override
|
||||
public void onShown(WindowEvent windowEvent) {
|
||||
super.onShown(windowEvent);
|
||||
getTitle().set("用友U8配置");
|
||||
|
||||
auto_create_company_after.setConverter(getCurrentEmployee().getLocalDateStringConverter());
|
||||
config1.setControl(auto_create_company_after);
|
||||
config1.initialize();
|
||||
|
||||
config2.setControl(contract_latest_date);
|
||||
config2.setControlConverter(getCurrentEmployee().getLocalDateTimeStringConverter());
|
||||
config2.initialize();
|
||||
|
||||
config3.setControl(contract_latest_id);
|
||||
config3.initialize();
|
||||
|
||||
config4.setControl(sync_elapse);
|
||||
config4.initialize();
|
||||
|
||||
config5.setControl(use_latest_id);
|
||||
config5.initialize();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.ecep.contract.controller.cloud.u8;
|
||||
|
||||
import com.ecep.contract.controller.AbstEntityManagerSkin;
|
||||
import com.ecep.contract.controller.ManagerSkin;
|
||||
import com.ecep.contract.controller.company.CompanyWindowController;
|
||||
import com.ecep.contract.controller.table.cell.CompanyTableCell;
|
||||
import com.ecep.contract.controller.table.cell.LocalDateFieldTableCell;
|
||||
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.service.YongYouU8Service;
|
||||
import com.ecep.contract.vm.CloudYuInfoViewModel;
|
||||
import com.ecep.contract.vo.CloudYuVo;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.cell.CheckBoxTableCell;
|
||||
|
||||
public class YongYouU8ManagerSkin
|
||||
extends
|
||||
AbstEntityManagerSkin<CloudYuVo, CloudYuInfoViewModel, YongYouU8ManagerSkin, YongYouU8ManagerWindowController>
|
||||
implements ManagerSkin {
|
||||
|
||||
public YongYouU8ManagerSkin(YongYouU8ManagerWindowController controller) {
|
||||
super(controller);
|
||||
}
|
||||
|
||||
YongYouU8Service getU8Service() {
|
||||
return getBean(YongYouU8Service.class);
|
||||
}
|
||||
|
||||
CompanyService getCompanyService() {
|
||||
return getBean(CompanyService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTable() {
|
||||
controller.idColumn.setCellValueFactory(param -> param.getValue().getId());
|
||||
|
||||
controller.companyColumn.setCellValueFactory(param -> param.getValue().getCompany());
|
||||
controller.companyColumn.setCellFactory( CompanyTableCell.forTableColumn(getCompanyService()));
|
||||
|
||||
controller.latestUpdateColumn.setCellValueFactory(param -> param.getValue().getLatestUpdate());
|
||||
controller.latestUpdateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
|
||||
|
||||
controller.cloudLatestColumn.setCellValueFactory(param -> param.getValue().getCloudLatest());
|
||||
controller.cloudLatestColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
|
||||
|
||||
controller.cloudVendorUpdateDateColumn.setCellValueFactory(param -> param.getValue().getVendorUpdateDate());
|
||||
|
||||
controller.cloudCustomerUpdateDateColumn.setCellValueFactory(param -> param.getValue().getCustomerUpdateDate());
|
||||
|
||||
controller.activeColumn.setCellValueFactory(param -> param.getValue().getActive());
|
||||
controller.activeColumn.setCellFactory(CheckBoxTableCell.forTableColumn(controller.activeColumn));
|
||||
|
||||
controller.descriptionColumn.setCellValueFactory(param -> param.getValue().getExceptionMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createContextMenu(ContextMenu contextMenu) {
|
||||
MenuItem item2 = new MenuItem("刷新");
|
||||
item2.setOnAction(this::onTableRefreshAction);
|
||||
|
||||
MenuItem item3 = new MenuItem("清空备注");
|
||||
item3.setOnAction(this::onTableClearDescriptionAction);
|
||||
|
||||
contextMenu.getItems().addAll(item2, item3);
|
||||
}
|
||||
|
||||
/**
|
||||
* 请空选择行的注释
|
||||
*
|
||||
* @param event event
|
||||
*/
|
||||
public void onTableClearDescriptionAction(ActionEvent event) {
|
||||
ObservableList<CloudYuInfoViewModel> selectedItems = getTableView().getSelectionModel().getSelectedItems();
|
||||
if (selectedItems.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (CloudYuInfoViewModel selectedItem : selectedItems) {
|
||||
CloudYuVo yongYouU8Vo = getU8Service().findById(selectedItem.getId().get());
|
||||
selectedItem.getExceptionMessage().set("");
|
||||
if (selectedItem.copyTo(yongYouU8Vo)) {
|
||||
CloudYuVo saved = getU8Service().save(yongYouU8Vo);
|
||||
selectedItem.update(saved);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTableRowDoubleClickedAction(CloudYuInfoViewModel item) {
|
||||
Integer companyId = item.getCompany().get();
|
||||
CompanyVo company = getCompanyService().findById(companyId);
|
||||
CompanyWindowController.show(company, getTableView().getScene().getWindow());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.ecep.contract.controller.cloud.u8;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import com.ecep.contract.controller.AbstManagerWindowController;
|
||||
import com.ecep.contract.controller.BaseController;
|
||||
import com.ecep.contract.task.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.Desktop;
|
||||
import com.ecep.contract.service.YongYouU8Service;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.CloudYuInfoViewModel;
|
||||
import com.ecep.contract.vo.CloudYuVo;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
@FxmlPath("/ui/cloud/u8_manager.fxml")
|
||||
public class YongYouU8ManagerWindowController
|
||||
extends AbstManagerWindowController<CloudYuVo, CloudYuInfoViewModel, YongYouU8ManagerSkin> {
|
||||
private static final Logger logger = LoggerFactory.getLogger(YongYouU8ManagerWindowController.class);
|
||||
|
||||
public static void show() {
|
||||
show(YongYouU8ManagerWindowController.class, null);
|
||||
}
|
||||
|
||||
public TableColumn<CloudYuInfoViewModel, Number> idColumn;
|
||||
public TableColumn<CloudYuInfoViewModel, LocalDateTime> latestUpdateColumn;
|
||||
public TableColumn<CloudYuInfoViewModel, Integer> companyColumn;
|
||||
public TableColumn<CloudYuInfoViewModel, LocalDateTime> cloudLatestColumn;
|
||||
public TableColumn<CloudYuInfoViewModel, java.time.LocalDate> cloudVendorUpdateDateColumn;
|
||||
public TableColumn<CloudYuInfoViewModel, java.time.LocalDate> cloudCustomerUpdateDateColumn;
|
||||
public TableColumn<CloudYuInfoViewModel, Boolean> activeColumn;
|
||||
public TableColumn<CloudYuInfoViewModel, String> descriptionColumn;
|
||||
|
||||
@Override
|
||||
public YongYouU8Service getViewModelService() {
|
||||
return getSkin().getU8Service();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected YongYouU8ManagerSkin createDefaultSkin() {
|
||||
return new YongYouU8ManagerSkin(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show(Stage stage) {
|
||||
super.show(stage);
|
||||
getTitle().set("用友U8");
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开配置窗口
|
||||
*/
|
||||
public void onConfigAction(ActionEvent event) {
|
||||
BaseController.show(YongYouU8ConfigWindowController.class, null);
|
||||
}
|
||||
|
||||
public void onPersonSyncAction(ActionEvent event) {
|
||||
EmployeesSyncTask task = new EmployeesSyncTask();
|
||||
UITools.showTaskDialogAndWait("员工数据同步", task, null);
|
||||
}
|
||||
|
||||
public void onContractSyncAction(ActionEvent event) {
|
||||
ContractSyncTask task = new ContractSyncTask();
|
||||
UITools.showTaskDialogAndWait("合同数据增量同步", task, null);
|
||||
}
|
||||
|
||||
public void onContractAllSyncAction(ActionEvent event) {
|
||||
ContractSyncAllTask task = new ContractSyncAllTask();
|
||||
UITools.showTaskDialogAndWait("合同数据全量同步", task, null);
|
||||
}
|
||||
|
||||
public void onVendorSyncAction(ActionEvent event) {
|
||||
VendorSyncTask task = new VendorSyncTask();
|
||||
UITools.showTaskDialogAndWait("供应商数据同步", task, null);
|
||||
}
|
||||
|
||||
public void onCustomerSyncAction(ActionEvent event) {
|
||||
CustomerSyncTask task = new CustomerSyncTask();
|
||||
UITools.showTaskDialogAndWait("客户数据同步", task, null);
|
||||
}
|
||||
|
||||
public void onContractGroupSyncAction(ActionEvent event) {
|
||||
ContractGroupSyncTask task = new ContractGroupSyncTask();
|
||||
UITools.showTaskDialogAndWait("合同组数据同步", task, null);
|
||||
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
|
||||
}
|
||||
|
||||
public void onContractTypeSyncAction(ActionEvent event) {
|
||||
ContractTypeSyncTask task = new ContractTypeSyncTask();
|
||||
UITools.showTaskDialogAndWait("合同类型数据同步", task, null);
|
||||
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
|
||||
}
|
||||
|
||||
public void onContractKindSyncAction(ActionEvent event) {
|
||||
ContractKindSyncTask task = new ContractKindSyncTask();
|
||||
UITools.showTaskDialogAndWait("合同类型数据同步", task, null);
|
||||
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
|
||||
}
|
||||
|
||||
public void onVendorClassSyncAction(ActionEvent event) {
|
||||
VendorClassSyncTask task = new VendorClassSyncTask();
|
||||
UITools.showTaskDialogAndWait("客户分类数据同步", task, null);
|
||||
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
|
||||
}
|
||||
|
||||
public void onCustomerClassSyncAction(ActionEvent event) {
|
||||
CustomerClassSyncTask task = new CustomerClassSyncTask();
|
||||
UITools.showTaskDialogAndWait("客户分类数据同步", task, null);
|
||||
// Desktop.instance.getTaskMonitorCenter().registerAndStartTask(task);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.ecep.contract.controller.company;
|
||||
|
||||
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.vm.CompanyViewModel;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
|
||||
import javafx.concurrent.Task;
|
||||
|
||||
public abstract class AbstCompanyBasedTabSkin
|
||||
extends AbstEntityBasedTabSkin<CompanyWindowController, CompanyVo, CompanyViewModel>
|
||||
implements TabSkin {
|
||||
|
||||
public AbstCompanyBasedTabSkin(CompanyWindowController controller) {
|
||||
super(controller);
|
||||
}
|
||||
|
||||
protected CompanyService getCompanyService() {
|
||||
Task<CompanyService> task = new Task<>() {
|
||||
@Override
|
||||
protected CompanyService call() throws Exception {
|
||||
return controller.getViewModelService();
|
||||
}
|
||||
};
|
||||
return controller.getViewModelService();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.ecep.contract.controller.company;
|
||||
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.controller.table.AbstEntityTableTabSkin;
|
||||
import com.ecep.contract.controller.table.TableOfTabSkin;
|
||||
import com.ecep.contract.model.IdentityEntity;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import com.ecep.contract.vm.CompanyBasedViewModel;
|
||||
import com.ecep.contract.vm.CompanyViewModel;
|
||||
import com.ecep.contract.vm.IdentityViewModel;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
|
||||
public abstract class AbstCompanyTableTabSkin<T extends IdentityEntity, TV extends IdentityViewModel<T>>
|
||||
extends AbstEntityTableTabSkin<CompanyWindowController, CompanyVo, CompanyViewModel, T, TV>
|
||||
implements TabSkin, TableOfTabSkin<CompanyVo, T, TV> {
|
||||
|
||||
public AbstCompanyTableTabSkin(CompanyWindowController controller) {
|
||||
super(controller);
|
||||
viewModel = controller.getViewModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TV createNewViewModel() {
|
||||
TV model = super.createNewViewModel();
|
||||
if (model instanceof CompanyBasedViewModel m) {
|
||||
m.getCompanyId().set(getEntity().getId());
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
protected CompanyService getCompanyService() {
|
||||
return getParentService();
|
||||
}
|
||||
|
||||
protected CompanyService getParentService() {
|
||||
return controller.getViewModelService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamUtils.Builder getSpecification(CompanyVo parent) {
|
||||
return ParamUtils.builder().equals("company", parent.getId());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
package com.ecep.contract.manager.ds.company.controller.contact;
|
||||
package com.ecep.contract.controller.company;
|
||||
|
||||
import com.ecep.contract.manager.ds.company.model.CompanyContact;
|
||||
import com.ecep.contract.manager.ds.company.repository.CompanyContactRepository;
|
||||
import com.ecep.contract.manager.ui.BaseController;
|
||||
import com.ecep.contract.manager.ds.company.vo.CompanyContactViewModel;
|
||||
import com.ecep.contract.manager.util.FxmlUtils;
|
||||
import com.ecep.contract.manager.util.UITools;
|
||||
import com.ecep.contract.controller.BaseController;
|
||||
import com.ecep.contract.service.CompanyContactService;
|
||||
import com.ecep.contract.util.FxmlUtils;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.CompanyContactViewModel;
|
||||
import com.ecep.contract.vo.CompanyContactVo;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
@@ -25,13 +25,14 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
public class CompanyContactWindowController extends BaseController {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CompanyContactWindowController.class);
|
||||
|
||||
/**
|
||||
* 显示界面
|
||||
*/
|
||||
@@ -59,8 +60,9 @@ public class CompanyContactWindowController extends BaseController {
|
||||
@Getter
|
||||
@Setter
|
||||
private CompanyContactViewModel viewModel;
|
||||
|
||||
@Autowired
|
||||
private CompanyContactRepository companyContactRepository;
|
||||
private CompanyContactService companyContactService;
|
||||
|
||||
public TextField nameField;
|
||||
public TextField positionField;
|
||||
@@ -73,12 +75,13 @@ public class CompanyContactWindowController extends BaseController {
|
||||
public Label versionLabel;
|
||||
public Button saveBtn;
|
||||
|
||||
private CompletableFuture<CompanyContact> companyContactLoadedFuture;
|
||||
private CompletableFuture<CompanyContactVo> companyContactLoadedFuture;
|
||||
|
||||
@Override
|
||||
public void show(Stage stage) {
|
||||
super.show(stage);
|
||||
getTitle().bind(viewModel.getName().map(v -> "[" + viewModel.getId().get() + "] " + viewModel.getName().getValue() + " 曾用名详情"));
|
||||
getTitle().bind(viewModel.getName()
|
||||
.map(v -> "[" + viewModel.getId().get() + "] " + viewModel.getName().getValue() + " 曾用名详情"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -91,24 +94,19 @@ public class CompanyContactWindowController extends BaseController {
|
||||
initializeBaseTab();
|
||||
|
||||
companyContactLoadedFuture = CompletableFuture.supplyAsync(() -> {
|
||||
Optional<CompanyContact> optional = companyContactRepository.findById(viewModel.getId().get());
|
||||
if (optional.isPresent()) {
|
||||
CompanyContact oldName = optional.get();
|
||||
Platform.runLater(() -> {
|
||||
viewModel.update(oldName);
|
||||
viewModel.bindListener();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("bind ViewModel({}) Listener", viewModel.getName().get());
|
||||
}
|
||||
tabPane.getSelectionModel().getSelectedItem().getOnSelectionChanged().handle(null);
|
||||
});
|
||||
return oldName;
|
||||
}
|
||||
return null;
|
||||
CompanyContactVo oldName = companyContactService.findById(viewModel.getId().get());
|
||||
Platform.runLater(() -> {
|
||||
viewModel.update(oldName);
|
||||
viewModel.bindListener();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("bind ViewModel({}) Listener", viewModel.getName().get());
|
||||
}
|
||||
tabPane.getSelectionModel().getSelectedItem().getOnSelectionChanged().handle(null);
|
||||
});
|
||||
return oldName;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void initializeBaseTab() {
|
||||
baseInfoTab.setOnSelectionChanged(event -> {
|
||||
if (logger.isDebugEnabled()) {
|
||||
@@ -121,9 +119,9 @@ public class CompanyContactWindowController extends BaseController {
|
||||
saveBtn.disableProperty().bind(viewModel.getChanged().not());
|
||||
saveBtn.setOnAction(event -> {
|
||||
try {
|
||||
CompanyContact contact = companyContactLoadedFuture.join();
|
||||
CompanyContactVo contact = companyContactLoadedFuture.join();
|
||||
viewModel.copyTo(contact);
|
||||
CompanyContact saved = companyContactRepository.save(contact);
|
||||
CompanyContactVo saved = companyContactService.save(contact);
|
||||
viewModel.update(saved);
|
||||
companyContactLoadedFuture = CompletableFuture.completedFuture(saved);
|
||||
} catch (Exception e) {
|
||||
@@ -155,7 +153,7 @@ public class CompanyContactWindowController extends BaseController {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("onBaseTabShown company contact {}", contact.getName());
|
||||
}
|
||||
// viewModel.update(contact);
|
||||
// viewModel.update(contact);
|
||||
}).exceptionally(ex -> {
|
||||
UITools.showExceptionAndWait(ex.getMessage(), ex);
|
||||
return null;
|
||||
@@ -176,4 +174,3 @@ public class CompanyContactWindowController extends BaseController {
|
||||
super.onHidden(windowEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
package com.ecep.contract.manager.ds.company.controller;
|
||||
package com.ecep.contract.controller.company;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.ecep.contract.controller.AbstEntityManagerSkin;
|
||||
import com.ecep.contract.service.CompanyOldNameService;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.vm.CompanyViewModel;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
|
||||
import com.ecep.contract.manager.ds.company.model.Company;
|
||||
import com.ecep.contract.manager.ds.company.service.CompanyOldNameService;
|
||||
import com.ecep.contract.manager.ds.company.service.CompanyService;
|
||||
import com.ecep.contract.manager.ds.company.vo.CompanyViewModel;
|
||||
import com.ecep.contract.manager.ui.AbstEntityManagerSkin;
|
||||
import com.ecep.contract.manager.ui.ManagerSkin;
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.TextInputDialog;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class CompanyManagerSkin
|
||||
extends AbstEntityManagerSkin<Company, CompanyViewModel, CompanyManagerSkin, CompanyManagerWindowController> {
|
||||
extends AbstEntityManagerSkin<CompanyVo, CompanyViewModel, CompanyManagerSkin, CompanyManagerWindowController> {
|
||||
|
||||
@Setter
|
||||
private CompanyOldNameService companyOldNameService;
|
||||
@@ -65,11 +65,11 @@ public class CompanyManagerSkin
|
||||
if (optional.isPresent()) {
|
||||
CompanyService companyService = getCompanyService();
|
||||
String newCompanyName = optional.get();
|
||||
List<Company> list = companyService.findAllByName(newCompanyName);
|
||||
List<CompanyVo> list = companyService.findAllByName(newCompanyName);
|
||||
if (list == null || list.isEmpty()) {
|
||||
// 未登记过
|
||||
Company company = companyService.createNewCompany(newCompanyName);
|
||||
Company saved = companyService.save(company);
|
||||
CompanyVo company = companyService.createNewCompany(newCompanyName);
|
||||
CompanyVo saved = companyService.save(company);
|
||||
CompanyWindowController.show(saved, getTableView().getScene().getWindow());
|
||||
} else {
|
||||
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.ecep.contract.manager.ds.company.controller;
|
||||
package com.ecep.contract.controller.company;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@@ -7,13 +7,13 @@ import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.manager.ds.company.model.Company;
|
||||
import com.ecep.contract.manager.ds.company.service.CompanyService;
|
||||
import com.ecep.contract.manager.ds.company.tasker.CompanyFilesRebuildTasker;
|
||||
import com.ecep.contract.manager.ds.company.vo.CompanyViewModel;
|
||||
import com.ecep.contract.manager.ui.AbstManagerWindowController;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.util.UITools;
|
||||
import com.ecep.contract.controller.AbstManagerWindowController;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.task.CompanyFilesRebuildTasker;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
import com.ecep.contract.vm.CompanyViewModel;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
@@ -25,7 +25,7 @@ import javafx.stage.Stage;
|
||||
@Component
|
||||
@FxmlPath("/ui/company/company-manager.fxml")
|
||||
public class CompanyManagerWindowController
|
||||
extends AbstManagerWindowController<Company, CompanyViewModel, CompanyManagerSkin> {
|
||||
extends AbstManagerWindowController<CompanyVo, CompanyViewModel, CompanyManagerSkin> {
|
||||
|
||||
// columns
|
||||
@FXML
|
||||
@@ -1,11 +1,12 @@
|
||||
package com.ecep.contract.manager.ds.company.controller;
|
||||
package com.ecep.contract.controller.company;
|
||||
|
||||
import com.ecep.contract.manager.ui.BaseController;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.controller.BaseController;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
@@ -1,32 +1,8 @@
|
||||
package com.ecep.contract.manager.ds.company.controller;
|
||||
package com.ecep.contract.controller.company;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.ecep.contract.manager.SpringApp;
|
||||
import com.ecep.contract.manager.ds.company.model.Company;
|
||||
import com.ecep.contract.manager.ds.company.service.CompanyFileService;
|
||||
import com.ecep.contract.manager.ds.company.service.CompanyService;
|
||||
import com.ecep.contract.manager.ds.company.tasker.CompanyCompositeUpdateTasker;
|
||||
import com.ecep.contract.manager.ds.company.tasker.CompanyVerifyTasker;
|
||||
import com.ecep.contract.manager.ds.company.vo.CompanyViewModel;
|
||||
import com.ecep.contract.manager.ds.contract.service.ContractService;
|
||||
import com.ecep.contract.manager.ds.customer.controller.CompanyCustomerWindowController;
|
||||
import com.ecep.contract.manager.ds.customer.model.CompanyCustomer;
|
||||
import com.ecep.contract.manager.ds.customer.service.CompanyCustomerService;
|
||||
import com.ecep.contract.manager.ds.vendor.controller.CompanyVendorWindowController;
|
||||
import com.ecep.contract.manager.ds.vendor.model.CompanyVendor;
|
||||
import com.ecep.contract.manager.ds.vendor.service.CompanyVendorService;
|
||||
import com.ecep.contract.manager.ui.tab.AbstEntityBasedTabSkin;
|
||||
import com.ecep.contract.manager.ui.AbstEntityController;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.util.DesktopUtils;
|
||||
import com.ecep.contract.manager.util.UITools;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import org.hibernate.Hibernate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -35,25 +11,59 @@ import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
import com.ecep.contract.DesktopUtils;
|
||||
import com.ecep.contract.controller.AbstEntityController;
|
||||
import com.ecep.contract.controller.customer.CompanyCustomerWindowController;
|
||||
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
|
||||
import com.ecep.contract.controller.tab.CompanyTabSkinBankAccount;
|
||||
import com.ecep.contract.controller.tab.CompanyTabSkinBase;
|
||||
import com.ecep.contract.controller.tab.CompanyTabSkinBlackReason;
|
||||
import com.ecep.contract.controller.tab.CompanyTabSkinContact;
|
||||
import com.ecep.contract.controller.tab.CompanyTabSkinContract;
|
||||
import com.ecep.contract.controller.tab.CompanyTabSkinFile;
|
||||
import com.ecep.contract.controller.tab.CompanyTabSkinInvoice;
|
||||
import com.ecep.contract.controller.tab.CompanyTabSkinOldName;
|
||||
import com.ecep.contract.controller.tab.CompanyTabSkinOther;
|
||||
import com.ecep.contract.controller.tab.CompanyTabSkinPurchaseBillVoucher;
|
||||
import com.ecep.contract.controller.vendor.VendorWindowController;
|
||||
import com.ecep.contract.service.CustomerService;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.service.VendorService;
|
||||
import com.ecep.contract.task.CompanyCompositeUpdateTasker;
|
||||
import com.ecep.contract.task.CompanyVerifyTasker;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.CompanyViewModel;
|
||||
import com.ecep.contract.vo.CustomerVo;
|
||||
import com.ecep.contract.vo.VendorVo;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.DatePicker;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TabPane;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
@FxmlPath("/ui/company/company.fxml")
|
||||
public class CompanyWindowController
|
||||
extends AbstEntityController<Company, CompanyViewModel> {
|
||||
extends AbstEntityController<CompanyVo, CompanyViewModel> {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CompanyWindowController.class);
|
||||
|
||||
|
||||
public static void show(Company company, Window window) {
|
||||
CompanyViewModel viewModel = new CompanyViewModel();
|
||||
if (!Hibernate.isInitialized(company)) {
|
||||
company = SpringApp.getBean(CompanyService.class).findById(company.getId());
|
||||
}
|
||||
viewModel.update(company);
|
||||
show(viewModel, window);
|
||||
public static void show(CompanyVo company, Window window) {
|
||||
show(CompanyViewModel.from(company), window);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,14 +75,6 @@ public class CompanyWindowController
|
||||
|
||||
@Autowired
|
||||
private CompanyService companyService;
|
||||
@Autowired
|
||||
private CompanyFileService companyFileService;
|
||||
@Autowired
|
||||
private ContractService contractService;
|
||||
@Autowired
|
||||
private CompanyCustomerService companyCustomerService;
|
||||
@Autowired
|
||||
private CompanyVendorService companyVendorService;
|
||||
|
||||
public BorderPane root;
|
||||
public TabPane tabPane;
|
||||
@@ -117,12 +119,13 @@ public class CompanyWindowController
|
||||
public Button companyPathChangeBtn;
|
||||
public Button companyPathSameAsNameBtn;
|
||||
|
||||
// private final CompanyCustomerViewModel companyCustomerViewModel = new
|
||||
// CompanyCustomerViewModel();
|
||||
// private final CompanyVendorViewModel companyVendorViewModel = new
|
||||
// CompanyVendorViewModel();
|
||||
|
||||
// private final CompanyCustomerViewModel companyCustomerViewModel = new CompanyCustomerViewModel();
|
||||
// private final CompanyVendorViewModel companyVendorViewModel = new CompanyVendorViewModel();
|
||||
|
||||
private final SimpleObjectProperty<CompanyCustomer> companyCustomerProperty = new SimpleObjectProperty<>();
|
||||
private final SimpleObjectProperty<CompanyVendor> companyVendorProperty = new SimpleObjectProperty<>();
|
||||
private final SimpleObjectProperty<CustomerVo> companyCustomerProperty = new SimpleObjectProperty<>();
|
||||
private final SimpleObjectProperty<VendorVo> companyVendorProperty = new SimpleObjectProperty<>();
|
||||
|
||||
public Pane customerTab_pane1;
|
||||
public Button customerTab_openBtn;
|
||||
@@ -134,7 +137,6 @@ public class CompanyWindowController
|
||||
public Pane vendorTab_pane2;
|
||||
public Button vendorTab_createBtn;
|
||||
|
||||
|
||||
@Override
|
||||
public void show(Stage stage) {
|
||||
super.show(stage);
|
||||
@@ -143,17 +145,6 @@ public class CompanyWindowController
|
||||
getTitle().set("[" + viewModel.getId().get() + "] " + viewModel.getName().getValue() + " 公司详情");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Company loadEntity() {
|
||||
return companyService.findById(viewModel.getId().get());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Company saveEntity(Company entity) {
|
||||
return companyService.save(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerTabSkins() {
|
||||
registerTabSkin(baseInfoTab, tab1 -> new CompanyTabSkinBase(this));
|
||||
@@ -161,18 +152,16 @@ public class CompanyWindowController
|
||||
registerTabSkin(contactTab, tab1 -> new CompanyTabSkinContact(this));
|
||||
registerTabSkin(blackReasonTab, tab1 -> new CompanyTabSkinBlackReason(this));
|
||||
registerTabSkin(bankAccountTab, tab1 -> new CompanyTabSkinBankAccount(this));
|
||||
registerTabSkin(contractTab, this::createContractTabSkin);
|
||||
registerTabSkin(fileTab, this::createFileTabSkin);
|
||||
registerTabSkin(contractTab, tab -> new CompanyTabSkinContract(this));
|
||||
registerTabSkin(fileTab, tab -> new CompanyTabSkinFile(this));
|
||||
registerTabSkin(invoiceTab, tab -> new CompanyTabSkinInvoice(this));
|
||||
registerTabSkin(purchaseBillVoucherTab, tab -> new CompanyTabSkinPurchaseBillVoucher(this));
|
||||
registerTabSkin(otherTab, tab -> new CompanyTabSkinOther(this));
|
||||
|
||||
|
||||
initializeVendorTab();
|
||||
initializeCustomerTab();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected <K extends AbstEntityBasedTabSkin<?, ?, ?>> K registerTabSkin(Tab tab, Function<Tab, K> func) {
|
||||
K skin = super.registerTabSkin(tab, func);
|
||||
@@ -187,19 +176,6 @@ public class CompanyWindowController
|
||||
return companyService;
|
||||
}
|
||||
|
||||
|
||||
private CompanyTabSkinContract createContractTabSkin(Tab tab) {
|
||||
CompanyTabSkinContract skin = new CompanyTabSkinContract(this);
|
||||
skin.setContractService(contractService);
|
||||
return skin;
|
||||
}
|
||||
|
||||
private CompanyTabSkinFile createFileTabSkin(Tab tab) {
|
||||
CompanyTabSkinFile skin = new CompanyTabSkinFile(this);
|
||||
skin.setCompanyFileService(companyFileService);
|
||||
return skin;
|
||||
}
|
||||
|
||||
private void initializeCustomerTab() {
|
||||
customerTab.setOnSelectionChanged(event -> {
|
||||
if (logger.isDebugEnabled()) {
|
||||
@@ -210,7 +186,6 @@ public class CompanyWindowController
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
customerTab_pane1.visibleProperty().bind(customerTab_pane2.visibleProperty().not());
|
||||
customerTab_pane2.visibleProperty().bind(companyCustomerProperty.isNull());
|
||||
customerTab_createBtn.setOnAction(event -> {
|
||||
@@ -227,7 +202,8 @@ public class CompanyWindowController
|
||||
logger.debug("onCustomerTabShown");
|
||||
}
|
||||
getLoadedFuture().thenAcceptAsync(company -> {
|
||||
companyCustomerProperty.set(companyCustomerService.findByCompany(company));
|
||||
CustomerVo customerVo = getCachedBean(CustomerService.class).findByCompany(company);
|
||||
companyCustomerProperty.set(customerVo);
|
||||
}).exceptionally(ex -> {
|
||||
UITools.showExceptionAndWait(ex.getMessage(), ex);
|
||||
return null;
|
||||
@@ -251,7 +227,7 @@ public class CompanyWindowController
|
||||
});
|
||||
|
||||
vendorTab_openBtn.setOnAction(event -> {
|
||||
CompanyVendorWindowController.show(companyVendorProperty.get(), root.getScene().getWindow());
|
||||
VendorWindowController.show(companyVendorProperty.get(), root.getScene().getWindow());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -263,7 +239,8 @@ public class CompanyWindowController
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("onVendorTabShown company {}", company.getName());
|
||||
}
|
||||
companyVendorProperty.set(companyVendorService.findByCompany(company));
|
||||
VendorService vendorService = getBean(VendorService.class);
|
||||
companyVendorProperty.set(vendorService.findByCompany(company));
|
||||
}).exceptionally(ex -> {
|
||||
UITools.showExceptionAndWait(ex.getMessage(), ex);
|
||||
return null;
|
||||
@@ -277,29 +254,29 @@ public class CompanyWindowController
|
||||
CompanyCompositeUpdateTasker task = new CompanyCompositeUpdateTasker();
|
||||
task.setCompany(getEntity());
|
||||
UITools.showTaskDialogAndWait("更新企业信息", task, null);
|
||||
refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* 企业合规检查
|
||||
*/
|
||||
public void onCompanyVerifyAction(ActionEvent event) {
|
||||
Company company = getEntity();
|
||||
|
||||
CompanyVo company = getEntity();
|
||||
CompanyVerifyTasker task = new CompanyVerifyTasker();
|
||||
task.setCompanyService(companyService);
|
||||
task.setCompany(company);
|
||||
UITools.showTaskDialogAndWait("企业合规性验证", task, null);
|
||||
refresh();
|
||||
}
|
||||
|
||||
public void onCompanyOpenInExplorerAction(ActionEvent event) {
|
||||
Company company = getEntity();
|
||||
CompanyVo company = getEntity();
|
||||
String path = company.getPath();
|
||||
|
||||
CompletableFuture.runAsync(() -> {
|
||||
if (!StringUtils.hasText(path)) {
|
||||
ButtonType buttonType = UITools.showConfirmation("目录未设置", "是否创建目录").join();
|
||||
if (buttonType == ButtonType.OK) {
|
||||
if (companyService.makePathAbsent(company)) {
|
||||
if (companyService.makePathAbsent(company, (level, message) -> setStatus(message))) {
|
||||
save(company);
|
||||
}
|
||||
} else {
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.ecep.contract.controller.company.bank_account;
|
||||
|
||||
import com.ecep.contract.controller.company.CompanyWindowController;
|
||||
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.service.BankService;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.CompanyBankAccountViewModel;
|
||||
import com.ecep.contract.vo.CompanyBankAccountVo;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
|
||||
import javafx.scene.control.Tab;
|
||||
|
||||
public class BankAccountBaseTabSkin
|
||||
extends AbstEntityBasedTabSkin<BankAccountWindowController, CompanyBankAccountVo, CompanyBankAccountViewModel>
|
||||
implements TabSkin {
|
||||
|
||||
public BankAccountBaseTabSkin(BankAccountWindowController controller) {
|
||||
super(controller);
|
||||
viewModel = controller.getViewModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tab getTab() {
|
||||
return controller.baseInfoTab;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTab() {
|
||||
UITools.autoCompletion(controller.companyField, viewModel.getCompanyId(), getCompanyService());
|
||||
|
||||
UITools.autoCompletion(controller.bankField, viewModel.getBankId(), getBankService());
|
||||
|
||||
controller.openingBankField.textProperty().bindBidirectional(viewModel.getOpeningBank());
|
||||
controller.bankAccountField.textProperty().bindBidirectional(viewModel.getAccount());
|
||||
|
||||
// controller.descriptionField.textProperty().bindBidirectional(viewModel.getDescription());
|
||||
controller.createdField.textProperty().bind(viewModel.getCreated().asString());
|
||||
controller.versionLabel.textProperty().bind(viewModel.getVersion().asString());
|
||||
|
||||
controller.relativeCompanyBtn.disableProperty().bind(viewModel.getCompanyId().isNull());
|
||||
controller.relativeCompanyBtn.setOnAction(event -> {
|
||||
Integer companyId = viewModel.getCompanyId().get();
|
||||
if (companyId != null) {
|
||||
CompanyVo company = getCompanyService().findById(companyId);
|
||||
CompanyWindowController.show(company, controller.root.getScene().getWindow());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public BankService getBankService() {
|
||||
return getCachedBean(BankService.class);
|
||||
}
|
||||
|
||||
public CompanyService getCompanyService() {
|
||||
return getCachedBean(CompanyService.class);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,31 @@
|
||||
package com.ecep.contract.manager.ds.company.controller.bank_account;
|
||||
package com.ecep.contract.controller.company.bank_account;
|
||||
|
||||
import com.ecep.contract.manager.ds.company.model.CompanyBankAccount;
|
||||
import com.ecep.contract.manager.ds.company.service.CompanyBankAccountService;
|
||||
import com.ecep.contract.manager.ds.company.vo.CompanyBankAccountViewModel;
|
||||
import com.ecep.contract.manager.ui.AbstEntityController;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.Window;
|
||||
import javafx.stage.WindowEvent;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.controller.AbstEntityController;
|
||||
import com.ecep.contract.service.CompanyBankAccountService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.vm.CompanyBankAccountViewModel;
|
||||
import com.ecep.contract.vo.CompanyBankAccountVo;
|
||||
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TabPane;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.Window;
|
||||
import javafx.stage.WindowEvent;
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
@FxmlPath("/ui/company/bank-account.fxml")
|
||||
public class BankAccountWindowController extends AbstEntityController<CompanyBankAccount, CompanyBankAccountViewModel> {
|
||||
public class BankAccountWindowController extends AbstEntityController<CompanyBankAccountVo, CompanyBankAccountViewModel> {
|
||||
public BorderPane root;
|
||||
public TabPane tabPane;
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
package com.ecep.contract.manager.ds.company.controller.old_name;
|
||||
package com.ecep.contract.controller.company.old_name;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.service.CompanyOldNameService;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.vo.CompanyOldNameVo;
|
||||
import com.ecep.contract.vm.CompanyOldNameViewModel;
|
||||
|
||||
import com.ecep.contract.manager.ds.company.model.CompanyOldName;
|
||||
import com.ecep.contract.manager.ds.company.service.CompanyOldNameService;
|
||||
import com.ecep.contract.manager.ds.company.service.CompanyService;
|
||||
import com.ecep.contract.manager.ds.company.vo.CompanyOldNameViewModel;
|
||||
import com.ecep.contract.manager.ui.tab.AbstEntityBasedTabSkin;
|
||||
import com.ecep.contract.manager.ui.tab.TabSkin;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.util.converter.LocalDateStringConverter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
|
||||
public class CompanyOldNameTabSkinBase
|
||||
extends AbstEntityBasedTabSkin<CompanyOldNameWindowController, CompanyOldName, CompanyOldNameViewModel>
|
||||
extends AbstEntityBasedTabSkin<CompanyOldNameWindowController, CompanyOldNameVo, CompanyOldNameViewModel>
|
||||
implements TabSkin {
|
||||
@Setter
|
||||
private CompanyService companyService;
|
||||
@@ -0,0 +1,366 @@
|
||||
package com.ecep.contract.controller.company.old_name;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.ecep.contract.CompanyFileType;
|
||||
import com.ecep.contract.DesktopUtils;
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.controller.table.AbstEntityTableTabSkin;
|
||||
import com.ecep.contract.service.CompanyFileService;
|
||||
import com.ecep.contract.service.CompanyOldNameService;
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import com.ecep.contract.util.ParamUtils.Builder;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.CompanyFileViewModel;
|
||||
import com.ecep.contract.vm.CompanyOldNameViewModel;
|
||||
import com.ecep.contract.vo.CompanyFileVo;
|
||||
import com.ecep.contract.vo.CompanyOldNameVo;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.Event;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableView;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class CompanyOldNameTabSkinFile
|
||||
extends
|
||||
AbstEntityTableTabSkin<CompanyOldNameWindowController, CompanyOldNameVo, CompanyOldNameViewModel, CompanyFileVo, CompanyFileViewModel>
|
||||
implements TabSkin {
|
||||
@Setter
|
||||
private CompanyOldNameService companyOldNameService;
|
||||
@Setter
|
||||
private CompanyFileService companyFileService;
|
||||
|
||||
public CompanyOldNameTabSkinFile(CompanyOldNameWindowController controller) {
|
||||
super(controller);
|
||||
setDragAndDrop(true);
|
||||
setDragAndDropFileHandler(this::moveFileToCompany);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tab getTab() {
|
||||
return controller.fileTab;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableView<CompanyFileViewModel> getTableView() {
|
||||
return controller.fileTable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CompanyFileService getViewModelService() {
|
||||
return companyFileService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder getSpecification(CompanyOldNameVo parent) {
|
||||
return ParamUtils.builder().equals("company", parent.getCompanyId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTab() {
|
||||
// controller.fileTable_file_move_btn.setOnAction(this::onTableMoveFileAction);
|
||||
// controller.fileTable_file_retrieve_from_download_dir_btn.setOnAction(this::onTableRetrieveFromDownloadDirAction);
|
||||
// controller.fileTable_file_reset_btn.setOnAction(this::onTableResetAction);
|
||||
//
|
||||
//
|
||||
// controller.fileTable_idColumn.setCellValueFactory(param ->
|
||||
// param.getValue().getId());
|
||||
// ObservableMap<CompanyFileType, CompanyFileTypeLocal> observableMapByLocal =
|
||||
// getBean(CompanyFileTypeLocalRepository.class).getObservableMapByLocal();
|
||||
// controller.fileTable_typeColumn.setCellValueFactory(param ->
|
||||
// Bindings.valueAt(observableMapByLocal,
|
||||
// param.getValue().getType()).map(CompanyFileTypeLocal::getValue));
|
||||
// controller.fileTable_filePathColumn.setCellValueFactory(param ->
|
||||
// param.getValue().getFilePath());
|
||||
// controller.fileTable_filePathColumn.setCellFactory(param -> new
|
||||
// FileTableFilePathTableCell());
|
||||
// controller.fileTable_applyDateColumn.setCellValueFactory(param ->
|
||||
// param.getValue().getApplyDate());
|
||||
// controller.fileTable_expiringDateColumn.setCellValueFactory(param ->
|
||||
// param.getValue().getExpiringDate());
|
||||
//
|
||||
//
|
||||
// controller.fileTable_menu_refresh.setOnAction(this::onTableRefreshAction);
|
||||
// controller.fileTable_menu_del.setOnAction(this::onTableDeleteAction);
|
||||
// controller.fileTable_menu_copy_as_matched_by_contract.setOnAction(this::onTableCopyAsMatchedByContractAction);
|
||||
// controller.fileTable_menu_copy_as_matched_by_contract.setOnMenuValidation(this::onTableCopyAsMatchedMenuValidation);
|
||||
|
||||
super.initializeTab();
|
||||
}
|
||||
|
||||
private void onTableResetAction(ActionEvent event) {
|
||||
CompanyOldNameVo oldName = getParent();
|
||||
// CompletableFuture.runAsync(() -> {
|
||||
// if (getCompanyFileService().reBuildingFiles(oldName, this::setStatus)) {
|
||||
// loadTableDataSet();
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 下载目录 中查找相关的资质文件
|
||||
*/
|
||||
private void onTableRetrieveFromDownloadDirAction(ActionEvent event) {
|
||||
// CompanyOldName oldName = getParent();
|
||||
// MyProperties myProperties = getMyProperties();
|
||||
// File dir = myProperties.getDownloadDirectory();
|
||||
// if (!dir.exists()) {
|
||||
// setStatus("下载目录 " + dir.getAbsolutePath() + " 不存在,请检查");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// setStatus("开始检索 下载 文件夹:" + dir.getAbsolutePath() + "...");
|
||||
// File[] files = dir.listFiles(File::isFile);
|
||||
// if (files == null) {
|
||||
// setStatus("检索 下载 文件夹失败");
|
||||
// return;
|
||||
// }
|
||||
// if (files.length == 0) {
|
||||
// setStatus("下载 文件夹没有文件");
|
||||
// return;
|
||||
// }
|
||||
// setStatus("下载 文件夹中共有文件 " + files.length + " 个文件");
|
||||
//
|
||||
// if (getCompanyOldNameService().retrieveFromDownloadFiles(oldName, files,
|
||||
// this::setStatus)) {
|
||||
// // fixed if update
|
||||
// viewModel.update(oldName);
|
||||
// loadTableDataSet();
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* 把文件从 老系统中移到 \\10.84.209.8\项目信息\相关方信息 目录中
|
||||
*/
|
||||
private void onTableMoveFileAction(ActionEvent event) {
|
||||
// CompanyFileService companyFileService = getCompanyFileService();
|
||||
// CompanyOldName oldName = getParent();
|
||||
// List<CompanyFile> list = companyFileService.findByCompany(oldName);
|
||||
// if (list.isEmpty()) {
|
||||
// return;
|
||||
// }
|
||||
// if (getCompanyService().makePathAbsent(oldName)) {
|
||||
// save(oldName);
|
||||
// }
|
||||
//
|
||||
// String path = oldName.getPath();
|
||||
// if (!StringUtils.hasText(path)) {
|
||||
// setStatus("异常, 企业目录未设置");
|
||||
// return;
|
||||
// }
|
||||
// File companyPath = new File(path);
|
||||
// for (CompanyFile companyFile : list) {
|
||||
// String filePath = companyFile.getFilePath();
|
||||
// if (StringUtils.hasText(filePath)) {
|
||||
// File file = new File(filePath);
|
||||
// if (file.exists()) {
|
||||
// if (file.getParentFile().equals(companyPath)) {
|
||||
// continue;
|
||||
// }
|
||||
// File dest = new File(companyPath, file.getName());
|
||||
// if (file.renameTo(dest)) {
|
||||
// companyFile.setFilePath(dest.getAbsolutePath());
|
||||
// companyFileService.save(companyFile);
|
||||
// setStatus(file.getName() + " 移动到 " + companyPath.getName());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private void onTableCopyAsMatchedByContractAction(ActionEvent event) {
|
||||
UITools.showDialogAndWait("复制资信评估报告", "按当前评估报告复制一个合同中最匹配的", list -> {
|
||||
onTableCopyAsMatchedAction_(msg -> {
|
||||
Platform.runLater(() -> {
|
||||
list.add(msg);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void onTableCopyAsMatchedAction_(Consumer<String> state) {
|
||||
// CompanyOldName oldName = getParent();
|
||||
//
|
||||
// CompanyFileViewModel selectedItem =
|
||||
// table.getSelectionModel().getSelectedItem();
|
||||
// if (selectedItem == null) {
|
||||
// state.accept("未选择行");
|
||||
// return;
|
||||
// }
|
||||
// if (selectedItem.getApplyDate().get() == null) {
|
||||
// state.accept("有效日期不能未空");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// LocalDate nextCreditReportDate = null;
|
||||
// try {
|
||||
// nextCreditReportDate = companyFileService.getNextCreditReportDate(oldName,
|
||||
// state);
|
||||
// if (nextCreditReportDate == null) {
|
||||
// state.accept("没有找到下一个咨询评估日期");
|
||||
// return;
|
||||
// }
|
||||
// } catch (Exception e) {
|
||||
// state.accept("获取下一个咨询评估日期失败:" + e.getMessage());
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// state.accept("下一个咨询评估日期:" + nextCreditReportDate);
|
||||
//
|
||||
// if (!nextCreditReportDate.isBefore(selectedItem.getApplyDate().get())) {
|
||||
// state.accept("咨询评估日期晚于下一个咨询评估日期");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// File src = new File(selectedItem.getFilePath().get());
|
||||
// if (!src.exists()) {
|
||||
// state.accept("当前选择行的文件不存在");
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// String srcDate = MyDateTimeUtils.format(selectedItem.getApplyDate().get());
|
||||
// String destDate = MyDateTimeUtils.format(nextCreditReportDate);
|
||||
// String srcFileName = src.getName();
|
||||
// String destFileName;
|
||||
//
|
||||
// // 天眼查的报告
|
||||
// if (CloudTycService.isTycReport(srcFileName)) {
|
||||
// state.accept("天眼查的报告按标准格式命名");
|
||||
// String name = oldName.getName() + "_" + CloudTycService.NAME;
|
||||
// if (srcFileName.contains(CloudTycService.TYC_ENTERPRISE_BASIC_REPORT)) {
|
||||
// name = name + "_" + CloudTycService.TYC_ENTERPRISE_BASIC_REPORT;
|
||||
// } else if (srcFileName.contains(CloudTycService.TYC_ENTERPRISE_MAJOR_REPORT))
|
||||
// {
|
||||
// name = name + "_" + CloudTycService.TYC_ENTERPRISE_MAJOR_REPORT;
|
||||
// } else if
|
||||
// (srcFileName.contains(CloudTycService.TYC_ENTERPRISE_ANALYSIS_REPORT)) {
|
||||
// name = name + "_" + CloudTycService.TYC_ENTERPRISE_ANALYSIS_REPORT;
|
||||
// }
|
||||
// destFileName = name + "_" + destDate + "_cp." +
|
||||
// StringUtils.getFilenameExtension(srcFileName);
|
||||
// } else {
|
||||
// if (srcFileName.contains(srcDate)) {
|
||||
// // 如果文件名中包含日期,则替换为新日期
|
||||
// destFileName = srcFileName.replace(srcDate, destDate + "_cp");
|
||||
// } else {
|
||||
// // 如果文件名中不包含日期,则添加日期
|
||||
// destFileName = oldName.getName() + "_" + destDate + "_cp." +
|
||||
// StringUtils.getFilenameExtension(srcFileName);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// state.accept("新文件名:" + destFileName);
|
||||
//
|
||||
// File dest = new File(src.getParent(), destFileName);
|
||||
// try {
|
||||
// FileSystemUtils.copyRecursively(src, dest);
|
||||
// state.accept("新文件复制成功");
|
||||
// } catch (IOException e) {
|
||||
// state.accept("新文件复制失败:" + e.getMessage());
|
||||
// }
|
||||
//
|
||||
// CompanyFile companyFile = new CompanyFile();
|
||||
// companyFile.setFilePath(dest.getAbsolutePath());
|
||||
// companyFile.setApplyDate(nextCreditReportDate);
|
||||
// companyFile.setExpiringDate(nextCreditReportDate.plusYears(1));
|
||||
// companyFile.setType(CompanyFileType.CreditReport);
|
||||
// companyFile.setCompany(oldName);
|
||||
// companyFileService.save(companyFile);
|
||||
//
|
||||
// state.accept("新文件已记录");
|
||||
//
|
||||
// loadTableDataSet();
|
||||
// state.accept("文件表已刷新");
|
||||
}
|
||||
|
||||
/**
|
||||
* 当fileTable选中的行是咨询评估时,可用
|
||||
*
|
||||
* @param event event
|
||||
*/
|
||||
public void onTableCopyAsMatchedMenuValidation(Event event) {
|
||||
// 当fileTable选中的行是咨询评估时,可用
|
||||
CompanyFileViewModel selectedItem = getSelectedItem();
|
||||
if (selectedItem == null) {
|
||||
event.consume();
|
||||
return;
|
||||
}
|
||||
CompanyFileType type = selectedItem.getType().get();
|
||||
if (type != CompanyFileType.CreditReport) {
|
||||
event.consume();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void moveFileToCompany(List<File> files) {
|
||||
String path = viewModel.getPath().get();
|
||||
if (!StringUtils.hasText(path)) {
|
||||
setStatus("未设置目录");
|
||||
return;
|
||||
}
|
||||
File dir = new File(path);
|
||||
if (!dir.exists()) {
|
||||
setStatus("目录错误,不存在");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTableRowDoubleClickedAction(CompanyFileViewModel item) {
|
||||
String path = item.getFilePath().get();
|
||||
if (StringUtils.hasText(path)) {
|
||||
File file = new File(path);
|
||||
if (!file.exists()) {
|
||||
setStatus("文件不存在 " + file.getName());
|
||||
return;
|
||||
}
|
||||
DesktopUtils.showInExplorer(file);
|
||||
}
|
||||
}
|
||||
|
||||
class FileTableFilePathTableCell extends TableCell<CompanyFileViewModel, String> {
|
||||
@Override
|
||||
protected void updateItem(String item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty || item == null) {
|
||||
setText("");
|
||||
return;
|
||||
}
|
||||
String path = viewModel.getPath().get();
|
||||
if (StringUtils.hasText(path)) {
|
||||
if (item.startsWith(path)) {
|
||||
item = "~" + item.substring(path.length());
|
||||
}
|
||||
}
|
||||
setText(item);
|
||||
}
|
||||
}
|
||||
|
||||
private CompanyFileService getCompanyFileService() {
|
||||
if (companyFileService == null) {
|
||||
companyFileService = getBean(CompanyFileService.class);
|
||||
}
|
||||
return companyFileService;
|
||||
}
|
||||
|
||||
public CompanyOldNameService getCompanyOldNameService() {
|
||||
if (companyOldNameService == null) {
|
||||
companyOldNameService = getBean(CompanyOldNameService.class);
|
||||
}
|
||||
return companyOldNameService;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,44 @@
|
||||
package com.ecep.contract.manager.ds.company.controller.old_name;
|
||||
package com.ecep.contract.controller.company.old_name;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import com.ecep.contract.manager.ds.company.model.CompanyOldName;
|
||||
import com.ecep.contract.manager.ds.company.service.CompanyOldNameService;
|
||||
import com.ecep.contract.manager.ds.company.service.CompanyService;
|
||||
import com.ecep.contract.manager.ds.company.vo.CompanyFileViewModel;
|
||||
import com.ecep.contract.manager.ds.company.vo.CompanyOldNameViewModel;
|
||||
import com.ecep.contract.manager.ui.AbstEntityController;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.ui.ViewModelService;
|
||||
import com.ecep.contract.manager.util.DesktopUtils;
|
||||
import com.ecep.contract.manager.util.UITools;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.Window;
|
||||
import javafx.stage.WindowEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import com.ecep.contract.DesktopUtils;
|
||||
import com.ecep.contract.controller.AbstEntityController;
|
||||
import com.ecep.contract.service.CompanyOldNameService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vo.CompanyOldNameVo;
|
||||
import com.ecep.contract.vm.CompanyFileViewModel;
|
||||
import com.ecep.contract.vm.CompanyOldNameViewModel;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.DatePicker;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TabPane;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.Window;
|
||||
import javafx.stage.WindowEvent;
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
@FxmlPath("/ui/company/company_old_name.fxml")
|
||||
public class CompanyOldNameWindowController extends AbstEntityController<CompanyOldName, CompanyOldNameViewModel> {
|
||||
public class CompanyOldNameWindowController extends AbstEntityController<CompanyOldNameVo, CompanyOldNameViewModel> {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CompanyOldNameWindowController.class);
|
||||
|
||||
/**
|
||||
@@ -45,12 +53,6 @@ public class CompanyOldNameWindowController extends AbstEntityController<Company
|
||||
public TabPane tabPane;
|
||||
public Tab fileTab;
|
||||
|
||||
|
||||
@Autowired
|
||||
private CompanyOldNameService companyOldNameService;
|
||||
@Autowired
|
||||
private CompanyService companyService;
|
||||
|
||||
public TextField nameField;
|
||||
public CheckBox ambiguityField;
|
||||
public DatePicker startDateField;
|
||||
@@ -62,26 +64,14 @@ public class CompanyOldNameWindowController extends AbstEntityController<Company
|
||||
public Button saveBtn;
|
||||
public Button saveBtn2;
|
||||
|
||||
|
||||
public TableView<CompanyFileViewModel> fileTable;
|
||||
|
||||
|
||||
@Override
|
||||
public void onShown(WindowEvent windowEvent) {
|
||||
super.onShown(windowEvent);
|
||||
getTitle().set("[" + viewModel.getId().get() + "] " + viewModel.getName().getValue() + " 曾用名详情");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CompanyOldName loadEntity() {
|
||||
return companyOldNameService.findById(viewModel.getId().get());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CompanyOldName saveEntity(CompanyOldName entity) {
|
||||
return companyOldNameService.save(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerTabSkins() {
|
||||
registerTabSkin(baseInfoTab, this::createBaseTabSkin);
|
||||
@@ -90,32 +80,31 @@ public class CompanyOldNameWindowController extends AbstEntityController<Company
|
||||
|
||||
@Override
|
||||
public CompanyOldNameService getViewModelService() {
|
||||
return companyOldNameService;
|
||||
return getCachedBean(CompanyOldNameService.class);
|
||||
}
|
||||
|
||||
private CompanyOldNameTabSkinBase createBaseTabSkin(Tab tab) {
|
||||
CompanyOldNameTabSkinBase skin = new CompanyOldNameTabSkinBase(this);
|
||||
skin.setCompanyOldNameService(companyOldNameService);
|
||||
skin.setCompanyOldNameService(getViewModelService());
|
||||
return skin;
|
||||
}
|
||||
|
||||
private CompanyOldNameTabSkinFile createFileTabSkin(Tab tab) {
|
||||
CompanyOldNameTabSkinFile skin = new CompanyOldNameTabSkinFile(this);
|
||||
skin.setCompanyOldNameService(companyOldNameService);
|
||||
// skin.setCompanyFileService(companyFileService);
|
||||
skin.setCompanyOldNameService(getViewModelService());
|
||||
// skin.setCompanyFileService(companyFileService);
|
||||
return skin;
|
||||
}
|
||||
|
||||
|
||||
public void onOldCompanyOpenInExplorerAction(ActionEvent event) {
|
||||
CompanyOldName companyOldName = getEntity();
|
||||
CompanyOldNameVo companyOldName = getEntity();
|
||||
String path = companyOldName.getPath();
|
||||
|
||||
CompletableFuture.runAsync(() -> {
|
||||
if (!StringUtils.hasText(path)) {
|
||||
ButtonType buttonType = UITools.showConfirmation("目录未设置", "是否创建目录").join();
|
||||
if (buttonType == ButtonType.OK) {
|
||||
if (companyOldNameService.makePathAbsent(companyOldName)) {
|
||||
if (getViewModelService().makePathAbsent(companyOldName)) {
|
||||
save(companyOldName);
|
||||
}
|
||||
} else {
|
||||
@@ -128,4 +117,3 @@ public class CompanyOldNameWindowController extends AbstEntityController<Company
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.ecep.contract.controller.contract;
|
||||
|
||||
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
|
||||
import com.ecep.contract.service.ContractService;
|
||||
import com.ecep.contract.vm.ContractViewModel;
|
||||
import com.ecep.contract.vo.ContractVo;
|
||||
|
||||
public abstract class AbstContractBasedTabSkin
|
||||
extends AbstEntityBasedTabSkin<ContractWindowController, ContractVo, ContractViewModel>
|
||||
implements TabSkin {
|
||||
|
||||
public AbstContractBasedTabSkin(ContractWindowController controller) {
|
||||
super(controller);
|
||||
viewModel = controller.getViewModel();
|
||||
}
|
||||
|
||||
public ContractService getContractService() {
|
||||
return controller.getViewModelService();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.ecep.contract.controller.contract;
|
||||
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.controller.table.AbstEntityTableTabSkin;
|
||||
import com.ecep.contract.controller.table.TableOfTabSkin;
|
||||
|
||||
import com.ecep.contract.model.IdentityEntity;
|
||||
import com.ecep.contract.service.ContractService;
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import com.ecep.contract.vm.ContractViewModel;
|
||||
import com.ecep.contract.vm.IdentityViewModel;
|
||||
import com.ecep.contract.vo.ContractVo;
|
||||
|
||||
public abstract class AbstContractTableTabSkin<T extends IdentityEntity, TV extends IdentityViewModel<T>>
|
||||
extends AbstEntityTableTabSkin<ContractWindowController, ContractVo, ContractViewModel, T, TV>
|
||||
implements TabSkin, TableOfTabSkin<ContractVo, T, TV> {
|
||||
|
||||
public AbstContractTableTabSkin(ContractWindowController controller) {
|
||||
super(controller);
|
||||
}
|
||||
|
||||
public ContractService getContractService() {
|
||||
return controller.getViewModelService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamUtils.Builder getSpecification(ContractVo parent) {
|
||||
ParamUtils.Builder params = getSpecification();
|
||||
params.equals("contract", parent.getId());
|
||||
return params;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.ecep.contract.controller.contract;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.ecep.contract.controller.AbstEntityManagerSkin;
|
||||
import com.ecep.contract.vm.ContractInvoiceViewModel;
|
||||
import com.ecep.contract.vo.ContractInvoiceVo;
|
||||
|
||||
public class ContractInvoiceManagerSkin extends
|
||||
AbstEntityManagerSkin<ContractInvoiceVo, ContractInvoiceViewModel, ContractInvoiceManagerSkin, ContractInvoiceManagerWindowController> {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ContractInvoiceManagerSkin.class);
|
||||
|
||||
public ContractInvoiceManagerSkin(ContractInvoiceManagerWindowController controller) {
|
||||
super(controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTable() {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'initializeTable'");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.ecep.contract.controller.contract;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.controller.AbstManagerWindowController;
|
||||
import com.ecep.contract.service.ContractInvoiceService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.vm.ContractInvoiceViewModel;
|
||||
import com.ecep.contract.vo.ContractInvoiceVo;
|
||||
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
@FxmlPath("/ui/contract/contract-invoice-manager.fxml")
|
||||
public class ContractInvoiceManagerWindowController extends AbstManagerWindowController<ContractInvoiceVo, ContractInvoiceViewModel, ContractInvoiceManagerSkin> {
|
||||
|
||||
public TableColumn<ContractInvoiceViewModel, Number> idColumn;
|
||||
public TableColumn<ContractInvoiceViewModel, String> codeColumn;
|
||||
public TableColumn<ContractInvoiceViewModel, String> nameColumn;
|
||||
public TableColumn<ContractInvoiceViewModel, Integer> contractColumn;
|
||||
public TableColumn<ContractInvoiceViewModel, Integer> contractItemColumn;
|
||||
|
||||
@Autowired
|
||||
private ContractInvoiceService contractInvoiceService;
|
||||
|
||||
@Override
|
||||
public ContractInvoiceService getViewModelService() {
|
||||
return contractInvoiceService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ContractInvoiceManagerSkin createDefaultSkin() {
|
||||
ContractInvoiceManagerSkin skin = new ContractInvoiceManagerSkin(this);
|
||||
return skin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show(Stage stage) {
|
||||
super.show(stage);
|
||||
getTitle().set("合同发票管理");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.ecep.contract.controller.contract;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.controller.AbstEntityController;
|
||||
import com.ecep.contract.controller.tab.ContractInvoiceTabSkinBase;
|
||||
import com.ecep.contract.service.ContractInvoiceService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.vm.ContractInvoiceViewModel;
|
||||
import com.ecep.contract.vo.ContractInvoiceVo;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TabPane;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.stage.Window;
|
||||
|
||||
@FxmlPath("/ui/contract/contract-invoice.fxml")
|
||||
@Component
|
||||
public class ContractInvoiceWindowController
|
||||
extends AbstEntityController<ContractInvoiceVo, ContractInvoiceViewModel> {
|
||||
|
||||
public static void show(ContractInvoiceViewModel viewModel, Window owner) {
|
||||
show(ContractInvoiceWindowController.class, viewModel, owner);
|
||||
}
|
||||
|
||||
public Tab baseInfoTab;
|
||||
public TextField codeField;
|
||||
public TextField nameField;
|
||||
public TextField invoiceField;
|
||||
public TextField amountField;
|
||||
public TextArea remarkField;
|
||||
public Button saveBtn;
|
||||
public Button cancelBtn;
|
||||
|
||||
@Override
|
||||
public ContractInvoiceService getViewModelService() {
|
||||
return getBean(ContractInvoiceService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerTabSkins() {
|
||||
TabPane tabPane = baseInfoTab.getTabPane();
|
||||
ObservableList<Tab> tabs = tabPane.getTabs();
|
||||
registerTabSkin(baseInfoTab, tab -> new ContractInvoiceTabSkinBase(this));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package com.ecep.contract.controller.contract;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.controller.AbstEntityController;
|
||||
import com.ecep.contract.controller.inventory.InventoryWindowController;
|
||||
import com.ecep.contract.controller.tab.ContractItemTabSkinBase;
|
||||
import com.ecep.contract.service.ContractItemService;
|
||||
import com.ecep.contract.service.InventoryService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.ContractItemViewModel;
|
||||
import com.ecep.contract.vm.InventoryViewModel;
|
||||
import com.ecep.contract.vo.ContractItemVo;
|
||||
import com.ecep.contract.vo.InventoryVo;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.DatePicker;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TabPane;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.Window;
|
||||
import javafx.stage.WindowEvent;
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
@FxmlPath("/ui/contract/contract-item.fxml")
|
||||
public class ContractItemWindowController
|
||||
extends AbstEntityController<ContractItemVo, ContractItemViewModel> {
|
||||
|
||||
public static void show(ContractItemVo contractItem, Window owner) {
|
||||
show(ContractItemViewModel.from(contractItem), owner);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示界面
|
||||
*/
|
||||
public static void show(ContractItemViewModel viewModel, Window window) {
|
||||
show(ContractItemWindowController.class, viewModel, window);
|
||||
}
|
||||
|
||||
public BorderPane root;
|
||||
public Tab baseInfoTab;
|
||||
|
||||
// 基本信息字段
|
||||
public Label idLabel;
|
||||
public TextField codeField;
|
||||
public TextField titleField;
|
||||
public TextField specificationField;
|
||||
public TextField unitField;
|
||||
public TextField inventoryField;
|
||||
public Button openInventoryBtn;
|
||||
|
||||
// 财务信息字段
|
||||
public TextField exclusiveTaxPriceField;
|
||||
public TextField taxRateField;
|
||||
public TextField taxPriceField;
|
||||
public TextField quantityField;
|
||||
public TextField taxAmountField;
|
||||
public TextField exclusiveTaxAmountField;
|
||||
|
||||
// 日期信息字段
|
||||
public DatePicker startDateField;
|
||||
public DatePicker endDateField;
|
||||
public TextField createDateLabel;
|
||||
public TextField updateDateLabel;
|
||||
public TextField creatorLabel;
|
||||
public TextField updaterLabel;
|
||||
|
||||
// 其他信息
|
||||
public TextField refIdField;
|
||||
public TextArea remarkField;
|
||||
|
||||
@Override
|
||||
public ContractItemService getViewModelService() {
|
||||
return getCachedBean(ContractItemService.class);
|
||||
}
|
||||
|
||||
public InventoryService getInventoryService() {
|
||||
return getCachedBean(InventoryService.class);
|
||||
}
|
||||
|
||||
public void onShown(WindowEvent windowEvent) {
|
||||
super.onShown(windowEvent);
|
||||
getTitle().bind(viewModel.getTitle()
|
||||
.map(title -> "[" + viewModel.getId() + "] " + title + " 合同内容详情"));
|
||||
root.getScene().getStylesheets().add("/ui/contract/contract.css");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerTabSkins() {
|
||||
super.registerTabSkins();
|
||||
TabPane tabPane = baseInfoTab.getTabPane();
|
||||
ObservableList<Tab> tabs = tabPane.getTabs();
|
||||
registerTabSkin(baseInfoTab, tab -> new ContractItemTabSkinBase(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开关联的存货信息
|
||||
*/
|
||||
public void onOpenInventoryAction(ActionEvent event) {
|
||||
try {
|
||||
if (viewModel.getInventory() == null) {
|
||||
UITools.showAlertAndWait("没有关联的存货信息");
|
||||
return;
|
||||
}
|
||||
Integer inventoryId = viewModel.getInventory().get();
|
||||
InventoryVo inventory = getInventoryService().findById(inventoryId);
|
||||
if (inventory != null) {
|
||||
InventoryWindowController.show(InventoryViewModel.from(inventory), root.getScene().getWindow());
|
||||
} else {
|
||||
UITools.showAlertAndWait("未找到关联的存货信息");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
UITools.showAlertAndWait("打开存货信息失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算税额和金额
|
||||
*/
|
||||
public void onCalculateAmountsAction(ActionEvent event) {
|
||||
try {
|
||||
viewModel.calculateAmounts();
|
||||
UITools.showAlertAndWait("金额计算完成");
|
||||
} catch (Exception e) {
|
||||
UITools.showAlertAndWait("计算失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.ecep.contract.manager.ds.contract.controller;
|
||||
package com.ecep.contract.controller.contract;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -8,19 +8,16 @@ import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.manager.ds.company.model.Company;
|
||||
import com.ecep.contract.manager.ds.contract.model.Contract;
|
||||
import com.ecep.contract.manager.ds.contract.model.ContractGroup;
|
||||
import com.ecep.contract.manager.ds.contract.model.ContractKind;
|
||||
import com.ecep.contract.manager.ds.contract.model.ContractType;
|
||||
import com.ecep.contract.manager.ds.contract.service.ContractService;
|
||||
import com.ecep.contract.manager.ds.contract.tasker.ContractFilesRebuildAllTasker;
|
||||
import com.ecep.contract.manager.ds.contract.tasker.ContractRepairAllTasker;
|
||||
import com.ecep.contract.manager.ds.contract.vo.ContractViewModel;
|
||||
import com.ecep.contract.manager.ds.other.model.Employee;
|
||||
import com.ecep.contract.manager.ui.AbstManagerWindowController;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.util.UITools;
|
||||
import com.ecep.contract.controller.AbstManagerWindowController;
|
||||
import com.ecep.contract.controller.tab.ContractManagerSkin;
|
||||
import com.ecep.contract.service.ContractService;
|
||||
import com.ecep.contract.task.ContractFilesRebuildAllTasker;
|
||||
import com.ecep.contract.task.ContractRepairAllTasker;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.ContractViewModel;
|
||||
import com.ecep.contract.vo.ContractGroupVo;
|
||||
import com.ecep.contract.vo.ContractVo;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.CheckBox;
|
||||
@@ -33,27 +30,27 @@ import javafx.stage.Stage;
|
||||
@Component
|
||||
@FxmlPath("/ui/contract/contract-manager.fxml")
|
||||
public class ContractManagerWindowController
|
||||
extends AbstManagerWindowController<Contract, ContractViewModel, ContractManagerSkin> {
|
||||
extends AbstManagerWindowController<ContractVo, ContractViewModel, ContractManagerSkin> {
|
||||
|
||||
public ComboBox<ContractGroup> groupSelector;
|
||||
public ComboBox<ContractGroupVo> groupSelector;
|
||||
public CheckBox composeViewBtn;
|
||||
|
||||
// columns
|
||||
public TableColumn<ContractViewModel, Number> idColumn;
|
||||
public TableColumn<ContractViewModel, String> nameColumn;
|
||||
public TableColumn<ContractViewModel, String> codeColumn;
|
||||
public TableColumn<ContractViewModel, ContractGroup> groupColumn;
|
||||
public TableColumn<ContractViewModel, ContractType> typeColumn;
|
||||
public TableColumn<ContractViewModel, ContractKind> kindColumn;
|
||||
public TableColumn<ContractViewModel, Integer> groupColumn;
|
||||
public TableColumn<ContractViewModel, Integer> typeColumn;
|
||||
public TableColumn<ContractViewModel, Integer> kindColumn;
|
||||
|
||||
public TableColumn<ContractViewModel, String> parentCodeColumn;
|
||||
public TableColumn<ContractViewModel, LocalDate> setupDateColumn;
|
||||
public TableColumn<ContractViewModel, LocalDate> orderDateColumn;
|
||||
public TableColumn<ContractViewModel, LocalDate> startDateColumn;
|
||||
public TableColumn<ContractViewModel, Employee> employeeColumn;
|
||||
public TableColumn<ContractViewModel, Integer> employeeColumn;
|
||||
public TableColumn<ContractViewModel, LocalDateTime> createdColumn;
|
||||
public TableColumn<ContractViewModel, Number> amountColumn;
|
||||
public TableColumn<ContractViewModel, Company> companyColumn;
|
||||
public TableColumn<ContractViewModel, Integer> companyColumn;
|
||||
|
||||
@Autowired
|
||||
private ContractService contractService;
|
||||
@@ -1,20 +1,16 @@
|
||||
package com.ecep.contract.manager.ds.contract.controller;
|
||||
package com.ecep.contract.controller.contract;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
|
||||
import com.ecep.contract.manager.SpringApp;
|
||||
import com.ecep.contract.manager.ds.contract.model.Contract;
|
||||
import com.ecep.contract.manager.ds.contract.model.ExtendVendorInfo;
|
||||
import com.ecep.contract.manager.ds.contract.service.ExtendVendorInfoService;
|
||||
import com.ecep.contract.manager.ds.contract.vo.ExtendVendorInfoViewModel;
|
||||
import com.ecep.contract.manager.ds.vendor.model.VendorGroup;
|
||||
import com.ecep.contract.manager.ds.vendor.service.VendorGroupService;
|
||||
import com.ecep.contract.manager.ui.ComboBoxUtils;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.util.UITools;
|
||||
import com.ecep.contract.util.ComboBoxUtils;
|
||||
import com.ecep.contract.service.ExtendVendorInfoService;
|
||||
import com.ecep.contract.service.VendorGroupService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.ExtendVendorInfoViewModel;
|
||||
import com.ecep.contract.vo.ContractVo;
|
||||
import com.ecep.contract.vo.ExtendVendorInfoVo;
|
||||
import com.ecep.contract.vo.VendorGroupVo;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
@@ -26,19 +22,15 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.util.converter.NumberStringConverter;
|
||||
import lombok.Setter;
|
||||
|
||||
@FxmlPath("/ui/contract/contract-tab-ext-vendor-info.fxml")
|
||||
public class ContractTabSkinExtendVendorInfo
|
||||
extends AbstContractBasedTabSkin {
|
||||
|
||||
@Setter
|
||||
private ExtendVendorInfoService extendVendorInfoService;
|
||||
@Setter
|
||||
private VendorGroupService vendorGroupService;
|
||||
public class ContractTabSkinExtendVendorInfo extends AbstContractBasedTabSkin {
|
||||
|
||||
/**
|
||||
* VendorGroup
|
||||
*/
|
||||
@FXML
|
||||
public ComboBox<VendorGroup> vendorGroupField;
|
||||
public ComboBox<VendorGroupVo> vendorGroupField;
|
||||
@FXML
|
||||
public Label vendorGroupLabel;
|
||||
@FXML
|
||||
@@ -48,7 +40,7 @@ public class ContractTabSkinExtendVendorInfo
|
||||
@FXML
|
||||
public CheckBox prePurchaseField;
|
||||
|
||||
CompletableFuture<ExtendVendorInfo> loadedFuture;
|
||||
CompletableFuture<ExtendVendorInfoVo> loadedFuture;
|
||||
private ExtendVendorInfoViewModel viewModel = new ExtendVendorInfoViewModel();
|
||||
|
||||
public ContractTabSkinExtendVendorInfo(ContractWindowController controller) {
|
||||
@@ -76,13 +68,13 @@ public class ContractTabSkinExtendVendorInfo
|
||||
if (loadedFuture == null) {
|
||||
loadedFuture = CompletableFuture.supplyAsync(() -> {
|
||||
initializeTab();
|
||||
Contract contract = getEntity();
|
||||
ContractVo contract = getEntity();
|
||||
return loadExtendVendorInfo(contract);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void updateViewModel(ExtendVendorInfo info) {
|
||||
void updateViewModel(ExtendVendorInfoVo info) {
|
||||
if (Platform.isFxApplicationThread()) {
|
||||
viewModel.update(info);
|
||||
} else {
|
||||
@@ -90,13 +82,14 @@ public class ContractTabSkinExtendVendorInfo
|
||||
}
|
||||
}
|
||||
|
||||
private ExtendVendorInfo loadExtendVendorInfo(Contract contract) {
|
||||
private ExtendVendorInfoVo loadExtendVendorInfo(ContractVo contract) {
|
||||
ExtendVendorInfoService service = getExtendVendorInfoService();
|
||||
try {
|
||||
ExtendVendorInfo info = service.findByContract(contract);
|
||||
ExtendVendorInfoVo info = service.findByContract(contract);
|
||||
if (info == null) {
|
||||
info = service.newInstanceByContract(contract);
|
||||
info = service.save(info);
|
||||
info = new ExtendVendorInfoVo();
|
||||
info.setContractId(contract.getId());
|
||||
// 注意:这里可能需要调整,取决于service接口的实现
|
||||
}
|
||||
updateViewModel(info);
|
||||
viewModel.bindListener();
|
||||
@@ -109,17 +102,11 @@ public class ContractTabSkinExtendVendorInfo
|
||||
|
||||
@Override
|
||||
public void initializeTab() {
|
||||
List<VendorGroup> groups = getVendorGroupService().findAll();
|
||||
ComboBoxUtils.initialComboBox(vendorGroupField, groups, true);
|
||||
vendorGroupField.valueProperty().bindBidirectional(viewModel.getGroup());
|
||||
ComboBoxUtils.initialComboBox(vendorGroupField, viewModel.getGroup(), getVendorGroupService(), true);
|
||||
vendorGroupLabel.textProperty().bind(vendorGroupField.valueProperty().map(v -> {
|
||||
if (v == null) {
|
||||
return "-";
|
||||
}
|
||||
if (!Hibernate.isInitialized(v)) {
|
||||
v = getVendorGroupService().findById(v.getId());
|
||||
viewModel.getGroup().set(v);
|
||||
}
|
||||
return v.getDescription();
|
||||
}));
|
||||
|
||||
@@ -127,7 +114,11 @@ public class ContractTabSkinExtendVendorInfo
|
||||
new NumberStringConverter());
|
||||
assignedProviderField.selectedProperty().bindBidirectional(viewModel.getAssignedProvider());
|
||||
assignedProviderField.disableProperty().bind(Bindings.createBooleanBinding(() -> {
|
||||
VendorGroup group = viewModel.getGroup().get();
|
||||
Integer groupId = viewModel.getGroup().get();
|
||||
if (groupId == null) {
|
||||
return false;
|
||||
}
|
||||
VendorGroupVo group = getVendorGroupService().findById(groupId);
|
||||
if (group == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -140,9 +131,10 @@ public class ContractTabSkinExtendVendorInfo
|
||||
@Override
|
||||
public void save() {
|
||||
if (loadedFuture != null) {
|
||||
ExtendVendorInfo vendorInfo = loadedFuture.join();
|
||||
ExtendVendorInfoVo vendorInfo = loadedFuture.join();
|
||||
if (viewModel.copyTo(vendorInfo)) {
|
||||
ExtendVendorInfo saved = getExtendVendorInfoService().save(vendorInfo);
|
||||
// 注意:这里需要根据实际service接口实现调整,可能需要调用不同的方法
|
||||
ExtendVendorInfoVo saved = getExtendVendorInfoService().save(vendorInfo);
|
||||
updateViewModel(saved);
|
||||
loadedFuture = CompletableFuture.completedFuture(saved);
|
||||
}
|
||||
@@ -150,16 +142,10 @@ public class ContractTabSkinExtendVendorInfo
|
||||
}
|
||||
|
||||
public ExtendVendorInfoService getExtendVendorInfoService() {
|
||||
if (extendVendorInfoService == null) {
|
||||
extendVendorInfoService = SpringApp.getBean(ExtendVendorInfoService.class);
|
||||
}
|
||||
return extendVendorInfoService;
|
||||
return getCachedBean(ExtendVendorInfoService.class);
|
||||
}
|
||||
|
||||
public VendorGroupService getVendorGroupService() {
|
||||
if (vendorGroupService == null) {
|
||||
vendorGroupService = SpringApp.getBean(VendorGroupService.class);
|
||||
}
|
||||
return vendorGroupService;
|
||||
return getCachedBean(VendorGroupService.class);
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,26 @@
|
||||
package com.ecep.contract.manager.ds.contract.controller;
|
||||
package com.ecep.contract.controller.contract;
|
||||
|
||||
import com.ecep.contract.manager.SpringApp;
|
||||
import com.ecep.contract.manager.cloud.u8.ctx.ContractCtx;
|
||||
import com.ecep.contract.manager.ds.company.CompanyFileUtils;
|
||||
import com.ecep.contract.manager.ds.contract.ContractFileType;
|
||||
import com.ecep.contract.manager.ds.contract.ContractPayWay;
|
||||
import com.ecep.contract.manager.ds.contract.model.Contract;
|
||||
import com.ecep.contract.manager.ds.contract.model.ContractFile;
|
||||
import com.ecep.contract.manager.ds.contract.model.ContractFileTypeLocal;
|
||||
import com.ecep.contract.manager.ds.contract.model.ContractType;
|
||||
import com.ecep.contract.manager.ds.contract.service.ContractFileService;
|
||||
import com.ecep.contract.manager.ds.contract.tasker.ContractFilesRebuildTasker;
|
||||
import com.ecep.contract.manager.ds.contract.tasker.CustomerContractCostFormUpdateTask;
|
||||
import com.ecep.contract.manager.ds.contract.vo.ContractFileViewModel;
|
||||
import com.ecep.contract.manager.ui.table.EditableEntityTableTabSkin;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.ui.tab.TabSkin;
|
||||
import com.ecep.contract.manager.ui.util.LocalDateFieldTableCell;
|
||||
import com.ecep.contract.manager.util.DesktopUtils;
|
||||
import com.ecep.contract.manager.util.MyDateTimeUtils;
|
||||
import com.ecep.contract.manager.util.UITools;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.MapChangeListener;
|
||||
import com.ecep.contract.*;
|
||||
import com.ecep.contract.constant.ContractConstant;
|
||||
import com.ecep.contract.controller.tab.ContractFilesRebuildTasker;
|
||||
import com.ecep.contract.controller.tab.CustomerContractCostFormUpdateTask;
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.controller.table.EditableEntityTableTabSkin;
|
||||
import com.ecep.contract.controller.table.cell.ContractFileTypeTableCell;
|
||||
import com.ecep.contract.controller.table.cell.LocalDateFieldTableCell;
|
||||
import com.ecep.contract.service.ContractFileService;
|
||||
import com.ecep.contract.service.ContractFileTypeService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.ContractFileViewModel;
|
||||
import com.ecep.contract.vo.ContractFileTypeLocalVo;
|
||||
import com.ecep.contract.vo.ContractFileVo;
|
||||
import com.ecep.contract.vo.ContractVo;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.ObservableMap;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.control.cell.TextFieldTableCell;
|
||||
import javafx.stage.WindowEvent;
|
||||
import javafx.util.StringConverter;
|
||||
import lombok.Setter;
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.io.IOUtils;
|
||||
@@ -60,8 +51,8 @@ import java.util.Objects;
|
||||
*/
|
||||
@FxmlPath("/ui/contract/contract-tab-file.fxml")
|
||||
public class ContractTabSkinFiles
|
||||
extends AbstContractTableTabSkin<ContractFile, ContractFileViewModel>
|
||||
implements TabSkin, EditableEntityTableTabSkin<ContractFile, ContractFileViewModel> {
|
||||
extends AbstContractTableTabSkin<ContractFileVo, ContractFileViewModel>
|
||||
implements TabSkin, EditableEntityTableTabSkin<ContractFileVo, ContractFileViewModel> {
|
||||
|
||||
public Button fileTableReBuildBtn;
|
||||
public MenuItem fileTable_menu_refresh;
|
||||
@@ -71,15 +62,13 @@ public class ContractTabSkinFiles
|
||||
public Menu fileTable_menu_change_type;
|
||||
public Menu fileTable_menu_change_type_and_name;
|
||||
public TableColumn<ContractFileViewModel, Number> fileTable_idColumn;
|
||||
public TableColumn<ContractFileViewModel, ContractFileTypeLocal> fileTable_typeColumn;
|
||||
public TableColumn<ContractFileViewModel, ContractFileType> fileTable_typeColumn;
|
||||
public TableColumn<ContractFileViewModel, String> fileTable_filePathColumn;
|
||||
public TableColumn<ContractFileViewModel, LocalDate> fileTable_applyDateColumn;
|
||||
public TableColumn<ContractFileViewModel, String> fileTable_descriptionColumn;
|
||||
|
||||
@Setter
|
||||
private ContractFileService contractFileService;
|
||||
private final ObservableMap<ContractFileType, ContractFileTypeLocal> fileTypeLocalMap = FXCollections.observableHashMap();
|
||||
|
||||
|
||||
public ContractTabSkinFiles(ContractWindowController controller) {
|
||||
super(controller);
|
||||
@@ -104,26 +93,10 @@ public class ContractTabSkinFiles
|
||||
return controller.fileTab;
|
||||
}
|
||||
|
||||
static class ContractFileTypeLocalStringConverter extends StringConverter<ContractFileTypeLocal> {
|
||||
@Override
|
||||
public String toString(ContractFileTypeLocal local) {
|
||||
if (local == null) {
|
||||
return "-";
|
||||
}
|
||||
return local.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContractFileTypeLocal fromString(String string) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTab() {
|
||||
TableView<ContractFileViewModel> table = getTableView();
|
||||
ContractPayWay payWay = viewModel.getPayWay().get();
|
||||
ContractType contractType = viewModel.getType().get();
|
||||
boolean isCustomer = payWay == ContractPayWay.RECEIVE;
|
||||
boolean isVendor = payWay == ContractPayWay.PAY;
|
||||
|
||||
@@ -139,47 +112,13 @@ public class ContractTabSkinFiles
|
||||
fileTable_idColumn.setEditable(false);
|
||||
fileTable_idColumn.setReorderable(false);
|
||||
|
||||
|
||||
fileTable_typeColumn.setCellValueFactory(param -> Bindings.valueAt(fileTypeLocalMap, param.getValue().getType()));
|
||||
fileTable_typeColumn.setCellFactory(param -> new TextFieldTableCell<>(new ContractFileTypeLocalStringConverter()));
|
||||
// 监听 type map 变化
|
||||
fileTypeLocalMap.addListener((MapChangeListener<? super ContractFileType, ? super ContractFileTypeLocal>) change -> {
|
||||
List<ContractFileTypeLocal> types = fileTypeLocalMap.values().stream().filter(typeLocal -> {
|
||||
ContractFileType type = typeLocal.getType();
|
||||
if (type == null) {
|
||||
return false;
|
||||
}
|
||||
if (isCustomer && !type.isSupportCustomer()) {
|
||||
return false;
|
||||
}
|
||||
return !isVendor || type.isSupportVendor();
|
||||
}).toList();
|
||||
|
||||
|
||||
fileTable_menu_change_type.getItems().setAll(types.stream()
|
||||
.map(typeLocal -> {
|
||||
MenuItem item = new MenuItem();
|
||||
item.setText(typeLocal.getValue());
|
||||
item.getProperties().put("typeLocal", typeLocal);
|
||||
item.setOnAction(this::onFileTableContextMenuChangeTypeAndNameAction);
|
||||
return item;
|
||||
}).toList());
|
||||
|
||||
fileTable_menu_change_type_and_name.getItems().setAll(types.stream()
|
||||
.filter(typeLocal -> StringUtils.hasText(typeLocal.getSuggestFileName()))
|
||||
.map(typeLocal -> {
|
||||
MenuItem item = new MenuItem();
|
||||
item.setText(typeLocal.getValue());
|
||||
item.getProperties().put("typeLocal", typeLocal);
|
||||
item.getProperties().put("rename", true);
|
||||
item.setOnAction(this::onFileTableContextMenuChangeTypeAndNameAction);
|
||||
return item;
|
||||
}).toList());
|
||||
});
|
||||
|
||||
|
||||
fileTable_typeColumn
|
||||
.setCellValueFactory(param -> param.getValue().getType());
|
||||
fileTable_typeColumn
|
||||
.setCellFactory(ContractFileTypeTableCell.forTableColumn(getCachedBean(ContractFileTypeService.class)));
|
||||
fileTable_typeColumn.setEditable(false);
|
||||
|
||||
|
||||
/* 文件名编辑器 */
|
||||
fileTable_filePathColumn.setCellValueFactory(param -> param.getValue().getFileName());
|
||||
fileTable_filePathColumn.setCellFactory(TextFieldTableCell.forTableColumn());
|
||||
@@ -232,17 +171,41 @@ public class ContractTabSkinFiles
|
||||
System.out.println("fileTable_menu_change_type_and_name:setOnMenuValidation");
|
||||
});
|
||||
|
||||
|
||||
fileTable_menu_update.getItems().setAll(
|
||||
createCustomerContractCostByTemplateUpdateMenuItem(),
|
||||
createCustomerContractBidByTemplateUpdateMenuItem(),
|
||||
createCustomerContractApplyByTemplateUpdateMenuItem(),
|
||||
createVendorContractRequestByTemplateUpdateMenuItem(),
|
||||
createVendorContractApplyByTemplateUpdateMenuItem()
|
||||
);
|
||||
createVendorContractApplyByTemplateUpdateMenuItem());
|
||||
|
||||
|
||||
fileTypeLocalMap.putAll(getContractFileService().findAllFileTypes(getLocale().toLanguageTag()));
|
||||
runAsync(() -> {
|
||||
getCachedBean(ContractFileTypeService.class).findAll(getLocale()).forEach((k, v) -> {
|
||||
if (isCustomer && !k.isSupportCustomer()) {
|
||||
return;
|
||||
}
|
||||
if (isVendor && !k.isSupportVendor()) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
MenuItem item = new MenuItem();
|
||||
item.setText(v.getValue());
|
||||
item.getProperties().put("typeLocal", v);
|
||||
item.setOnAction(this::onFileTableContextMenuChangeTypeAndNameAction);
|
||||
fileTable_menu_change_type.getItems().add(item);
|
||||
}
|
||||
if (StringUtils.hasText(v.getSuggestFileName())) {
|
||||
MenuItem item = new MenuItem();
|
||||
item.setText(v.getValue());
|
||||
item.getProperties().put("typeLocal", v);
|
||||
item.getProperties().put("rename", true);
|
||||
item.setOnAction(this::onFileTableContextMenuChangeTypeAndNameAction);
|
||||
fileTable_menu_change_type_and_name.getItems().add(item);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
super.initializeTab();
|
||||
}
|
||||
|
||||
@@ -266,7 +229,7 @@ public class ContractTabSkinFiles
|
||||
* @param event 菜单事件
|
||||
*/
|
||||
private void onFileTableComposePDFAction(ActionEvent event) {
|
||||
Contract contract = getParent();
|
||||
ContractVo contract = getParent();
|
||||
if (!StringUtils.hasText(contract.getPath())) {
|
||||
setStatus("合同未设置路径");
|
||||
return;
|
||||
@@ -296,7 +259,6 @@ public class ContractTabSkinFiles
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
PDFMergerUtility merger = new PDFMergerUtility();
|
||||
File dest = new File(contractPath, "合并的" + rows.getFirst().getFileName().get());
|
||||
merger.setDestinationFileName(dest.getAbsolutePath());
|
||||
@@ -323,7 +285,7 @@ public class ContractTabSkinFiles
|
||||
}
|
||||
|
||||
ContractFileViewModel append = new ContractFileViewModel();
|
||||
append.getContract().set(contract);
|
||||
append.getContract().set(contract.getId());
|
||||
append.getFileName().set(dest.getName());
|
||||
append.getType().set(ContractFileType.General);
|
||||
saveRow(append);
|
||||
@@ -337,7 +299,7 @@ public class ContractTabSkinFiles
|
||||
}
|
||||
|
||||
private void onFileTableComposePDFAction_V2(ActionEvent event) {
|
||||
Contract contract = getParent();
|
||||
ContractVo contract = getParent();
|
||||
if (!StringUtils.hasText(contract.getPath())) {
|
||||
setStatus("合同未设置路径");
|
||||
return;
|
||||
@@ -399,8 +361,7 @@ public class ContractTabSkinFiles
|
||||
// 加载 PNG 图片
|
||||
BufferedImage image = ImageIO.read(file);
|
||||
// 将图片添加到 PDF
|
||||
try (PDPageContentStream contentStream =
|
||||
new PDPageContentStream(destination, page)) {
|
||||
try (PDPageContentStream contentStream = new PDPageContentStream(destination, page)) {
|
||||
PDImageXObject pdImage = LosslessFactory.createFromImage(destination, image);
|
||||
float scale = 1f; // 图片缩放比例
|
||||
contentStream.drawImage(pdImage, 0, 0,
|
||||
@@ -421,7 +382,7 @@ public class ContractTabSkinFiles
|
||||
}
|
||||
|
||||
ContractFileViewModel append = new ContractFileViewModel();
|
||||
append.getContract().set(contract);
|
||||
append.getContract().set(contract.getId());
|
||||
append.getFileName().set(dest.getName());
|
||||
append.getType().set(ContractFileType.General);
|
||||
saveRow(append);
|
||||
@@ -464,7 +425,6 @@ public class ContractTabSkinFiles
|
||||
return item;
|
||||
}
|
||||
|
||||
|
||||
private MenuItem createCustomerContractBidByTemplateUpdateMenuItem() {
|
||||
MenuItem item = new MenuItem();
|
||||
item.setText("投标报价审批表");
|
||||
@@ -478,7 +438,7 @@ public class ContractTabSkinFiles
|
||||
item.setText("成本核算审批表");
|
||||
item.visibleProperty().bind(viewModel.getPayWay().isEqualTo(ContractPayWay.RECEIVE));
|
||||
item.setOnAction(event -> {
|
||||
Contract contract = getContractService().findById(viewModel.getId().get());
|
||||
ContractVo contract = getContractService().findById(viewModel.getId().get());
|
||||
if (contract == null) {
|
||||
setStatus("异常,对应的合同不存在#" + viewModel.getId().get());
|
||||
return;
|
||||
@@ -488,7 +448,7 @@ public class ContractTabSkinFiles
|
||||
return;
|
||||
}
|
||||
|
||||
String template = controller.getConfService().getString(ContractFileService.KEY_CUSTOMER_COST_TEMPLATE);
|
||||
String template = controller.getConfService().getString(ContractConstant.KEY_CUSTOMER_COST_TEMPLATE);
|
||||
if (!StringUtils.hasText(template)) {
|
||||
setStatus("模板文件未配置");
|
||||
return;
|
||||
@@ -498,14 +458,13 @@ public class ContractTabSkinFiles
|
||||
.filter(v -> v.getType().get().equals(ContractFileType.Cost))
|
||||
.findFirst().orElse(null);
|
||||
if (model == null) {
|
||||
ContractFile file = new ContractFile();
|
||||
file.setContract(contract);
|
||||
ContractFileVo file = new ContractFileVo();
|
||||
file.setContractId(contract.getId());
|
||||
file.setType(ContractFileType.Cost);
|
||||
file.setApplyDate(LocalDate.now());
|
||||
|
||||
|
||||
String fileName = item.getText();
|
||||
ContractFileTypeLocal local = fileTypeLocalMap.get(ContractFileType.CostForm);
|
||||
ContractFileTypeLocalVo local = getCachedBean(ContractFileTypeService.class).findByType(getLocale(), ContractFileType.CostForm);
|
||||
if (local != null) {
|
||||
if (StringUtils.hasText(local.getSuggestFileName())) {
|
||||
fileName = local.getSuggestFileName();
|
||||
@@ -530,16 +489,17 @@ public class ContractTabSkinFiles
|
||||
|
||||
UITools.showTaskDialogAndWait("更新 " + model.getFileName().get(), task, null);
|
||||
|
||||
|
||||
});
|
||||
return item;
|
||||
}
|
||||
|
||||
private void onFileTableDescriptionColumnEditCommitAction(TableColumn.CellEditEvent<ContractFileViewModel, String> event) {
|
||||
private void onFileTableDescriptionColumnEditCommitAction(
|
||||
TableColumn.CellEditEvent<ContractFileViewModel, String> event) {
|
||||
acceptCellEditEvent(event, ContractFileViewModel::getDescription);
|
||||
}
|
||||
|
||||
private void onFileTableApplyDateColumnEditCommitAction(TableColumn.CellEditEvent<ContractFileViewModel, LocalDate> event) {
|
||||
private void onFileTableApplyDateColumnEditCommitAction(
|
||||
TableColumn.CellEditEvent<ContractFileViewModel, LocalDate> event) {
|
||||
acceptCellEditEvent(event, ContractFileViewModel::getApplyDate);
|
||||
}
|
||||
|
||||
@@ -563,7 +523,7 @@ public class ContractTabSkinFiles
|
||||
@Override
|
||||
protected void onTableRowDoubleClickedAction(ContractFileViewModel item) {
|
||||
String parent = viewModel.getPath().get();
|
||||
if (!CompanyFileUtils.exists(parent)) {
|
||||
if (!getContractService().existsContractPath(getEntity())) {
|
||||
setStatus("合同的目录未设置或者无效,请检查!");
|
||||
return;
|
||||
}
|
||||
@@ -582,7 +542,7 @@ public class ContractTabSkinFiles
|
||||
if (selectedItem == null) {
|
||||
// 未选择行
|
||||
windowEvent.consume();
|
||||
//contextMenu.hide();
|
||||
// contextMenu.hide();
|
||||
fileTable_menu_del.setVisible(false);
|
||||
fileTable_menu_change_type.setVisible(false);
|
||||
fileTable_menu_change_type_and_name.setVisible(false);
|
||||
@@ -593,19 +553,18 @@ public class ContractTabSkinFiles
|
||||
//
|
||||
fileTable_menu_change_type.setVisible(true);
|
||||
for (MenuItem item : fileTable_menu_change_type.getItems()) {
|
||||
ContractFileTypeLocal typeLocal = (ContractFileTypeLocal) item.getProperties().get("typeLocal");
|
||||
ContractFileTypeLocalVo typeLocal = (ContractFileTypeLocalVo) item.getProperties().get("typeLocal");
|
||||
item.setVisible(typeLocal.getType() != selectedItem.getType().get());
|
||||
}
|
||||
|
||||
fileTable_menu_change_type_and_name.setVisible(true);
|
||||
for (MenuItem item : fileTable_menu_change_type.getItems()) {
|
||||
ContractFileTypeLocal typeLocal = (ContractFileTypeLocal) item.getProperties().get("typeLocal");
|
||||
ContractFileTypeLocalVo typeLocal = (ContractFileTypeLocalVo) item.getProperties().get("typeLocal");
|
||||
item.setVisible(typeLocal.getType() != selectedItem.getType().get());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void onFileTableContextMenuChangeTypeAndNameAction(ActionEvent event) {
|
||||
MenuItem item = (MenuItem) event.getSource();
|
||||
|
||||
@@ -613,7 +572,7 @@ public class ContractTabSkinFiles
|
||||
if (selectedItems == null || selectedItems.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
ContractFileTypeLocal typeLocal = (ContractFileTypeLocal) item.getProperties().get("typeLocal");
|
||||
ContractFileTypeLocalVo typeLocal = (ContractFileTypeLocalVo) item.getProperties().get("typeLocal");
|
||||
if (typeLocal == null) {
|
||||
return;
|
||||
}
|
||||
@@ -630,7 +589,8 @@ public class ContractTabSkinFiles
|
||||
String extension = StringUtils.getFilenameExtension(fileName);
|
||||
LocalDate localDate = selectedItem.getApplyDate().get();
|
||||
String contractCode = viewModel.getCode().get();
|
||||
String newFileName = contractCode + "-" + suggestFileName + (localDate == null ? "" : ("-" + MyDateTimeUtils.format(localDate))) + "." + extension;
|
||||
String newFileName = contractCode + "-" + suggestFileName
|
||||
+ (localDate == null ? "" : ("-" + MyDateTimeUtils.format(localDate))) + "." + extension;
|
||||
|
||||
// 新旧文件名不一样,需要改名
|
||||
if (!Objects.equals(newFileName, fileName)) {
|
||||
@@ -653,30 +613,18 @@ public class ContractTabSkinFiles
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContractFile loadRowData(ContractFileViewModel row) {
|
||||
public ContractFileVo loadRowData(ContractFileViewModel row) {
|
||||
return getContractFileService().findById(row.getId().get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContractFile saveRowData(ContractFile entity) {
|
||||
return getContractFileService().save(entity);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void deleteRowData(ContractFile entity) {
|
||||
getContractFileService().delete(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置文件
|
||||
* <br>
|
||||
* 依据已存在的文件重建
|
||||
*/
|
||||
public void onFileReBuildingAction(ActionEvent event) {
|
||||
Contract contract = getParent();
|
||||
ContractVo contract = getParent();
|
||||
ContractFilesRebuildTasker task = new ContractFilesRebuildTasker();
|
||||
task.setContractService(getContractService());
|
||||
task.setContract(contract);
|
||||
UITools.showTaskDialogAndWait("文件重置", task, null);
|
||||
|
||||
@@ -696,13 +644,13 @@ public class ContractTabSkinFiles
|
||||
setStatus("目录错误,不存在");
|
||||
return;
|
||||
}
|
||||
Contract contract = getParent();
|
||||
ContractVo contract = getParent();
|
||||
|
||||
for (File file : files) {
|
||||
File dest = new File(dir, file.getName());
|
||||
if (file.renameTo(dest)) {
|
||||
ContractFile ccf = new ContractFile();
|
||||
ccf.setContract(contract);
|
||||
ContractFileVo ccf = new ContractFileVo();
|
||||
ccf.setContractId(contract.getId());
|
||||
ccf.setType(ContractFileType.General);
|
||||
ccf.setFileName(dest.getName());
|
||||
getContractFileService().save(ccf);
|
||||
@@ -719,7 +667,7 @@ public class ContractTabSkinFiles
|
||||
return;
|
||||
}
|
||||
|
||||
Contract contract = getParent();
|
||||
ContractVo contract = getParent();
|
||||
if (!StringUtils.hasText(contract.getPath())) {
|
||||
setStatus("合同未设置路径");
|
||||
return;
|
||||
@@ -746,10 +694,6 @@ public class ContractTabSkinFiles
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO 放到其他线程中, UI线程卡了
|
||||
ContractCtx contractCtx = new ContractCtx();
|
||||
|
||||
|
||||
try (PDDocument pdDocument = Loader.loadPDF(pdfFile)) {
|
||||
Splitter splitter = new Splitter();
|
||||
List<PDDocument> pages = splitter.split(pdDocument);
|
||||
@@ -759,12 +703,12 @@ public class ContractTabSkinFiles
|
||||
File outputFile = new File(contractPath, name + "-" + (i + 1) + ".pdf");
|
||||
page.save(outputFile);
|
||||
page.close();
|
||||
ContractFile contractFile = new ContractFile();
|
||||
contractFile.setContract(contract);
|
||||
contractCtx.syncContractFile(contractFile, outputFile, (lv, message) -> {
|
||||
ContractFileVo contractFile = new ContractFileVo();
|
||||
contractFile.setContractId(contract.getId());
|
||||
getContractService().syncContractFile(contractFile, outputFile, (lv, message) -> {
|
||||
setStatus(message);
|
||||
});
|
||||
ContractFile saved = getContractFileService().save(contractFile);
|
||||
ContractFileVo saved = getContractFileService().save(contractFile);
|
||||
dataSet.add(ContractFileViewModel.from(saved));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -777,7 +721,7 @@ public class ContractTabSkinFiles
|
||||
|
||||
@Override
|
||||
protected void onTableDeleteAction(ActionEvent event) {
|
||||
Contract contract = getParent();
|
||||
ContractVo contract = getParent();
|
||||
String path = contract.getPath();
|
||||
if (!StringUtils.hasText(path)) {
|
||||
setStatus("未设置目录");
|
||||
@@ -794,7 +738,7 @@ public class ContractTabSkinFiles
|
||||
protected boolean deleteRow(ContractFileViewModel row, boolean confirm) {
|
||||
boolean deleted = super.deleteRow(row);
|
||||
if (deleted) {
|
||||
Contract contract = getParent();
|
||||
ContractVo contract = getParent();
|
||||
File file = new File(contract.getPath(), row.getFileName().get());
|
||||
if (!file.exists()) {
|
||||
setStatus(file.getAbsolutePath() + " 文件不存在, 无法删除");
|
||||
@@ -1,11 +1,10 @@
|
||||
package com.ecep.contract.manager.ds.contract.controller;
|
||||
package com.ecep.contract.controller.contract;
|
||||
|
||||
import com.ecep.contract.manager.ds.contract.model.ContractPayPlan;
|
||||
import com.ecep.contract.manager.ds.contract.service.ContractPayPlanService;
|
||||
import com.ecep.contract.manager.ds.contract.vo.ContractPayPlanViewModel;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.ui.ViewModelService;
|
||||
import com.ecep.contract.manager.ui.table.cell.LocalDateTimeTableCell;
|
||||
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
|
||||
import com.ecep.contract.service.ContractPayPlanService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.vm.ContractPayPlanViewModel;
|
||||
import com.ecep.contract.vo.ContractPayPlanVo;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TableColumn;
|
||||
@@ -18,7 +17,7 @@ import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@FxmlPath("/ui/contract/contract-tab-pay-plan.fxml")
|
||||
public class ContractTabSkinPayPlan extends AbstContractTableTabSkin<ContractPayPlan, ContractPayPlanViewModel> {
|
||||
public class ContractTabSkinPayPlan extends AbstContractTableTabSkin<ContractPayPlanVo, ContractPayPlanViewModel> {
|
||||
|
||||
public TableColumn<ContractPayPlanViewModel, Number> idColumn;
|
||||
public TableColumn<ContractPayPlanViewModel, LocalDate> payDateColumn;
|
||||
@@ -47,7 +46,7 @@ public class ContractTabSkinPayPlan extends AbstContractTableTabSkin<ContractPay
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ViewModelService<ContractPayPlan, ContractPayPlanViewModel> getViewModelService() {
|
||||
protected ContractPayPlanService getViewModelService() {
|
||||
return getPayPlanService();
|
||||
}
|
||||
|
||||
@@ -1,37 +1,38 @@
|
||||
package com.ecep.contract.manager.ds.contract.controller;
|
||||
|
||||
import com.ecep.contract.manager.ds.contract.ContractPayWay;
|
||||
import com.ecep.contract.manager.ds.contract.controller.purchase_order.PurchaseOrderWindowController;
|
||||
import com.ecep.contract.manager.ds.contract.model.PurchaseOrder;
|
||||
import com.ecep.contract.manager.ds.contract.service.PurchaseOrdersService;
|
||||
import com.ecep.contract.manager.ds.contract.vo.PurchaseOrderViewModel;
|
||||
import com.ecep.contract.manager.ds.other.EmployeeStringConverter;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.ui.tab.TabSkin;
|
||||
import com.ecep.contract.manager.ui.table.cell.LocalDateTimeTableCell;
|
||||
import javafx.scene.control.*;
|
||||
package com.ecep.contract.controller.contract;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import com.ecep.contract.ContractPayWay;
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.controller.table.cell.EmployeeTableCell;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.controller.vendor.purchase.order.PurchaseOrderWindowController;
|
||||
import com.ecep.contract.converter.EmployeeStringConverter;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.service.PurchaseOrdersService;
|
||||
import com.ecep.contract.vm.PurchaseOrderViewModel;
|
||||
import com.ecep.contract.vo.PurchaseOrderVo;
|
||||
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
|
||||
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TableColumn;
|
||||
|
||||
/**
|
||||
* 子合同
|
||||
*/
|
||||
@FxmlPath("/ui/contract/contract-tab-purchase-orders.fxml")
|
||||
public class ContractTabSkinPurchaseOrders
|
||||
extends AbstContractTableTabSkin<PurchaseOrder, PurchaseOrderViewModel>
|
||||
extends AbstContractTableTabSkin<PurchaseOrderVo, PurchaseOrderViewModel>
|
||||
implements TabSkin {
|
||||
|
||||
|
||||
private PurchaseOrdersService purchaseOrdersService;
|
||||
private EmployeeStringConverter employeeStringConverter;
|
||||
|
||||
public TableColumn<PurchaseOrderViewModel, Number> idColumn;
|
||||
public TableColumn<PurchaseOrderViewModel, String> codeColumn;
|
||||
public TableColumn<PurchaseOrderViewModel, String> table_makerColumn;
|
||||
public TableColumn<PurchaseOrderViewModel, Integer> table_makerColumn;
|
||||
public TableColumn<PurchaseOrderViewModel, LocalDateTime> table_makerDateColumn;
|
||||
public TableColumn<PurchaseOrderViewModel, String> table_verifierColumn;
|
||||
public TableColumn<PurchaseOrderViewModel, Integer> table_verifierColumn;
|
||||
public TableColumn<PurchaseOrderViewModel, LocalDateTime> table_verifierDateColumn;
|
||||
public TableColumn<PurchaseOrderViewModel, String> table_closerColumn;
|
||||
public TableColumn<PurchaseOrderViewModel, Integer> table_closerColumn;
|
||||
public TableColumn<PurchaseOrderViewModel, LocalDateTime> table_closerDateColumn;
|
||||
public TableColumn<PurchaseOrderViewModel, String> table_descriptionColumn;
|
||||
|
||||
@@ -68,14 +69,16 @@ public class ContractTabSkinPurchaseOrders
|
||||
idColumn.setCellValueFactory(param -> param.getValue().getId());
|
||||
codeColumn.setCellValueFactory(param -> param.getValue().getCode());
|
||||
|
||||
EmployeeStringConverter converter = getEmployeeStringConverter();
|
||||
table_makerColumn.setCellValueFactory(param -> param.getValue().getMaker().map(converter::toString));
|
||||
table_makerColumn.setCellValueFactory(param -> param.getValue().getMaker());
|
||||
table_makerColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
|
||||
table_makerDateColumn.setCellValueFactory(param -> param.getValue().getMakerDate());
|
||||
table_makerDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
|
||||
table_verifierColumn.setCellValueFactory(param -> param.getValue().getMaker().map(converter::toString));
|
||||
table_verifierColumn.setCellValueFactory(param -> param.getValue().getMaker());
|
||||
table_verifierColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
|
||||
table_verifierDateColumn.setCellValueFactory(param -> param.getValue().getVerifierDate());
|
||||
table_verifierDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
|
||||
table_closerColumn.setCellValueFactory(param -> param.getValue().getMaker().map(converter::toString));
|
||||
table_closerColumn.setCellValueFactory(param -> param.getValue().getMaker());
|
||||
table_closerColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
|
||||
table_closerDateColumn.setCellValueFactory(param -> param.getValue().getCloserDate());
|
||||
table_closerDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
|
||||
|
||||
@@ -89,16 +92,14 @@ public class ContractTabSkinPurchaseOrders
|
||||
}
|
||||
|
||||
PurchaseOrdersService getPurchaseOrdersService() {
|
||||
if (purchaseOrdersService == null) {
|
||||
purchaseOrdersService = getBean(PurchaseOrdersService.class);
|
||||
}
|
||||
return purchaseOrdersService;
|
||||
return getBean(PurchaseOrdersService.class);
|
||||
}
|
||||
|
||||
EmployeeStringConverter getEmployeeStringConverter() {
|
||||
if (employeeStringConverter == null) {
|
||||
employeeStringConverter = getBean(EmployeeStringConverter.class);
|
||||
}
|
||||
return employeeStringConverter;
|
||||
return getBean(EmployeeStringConverter.class);
|
||||
}
|
||||
|
||||
CompanyService getCompanyService() {
|
||||
return controller.getCompanyService();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.ecep.contract.controller.contract;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
import com.ecep.contract.ContractPayWay;
|
||||
import com.ecep.contract.controller.contract.sale_order.SalesOrderWindowController;
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.controller.table.cell.CompanyTableCell;
|
||||
import com.ecep.contract.controller.table.cell.EmployeeTableCell;
|
||||
import com.ecep.contract.controller.table.cell.LocalDateFieldTableCell;
|
||||
import com.ecep.contract.converter.EmployeeStringConverter;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.service.SaleOrdersService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.vm.SalesOrderViewModel;
|
||||
import com.ecep.contract.vo.SalesOrderVo;
|
||||
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 子合同
|
||||
*/
|
||||
@FxmlPath("/ui/contract/contract-tab-sale-orders.fxml")
|
||||
public class ContractTabSkinSaleOrders
|
||||
extends AbstContractTableTabSkin<SalesOrderVo, SalesOrderViewModel>
|
||||
implements TabSkin {
|
||||
|
||||
@Setter
|
||||
private SaleOrdersService saleOrdersService;
|
||||
private CompanyService companyService;
|
||||
|
||||
@Setter
|
||||
private EmployeeStringConverter employeeStringConverter;
|
||||
|
||||
public TableColumn<SalesOrderViewModel, Number> idColumn;
|
||||
public TableColumn<SalesOrderViewModel, String> codeColumn;
|
||||
/**
|
||||
* 业务员, Employee
|
||||
*/
|
||||
public TableColumn<SalesOrderViewModel, Integer> employeeColumn;
|
||||
/**
|
||||
* 创建人, Employee
|
||||
*/
|
||||
public TableColumn<SalesOrderViewModel, Integer> makerColumn;
|
||||
public TableColumn<SalesOrderViewModel, LocalDate> makerDateColumn;
|
||||
/**
|
||||
* 审核人, Employee
|
||||
*/
|
||||
public TableColumn<SalesOrderViewModel, Integer> verifierColumn;
|
||||
public TableColumn<SalesOrderViewModel, LocalDate> verifierDateColumn;
|
||||
public TableColumn<SalesOrderViewModel, Number> refIdColumn;
|
||||
public TableColumn<SalesOrderViewModel, Number> taxRateColumn;
|
||||
public TableColumn<SalesOrderViewModel, String> customerAddressColumn;
|
||||
/**
|
||||
* 修改人, Employee
|
||||
*/
|
||||
public TableColumn<SalesOrderViewModel, Integer> modifierColumn;
|
||||
public TableColumn<SalesOrderViewModel, LocalDate> modifierDateColumn;
|
||||
/**
|
||||
* 关闭人, Employee
|
||||
*/
|
||||
public TableColumn<SalesOrderViewModel, Integer> closerColumn;
|
||||
public TableColumn<SalesOrderViewModel, LocalDate> closerDateColumn;
|
||||
public TableColumn<SalesOrderViewModel, String> descriptionColumn;
|
||||
public MenuItem subContractTable_menu_refresh;
|
||||
private Tab tab;
|
||||
|
||||
public ContractTabSkinSaleOrders(ContractWindowController controller, Tab tab) {
|
||||
super(controller);
|
||||
this.tab = tab;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeUIComponents() {
|
||||
super.initializeUIComponents();
|
||||
// 合同是收时,有关联子合同
|
||||
getTab().disableProperty().bind(viewModel.getPayWay().isEqualTo(ContractPayWay.RECEIVE).not());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tab getTab() {
|
||||
return tab;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SaleOrdersService getViewModelService() {
|
||||
return getSaleOrdersService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTab() {
|
||||
super.initializeTab();
|
||||
subContractTable_menu_refresh.setOnAction(this::onTableRefreshAction);
|
||||
|
||||
idColumn.setCellValueFactory(param -> param.getValue().getId());
|
||||
codeColumn.setCellValueFactory(param -> param.getValue().getCode());
|
||||
employeeColumn.setCellValueFactory(param -> param.getValue().getEmployee());
|
||||
employeeColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
|
||||
makerColumn.setCellValueFactory(param -> param.getValue().getMaker());
|
||||
makerColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
|
||||
makerDateColumn.setCellValueFactory(param -> param.getValue().getMakerDate());
|
||||
makerDateColumn.setCellFactory(LocalDateFieldTableCell.forTableColumn());
|
||||
verifierColumn.setCellValueFactory(param -> param.getValue().getVerifier());
|
||||
verifierColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
|
||||
verifierDateColumn.setCellValueFactory(param -> param.getValue().getVerifierDate());
|
||||
verifierDateColumn.setCellFactory(LocalDateFieldTableCell.forTableColumn());
|
||||
|
||||
// 设置新增字段的单元格值工厂和工厂类
|
||||
refIdColumn.setCellValueFactory(param -> param.getValue().getRefId());
|
||||
taxRateColumn.setCellValueFactory(param -> param.getValue().getTaxRate());
|
||||
customerAddressColumn.setCellValueFactory(param -> param.getValue().getCustomerAddress());
|
||||
modifierColumn.setCellValueFactory(param -> param.getValue().getModifier());
|
||||
modifierColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
|
||||
modifierDateColumn.setCellValueFactory(param -> param.getValue().getModifierDate());
|
||||
modifierDateColumn.setCellFactory(LocalDateFieldTableCell.forTableColumn());
|
||||
closerColumn.setCellValueFactory(param -> param.getValue().getCloser());
|
||||
closerColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
|
||||
closerDateColumn.setCellValueFactory(param -> param.getValue().getCloserDate());
|
||||
closerDateColumn.setCellFactory(LocalDateFieldTableCell.forTableColumn());
|
||||
|
||||
descriptionColumn.setCellValueFactory(param -> param.getValue().getDescription());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTableRowDoubleClickedAction(SalesOrderViewModel item) {
|
||||
SalesOrderWindowController.show(item, getTableView().getScene().getWindow());
|
||||
}
|
||||
|
||||
SaleOrdersService getSaleOrdersService() {
|
||||
if (saleOrdersService == null) {
|
||||
saleOrdersService = getBean(SaleOrdersService.class);
|
||||
}
|
||||
return saleOrdersService;
|
||||
}
|
||||
|
||||
CompanyService getCompanyService() {
|
||||
if (companyService == null) {
|
||||
companyService = getBean(CompanyService.class);
|
||||
}
|
||||
return companyService;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,31 @@
|
||||
package com.ecep.contract.manager.ds.contract.controller;
|
||||
|
||||
import com.ecep.contract.manager.ds.contract.ContractPayWay;
|
||||
import com.ecep.contract.manager.ds.contract.model.Contract;
|
||||
import com.ecep.contract.manager.ds.contract.service.ContractService;
|
||||
import com.ecep.contract.manager.ds.contract.vo.ContractViewModel;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.ui.tab.TabSkin;
|
||||
import com.ecep.contract.manager.util.SpecificationUtils;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
package com.ecep.contract.controller.contract;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
import com.ecep.contract.ContractPayWay;
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.controller.table.cell.CompanyTableCell;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.service.ContractService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import com.ecep.contract.vm.ContractViewModel;
|
||||
import com.ecep.contract.vo.ContractVo;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.input.KeyCode;
|
||||
|
||||
/**
|
||||
* 子合同
|
||||
*/
|
||||
@FxmlPath("/ui/contract/contract-tab-sub-contract.fxml")
|
||||
public class ContractTabSkinSubContract
|
||||
extends AbstContractTableTabSkin<Contract, ContractViewModel>
|
||||
extends AbstContractTableTabSkin<ContractVo, ContractViewModel>
|
||||
implements TabSkin {
|
||||
|
||||
public TableColumn<ContractViewModel, Number> subContractTable_idColumn;
|
||||
@@ -29,6 +35,7 @@ public class ContractTabSkinSubContract
|
||||
public TableColumn<ContractViewModel, LocalDate> subContractTable_inureDateColumn;
|
||||
public TableColumn<ContractViewModel, LocalDate> subContractTable_orderDateColumn;
|
||||
public TableColumn<ContractViewModel, LocalDate> subContractTable_varyDateColumn;
|
||||
public TableColumn<ContractViewModel, Integer> subContractTable_companyColumn;
|
||||
public MenuItem subContractTable_menu_refresh;
|
||||
public TextField contractSearchKeyField;
|
||||
public Button contractSearchBtn;
|
||||
@@ -54,12 +61,11 @@ public class ContractTabSkinSubContract
|
||||
return getContractService();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Specification<Contract> getSpecification(Contract parent) {
|
||||
return SpecificationUtils.and(getSpecification(), (root, query, builder) -> {
|
||||
return builder.equal(root.get("parentCode"), parent.getCode());
|
||||
});
|
||||
public ParamUtils.Builder getSpecification(ContractVo parent) {
|
||||
ParamUtils.Builder params = getSpecification();
|
||||
params.equals("parentCode", parent.getCode());
|
||||
return params;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,6 +88,8 @@ public class ContractTabSkinSubContract
|
||||
subContractTable_inureDateColumn.setCellValueFactory(param -> param.getValue().getInureDate());
|
||||
subContractTable_orderDateColumn.setCellValueFactory(param -> param.getValue().getOrderDate());
|
||||
subContractTable_varyDateColumn.setCellValueFactory(param -> param.getValue().getVaryDate());
|
||||
subContractTable_companyColumn.setCellValueFactory(param -> param.getValue().getCompany());
|
||||
subContractTable_companyColumn.setCellFactory(CompanyTableCell.forTableColumn(getCachedBean(CompanyService.class)));
|
||||
|
||||
Platform.runLater(() -> {
|
||||
getTableView().getSortOrder().add(subContractTable_codeColumn);
|
||||
@@ -1,40 +1,52 @@
|
||||
package com.ecep.contract.manager.ds.contract.controller;
|
||||
|
||||
import com.ecep.contract.manager.SpringApp;
|
||||
import com.ecep.contract.manager.ds.company.model.Company;
|
||||
import com.ecep.contract.manager.ds.company.service.CompanyService;
|
||||
import com.ecep.contract.manager.ds.company.vo.CompanyViewModel;
|
||||
import com.ecep.contract.manager.ds.contract.ContractPayWay;
|
||||
import com.ecep.contract.manager.ds.contract.model.Contract;
|
||||
import com.ecep.contract.manager.ds.contract.model.ContractBidVendor;
|
||||
import com.ecep.contract.manager.ds.contract.model.ContractFile;
|
||||
import com.ecep.contract.manager.ds.contract.service.ContractBidVendorService;
|
||||
import com.ecep.contract.manager.ds.contract.service.ContractFileService;
|
||||
import com.ecep.contract.manager.ds.contract.vo.ContractBidVendorViewModel;
|
||||
import com.ecep.contract.manager.ui.table.EditableEntityTableTabSkin;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.ui.tab.TabSkin;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.util.StringConverter;
|
||||
import lombok.Setter;
|
||||
import org.controlsfx.control.textfield.TextFields;
|
||||
import org.hibernate.Hibernate;
|
||||
package com.ecep.contract.controller.contract;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.ecep.contract.service.ContractFileService;
|
||||
import org.controlsfx.control.textfield.TextFields;
|
||||
|
||||
import com.ecep.contract.ContractPayWay;
|
||||
import com.ecep.contract.SpringApp;
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.controller.table.EditableEntityTableTabSkin;
|
||||
import com.ecep.contract.controller.table.cell.CompanyTableCell;
|
||||
import com.ecep.contract.controller.table.cell.ContractFileTableCell;
|
||||
import com.ecep.contract.controller.vendor.bid.VendorBidWindowController;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.service.ContractBidVendorService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.vm.CompanyViewModel;
|
||||
import com.ecep.contract.vm.ContractBidVendorViewModel;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
import com.ecep.contract.vo.ContractBidVendorVo;
|
||||
import com.ecep.contract.vo.ContractVo;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ChoiceDialog;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.util.StringConverter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 以下是比价供应商
|
||||
*/
|
||||
@FxmlPath("/ui/contract/contract-tab-bid.fxml")
|
||||
public class ContractTabSkinVendorBid
|
||||
extends AbstContractTableTabSkin<ContractBidVendor, ContractBidVendorViewModel>
|
||||
implements TabSkin, EditableEntityTableTabSkin<ContractBidVendor, ContractBidVendorViewModel> {
|
||||
extends AbstContractTableTabSkin<ContractBidVendorVo, ContractBidVendorViewModel>
|
||||
implements TabSkin, EditableEntityTableTabSkin<ContractBidVendorVo, ContractBidVendorViewModel> {
|
||||
@Setter
|
||||
private ContractBidVendorService service;
|
||||
public TableColumn<ContractBidVendorViewModel, Number> bidVendorTable_idColumn;
|
||||
public TableColumn<ContractBidVendorViewModel, String> bidVendorTable_companyColumn;
|
||||
public TableColumn<ContractBidVendorViewModel, ContractFile> bidVendorTable_quotationSheetColumn;
|
||||
public TableColumn<ContractBidVendorViewModel, Integer> bidVendorTable_companyColumn;
|
||||
/**
|
||||
* 报价单, 合同文件, ContractFile
|
||||
*/
|
||||
public TableColumn<ContractBidVendorViewModel, Integer> bidVendorTable_quotationSheetColumn;
|
||||
|
||||
public Button bidVendorCreateBtn;
|
||||
public MenuItem bidVendorTable_menu_refresh;
|
||||
@@ -62,32 +74,6 @@ public class ContractTabSkinVendorBid
|
||||
return controller.bidVendorTab;
|
||||
}
|
||||
|
||||
static class QuotationSheetColumnTableCell extends TableCell<ContractBidVendorViewModel, ContractFile> {
|
||||
private ContractFileService contractFileService;
|
||||
|
||||
public ContractFileService getContractFileService() {
|
||||
if (contractFileService == null) {
|
||||
contractFileService = SpringApp.getBean(ContractFileService.class);
|
||||
}
|
||||
return contractFileService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateItem(ContractFile item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (empty || item == null) {
|
||||
setText(null);
|
||||
} else {
|
||||
if (!Hibernate.isInitialized(item)) {
|
||||
item = getContractFileService().findById(item.getId());
|
||||
ContractBidVendorViewModel viewModel = getTableRow().getItem();
|
||||
viewModel.getQuotationSheet().set(item);
|
||||
}
|
||||
setText(item.getFileName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTab() {
|
||||
bidVendorCreateBtn.setOnAction(this::onBidVendorTableCreateAction);
|
||||
@@ -96,15 +82,10 @@ public class ContractTabSkinVendorBid
|
||||
bidVendorTable_menu_chose_sheet.setOnAction(this::onBidVendorTableChoseQuotationSheetAction);
|
||||
|
||||
bidVendorTable_idColumn.setCellValueFactory(param -> param.getValue().getId());
|
||||
bidVendorTable_companyColumn.setCellValueFactory(param -> param.getValue().getCompany().map(c -> {
|
||||
if (c == null) {
|
||||
return null;
|
||||
} else {
|
||||
return c.getName();
|
||||
}
|
||||
}));
|
||||
bidVendorTable_quotationSheetColumn.setCellValueFactory(param -> param.getValue().getQuotationSheet());
|
||||
bidVendorTable_quotationSheetColumn.setCellFactory(param -> new QuotationSheetColumnTableCell());
|
||||
bidVendorTable_companyColumn.setCellValueFactory(param -> param.getValue().getCompanyId());
|
||||
bidVendorTable_companyColumn.setCellFactory(CompanyTableCell.forTableColumn(getCompanyService()));
|
||||
bidVendorTable_quotationSheetColumn.setCellValueFactory(param -> param.getValue().getQuotationSheetFileId());
|
||||
bidVendorTable_quotationSheetColumn.setCellFactory(ContractFileTableCell.forTableColumn(SpringApp.getBean(ContractFileService.class)));
|
||||
|
||||
super.initializeTab();
|
||||
}
|
||||
@@ -114,9 +95,9 @@ public class ContractTabSkinVendorBid
|
||||
showInOwner(VendorBidWindowController.class, item);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void onBidVendorTableCreateAction(ActionEvent event) {
|
||||
Contract contract = getParent();
|
||||
|
||||
ContractVo contract = getParent();
|
||||
|
||||
ChoiceDialog<String> dialog = new ChoiceDialog<>();
|
||||
dialog.setTitle("添加比价供应商");
|
||||
@@ -143,13 +124,13 @@ public class ContractTabSkinVendorBid
|
||||
comboBox.setEditable(true);
|
||||
dialog.showAndWait().ifPresent(selectedItem -> {
|
||||
|
||||
Company company = getCompanyService().findAllByName(selectedItem).getFirst();
|
||||
CompanyVo company = getCompanyService().findAllByName(selectedItem).getFirst();
|
||||
|
||||
List<ContractBidVendor> list = getService().findByContractAndCompany(contract, company);
|
||||
List<ContractBidVendorVo> list = getService().findByContractAndCompany(contract, company);
|
||||
if (list == null || list.isEmpty()) {
|
||||
ContractBidVendor bidVendor = new ContractBidVendor();
|
||||
bidVendor.setContract(contract);
|
||||
bidVendor.setCompany(company);
|
||||
ContractBidVendorVo bidVendor = new ContractBidVendorVo();
|
||||
bidVendor.setContractId(contract.getId());
|
||||
bidVendor.setCompanyId(company.getId());
|
||||
getService().save(bidVendor);
|
||||
loadTableDataSet();
|
||||
} else {
|
||||
@@ -161,7 +142,6 @@ public class ContractTabSkinVendorBid
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 匹配报价表
|
||||
*
|
||||
@@ -184,8 +164,7 @@ public class ContractTabSkinVendorBid
|
||||
}
|
||||
|
||||
private CompanyService getCompanyService() {
|
||||
return controller.companyService;
|
||||
return controller.getCompanyService();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,37 +1,20 @@
|
||||
package com.ecep.contract.manager.ds.contract.controller;
|
||||
package com.ecep.contract.controller.contract;
|
||||
|
||||
import com.ecep.contract.manager.ds.contract.ContractPayWay;
|
||||
import com.ecep.contract.manager.ds.contract.model.Contract;
|
||||
import com.ecep.contract.manager.ds.contract.service.ContractService;
|
||||
import com.ecep.contract.manager.ds.contract.tasker.ContractVerifyComm;
|
||||
import com.ecep.contract.manager.ds.contract.tasker.ContractVerifyResultExportAsExcelFile;
|
||||
import com.ecep.contract.manager.ui.BaseController;
|
||||
import com.ecep.contract.manager.ds.other.model.Employee;
|
||||
import com.ecep.contract.manager.ds.other.service.EmployeeService;
|
||||
import com.ecep.contract.manager.ds.project.service.SaleTypeService;
|
||||
import com.ecep.contract.manager.ds.vendor.service.VendorGroupService;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.ui.Message;
|
||||
import com.ecep.contract.manager.ui.MessageHolder;
|
||||
import com.ecep.contract.manager.ui.table.cell.EmployeeTableCell;
|
||||
import com.ecep.contract.manager.util.UITools;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.fxml.FXML;
|
||||
import static com.ecep.contract.util.TableViewUtils.bindDoubleClicked;
|
||||
import static com.ecep.contract.util.TableViewUtils.bindEnterPressed;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.stage.*;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.hibernate.Hibernate;
|
||||
import org.controlsfx.control.PopOver;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -40,21 +23,45 @@ import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.logging.Level;
|
||||
import com.ecep.contract.Message;
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.controller.BaseController;
|
||||
import com.ecep.contract.controller.table.cell.EmployeeTableCell;
|
||||
import com.ecep.contract.model.Employee;
|
||||
import com.ecep.contract.service.ContractService;
|
||||
import com.ecep.contract.service.EmployeeService;
|
||||
import com.ecep.contract.service.ProjectSaleTypeService;
|
||||
import com.ecep.contract.service.VendorGroupService;
|
||||
import com.ecep.contract.task.ContractVerifyComm;
|
||||
import com.ecep.contract.task.ContractVerifyResultExportAsExcelFileTasker;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vo.ContractVo;
|
||||
import com.ecep.contract.vo.EmployeeVo;
|
||||
|
||||
import static com.ecep.contract.manager.util.TableViewUtils.bindDoubleClicked;
|
||||
import static com.ecep.contract.manager.util.TableViewUtils.bindEnterPressed;
|
||||
import static java.util.concurrent.CompletableFuture.runAsync;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import javafx.stage.WindowEvent;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@@ -68,6 +75,7 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
return super.show(loader, owner, modality);
|
||||
}
|
||||
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
public static class MessageExt extends Message {
|
||||
@@ -78,14 +86,17 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Data
|
||||
public static class Model implements MessageHolder {
|
||||
public static class Model {
|
||||
private SimpleStringProperty code = new SimpleStringProperty();
|
||||
private SimpleStringProperty name = new SimpleStringProperty();
|
||||
private SimpleObjectProperty<Employee> employee = new SimpleObjectProperty<>();
|
||||
private SimpleObjectProperty<Integer> employee = new SimpleObjectProperty<>();
|
||||
private SimpleObjectProperty<LocalDate> setupDate = new SimpleObjectProperty<>();
|
||||
private SimpleListProperty<MessageExt> messages = new SimpleListProperty<>(FXCollections.observableArrayList());
|
||||
}
|
||||
|
||||
static class MessageHolderImpl implements MessageHolder {
|
||||
List<MessageExt> messages = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void addMessage(Level level, String message) {
|
||||
@@ -104,9 +115,6 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static class StateTableCell extends TableCell<Model, ObservableList<MessageExt>> {
|
||||
Label message2Label(MessageExt message) {
|
||||
Label label = new Label(message.getMessage());
|
||||
@@ -151,16 +159,10 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
ContractVerifyComm comm = new ContractVerifyComm();
|
||||
ContractVerifyComm comm = new ContractVerifyComm(this);
|
||||
|
||||
@Autowired
|
||||
private SaleTypeService saleTypeService;
|
||||
@Autowired
|
||||
private VendorGroupService vendorGroupService;
|
||||
@Autowired
|
||||
private ContractService contractService;
|
||||
@Autowired
|
||||
private EmployeeService employeeService;
|
||||
|
||||
@FXML
|
||||
public DatePicker setupDateBeginSelector;
|
||||
@@ -189,7 +191,6 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
@FXML
|
||||
public CheckMenuItem onlyShowVerifiedChecker;
|
||||
|
||||
|
||||
@FXML
|
||||
public TableView<Model> viewTable;
|
||||
private final ObservableList<Model> viewTableDataSet = FXCollections.observableArrayList();
|
||||
@@ -198,7 +199,7 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
@FXML
|
||||
public TableColumn<Model, String> viewTable_nameColumn;
|
||||
@FXML
|
||||
public TableColumn<Model, Employee> viewTable_employeeColumn;
|
||||
public TableColumn<Model, Integer> viewTable_employeeColumn;
|
||||
@FXML
|
||||
public TableColumn<Model, LocalDate> viewTable_setupDateColumn;
|
||||
@FXML
|
||||
@@ -212,7 +213,6 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
|
||||
public void onShown(WindowEvent windowEvent) {
|
||||
super.onShown(windowEvent);
|
||||
comm.setContractService(contractService);
|
||||
viewTable.getScene().getStylesheets().add("/ui/contract/contract-verify.css");
|
||||
|
||||
LocalDate now = LocalDate.now();
|
||||
@@ -224,9 +224,7 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
viewTable_employeeColumn.setCellValueFactory(param -> param.getValue().getEmployee());
|
||||
viewTable_employeeColumn.setCellFactory(param -> new EmployeeTableCell<>(getEmployeeService()));
|
||||
viewTable_setupDateColumn.setCellValueFactory(param -> param.getValue().getSetupDate());
|
||||
// viewTable_stateColumn.setCellValueFactory(param -> param.getValue().getMessages().map(messages -> {
|
||||
// return messages.stream().map(Message::getMessage).collect(Collectors.joining(", "));
|
||||
// }));
|
||||
|
||||
viewTable_stateColumn.setCellValueFactory(param -> param.getValue().getMessages());
|
||||
viewTable_stateColumn.setCellFactory(param -> new StateTableCell());
|
||||
|
||||
@@ -241,11 +239,9 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
comm.getVerifyCustomerFiles().bind(verifyCustomerFileChecker.selectedProperty());
|
||||
comm.getVerifyCustomerSubContractDate().bind(verifyCustomerSubContractDateChecker.selectedProperty());
|
||||
|
||||
|
||||
bindDoubleClicked(viewTable, this::showContract);
|
||||
bindEnterPressed(viewTable, this::reVerifyContract);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -257,40 +253,34 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
viewTableDataSet.clear();
|
||||
Pageable pageRequest = PageRequest.ofSize(200);
|
||||
AtomicInteger counter = new AtomicInteger(0);
|
||||
Specification<Contract> spec = (root, query, builder) -> {
|
||||
return builder.and(
|
||||
builder.or(
|
||||
builder.equal(root.get("payWay"), ContractPayWay.RECEIVE),
|
||||
builder.and(
|
||||
builder.equal(root.get("payWay"), ContractPayWay.PAY),
|
||||
builder.or(
|
||||
builder.isNull(root.get("parentCode")),
|
||||
builder.equal(root.get("parentCode"), "")
|
||||
)
|
||||
)
|
||||
),
|
||||
builder.between(root.get("setupDate"), setupDateBeginSelector.getValue(), setupDateEndSelector.getValue())
|
||||
);
|
||||
};
|
||||
long total = contractService.count(spec);
|
||||
|
||||
Map<String, Object> params = ParamUtils.builder().
|
||||
between("setupDate", setupDateBeginSelector.getValue(), setupDateEndSelector.getValue())
|
||||
.build();
|
||||
|
||||
|
||||
long total = contractService.count(params);
|
||||
setStatus("合同:" + total + " 条");
|
||||
|
||||
MessageHolderImpl messageHolder = new MessageHolderImpl();
|
||||
|
||||
while (true) {
|
||||
if (isCloseRequested()) {
|
||||
break;
|
||||
}
|
||||
|
||||
Page<Contract> page = contractService.findAll(spec, pageRequest);
|
||||
for (Contract contract : page) {
|
||||
Page<ContractVo> page = contractService.findAll(params, pageRequest);
|
||||
for (ContractVo contract : page) {
|
||||
messageHolder.messages.clear();
|
||||
if (isCloseRequested()) {
|
||||
break;
|
||||
}
|
||||
counter.incrementAndGet();
|
||||
Model model = new Model();
|
||||
viewTableDataSet.add(model);
|
||||
Employee handler = contract.getHandler();
|
||||
Integer handler = contract.getHandlerId();
|
||||
if (handler == null) {
|
||||
model.getEmployee().set(contract.getEmployee());
|
||||
model.getEmployee().set(contract.getEmployeeId());
|
||||
} else {
|
||||
model.getEmployee().set(handler);
|
||||
}
|
||||
@@ -298,11 +288,12 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
model.getCode().set(contract.getCode());
|
||||
model.getName().set(contract.getName());
|
||||
model.getSetupDate().set(contract.getSetupDate());
|
||||
comm.verify(contract, model);
|
||||
|
||||
comm.verify(contract, messageHolder);
|
||||
setStatus("合同验证进度:" + counter.get() + " / " + total);
|
||||
// 移除中间消息
|
||||
if (!model.getMessages().isEmpty()) {
|
||||
model.getMessages().removeIf(msg -> msg.getLevel().intValue() <= Level.INFO.intValue());
|
||||
if (!messageHolder.messages.isEmpty()) {
|
||||
model.getMessages().setAll(messageHolder.messages.stream().filter(msg -> msg.getLevel().intValue() > Level.INFO.intValue()).limit(50).toList());
|
||||
}
|
||||
if (model.getMessages().isEmpty()) {
|
||||
if (onlyShowVerifiedChecker.isSelected()) {
|
||||
@@ -323,7 +314,6 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void onContractReVerifyAction(ActionEvent event) {
|
||||
Model selectedItem = viewTable.getSelectionModel().getSelectedItem();
|
||||
if (selectedItem == null) {
|
||||
@@ -338,23 +328,31 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
return;
|
||||
}
|
||||
runAsync(() -> {
|
||||
Contract contract = contractService.findByCode(contractCode);
|
||||
ContractVo contract = null;
|
||||
MessageHolderImpl messageHolder = new MessageHolderImpl();
|
||||
try {
|
||||
contract = contractService.findByCode(contractCode);
|
||||
} catch (Exception e) {
|
||||
handleException("查找合同 " + contractCode + " 时发生错误", e);
|
||||
return;
|
||||
}
|
||||
if (contract == null) {
|
||||
return;
|
||||
}
|
||||
model.getMessages().clear();
|
||||
try {
|
||||
comm.verify(contract, model);
|
||||
// 移除中间消息
|
||||
if (!model.getMessages().isEmpty()) {
|
||||
model.getMessages().removeIf(msg -> msg.getLevel().intValue() <= Level.INFO.intValue());
|
||||
}
|
||||
comm.verify(contract, messageHolder);
|
||||
} catch (Exception e) {
|
||||
logger.error(model.getCode().get(), e);
|
||||
model.error(e.getMessage());
|
||||
messageHolder.error(e.getMessage());
|
||||
}
|
||||
|
||||
if (model.getMessages().isEmpty()) {
|
||||
// 移除中间消息
|
||||
if (!messageHolder.messages.isEmpty()) {
|
||||
model.getMessages().setAll(messageHolder.messages.stream().filter(msg -> msg.getLevel().intValue() > Level.INFO.intValue()).limit(50).toList());
|
||||
}
|
||||
|
||||
if (messageHolder.messages.isEmpty()) {
|
||||
Platform.runLater(() -> {
|
||||
viewTableDataSet.remove(model);
|
||||
});
|
||||
@@ -362,7 +360,6 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public void onShowContractDetailAction(ActionEvent event) {
|
||||
Model selectedItem = viewTable.getSelectionModel().getSelectedItem();
|
||||
if (selectedItem == null) {
|
||||
@@ -376,20 +373,58 @@ public class ContractVerifyWindowController extends BaseController {
|
||||
if (!StringUtils.hasText(contractCode)) {
|
||||
return;
|
||||
}
|
||||
Contract contract = contractService.findByCode(contractCode);
|
||||
ContractVo contract = null;
|
||||
try {
|
||||
contract = contractService.findByCode(contractCode);
|
||||
} catch (Exception e) {
|
||||
handleException("查找合同 " + contractCode + " 时发生错误", e);
|
||||
return;
|
||||
}
|
||||
if (contract == null) {
|
||||
return;
|
||||
}
|
||||
ContractWindowController.show(contract, viewTable.getScene().getWindow());
|
||||
}
|
||||
|
||||
|
||||
public void onShowVerifyStatusAction(ActionEvent event) {
|
||||
|
||||
// 在新新窗口中显示 状态消息 Model# messages
|
||||
|
||||
Model selectedItem = viewTable.getSelectionModel().getSelectedItem();
|
||||
if (selectedItem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ListView<MessageExt> listView = new ListView<>();
|
||||
listView.setItems(selectedItem.messages);
|
||||
listView.setCellFactory(v -> new ListCell<>() {
|
||||
@Override
|
||||
protected void updateItem(MessageExt item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item == null || empty) {
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
} else {
|
||||
setText(item.getMessage());
|
||||
setGraphic(new Label(item.getPrefix()));
|
||||
}
|
||||
}
|
||||
});
|
||||
PopOver popOver = new PopOver(listView);
|
||||
popOver.setArrowLocation(PopOver.ArrowLocation.TOP_LEFT);
|
||||
MenuItem menuItem = (MenuItem) event.getSource();
|
||||
Node node = viewTable.lookup(".table-row-cell:selected");
|
||||
popOver.show(node);
|
||||
}
|
||||
|
||||
public void onExportVerifyResultAsFileAction(ActionEvent e) {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle("导出核验结果");
|
||||
chooser.setInitialFileName("核验结果.xlsx");
|
||||
File selected = chooser.showSaveDialog(viewTable.getScene().getWindow());
|
||||
if (selected != null) {
|
||||
ContractVerifyResultExportAsExcelFile task = new ContractVerifyResultExportAsExcelFile();
|
||||
ContractVerifyResultExportAsExcelFileTasker task = new ContractVerifyResultExportAsExcelFileTasker();
|
||||
task.setDestFile(selected);
|
||||
task.setModels(new ArrayList<>(viewTableDataSet));
|
||||
UITools.showTaskDialogAndWait("导出中...", task, null);
|
||||
@@ -1,46 +1,47 @@
|
||||
package com.ecep.contract.manager.ds.contract.controller;
|
||||
package com.ecep.contract.controller.contract;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import com.ecep.contract.manager.ds.company.controller.CompanyWindowController;
|
||||
import com.ecep.contract.manager.ds.company.model.Company;
|
||||
import com.ecep.contract.manager.ds.company.service.CompanyService;
|
||||
import com.ecep.contract.manager.ds.contract.model.Contract;
|
||||
import com.ecep.contract.manager.ds.contract.service.ContractService;
|
||||
import com.ecep.contract.manager.ds.contract.tasker.ContractRepairTask;
|
||||
import com.ecep.contract.manager.ds.contract.tasker.ContractVerifyTasker;
|
||||
import com.ecep.contract.manager.ds.contract.vo.ContractViewModel;
|
||||
import com.ecep.contract.manager.ds.project.service.ProjectService;
|
||||
import com.ecep.contract.manager.ui.AbstEntityController;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.util.DesktopUtils;
|
||||
import com.ecep.contract.manager.util.UITools;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.Window;
|
||||
import javafx.stage.WindowEvent;
|
||||
import javafx.util.converter.LocalDateStringConverter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import com.ecep.contract.ContractPayWay;
|
||||
import com.ecep.contract.DesktopUtils;
|
||||
import com.ecep.contract.controller.AbstEntityController;
|
||||
import com.ecep.contract.controller.company.CompanyWindowController;
|
||||
import com.ecep.contract.controller.tab.ContractTabSkinBase;
|
||||
import com.ecep.contract.controller.tab.ContractTabSkinInvoices;
|
||||
import com.ecep.contract.controller.tab.ContractTabSkinItemsV2;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.service.ContractService;
|
||||
import com.ecep.contract.task.ContractRepairTask;
|
||||
import com.ecep.contract.task.ContractVerifyTasker;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.ContractViewModel;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
import com.ecep.contract.vo.ContractVo;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.Window;
|
||||
import javafx.stage.WindowEvent;
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
@FxmlPath("/ui/contract/contract.fxml")
|
||||
public class ContractWindowController
|
||||
extends AbstEntityController<Contract, ContractViewModel> {
|
||||
extends AbstEntityController<ContractVo, ContractViewModel> {
|
||||
|
||||
|
||||
public static void show(Contract contract, Window owner) {
|
||||
ContractViewModel model = new ContractViewModel();
|
||||
model.update(contract);
|
||||
show(model, owner);
|
||||
|
||||
public static void show(ContractVo contract, Window owner) {
|
||||
show(ContractViewModel.from(contract), owner);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,17 +64,8 @@ public class ContractWindowController
|
||||
public Button openRelativeCompanyCustomerBtn;
|
||||
public Button openRelativeCompanyVendorBtn;
|
||||
|
||||
@Autowired
|
||||
CompanyService companyService;
|
||||
@Autowired
|
||||
ContractService contractService;
|
||||
@Autowired
|
||||
ProjectService projectService;
|
||||
|
||||
LocalDateStringConverter localDateStringConverter = new LocalDateStringConverter(DateTimeFormatter.ISO_LOCAL_DATE, null);
|
||||
|
||||
|
||||
public TextField nameField;
|
||||
public CheckBox contractNameLockedCk;
|
||||
public TextField guidField;
|
||||
public TextField codeField;
|
||||
public TextField parentCodeField;
|
||||
@@ -124,12 +116,17 @@ public class ContractWindowController
|
||||
|
||||
@Override
|
||||
public ContractService getViewModelService() {
|
||||
return contractService;
|
||||
return getCachedBean(ContractService.class);
|
||||
}
|
||||
|
||||
public CompanyService getCompanyService() {
|
||||
return getCachedBean(CompanyService.class);
|
||||
}
|
||||
|
||||
public void onShown(WindowEvent windowEvent) {
|
||||
super.onShown(windowEvent);
|
||||
getTitle().bind(viewModel.getName().map(name -> "[" + viewModel.getId().get() + "] " + viewModel.getCode().get() + " " + name + " 合同详情"));
|
||||
getTitle().bind(viewModel.getName()
|
||||
.map(name -> "[" + viewModel.getId().get() + "] " + viewModel.getCode().get() + " " + name + " 合同详情"));
|
||||
root.getScene().getStylesheets().add("/ui/contract/contract.css");
|
||||
}
|
||||
|
||||
@@ -139,31 +136,35 @@ public class ContractWindowController
|
||||
ObservableList<Tab> tabs = tabPane.getTabs();
|
||||
|
||||
registerTabSkin(baseInfoTab, tab -> new ContractTabSkinBase(this));
|
||||
switch (viewModel.getPayWay().get()) {
|
||||
case RECEIVE -> {
|
||||
tabs.remove(extendVendorInfo);
|
||||
registerTabSkin(contractTab, t -> new ContractTabSkinSubContract(this));
|
||||
tabs.remove(bidVendorTab);
|
||||
Tab saleOrderTab = new Tab("销售订单");
|
||||
tabs.add(saleOrderTab);
|
||||
registerTabSkin(saleOrderTab, tab -> new ContractTabSkinSaleOrders(this, tab));
|
||||
tabs.add(new Tab("票据"));
|
||||
break;
|
||||
}
|
||||
case PAY -> {
|
||||
registerTabSkin(extendVendorInfo, t -> new ContractTabSkinExtendVendorInfo(this));
|
||||
tabs.remove(contractTab);
|
||||
registerTabSkin(bidVendorTab, t -> new ContractTabSkinVendorBid(this));
|
||||
ContractPayWay payWay = viewModel.getPayWay().get();
|
||||
if (payWay == ContractPayWay.RECEIVE) {
|
||||
tabs.remove(extendVendorInfo);
|
||||
registerTabSkin(contractTab, t -> new ContractTabSkinSubContract(this));
|
||||
tabs.remove(bidVendorTab);
|
||||
Tab saleOrderTab = new Tab("销售订单");
|
||||
payPlanTab.setText("收款计划");
|
||||
tabs.add(saleOrderTab);
|
||||
registerTabSkin(saleOrderTab, tab -> new ContractTabSkinSaleOrders(this, tab));
|
||||
Tab invoiceTab = new Tab("发票");
|
||||
tabs.add(invoiceTab);
|
||||
registerTabSkin(invoiceTab, tab -> new ContractTabSkinInvoices(this, tab));
|
||||
tabs.add(new Tab("出库单"));
|
||||
tabs.add(new Tab("发货单"));
|
||||
tabs.add(new Tab("签收单"));
|
||||
|
||||
Tab purchaseOrderTab = new Tab("采购订单");
|
||||
tabs.add(purchaseOrderTab);
|
||||
registerTabSkin(purchaseOrderTab, tab -> new ContractTabSkinPurchaseOrders(this, tab));
|
||||
} else if (payWay == ContractPayWay.PAY) {
|
||||
registerTabSkin(extendVendorInfo, t -> new ContractTabSkinExtendVendorInfo(this));
|
||||
tabs.remove(contractTab);
|
||||
registerTabSkin(bidVendorTab, t -> new ContractTabSkinVendorBid(this));
|
||||
|
||||
tabs.add(new Tab("发货单"));
|
||||
tabs.add(new Tab("签收单"));
|
||||
tabs.add(new Tab("付款单"));
|
||||
break;
|
||||
}
|
||||
Tab purchaseOrderTab = new Tab("采购订单");
|
||||
tabs.add(purchaseOrderTab);
|
||||
registerTabSkin(purchaseOrderTab, tab -> new ContractTabSkinPurchaseOrders(this, tab));
|
||||
|
||||
tabs.add(new Tab("入库单"));
|
||||
tabs.add(new Tab("付款单"));
|
||||
|
||||
payPlanTab.setText("付款计划");
|
||||
}
|
||||
|
||||
registerTabSkin(itemTab, tab -> new ContractTabSkinItemsV2(this));
|
||||
@@ -171,9 +172,8 @@ public class ContractWindowController
|
||||
registerTabSkin(fileTab, tab -> new ContractTabSkinFiles(this));
|
||||
}
|
||||
|
||||
|
||||
public void onContractOpenInExplorerAction(ActionEvent event) {
|
||||
Contract contract = getEntity();
|
||||
ContractVo contract = getEntity();
|
||||
String path = contract.getPath();
|
||||
if (!StringUtils.hasText(path)) {
|
||||
setStatus("未设置目录");
|
||||
@@ -188,13 +188,13 @@ public class ContractWindowController
|
||||
}
|
||||
|
||||
public void onContractOpenRelativeCompanyAction(ActionEvent event) {
|
||||
Contract contract = getEntity();
|
||||
if (contract.getCompany() == null) {
|
||||
ContractVo contract = getEntity();
|
||||
if (contract.getCompanyId() == null) {
|
||||
UITools.showAlertAndWait("没有关联的公司,你可以尝试同步修复异常。");
|
||||
return;
|
||||
}
|
||||
Integer companyId = contract.getCompany().getId();
|
||||
Company company = companyService.findById(companyId);
|
||||
Integer companyId = contract.getCompanyId();
|
||||
CompanyVo company = getCompanyService().findById(companyId);
|
||||
if (company != null) {
|
||||
CompanyWindowController.show(company, root.getScene().getWindow());
|
||||
}
|
||||
@@ -202,7 +202,6 @@ public class ContractWindowController
|
||||
|
||||
public void onSyncContractAction(ActionEvent event) {
|
||||
ContractRepairTask task = new ContractRepairTask();
|
||||
task.setContractService(contractService);
|
||||
task.setContract(getEntity());
|
||||
UITools.showTaskDialogAndWait("同步合同", task, null);
|
||||
if (task.isRepaired()) {
|
||||
@@ -221,7 +220,7 @@ public class ContractWindowController
|
||||
}
|
||||
}
|
||||
if (task.isItemsUpdated()) {
|
||||
ContractTabSkinItems tabSkin = getTabSkin(ContractTabSkinItems.class);
|
||||
ContractTabSkinItemsV2 tabSkin = getTabSkin(ContractTabSkinItemsV2.class);
|
||||
if (tabSkin != null) {
|
||||
tabSkin.loadTableDataSet();
|
||||
}
|
||||
@@ -234,14 +233,12 @@ public class ContractWindowController
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 验证合同合规性
|
||||
*/
|
||||
public void onContractVerifyAction(ActionEvent event) {
|
||||
Contract contract = getEntity();
|
||||
ContractVo contract = getEntity();
|
||||
ContractVerifyTasker task = new ContractVerifyTasker();
|
||||
task.setContractService(contractService);
|
||||
task.setContract(contract);
|
||||
UITools.showTaskDialogAndWait("同步合规性验证", task, null);
|
||||
}
|
||||
@@ -1,25 +1,33 @@
|
||||
package com.ecep.contract.manager.ds.contract.controller.sale_order;
|
||||
package com.ecep.contract.controller.contract.sale_order;
|
||||
|
||||
import com.ecep.contract.manager.ds.contract.model.SalesOrder;
|
||||
import com.ecep.contract.manager.ds.contract.service.SaleOrdersService;
|
||||
import com.ecep.contract.manager.ds.contract.vo.SalesOrderViewModel;
|
||||
import com.ecep.contract.manager.ui.AbstEntityController;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import com.ecep.contract.vo.SalesOrderVo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.controller.AbstEntityController;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.controller.tab.SalesOrderTabSkinBase;
|
||||
import com.ecep.contract.controller.tab.SalesOrderTabSkinBillVoucher;
|
||||
import com.ecep.contract.controller.tab.SalesOrderTabSkinItems;
|
||||
import com.ecep.contract.service.SaleOrdersService;
|
||||
import com.ecep.contract.vm.SalesOrderViewModel;
|
||||
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TabPane;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
@FxmlPath("/ui/contract/sale-orders.fxml")
|
||||
public class SalesOrderWindowController extends AbstEntityController<SalesOrder, SalesOrderViewModel> {
|
||||
public class SalesOrderWindowController extends AbstEntityController<SalesOrderVo, SalesOrderViewModel> {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SalesOrderWindowController.class);
|
||||
public TabPane tabPane;
|
||||
public Button saveBtn;
|
||||
@@ -35,25 +43,21 @@ public class SalesOrderWindowController extends AbstEntityController<SalesOrder,
|
||||
public TextField makeDateField;
|
||||
public TextField makerField;
|
||||
public TextArea descriptionField;
|
||||
public TextField refIdField;
|
||||
public TextField taxRateField;
|
||||
public TextField customerField;
|
||||
public TextField customerAddressField;
|
||||
public TextField modifierField;
|
||||
public TextField modifierDateField;
|
||||
public TextField closerField;
|
||||
public TextField closerDateField;
|
||||
public TextField contractField;
|
||||
|
||||
|
||||
public static void show(SalesOrderViewModel viewModel, Window window) {
|
||||
show(SalesOrderWindowController.class, viewModel, window);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private SaleOrdersService service;
|
||||
|
||||
@Override
|
||||
protected SalesOrder loadEntity() {
|
||||
return service.findById(viewModel.getId().get());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SalesOrder saveEntity(SalesOrder entity) {
|
||||
return service.save(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show(Stage stage) {
|
||||
super.show(stage);
|
||||
@@ -69,6 +73,6 @@ public class SalesOrderWindowController extends AbstEntityController<SalesOrder,
|
||||
|
||||
@Override
|
||||
public SaleOrdersService getViewModelService() {
|
||||
return service;
|
||||
return getCachedBean(SaleOrdersService.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.ecep.contract.controller.customer;
|
||||
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.controller.table.AbstEntityTableTabSkin;
|
||||
import com.ecep.contract.model.IdentityEntity;
|
||||
import com.ecep.contract.service.CustomerService;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.vm.CompanyCustomerViewModel;
|
||||
import com.ecep.contract.vm.IdentityViewModel;
|
||||
import com.ecep.contract.vo.CustomerVo;
|
||||
|
||||
public abstract class AbstCompanyCustomerTableTabSkin<T extends IdentityEntity, TV extends IdentityViewModel<T>>
|
||||
extends
|
||||
AbstEntityTableTabSkin<CompanyCustomerWindowController, CustomerVo, CompanyCustomerViewModel, T, TV>
|
||||
implements TabSkin {
|
||||
|
||||
public AbstCompanyCustomerTableTabSkin(CompanyCustomerWindowController controller) {
|
||||
super(controller);
|
||||
}
|
||||
|
||||
public CompanyService getCompanyService() {
|
||||
return controller.getCompanyService();
|
||||
}
|
||||
|
||||
protected CustomerService getCompanyCustomerService() {
|
||||
return getCachedBean(CustomerService.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,549 @@
|
||||
package com.ecep.contract.controller.customer;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.ecep.contract.controller.AbstEntityController;
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.service.CompanyCustomerFileTypeService;
|
||||
import com.ecep.contract.service.ViewModelService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.vo.CustomerFileTypeLocalVo;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.FontWeight;
|
||||
import javafx.util.converter.LocalDateStringConverter;
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.ecep.contract.controller.BaseController;
|
||||
import com.ecep.contract.vo.CompanyCustomerEvaluationFormFileVo;
|
||||
import com.ecep.contract.vo.CustomerFileVo;
|
||||
import com.ecep.contract.service.CompanyCustomerFileService;
|
||||
import com.ecep.contract.service.CompanyCustomerEvaluationFormFileService;
|
||||
import com.ecep.contract.util.FileUtils;
|
||||
import com.ecep.contract.util.FxmlUtils;
|
||||
import com.ecep.contract.vm.CompanyCustomerEvaluationFormFileViewModel;
|
||||
import com.ecep.contract.vm.CustomerFileViewModel;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.geometry.Bounds;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.DatePicker;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.SplitPane;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.Toggle;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.image.PixelWriter;
|
||||
import javafx.scene.image.WritableImage;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import javafx.stage.WindowEvent;
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
@FxmlPath("/ui/company/customer/customer_evaluation_form.fxml")
|
||||
public class CompanyCustomerEvaluationFormFileWindowController
|
||||
extends AbstEntityController<CompanyCustomerEvaluationFormFileVo, CompanyCustomerEvaluationFormFileViewModel> {
|
||||
private static final Logger logger = LoggerFactory
|
||||
.getLogger(CompanyCustomerEvaluationFormFileWindowController.class);
|
||||
|
||||
public static void show(CustomerFileViewModel item, Window window) {
|
||||
show(CompanyCustomerEvaluationFormFileWindowController.class, window, controller -> {
|
||||
controller.fileViewModel = item;
|
||||
});
|
||||
}
|
||||
|
||||
public static void show(CompanyCustomerEvaluationFormFileVo saved, Window window) {
|
||||
show(CompanyCustomerEvaluationFormFileViewModel.from(saved), window);
|
||||
}
|
||||
|
||||
public static void show(CompanyCustomerEvaluationFormFileViewModel viewModel, Window window) {
|
||||
show(CompanyCustomerEvaluationFormFileWindowController.class, viewModel, window);
|
||||
}
|
||||
|
||||
public Label idField;
|
||||
public TextField filePathField;
|
||||
public CheckBox validField;
|
||||
public TextField editFilePathField;
|
||||
public DatePicker signDateField;
|
||||
public ToggleGroup catalog;
|
||||
public ToggleGroup level;
|
||||
public ToggleGroup score1;
|
||||
public ToggleGroup score2;
|
||||
public ToggleGroup score3;
|
||||
public ToggleGroup score4;
|
||||
public ToggleGroup score5;
|
||||
public ToggleGroup creditLevel;
|
||||
public ImageView imageView;
|
||||
public SplitPane splitPane;
|
||||
public ScrollPane leftPane;
|
||||
public Label totalCreditScoreLabel;
|
||||
|
||||
private final SimpleIntegerProperty totalCreditScoreProperty = new SimpleIntegerProperty(-1);
|
||||
|
||||
private CustomerFileViewModel fileViewModel;
|
||||
|
||||
private SimpleBooleanProperty changed = new SimpleBooleanProperty(false);
|
||||
|
||||
@Lazy
|
||||
@Autowired
|
||||
private CompanyCustomerEvaluationFormFileService evaluationFormFileService;
|
||||
|
||||
@Override
|
||||
public void show(Stage stage) {
|
||||
super.show(stage);
|
||||
stage.setFullScreen(false);
|
||||
stage.setMaximized(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShown(WindowEvent windowEvent) {
|
||||
super.onShown(windowEvent);
|
||||
getTitle().set("客户评估表单");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewModelService<CompanyCustomerEvaluationFormFileVo, CompanyCustomerEvaluationFormFileViewModel> getViewModelService() {
|
||||
return evaluationFormFileService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initializeData() {
|
||||
CompanyCustomerEvaluationFormFileViewModel viewModel = new CompanyCustomerEvaluationFormFileViewModel();
|
||||
setViewModel(viewModel);
|
||||
runAsync(() -> {
|
||||
CompanyCustomerEvaluationFormFileVo item = getCachedBean(CompanyCustomerEvaluationFormFileService.class)
|
||||
.findByCustomerFile(fileViewModel.getId().get());
|
||||
viewModel.getId().set(item.getId());
|
||||
updateViewModel(item);
|
||||
super.initializeData();
|
||||
Platform.runLater(this::initializePane);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateViewModel(CompanyCustomerEvaluationFormFileVo entity) {
|
||||
super.updateViewModel(entity);
|
||||
changed.set(false);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public BooleanBinding createTabSkinChangedBindings() {
|
||||
return viewModel.getChanged().or(fileViewModel.getChanged()).or(changed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveTabSkins() {
|
||||
save();
|
||||
changed.setValue(false);
|
||||
}
|
||||
|
||||
BiConsumer<ToggleGroup, String> stringRadioGroupUpdater = (group, newValue) -> {
|
||||
if (newValue != null) {
|
||||
for (Toggle toggle : group.getToggles()) {
|
||||
String data = (String) toggle.getUserData();
|
||||
if (data == null) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("toggle unset userData: {}", toggle);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (newValue.equals(data)) {
|
||||
toggle.setSelected(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
changed.set(true);
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
group.selectToggle(null);
|
||||
// Toggle first = group.getToggles().getFirst();
|
||||
// first.setSelected(true);
|
||||
// first.setSelected(false);
|
||||
// RadioButton btn = (RadioButton) first;
|
||||
// btn.setText(newValue);
|
||||
});
|
||||
};
|
||||
|
||||
BiConsumer<SimpleStringProperty, Toggle> stringPropertyUpdater = (property, toggle) -> {
|
||||
if (toggle == null) {
|
||||
property.set("");
|
||||
return;
|
||||
}
|
||||
String data = (String) toggle.getUserData();
|
||||
property.set(data);
|
||||
changed.set(true);
|
||||
};
|
||||
|
||||
BiConsumer<ToggleGroup, Number> numberRadioGroupUpdater = (group, newValue) -> {
|
||||
System.out.println("group = " + group + ", newValue = " + newValue);
|
||||
String value = String.valueOf(newValue);
|
||||
for (Toggle toggle : group.getToggles()) {
|
||||
String data = (String) toggle.getUserData();
|
||||
if (value.equals(data)) {
|
||||
toggle.setSelected(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Platform.runLater(() -> {
|
||||
group.selectToggle(null);
|
||||
// Toggle first = group.getToggles().getFirst();
|
||||
// first.setSelected(true);
|
||||
// first.setSelected(false);
|
||||
// RadioButton btn = (RadioButton) first;
|
||||
// btn.setText(String.valueOf(newValue));
|
||||
});
|
||||
changed.set(true);
|
||||
};
|
||||
|
||||
BiConsumer<SimpleIntegerProperty, Toggle> numberPropertyUpdater = (property, toggle) -> {
|
||||
if (toggle == null) {
|
||||
property.set(0);
|
||||
return;
|
||||
}
|
||||
String data = (String) toggle.getUserData();
|
||||
property.set(Integer.parseInt(data));
|
||||
changed.set(true);
|
||||
};
|
||||
|
||||
int totalScoreToLevel(int score) {
|
||||
if (score >= 200) {
|
||||
return 4;
|
||||
} else if (score >= 150) {
|
||||
return 3;
|
||||
} else if (score >= 100) {
|
||||
return 2;
|
||||
} else if (score >= 60) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
boolean calcValid() {
|
||||
if (viewModel.getCreditLevel().get() <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (!StringUtils.hasText(viewModel.getCatalog().get())) {
|
||||
return false;
|
||||
}
|
||||
if (!StringUtils.hasText(viewModel.getLevel().get())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (viewModel.getScore1().get() <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (viewModel.getScore2().get() <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (viewModel.getScore3().get() <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (viewModel.getScore4().get() <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (viewModel.getScore5().get() <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (viewModel.getCreditLevel().get() <= 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void initializePane() {
|
||||
setStatus("");
|
||||
idField.textProperty().bind(viewModel.getId().asString());
|
||||
filePathField.textProperty().bind(fileViewModel.getFilePath());
|
||||
editFilePathField.textProperty().bind(fileViewModel.getEditFilePath());
|
||||
String pattern = "yyyy-MM-dd";
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
signDateField.setConverter(new LocalDateStringConverter(formatter, null));
|
||||
signDateField.valueProperty().bindBidirectional(fileViewModel.getSignDate());
|
||||
signDateField.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
changed.set(true);
|
||||
});
|
||||
|
||||
initializeRadioGroup(catalog, viewModel.getCatalog());
|
||||
initializeRadioGroup(level, viewModel.getLevel());
|
||||
|
||||
initializeRadioGroup(score1, viewModel.getScore1());
|
||||
initializeRadioGroup(score2, viewModel.getScore2());
|
||||
initializeRadioGroup(score3, viewModel.getScore3());
|
||||
initializeRadioGroup(score4, viewModel.getScore4());
|
||||
initializeRadioGroup(score5, viewModel.getScore5());
|
||||
|
||||
// 信用等级
|
||||
viewModel.getCreditLevel().addListener((observable, oldValue, newValue) -> {
|
||||
numberRadioGroupUpdater.accept(creditLevel, newValue);
|
||||
});
|
||||
numberRadioGroupUpdater.accept(creditLevel, viewModel.getCreditLevel().get());
|
||||
|
||||
SimpleIntegerProperty[] scores = new SimpleIntegerProperty[]{viewModel.getScore1(), viewModel.getScore2(),
|
||||
viewModel.getScore3(), viewModel.getScore4(), viewModel.getScore5()};
|
||||
totalCreditScoreProperty.bind(Bindings.createIntegerBinding(() -> {
|
||||
int total = 0;
|
||||
for (SimpleIntegerProperty score : scores) {
|
||||
total += score.get();
|
||||
}
|
||||
viewModel.getCreditLevel().set(totalScoreToLevel(total));
|
||||
return total;
|
||||
}, scores));
|
||||
|
||||
totalCreditScoreLabel.textProperty().bind(totalCreditScoreProperty.map(score -> {
|
||||
return "合计总分:" + score;
|
||||
}));
|
||||
|
||||
Bindings.createBooleanBinding(this::calcValid, viewModel.getCatalog(), viewModel.getLevel(),
|
||||
viewModel.getScore1(), viewModel.getScore2(), viewModel.getScore3(), viewModel.getScore4(),
|
||||
viewModel.getScore5(), viewModel.getCreditLevel())
|
||||
.addListener((observable, oldValue, newValue) -> {
|
||||
fileViewModel.getValid().set(newValue);
|
||||
changed.set(true);
|
||||
});
|
||||
validField.selectedProperty().bindBidirectional(fileViewModel.getValid());
|
||||
validField.setSelected(fileViewModel.getValid().getValue());
|
||||
|
||||
fileViewModel.getFilePath().addListener((observable, oldValue, newValue) -> {
|
||||
File file = new File(newValue);
|
||||
loadFile(file);
|
||||
});
|
||||
if (StringUtils.hasText(fileViewModel.getFilePath().get())) {
|
||||
loadFile(new File(fileViewModel.getFilePath().get()));
|
||||
}
|
||||
|
||||
|
||||
leftPane.widthProperty().addListener((observable, oldValue, newValue) -> {
|
||||
Platform.runLater(() -> {
|
||||
imageView.setFitWidth(leftPane.getWidth());
|
||||
imageView.setFitHeight(-1);
|
||||
});
|
||||
});
|
||||
|
||||
imageView.setFitWidth(leftPane.getWidth());
|
||||
imageView.setFitHeight(-1);
|
||||
|
||||
imageView.setOnScroll(event -> {
|
||||
Bounds bounds = imageView.getBoundsInLocal();
|
||||
imageView.setFitWidth(bounds.getWidth() + event.getDeltaY());
|
||||
imageView.setFitHeight(-1);
|
||||
event.consume();
|
||||
});
|
||||
|
||||
imageView.setOnMouseClicked(event -> {
|
||||
System.out.println("imageView.getFitWidth() = " + imageView.getFitWidth());
|
||||
System.out.println("imageView.getFitHeight() = " + imageView.getFitHeight());
|
||||
|
||||
System.out.println("leftPane.getWidth() = " + leftPane.getWidth());
|
||||
System.out.println("leftPane.getViewportBounds().getWidth() = " + leftPane.getViewportBounds().getWidth());
|
||||
|
||||
if (event.getClickCount() == 2 && event.getButton() == MouseButton.PRIMARY) {
|
||||
Image image = imageView.getImage();
|
||||
if (image != null) {
|
||||
System.out.println("image.getWidth() = " + image.getWidth());
|
||||
if (image.getWidth() > imageView.getFitWidth()) {
|
||||
imageView.setFitWidth(image.getWidth());
|
||||
} else {
|
||||
imageView.setFitWidth(leftPane.getWidth());
|
||||
}
|
||||
imageView.setFitHeight(-1);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadFile(File file) {
|
||||
setStatus("文件" + file.getAbsolutePath() + " 加载中...");
|
||||
if (FileUtils.withExtensions(file.getName(), FileUtils.PDF)) {
|
||||
loadPdf(file);
|
||||
return;
|
||||
}
|
||||
Image image = new Image(file.toURI().toString(), true);
|
||||
imageView.setImage(image);
|
||||
}
|
||||
|
||||
private void loadPdf(File pdfFile) {
|
||||
// 绘制文字, 等待加载
|
||||
// 创建画布并绘制备用占位文字
|
||||
javafx.scene.canvas.Canvas canvas = new javafx.scene.canvas.Canvas(leftPane.getWidth(), leftPane.getHeight());
|
||||
GraphicsContext gc = canvas.getGraphicsContext2D();
|
||||
gc.setFill(javafx.scene.paint.Color.RED);
|
||||
var h1 = javafx.scene.text.Font.font("Microsoft YaHei", FontWeight.BOLD, 24);
|
||||
var h2 = javafx.scene.text.Font.font("Microsoft YaHei", FontWeight.NORMAL, 18);
|
||||
gc.setFont(h1);
|
||||
gc.fillText(fileViewModel.getType().get().name(), 50, 100);
|
||||
|
||||
Runnable updateImage = () -> {
|
||||
WritableImage writableImage = new WritableImage((int) canvas.getWidth(), (int) canvas.getHeight());
|
||||
// 将画布内容转为图像
|
||||
Platform.runLater(() -> {
|
||||
canvas.snapshot(null, writableImage);
|
||||
imageView.setImage(writableImage);
|
||||
});
|
||||
};
|
||||
|
||||
runAsync(() -> {
|
||||
CustomerFileTypeLocalVo localVo = getCachedBean(CompanyCustomerFileTypeService.class).findByLocaleAndType(getLocale(), fileViewModel.getType().get());
|
||||
gc.setFill(Color.WHITE);
|
||||
// 覆盖 fileViewModel.getType() 文字
|
||||
gc.fillRect(0, 55, canvas.getWidth(), 55);
|
||||
|
||||
// 绘制 文件类型
|
||||
gc.setFill(javafx.scene.paint.Color.RED);
|
||||
gc.setFont(h1);
|
||||
gc.fillText(localVo.getValue(), 50, 100);
|
||||
|
||||
updateImage.run();
|
||||
});
|
||||
|
||||
gc.setStroke(javafx.scene.paint.Color.BLACK);
|
||||
gc.setFont(h2);
|
||||
gc.strokeText("正在加载文件..." + pdfFile.getName(), 50, 150);
|
||||
updateImage.run();
|
||||
|
||||
runAsync(() -> {
|
||||
|
||||
//FileSystemUtils.
|
||||
long fileSize = pdfFile.length();
|
||||
byte[] bytes = new byte[0];
|
||||
try (java.io.FileInputStream fis = new java.io.FileInputStream(pdfFile);
|
||||
java.io.BufferedInputStream bis = new java.io.BufferedInputStream(fis)) {
|
||||
|
||||
bytes = new byte[(int) fileSize];
|
||||
int totalBytesRead = 0;
|
||||
int bytesRead;
|
||||
byte[] buffer = new byte[8192]; // 8KB buffer
|
||||
|
||||
while ((bytesRead = bis.read(buffer)) != -1) {
|
||||
System.arraycopy(buffer, 0, bytes, totalBytesRead, bytesRead);
|
||||
totalBytesRead += bytesRead;
|
||||
|
||||
// 更新进度
|
||||
double progress = (double) totalBytesRead / fileSize * 100;
|
||||
final String status = String.format("正在加载文件... %s (%.1f%%)",
|
||||
pdfFile.getName(), progress);
|
||||
|
||||
gc.setFill(Color.WHITE);
|
||||
gc.fillRect(0, 200, canvas.getWidth(), 80);
|
||||
|
||||
|
||||
gc.setFill(Color.BLACK);
|
||||
gc.setFont(h2);
|
||||
gc.fillText(status, 50, 250);
|
||||
|
||||
|
||||
// 绘制进度条背景
|
||||
gc.setFill(Color.LIGHTGRAY);
|
||||
gc.fillRect(50, 270, 400, 20);
|
||||
|
||||
// 绘制进度条
|
||||
gc.setFill(Color.GREEN);
|
||||
gc.fillRect(50, 270, 400 * (totalBytesRead / (double) fileSize), 20);
|
||||
|
||||
// 绘制进度条边框
|
||||
gc.setStroke(Color.BLACK);
|
||||
gc.setLineWidth(1);
|
||||
gc.strokeRect(50, 270, 400, 20);
|
||||
|
||||
|
||||
updateImage.run();
|
||||
}
|
||||
|
||||
gc.setFill(Color.BLACK);
|
||||
gc.setFont(h2);
|
||||
gc.fillText("Loading file: " + pdfFile.getName() + ", size: " + bytes.length + " bytes", 50, 320);
|
||||
updateImage.run();
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
try (PDDocument pdDocument = Loader.loadPDF(bytes)) {
|
||||
gc.setFill(Color.BLACK);
|
||||
gc.setFont(h2);
|
||||
gc.fillText("PDF has " + pdDocument.getNumberOfPages() + " pages", 50, 380);
|
||||
updateImage.run();
|
||||
|
||||
PDFRenderer pdfRenderer = new PDFRenderer(pdDocument);
|
||||
|
||||
BufferedImage bufferedImage = pdfRenderer.renderImageWithDPI(0, 300);
|
||||
|
||||
// 获取 BufferedImage 的宽度和高度
|
||||
int width = bufferedImage.getWidth();
|
||||
int height = bufferedImage.getHeight();
|
||||
canvas.resize(width, height);
|
||||
GraphicsContext graphic = canvas.getGraphicsContext2D();
|
||||
|
||||
WritableImage writableImage1 = new WritableImage(width, height);
|
||||
|
||||
PixelWriter pixelWriter = writableImage1.getPixelWriter();
|
||||
// 将 BufferedImage 的像素数据复制到 WritableImage
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int argb = bufferedImage.getRGB(x, y);
|
||||
pixelWriter.setArgb(x, y, argb);
|
||||
}
|
||||
}
|
||||
setStatus();
|
||||
Platform.runLater(() -> {
|
||||
imageView.setImage(writableImage1);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
setStatus(e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initializeRadioGroup(ToggleGroup toggleGroup, SimpleStringProperty property) {
|
||||
property.addListener((observable, oldValue, newValue) -> {
|
||||
stringRadioGroupUpdater.accept(toggleGroup, newValue);
|
||||
});
|
||||
toggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
|
||||
stringPropertyUpdater.accept(property, newValue);
|
||||
});
|
||||
stringRadioGroupUpdater.accept(toggleGroup, property.getValue());
|
||||
}
|
||||
|
||||
private void initializeRadioGroup(ToggleGroup toggleGroup, SimpleIntegerProperty property) {
|
||||
property.addListener((observable, oldValue, newValue) -> {
|
||||
numberRadioGroupUpdater.accept(toggleGroup, newValue);
|
||||
});
|
||||
|
||||
toggleGroup.selectedToggleProperty().addListener((observable, oldValue, newValue) -> {
|
||||
numberPropertyUpdater.accept(property, newValue);
|
||||
});
|
||||
numberRadioGroupUpdater.accept(toggleGroup, property.getValue());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,52 +1,65 @@
|
||||
package com.ecep.contract.manager.ds.customer.controller;
|
||||
package com.ecep.contract.controller.customer;
|
||||
|
||||
import com.ecep.contract.manager.ds.company.model.Company;
|
||||
import com.ecep.contract.manager.ds.customer.model.CompanyCustomer;
|
||||
import com.ecep.contract.manager.ds.customer.model.CompanyCustomerEntity;
|
||||
import com.ecep.contract.manager.ds.customer.model.CompanyCustomerEvaluationFormFile;
|
||||
import com.ecep.contract.manager.ds.customer.model.CompanyCustomerFile;
|
||||
import com.ecep.contract.manager.ds.customer.service.CompanyCustomerEntityService;
|
||||
import com.ecep.contract.manager.ds.customer.service.CompanyCustomerFileService;
|
||||
import com.ecep.contract.manager.ds.customer.service.CompanyCustomerService;
|
||||
import com.ecep.contract.manager.ui.MessageHolder;
|
||||
import com.ecep.contract.manager.ui.Tasker;
|
||||
import com.ecep.contract.manager.util.MyDateTimeUtils;
|
||||
import com.ecep.contract.manager.util.UITools;
|
||||
import lombok.Setter;
|
||||
import org.apache.poi.ss.SpreadsheetVersion;
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.ss.util.AreaReference;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
||||
import org.apache.poi.xssf.usermodel.XSSFTable;
|
||||
import org.hibernate.Hibernate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.util.StringUtils;
|
||||
import static com.ecep.contract.util.ExcelUtils.setCellValue;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import static com.ecep.contract.manager.util.ExcelUtils.*;
|
||||
import org.apache.poi.ss.SpreadsheetVersion;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.PrintSetup;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||
import org.apache.poi.ss.util.AreaReference;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
||||
import org.apache.poi.xssf.usermodel.XSSFTable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.MyDateTimeUtils;
|
||||
import com.ecep.contract.service.CompanyCustomerEntityService;
|
||||
import com.ecep.contract.service.CompanyCustomerEvaluationFormFileService;
|
||||
import com.ecep.contract.service.CompanyCustomerFileService;
|
||||
import com.ecep.contract.service.CustomerService;
|
||||
import com.ecep.contract.task.Tasker;
|
||||
import com.ecep.contract.util.ExcelUtils;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vo.CompanyCustomerEntityVo;
|
||||
import com.ecep.contract.vo.CompanyCustomerEvaluationFormFileVo;
|
||||
import com.ecep.contract.vo.CustomerFileVo;
|
||||
import com.ecep.contract.vo.CustomerVo;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
|
||||
import lombok.Setter;
|
||||
|
||||
public class CompanyCustomerExportExcelTasker extends Tasker<Object> {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CompanyCustomerExportExcelTasker.class);
|
||||
|
||||
|
||||
@Setter
|
||||
File destFile;
|
||||
|
||||
CompanyCustomerService customerService;
|
||||
CustomerService customerService;
|
||||
CompanyCustomerEntityService customerEntityService;
|
||||
CompanyCustomerFileService customerFileService;
|
||||
CompanyCustomerEvaluationFormFileService customerEvaluationFormFileService;
|
||||
|
||||
CompanyCustomerService getCustomerService() {
|
||||
CustomerService getCustomerService() {
|
||||
if (customerService == null)
|
||||
customerService = getBean(CompanyCustomerService.class);
|
||||
customerService = getBean(CustomerService.class);
|
||||
return customerService;
|
||||
}
|
||||
|
||||
@@ -55,12 +68,19 @@ public class CompanyCustomerExportExcelTasker extends Tasker<Object> {
|
||||
customerFileService = getBean(CompanyCustomerFileService.class);
|
||||
return customerFileService;
|
||||
}
|
||||
|
||||
CompanyCustomerEntityService getCustomerEntityService() {
|
||||
if (customerEntityService == null)
|
||||
customerEntityService = getBean(CompanyCustomerEntityService.class);
|
||||
return customerEntityService;
|
||||
}
|
||||
|
||||
CompanyCustomerEvaluationFormFileService getCustomerEvaluationFormFileService() {
|
||||
if (customerEvaluationFormFileService == null)
|
||||
customerEvaluationFormFileService = getBean(CompanyCustomerEvaluationFormFileService.class);
|
||||
return customerEvaluationFormFileService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
if (destFile.exists()) {
|
||||
@@ -78,8 +98,7 @@ public class CompanyCustomerExportExcelTasker extends Tasker<Object> {
|
||||
}
|
||||
try (
|
||||
InputStream inp = new FileInputStream(template);
|
||||
Workbook wb = WorkbookFactory.create(inp);
|
||||
) {
|
||||
Workbook wb = WorkbookFactory.create(inp);) {
|
||||
String sheetName = "客户资信台账";
|
||||
Sheet sheet = null;
|
||||
for (int i = 0; i < wb.getNumberOfSheets(); i++) {
|
||||
@@ -99,55 +118,49 @@ public class CompanyCustomerExportExcelTasker extends Tasker<Object> {
|
||||
setCellValue(sheet, "A19", "Build by CMS @ " + MyDateTimeUtils.format(LocalDateTime.now()));
|
||||
|
||||
int rowIndex = 0;
|
||||
for (CompanyCustomer customer : getCustomerService().findAll(null, Pageable.unpaged())) {
|
||||
Company company = customer.getCompany();
|
||||
for (CustomerVo customer : getCustomerService().findAll(null, Pageable.unpaged())) {
|
||||
Integer companyId = customer.getCompanyId();
|
||||
;
|
||||
CompanyVo company = getCompanyService().findById(companyId);
|
||||
if (company == null) {
|
||||
holder.warn("客户 #" + customer.getId() + " 不存在对应公司");
|
||||
continue;
|
||||
}
|
||||
if (!Hibernate.isInitialized(company)) {
|
||||
company = getCompanyService().findById(company.getId());
|
||||
}
|
||||
// VO类不需要延迟加载代理,直接使用即可
|
||||
|
||||
LocalDate devDate = null;
|
||||
List<CompanyCustomerEntity> entities = getCustomerEntityService() .findAllByCustomer(customer);
|
||||
for (CompanyCustomerEntity entity : entities) {
|
||||
List<CompanyCustomerEntityVo> entities = getCustomerEntityService().findAllByCustomer(customer);
|
||||
for (CompanyCustomerEntityVo entity : entities) {
|
||||
if (devDate == null || devDate.isAfter(entity.getDevelopDate())) {
|
||||
devDate = entity.getDevelopDate();
|
||||
}
|
||||
}
|
||||
|
||||
CustomerFileVo customerFile = getCustomerFileService()
|
||||
.findAllByCustomer(customer).stream().filter(v -> v.isValid())
|
||||
.max(Comparator.comparing(v -> v.getSignDate())).orElse(null);
|
||||
|
||||
CompanyCustomerEvaluationFormFile evaluationFormFile = getCustomerFileService().findAllCustomerEvaluationFormFiles(customer).stream().filter(v -> {
|
||||
CompanyCustomerFile customerFile = v.getCustomerFile();
|
||||
if (customerFile == null) {
|
||||
return false;
|
||||
}
|
||||
return customerFile.isValid();
|
||||
}).max(Comparator.comparing(v -> v.getCustomerFile().getSignDate())).orElse(null);
|
||||
|
||||
if (evaluationFormFile == null) {
|
||||
if (customerFile == null) {
|
||||
holder.warn(company.getName() + " 未匹配的客户评估");
|
||||
continue;
|
||||
}
|
||||
|
||||
CompanyCustomerFile customerFile = evaluationFormFile.getCustomerFile();
|
||||
if (devDate != null && devDate.isAfter(customerFile.getSignDate())) {
|
||||
holder.debug(company.getName() + " 最新评估日期早于客户开发日期,评估日期:" + customerFile.getSignDate() + ", 开发日期:" + devDate);
|
||||
holder.debug(company.getName() + " 最新评估日期早于客户开发日期,评估日期:" + customerFile.getSignDate() + ", 开发日期:"
|
||||
+ devDate);
|
||||
}
|
||||
|
||||
|
||||
rowIndex++;
|
||||
|
||||
if (rowIndex > 11) {
|
||||
// 插入行,并复制行的格式
|
||||
sheet.shiftRows(rowIndex + 3, sheet.getLastRowNum(), 1);
|
||||
Row templateRow = getRow(sheet, rowIndex + 2, true);
|
||||
Row newRow = getRow(sheet, rowIndex + 3, true);
|
||||
Row templateRow = ExcelUtils.getRow(sheet, rowIndex + 2, true);
|
||||
Row newRow = ExcelUtils.getRow(sheet, rowIndex + 3, true);
|
||||
if (templateRow != null && newRow != null) {
|
||||
for (int i = 0; i < templateRow.getLastCellNum(); i++) {
|
||||
Cell templateCell = templateRow.getCell(i);
|
||||
Cell newCell = getCell(newRow, i, true);
|
||||
Cell newCell = ExcelUtils.getCell(newRow, i, true);
|
||||
if (templateCell != null && newCell != null) {
|
||||
newCell.setCellStyle(templateCell.getCellStyle());
|
||||
}
|
||||
@@ -155,8 +168,9 @@ public class CompanyCustomerExportExcelTasker extends Tasker<Object> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Row row = getRow(sheet, rowIndex + 3, true);
|
||||
Row row = ExcelUtils.getRow(sheet, rowIndex + 3, true);
|
||||
CompanyCustomerEvaluationFormFileVo evaluationFormFile = getCustomerEvaluationFormFileService()
|
||||
.findByCustomerFile(customerFile);
|
||||
|
||||
setCellValue(row, 0, rowIndex);
|
||||
setCellValue(row, 1, company.getName());
|
||||
@@ -173,10 +187,11 @@ public class CompanyCustomerExportExcelTasker extends Tasker<Object> {
|
||||
for (XSSFTable table : ((XSSFSheet) sheet).getTables()) {
|
||||
if ("表2".equals(table.getName())) {
|
||||
holder.info("找到表=" + table.getName() + ", style=" + table.getStyleName());
|
||||
table.setCellReferences(new AreaReference("$A$4:$H$" + (rowIndex + 4), SpreadsheetVersion.EXCEL2007));
|
||||
table.setCellReferences(
|
||||
new AreaReference("$A$4:$H$" + (rowIndex + 4), SpreadsheetVersion.EXCEL2007));
|
||||
|
||||
// table.setDataRowCount(rowIndex);
|
||||
//table.getCTTable().setRef("$A$4:$H$" + (rowIndex + 4));
|
||||
// table.setDataRowCount(rowIndex);
|
||||
// table.getCTTable().setRef("$A$4:$H$" + (rowIndex + 4));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +215,6 @@ public class CompanyCustomerExportExcelTasker extends Tasker<Object> {
|
||||
UITools.showExceptionAndWait("保存失败", e);
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,31 @@
|
||||
package com.ecep.contract.manager.ds.customer.controller;
|
||||
package com.ecep.contract.controller.customer;
|
||||
|
||||
import com.ecep.contract.manager.ui.table.cell.CompanyTableCell;
|
||||
import com.ecep.contract.manager.ds.company.service.CompanyService;
|
||||
import com.ecep.contract.manager.ds.customer.model.CompanyCustomer;
|
||||
import com.ecep.contract.manager.ds.customer.service.CompanyCustomerService;
|
||||
import com.ecep.contract.manager.ds.customer.vo.CompanyCustomerViewModel;
|
||||
import com.ecep.contract.manager.ui.AbstEntityManagerSkin;
|
||||
import com.ecep.contract.manager.util.MyDateTimeUtils;
|
||||
import com.ecep.contract.MyDateTimeUtils;
|
||||
import com.ecep.contract.controller.AbstEntityManagerSkin;
|
||||
import com.ecep.contract.controller.table.cell.CompanyTableCell;
|
||||
import com.ecep.contract.controller.table.cell.CustomerCatalogTableCell;
|
||||
import com.ecep.contract.service.CustomerService;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.service.CustomerCatalogService;
|
||||
import com.ecep.contract.vm.CompanyCustomerViewModel;
|
||||
import com.ecep.contract.vo.CustomerVo;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import lombok.Setter;
|
||||
|
||||
public class CompanyCustomerManagerSkin
|
||||
extends
|
||||
AbstEntityManagerSkin<CompanyCustomer, CompanyCustomerViewModel, CompanyCustomerManagerSkin, CompanyCustomerManagerWindowController> {
|
||||
|
||||
@Setter
|
||||
private CompanyService companyService;
|
||||
AbstEntityManagerSkin<CustomerVo, CompanyCustomerViewModel, CompanyCustomerManagerSkin, CompanyCustomerManagerWindowController> {
|
||||
|
||||
public CompanyCustomerManagerSkin(CompanyCustomerManagerWindowController controller) {
|
||||
super(controller);
|
||||
}
|
||||
|
||||
public CompanyService getCompanyService() {
|
||||
if (companyService == null) {
|
||||
companyService = getBean(CompanyService.class);
|
||||
}
|
||||
return companyService;
|
||||
return getBean(CompanyService.class);
|
||||
}
|
||||
|
||||
public CompanyCustomerService getCompanyCustomerService() {
|
||||
public CustomerService getCompanyCustomerService() {
|
||||
return controller.getViewModelService();
|
||||
}
|
||||
|
||||
@@ -37,7 +33,10 @@ public class CompanyCustomerManagerSkin
|
||||
public void initializeTable() {
|
||||
controller.idColumn.setCellValueFactory(param -> param.getValue().getId());
|
||||
controller.companyColumn.setCellValueFactory(param -> param.getValue().getCompany());
|
||||
controller.companyColumn.setCellFactory(param-> new CompanyTableCell<>(getCompanyService()));
|
||||
controller.companyColumn.setCellFactory(CompanyTableCell.forTableColumn(getCompanyService()));
|
||||
|
||||
controller.catalogColumn.setCellValueFactory(param -> param.getValue().getCatalog());
|
||||
controller.catalogColumn.setCellFactory(CustomerCatalogTableCell.forTableColumn(getBean(CustomerCatalogService.class)));
|
||||
|
||||
controller.developDateColumn.setCellValueFactory(param -> param.getValue().getDevelopDate());
|
||||
controller.pathColumn.setCellValueFactory(param -> param.getValue().getPath());
|
||||
@@ -1,11 +1,12 @@
|
||||
package com.ecep.contract.manager.ds.customer.controller;
|
||||
package com.ecep.contract.controller.customer;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.LocalDate;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import com.ecep.contract.vo.CustomerVo;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
@@ -14,15 +15,13 @@ import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.manager.ds.company.model.Company;
|
||||
import com.ecep.contract.manager.ds.company.service.CompanyService;
|
||||
import com.ecep.contract.manager.ds.customer.model.CompanyCustomer;
|
||||
import com.ecep.contract.manager.ds.customer.service.CompanyCustomerService;
|
||||
import com.ecep.contract.manager.ds.customer.vo.CompanyCustomerViewModel;
|
||||
import com.ecep.contract.manager.ui.AbstManagerWindowController;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.util.MyDateTimeUtils;
|
||||
import com.ecep.contract.manager.util.UITools;
|
||||
import com.ecep.contract.MyDateTimeUtils;
|
||||
import com.ecep.contract.controller.AbstManagerWindowController;
|
||||
import com.ecep.contract.service.CustomerService;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.CompanyCustomerViewModel;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
@@ -45,24 +44,27 @@ import javafx.stage.Stage;
|
||||
@Component
|
||||
@FxmlPath("/ui/company/customer/customer_manager.fxml")
|
||||
public class CompanyCustomerManagerWindowController
|
||||
extends AbstManagerWindowController<CompanyCustomer, CompanyCustomerViewModel, CompanyCustomerManagerSkin> {
|
||||
extends AbstManagerWindowController<CustomerVo, CompanyCustomerViewModel, CompanyCustomerManagerSkin> {
|
||||
|
||||
// columns
|
||||
public TableColumn<CompanyCustomerViewModel, Number> idColumn;
|
||||
public TableColumn<CompanyCustomerViewModel, Company> companyColumn;
|
||||
public TableColumn<CompanyCustomerViewModel, String> catalogColumn;
|
||||
/**
|
||||
* 客户所属公司,Company
|
||||
*/
|
||||
public TableColumn<CompanyCustomerViewModel, Integer> companyColumn;
|
||||
public TableColumn<CompanyCustomerViewModel, Integer> catalogColumn;
|
||||
public TableColumn<CompanyCustomerViewModel, LocalDate> developDateColumn;
|
||||
public TableColumn<CompanyCustomerViewModel, String> pathColumn;
|
||||
public TableColumn<CompanyCustomerViewModel, String> createdColumn;
|
||||
|
||||
@Autowired
|
||||
private CompanyService companyService;
|
||||
@Autowired
|
||||
private CompanyCustomerService companyCustomerService;
|
||||
|
||||
@Override
|
||||
public CompanyCustomerService getViewModelService() {
|
||||
return companyCustomerService;
|
||||
public CustomerService getViewModelService() {
|
||||
return getCachedBean(CustomerService.class);
|
||||
}
|
||||
|
||||
private CompanyService getCompanyService() {
|
||||
return getCachedBean(CompanyService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -74,7 +76,6 @@ public class CompanyCustomerManagerWindowController
|
||||
@Override
|
||||
protected CompanyCustomerManagerSkin createDefaultSkin() {
|
||||
CompanyCustomerManagerSkin skin = new CompanyCustomerManagerSkin(this);
|
||||
skin.setCompanyService(companyService);
|
||||
return skin;
|
||||
}
|
||||
|
||||
@@ -116,21 +117,18 @@ public class CompanyCustomerManagerWindowController
|
||||
CompletableFuture.runAsync(() -> {
|
||||
Pageable pageRequest = PageRequest.ofSize(50);
|
||||
while (!canceled.get()) {
|
||||
Page<CompanyCustomer> page = companyCustomerService.findAll(null, pageRequest);
|
||||
Page<CustomerVo> page = getViewModelService().findAll(null, pageRequest);
|
||||
int index = page.getNumber() * page.getSize();
|
||||
|
||||
int i = 1;
|
||||
for (CompanyCustomer companyCustomer : page) {
|
||||
for (CustomerVo companyCustomer : page) {
|
||||
if (canceled.get()) {
|
||||
return;
|
||||
}
|
||||
Company company = companyCustomer.getCompany();
|
||||
if (!Hibernate.isInitialized(company)) {
|
||||
company = companyService.findById(company.getId());
|
||||
}
|
||||
|
||||
CompanyVo company = getCompanyService().findById(companyCustomer.getCompanyId());
|
||||
String prefix = (index + i) + "/" + page.getTotalElements() + ", " + company.getName() + "> ";
|
||||
companyCustomerService.reBuildingFiles(companyCustomer, msg -> {
|
||||
getViewModelService().reBuildingFiles(companyCustomer, (level, msg) -> {
|
||||
Platform.runLater(() -> {
|
||||
listViewDataSet.add(prefix + msg);
|
||||
listView.scrollTo(listViewDataSet.size() - 1);
|
||||
@@ -164,7 +162,7 @@ public class CompanyCustomerManagerWindowController
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle("导出");
|
||||
fileChooser.setInitialFileName("客户资信台账-" + MyDateTimeUtils.format(LocalDate.now()) + ".xlsx");
|
||||
fileChooser.setInitialDirectory(companyCustomerService.getBasePath());
|
||||
fileChooser.setInitialDirectory(getViewModelService().getBasePath());
|
||||
File destFile = fileChooser.showSaveDialog(table.getScene().getWindow());
|
||||
tasker.setDestFile(destFile);
|
||||
UITools.showTaskDialogAndWait("导出Excel", tasker, null);
|
||||
@@ -1,45 +1,49 @@
|
||||
package com.ecep.contract.manager.ds.customer.controller;
|
||||
package com.ecep.contract.controller.customer;
|
||||
|
||||
import com.ecep.contract.manager.ds.company.service.CompanyContactService;
|
||||
import com.ecep.contract.manager.ds.company.service.CompanyService;
|
||||
import com.ecep.contract.manager.ds.customer.model.CompanyCustomer;
|
||||
import com.ecep.contract.manager.ds.customer.service.CompanyCustomerService;
|
||||
import com.ecep.contract.manager.ds.customer.vo.CompanyCustomerViewModel;
|
||||
import com.ecep.contract.manager.ui.AbstEntityController;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.ui.ViewModelService;
|
||||
import com.ecep.contract.manager.util.DesktopUtils;
|
||||
import java.io.File;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import javafx.stage.WindowEvent;
|
||||
import org.hibernate.Hibernate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import com.ecep.contract.DesktopUtils;
|
||||
import com.ecep.contract.controller.AbstEntityController;
|
||||
import com.ecep.contract.service.CompanyContactService;
|
||||
import com.ecep.contract.service.CustomerService;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.vm.CompanyCustomerViewModel;
|
||||
import com.ecep.contract.vo.CustomerVo;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.DatePicker;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TabPane;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import javafx.stage.WindowEvent;
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
@FxmlPath("/ui/company/customer/customer.fxml")
|
||||
public class CompanyCustomerWindowController extends AbstEntityController<CompanyCustomer, CompanyCustomerViewModel> {
|
||||
public class CompanyCustomerWindowController extends AbstEntityController<CustomerVo, CompanyCustomerViewModel> {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CompanyCustomerWindowController.class);
|
||||
|
||||
/**
|
||||
* 显示界面
|
||||
*/
|
||||
public static void show(CompanyCustomer customer, Window window) {
|
||||
CompanyCustomerViewModel viewModel = new CompanyCustomerViewModel();
|
||||
viewModel.update(customer);
|
||||
show(CompanyCustomerWindowController.class, viewModel, window);
|
||||
public static void show(CustomerVo customer, Window window) {
|
||||
show(CompanyCustomerWindowController.class, CompanyCustomerViewModel.from(customer), window);
|
||||
}
|
||||
|
||||
public Tab baseInfoTab;
|
||||
@@ -48,14 +52,6 @@ public class CompanyCustomerWindowController extends AbstEntityController<Compan
|
||||
public Tab fileTab;
|
||||
public Tab entityTab;
|
||||
public Tab satisfactionTab;
|
||||
|
||||
@Autowired
|
||||
CompanyService companyService;
|
||||
@Autowired
|
||||
CompanyCustomerService companyCustomerService;
|
||||
@Autowired
|
||||
CompanyContactService companyContactService;
|
||||
|
||||
public TextField companyField;
|
||||
public TextField contactField;
|
||||
public TextField pathField;
|
||||
@@ -70,33 +66,37 @@ public class CompanyCustomerWindowController extends AbstEntityController<Compan
|
||||
public Button pathAsNameBtn;
|
||||
public Button OpenCustomerPathInExplorerBtn;
|
||||
|
||||
|
||||
@Override
|
||||
public void show(Stage stage) {
|
||||
super.show(stage);
|
||||
getTitle().bind(viewModel.getCompany().map(company -> {
|
||||
if (company == null) {
|
||||
getTitle().bind(viewModel.getCompany().map(companyId -> {
|
||||
if (companyId == null) {
|
||||
return "-";
|
||||
}
|
||||
if (!Hibernate.isInitialized(company)) {
|
||||
company = companyService.findById(company.getId());
|
||||
viewModel.getCompany().set(company);
|
||||
}
|
||||
CompanyVo company = getCompanyService().findById(companyId);
|
||||
return getMessage("ui.customer.title", String.valueOf(viewModel.getId().get()), company.getName());
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerTabSkins() {
|
||||
registerTabSkin(baseInfoTab, tab -> new CompanyCustomerTabSkinBase(this));
|
||||
registerTabSkin(baseInfoTab, tab -> new CustomerTabSkinBase(this));
|
||||
registerTabSkin(fileTab, tab -> new CustomerTabSkinFile(this));
|
||||
registerTabSkin(entityTab, tab -> new CustomerTabSkinEntity(this));
|
||||
registerTabSkin(satisfactionTab, tab -> new CustomerTabSkinSatisfactionSurvey(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompanyCustomerService getViewModelService() {
|
||||
return companyCustomerService;
|
||||
public CustomerService getViewModelService() {
|
||||
return getCachedBean(CustomerService.class);
|
||||
}
|
||||
|
||||
public CompanyService getCompanyService() {
|
||||
return getCachedBean(CompanyService.class);
|
||||
}
|
||||
|
||||
public CompanyContactService getCompanyContactService() {
|
||||
return getCachedBean(CompanyContactService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -119,6 +119,4 @@ public class CompanyCustomerWindowController extends AbstEntityController<Compan
|
||||
DesktopUtils.showInExplorer(file);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
package com.ecep.contract.controller.customer;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.ecep.contract.MyDateTimeUtils;
|
||||
import com.ecep.contract.controller.company.CompanyWindowController;
|
||||
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.service.CompanyContactService;
|
||||
import com.ecep.contract.service.CustomerService;
|
||||
import com.ecep.contract.service.CompanyService;
|
||||
import com.ecep.contract.service.QueryService;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.CompanyContactViewModel;
|
||||
import com.ecep.contract.vm.CompanyCustomerViewModel;
|
||||
import com.ecep.contract.vo.CompanyContactVo;
|
||||
import com.ecep.contract.vo.CustomerVo;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
import com.ecep.contract.vo.VendorVo;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
import javafx.util.StringConverter;
|
||||
import javafx.util.converter.LocalDateStringConverter;
|
||||
|
||||
public class CustomerTabSkinBase
|
||||
extends AbstEntityBasedTabSkin<CompanyCustomerWindowController, CustomerVo, CompanyCustomerViewModel>
|
||||
implements TabSkin {
|
||||
|
||||
public CustomerTabSkinBase(CompanyCustomerWindowController controller) {
|
||||
super(controller);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tab getTab() {
|
||||
return controller.baseInfoTab;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTab() {
|
||||
initializeCompanyFieldAutoCompletion(controller.companyField);
|
||||
initializeContactFieldAutoCompletion(controller.contactField);
|
||||
|
||||
UITools.autoCompletion(controller.contactField, viewModel.getContact(),
|
||||
new QueryService<CompanyContactVo, CompanyContactViewModel>() {
|
||||
@Override
|
||||
public CompanyContactVo findById(Integer id) {
|
||||
return getCompanyContactService().findById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CompanyContactVo> search(String searchText) {
|
||||
CustomerVo vendor = getEntity();
|
||||
CompanyVo company = controller.getCompanyService().findById(vendor.getCompanyId());
|
||||
return getCompanyContactService().searchByCompany(company, searchText);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringConverter<CompanyContactVo> getStringConverter() {
|
||||
return getCompanyContactService().getStringConverter();
|
||||
}
|
||||
});
|
||||
|
||||
LocalDateStringConverter converter = new LocalDateStringConverter(DateTimeFormatter.ISO_LOCAL_DATE, null);
|
||||
|
||||
controller.developDateField.setConverter(converter);
|
||||
controller.developDateField.valueProperty().bindBidirectional(viewModel.getDevelopDate());
|
||||
|
||||
controller.pathField.textProperty().bind(viewModel.getPath());
|
||||
controller.descriptionField.textProperty().bindBidirectional(viewModel.getDescription());
|
||||
controller.createdField.textProperty().bind(
|
||||
Bindings.createStringBinding(
|
||||
() -> localDateTimeFormatter(viewModel.getCreated()),
|
||||
viewModel.getCreated()));
|
||||
controller.versionLabel.textProperty().bind(viewModel.getVersion().asString());
|
||||
|
||||
controller.relativeCompanyBtn.disableProperty().bind(viewModel.getCompany().isNull());
|
||||
controller.relativeCompanyBtn.setOnAction(event -> {
|
||||
Integer companyId = viewModel.getCompany().get();
|
||||
if (companyId != null) {
|
||||
CompanyVo company = getCompanyService().findById(companyId);
|
||||
if (company != null) {
|
||||
CompanyWindowController.show(company, controller.root.getScene().getWindow());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
controller.createPathBtn.setOnAction(this::onCompanyCustomerCreatePathAction);
|
||||
controller.changePathBtn.setOnAction(this::onCompanyCustomerChangePathAction);
|
||||
controller.pathAsNameBtn.setOnAction(this::onCompanyCustomerPathSameAsNameAction);
|
||||
}
|
||||
|
||||
protected String localDateTimeFormatter(SimpleObjectProperty<LocalDateTime> property) {
|
||||
LocalDateTime dateTime = property.get();
|
||||
if (dateTime == null) {
|
||||
return "";
|
||||
}
|
||||
return MyDateTimeUtils.format(dateTime);
|
||||
}
|
||||
|
||||
private void initializeContactFieldAutoCompletion(TextField textField) {
|
||||
UITools.autoCompletion(textField, viewModel.getContact(), getCompanyContactService());
|
||||
}
|
||||
|
||||
private void initializeCompanyFieldAutoCompletion(TextField textField) {
|
||||
UITools.autoCompletion(textField, viewModel.getCompany(), getCompanyService());
|
||||
}
|
||||
|
||||
public void onCompanyCustomerCreatePathAction(ActionEvent event) {
|
||||
CustomerVo companyCustomer = getCompanyCustomerService().findById(viewModel.getId().get());
|
||||
if (getCompanyCustomerService().makePathAbsent(companyCustomer)) {
|
||||
companyCustomer = getCompanyCustomerService().save(companyCustomer);
|
||||
viewModel.update(companyCustomer);
|
||||
} else {
|
||||
setStatus("目录存在或创建失败");
|
||||
}
|
||||
}
|
||||
|
||||
public void onCompanyCustomerChangePathAction(ActionEvent event) {
|
||||
DirectoryChooser chooser = new DirectoryChooser();
|
||||
CustomerVo entity = getEntity();
|
||||
String path = entity.getPath();
|
||||
File initialDirectory = null;
|
||||
|
||||
// 如果当前已经设置了目录并且路径有效,则设置初始目录为该目录
|
||||
if (StringUtils.hasText(path)) {
|
||||
File dir = new File(path);
|
||||
if (dir.exists()) {
|
||||
initialDirectory = dir;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有有效的初始目录,则使用基础路径
|
||||
if (initialDirectory == null) {
|
||||
initialDirectory = getCompanyCustomerService().getBasePath();
|
||||
}
|
||||
|
||||
if (initialDirectory != null) {
|
||||
chooser.setInitialDirectory(initialDirectory);
|
||||
}
|
||||
|
||||
File newDirectory = chooser.showDialog(getTab().getContent().getScene().getWindow());
|
||||
if (newDirectory != null) {
|
||||
entity.setPath(newDirectory.getAbsolutePath());
|
||||
save(entity);
|
||||
}
|
||||
}
|
||||
|
||||
public void onCompanyCustomerPathSameAsNameAction(ActionEvent event) {
|
||||
setStatus("未实现");
|
||||
}
|
||||
|
||||
public CustomerService getCompanyCustomerService() {
|
||||
return controller.getCachedBean(CustomerService.class);
|
||||
}
|
||||
|
||||
public CompanyContactService getCompanyContactService() {
|
||||
return controller.getCachedBean(CompanyContactService.class);
|
||||
}
|
||||
|
||||
public CompanyService getCompanyService() {
|
||||
return controller.getCachedBean(CompanyService.class);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,36 @@
|
||||
package com.ecep.contract.manager.ds.customer.controller;
|
||||
|
||||
import com.ecep.contract.manager.SpringApp;
|
||||
import com.ecep.contract.manager.ds.customer.model.CompanyCustomer;
|
||||
import com.ecep.contract.manager.ds.customer.model.CompanyCustomerEntity;
|
||||
import com.ecep.contract.manager.ds.customer.model.CustomerCatalog;
|
||||
import com.ecep.contract.manager.ds.customer.service.CompanyCustomerEntityService;
|
||||
import com.ecep.contract.manager.ds.customer.vo.CustomerEntityViewModel;
|
||||
import com.ecep.contract.manager.ds.other.EmployeeStringConverter;
|
||||
import com.ecep.contract.manager.ds.other.EntityStringConverter;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.ui.table.cell.LocalDateTimeTableCell;
|
||||
import com.ecep.contract.manager.util.SpecificationUtils;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
package com.ecep.contract.controller.customer;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import com.ecep.contract.controller.table.cell.CustomerCatalogTableCell;
|
||||
import com.ecep.contract.controller.table.cell.EmployeeTableCell;
|
||||
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
|
||||
import com.ecep.contract.service.CompanyCustomerEntityService;
|
||||
import com.ecep.contract.service.CustomerCatalogService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import com.ecep.contract.vm.CustomerEntityViewModel;
|
||||
import com.ecep.contract.vo.CompanyCustomerEntityVo;
|
||||
import com.ecep.contract.vo.CustomerVo;
|
||||
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TableColumn;
|
||||
|
||||
@FxmlPath("/ui/company/customer/customer-tab-entity.fxml")
|
||||
public class CustomerTabSkinEntity
|
||||
extends AbstCompanyCustomerTableTabSkin<CompanyCustomerEntity, CustomerEntityViewModel> {
|
||||
extends AbstCompanyCustomerTableTabSkin<CompanyCustomerEntityVo, CustomerEntityViewModel> {
|
||||
|
||||
// 关联项 tab
|
||||
public TableColumn<CustomerEntityViewModel, Number> entityTable_idColumn;
|
||||
public TableColumn<CustomerEntityViewModel, String> entityTable_catalogColumn;
|
||||
public TableColumn<CustomerEntityViewModel, Integer> entityTable_catalogColumn;
|
||||
public TableColumn<CustomerEntityViewModel, String> entityTable_nameColumn;
|
||||
public TableColumn<CustomerEntityViewModel, String> entityTable_abbNameColumn;
|
||||
public TableColumn<CustomerEntityViewModel, String> entityTable_codeColumn;
|
||||
public TableColumn<CustomerEntityViewModel, String> entityTable_creatorColumn;
|
||||
public TableColumn<CustomerEntityViewModel, Integer> entityTable_creatorColumn;
|
||||
public TableColumn<CustomerEntityViewModel, LocalDate> entityTable_developDateColumn;
|
||||
public TableColumn<CustomerEntityViewModel, String> entityTable_modifierColumn;
|
||||
public TableColumn<CustomerEntityViewModel, Integer> entityTable_modifierColumn;
|
||||
public TableColumn<CustomerEntityViewModel, LocalDate> entityTable_modifyDateColumn;
|
||||
public TableColumn<CustomerEntityViewModel, LocalDate> entityTable_updatedDateColumn;
|
||||
public TableColumn<CustomerEntityViewModel, LocalDateTime> fetchedTimeColumn;
|
||||
@@ -40,9 +38,6 @@ public class CustomerTabSkinEntity
|
||||
public MenuItem entityTable_menu_refresh;
|
||||
public MenuItem entityTable_menu_del;
|
||||
|
||||
@Setter
|
||||
private CompanyCustomerEntityService customerEntityService;
|
||||
|
||||
public CustomerTabSkinEntity(CompanyCustomerWindowController controller) {
|
||||
super(controller);
|
||||
}
|
||||
@@ -52,7 +47,6 @@ public class CustomerTabSkinEntity
|
||||
return controller.entityTab;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void initializeTab() {
|
||||
super.initializeTab();
|
||||
@@ -61,34 +55,28 @@ public class CustomerTabSkinEntity
|
||||
entityTable_nameColumn.setCellValueFactory(param -> param.getValue().getName());
|
||||
entityTable_abbNameColumn.setCellValueFactory(param -> param.getValue().getAbbName());
|
||||
entityTable_codeColumn.setCellValueFactory(param -> param.getValue().getCode());
|
||||
initializeEntityTabCatalogColumn(entityTable_catalogColumn);
|
||||
|
||||
EmployeeStringConverter stringConverter = SpringApp.getBean(EmployeeStringConverter.class);
|
||||
entityTable_catalogColumn.setCellValueFactory(param -> param.getValue().getCatalog());
|
||||
entityTable_catalogColumn.setCellFactory(CustomerCatalogTableCell.forTableColumn(getCustomerCatalogService()));
|
||||
|
||||
entityTable_developDateColumn.setCellValueFactory(param -> param.getValue().getDevelopDate());
|
||||
entityTable_modifyDateColumn.setCellValueFactory(param -> param.getValue().getModifyDate());
|
||||
entityTable_creatorColumn.setCellValueFactory(param -> param.getValue().getCreator().map(stringConverter::toString));
|
||||
entityTable_modifierColumn.setCellValueFactory(param -> param.getValue().getModifier().map(stringConverter::toString));
|
||||
entityTable_updatedDateColumn.setCellValueFactory(param -> param.getValue().getUpdatedDate());
|
||||
|
||||
fetchedTimeColumn.setCellValueFactory(param -> param.getValue().getFetchedTime());
|
||||
fetchedTimeColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
|
||||
|
||||
entityTable_creatorColumn.setCellValueFactory(param -> param.getValue().getCreator());
|
||||
entityTable_creatorColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
|
||||
entityTable_modifierColumn.setCellValueFactory(param -> param.getValue().getModifier());
|
||||
entityTable_modifierColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
|
||||
|
||||
entityTable_menu_refresh.setOnAction(this::onTableRefreshAction);
|
||||
entityTable_menu_del.setOnAction(this::onTableDeleteAction);
|
||||
}
|
||||
|
||||
|
||||
private void initializeEntityTabCatalogColumn(TableColumn<CustomerEntityViewModel, String> column) {
|
||||
EntityStringConverter<CustomerCatalog> converter = new EntityStringConverter<>();
|
||||
converter.setInitialized(v -> getCompanyCustomerService().findCatalogById(v.getId()));
|
||||
column.setCellValueFactory(param -> param.getValue().getCatalog().map(converter::toString));
|
||||
}
|
||||
|
||||
CompanyCustomerEntityService getCompanyCustomerEntityService() {
|
||||
if (customerEntityService == null) {
|
||||
customerEntityService = getBean(CompanyCustomerEntityService.class);
|
||||
}
|
||||
return customerEntityService;
|
||||
return getCachedBean(CompanyCustomerEntityService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -96,11 +84,15 @@ public class CustomerTabSkinEntity
|
||||
return getCompanyCustomerEntityService();
|
||||
}
|
||||
|
||||
CustomerCatalogService getCustomerCatalogService() {
|
||||
return getCachedBean(CustomerCatalogService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Specification<CompanyCustomerEntity> getSpecification(CompanyCustomer parent) {
|
||||
return SpecificationUtils.and(getSpecification(), (root, query, builder) -> {
|
||||
return builder.equal(root.get("customer"), parent);
|
||||
});
|
||||
public ParamUtils.Builder getSpecification(CustomerVo parent) {
|
||||
ParamUtils.Builder params = getSpecification();
|
||||
params.equals("customer", parent.getId());
|
||||
return params;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
package com.ecep.contract.controller.customer;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.ecep.contract.CustomerFileType;
|
||||
import com.ecep.contract.DesktopUtils;
|
||||
import com.ecep.contract.MyDateTimeUtils;
|
||||
import com.ecep.contract.SpringApp;
|
||||
import com.ecep.contract.constant.CompanyCustomerConstant;
|
||||
import com.ecep.contract.controller.customer.tasker.CustomerEvaluationFormUpdateTask;
|
||||
import com.ecep.contract.controller.customer.tasker.CustomerNextSignDateTask;
|
||||
import com.ecep.contract.controller.table.EditableEntityTableTabSkin;
|
||||
import com.ecep.contract.controller.table.cell.CompanyCustomerFileTableTypeTableCell;
|
||||
import com.ecep.contract.controller.table.cell.FilePathTableCell;
|
||||
import com.ecep.contract.service.CompanyCustomerEvaluationFormFileService;
|
||||
import com.ecep.contract.service.CompanyCustomerFileService;
|
||||
import com.ecep.contract.service.CompanyCustomerFileTypeService;
|
||||
import com.ecep.contract.service.CustomerService;
|
||||
import com.ecep.contract.task.CustomerFileMoveTasker;
|
||||
import com.ecep.contract.util.FileUtils;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.CustomerFileViewModel;
|
||||
import com.ecep.contract.vo.CompanyCustomerEvaluationFormFileVo;
|
||||
import com.ecep.contract.vo.CustomerFileVo;
|
||||
import com.ecep.contract.vo.CompanyVo;
|
||||
import com.ecep.contract.vo.CustomerVo;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.cell.CheckBoxTableCell;
|
||||
import javafx.scene.input.Dragboard;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import lombok.Setter;
|
||||
|
||||
@FxmlPath("/ui/company/customer/customer-tab-file.fxml")
|
||||
public class CustomerTabSkinFile
|
||||
extends AbstCompanyCustomerTableTabSkin<CustomerFileVo, CustomerFileViewModel>
|
||||
implements EditableEntityTableTabSkin<CustomerFileVo, CustomerFileViewModel> {
|
||||
|
||||
@Setter
|
||||
private CompanyCustomerFileService companyCustomerFileService;
|
||||
|
||||
public TableColumn<CustomerFileViewModel, Number> fileTable_idColumn;
|
||||
public TableColumn<CustomerFileViewModel, CustomerFileType> fileTable_typeColumn;
|
||||
public TableColumn<CustomerFileViewModel, String> fileTable_filePathColumn;
|
||||
public TableColumn<CustomerFileViewModel, String> fileTable_editFilePathColumn;
|
||||
public TableColumn<CustomerFileViewModel, LocalDate> fileTable_signDateColumn;
|
||||
public TableColumn<CustomerFileViewModel, Boolean> fileTable_validColumn;
|
||||
public TableColumn<CustomerFileViewModel, String> fileTable_descriptionColumn;
|
||||
|
||||
public Button fileTable_reBuildBtn;
|
||||
public Button fileTable_updateEvaluationFormBuildBtn;
|
||||
public Button fileTable_calcNextSignDateBtn;
|
||||
public MenuItem fileTable_menu_refresh;
|
||||
public MenuItem fileTable_menu_add;
|
||||
public MenuItem fileTable_menu_del;
|
||||
|
||||
public CustomerTabSkinFile(CompanyCustomerWindowController controller) {
|
||||
super(controller);
|
||||
setDragAndDrop(true);
|
||||
setDragAndDropFileHandler(this::moveFileToCustomer);
|
||||
}
|
||||
|
||||
// 文件 tab
|
||||
@Override
|
||||
public Tab getTab() {
|
||||
return controller.fileTab;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CompanyCustomerFileService getViewModelService() {
|
||||
return getCompanyCustomerFileService();
|
||||
}
|
||||
|
||||
public CompanyCustomerEvaluationFormFileService getEvaluationFormFileService() {
|
||||
return getCachedBean(CompanyCustomerEvaluationFormFileService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParamUtils.Builder getSpecification(CustomerVo parent) {
|
||||
ParamUtils.Builder params = getSpecification();
|
||||
params.equals("customer", parent.getId());
|
||||
return params;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTable() {
|
||||
super.initializeTable();
|
||||
|
||||
TableView<CustomerFileViewModel> table = getTableView();
|
||||
|
||||
table.disableProperty().bind(viewModel.getPath().isEmpty());
|
||||
fileTable_idColumn.setCellValueFactory(param -> param.getValue().getId());
|
||||
CompanyCustomerFileTypeService fileTypeService = getCachedBean(CompanyCustomerFileTypeService.class);
|
||||
fileTable_typeColumn.setCellValueFactory(param -> param.getValue().getType());
|
||||
fileTable_typeColumn.setCellFactory(CompanyCustomerFileTableTypeTableCell.forTableColumn(fileTypeService));
|
||||
|
||||
fileTable_filePathColumn.setCellValueFactory(param -> param.getValue().getFilePath());
|
||||
fileTable_filePathColumn.setCellFactory(FilePathTableCell.forTableColumn(viewModel.getPath()));
|
||||
|
||||
fileTable_editFilePathColumn.setCellValueFactory(param -> param.getValue().getEditFilePath());
|
||||
fileTable_editFilePathColumn.setCellFactory(FilePathTableCell.forTableColumn(viewModel.getPath()));
|
||||
|
||||
fileTable_signDateColumn.setCellValueFactory(param -> param.getValue().getSignDate());
|
||||
fileTable_validColumn.setEditable(true);
|
||||
fileTable_validColumn.setCellValueFactory(param -> param.getValue().getValid());
|
||||
fileTable_validColumn.setCellFactory(param -> new CheckBoxTableCell<>());
|
||||
|
||||
table.setOnDragOver(event -> {
|
||||
Dragboard dragboard = event.getDragboard();
|
||||
if (dragboard.hasFiles()) {
|
||||
event.acceptTransferModes(TransferMode.MOVE);
|
||||
}
|
||||
event.consume();
|
||||
});
|
||||
|
||||
table.setOnDragDropped(event -> {
|
||||
Dragboard dragboard = event.getDragboard();
|
||||
boolean success = false;
|
||||
if (dragboard.hasFiles()) {
|
||||
List<File> files = dragboard.getFiles();
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
moveFileToCustomer(files);
|
||||
} catch (Exception e) {
|
||||
UITools.showExceptionAndWait("移动文件出错", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
event.setDropCompleted(success);
|
||||
event.consume();
|
||||
});
|
||||
|
||||
fileTable_reBuildBtn.setOnAction(this::onFileReBuildingAction);
|
||||
fileTable_updateEvaluationFormBuildBtn.setOnAction(this::onUpdateEvaluationFormAction);
|
||||
fileTable_calcNextSignDateBtn.setOnAction(this::onCalcNextSignDateAction);
|
||||
|
||||
fileTable_menu_refresh.setOnAction(this::onTableRefreshAction);
|
||||
fileTable_menu_add.setOnAction(this::onTableDeleteAction);
|
||||
fileTable_menu_del.setOnAction(this::onFileTableMoveToCompanyPathAction);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTableRowDoubleClickedAction(CustomerFileViewModel item) {
|
||||
CustomerFileType fileType = item.getType().get();
|
||||
if (fileType == CustomerFileType.EvaluationForm) {
|
||||
|
||||
// 文件不是 Excel 文件时,打开编辑UI
|
||||
if (!FileUtils.withExtensions(item.getFilePath().get(), FileUtils.XLS,
|
||||
FileUtils.XLSX)) {
|
||||
CompanyCustomerEvaluationFormFileWindowController.show(item,
|
||||
controller.root.getScene().getWindow());
|
||||
return;
|
||||
}
|
||||
}
|
||||
File file = new File(item.getFilePath().get());
|
||||
if (!file.exists()) {
|
||||
setStatus("文件不存在 " + file.getName());
|
||||
return;
|
||||
}
|
||||
DesktopUtils.showInExplorer(file);
|
||||
}
|
||||
|
||||
private void moveFileToCustomer(List<File> files) {
|
||||
String path = viewModel.getPath().get();
|
||||
if (!StringUtils.hasText(path)) {
|
||||
setStatus("未设置目录");
|
||||
return;
|
||||
}
|
||||
File dir = new File(path);
|
||||
if (!dir.exists()) {
|
||||
setStatus("目录错误,不存在");
|
||||
return;
|
||||
}
|
||||
CustomerVo companyCustomer = getParent();
|
||||
LocalDate nextSignDate = getCompanyCustomerFileService().getNextSignDate(companyCustomer,
|
||||
((level, message) -> setStatus(message)));
|
||||
if (nextSignDate != null && files.size() == 1) {
|
||||
File file = files.getFirst();
|
||||
String fileName = file.getName();
|
||||
if (fileName.startsWith("S")) {
|
||||
String destFileName = CompanyCustomerConstant.EVALUATION_FORM_NAME2 + "_"
|
||||
+ MyDateTimeUtils.format(nextSignDate)
|
||||
+ "." + StringUtils.getFilenameExtension(fileName);
|
||||
File dest = new File(dir, destFileName);
|
||||
if (file.renameTo(dest)) {
|
||||
CustomerFileVo ccf = new CustomerFileVo();
|
||||
ccf.setCustomer(companyCustomer.getId());
|
||||
ccf.setType(CustomerFileType.EvaluationForm);
|
||||
ccf.setFilePath(dest.getAbsolutePath());
|
||||
ccf.setSignDate(nextSignDate);
|
||||
ccf.setValid(false);
|
||||
CustomerFileVo saved = getCompanyCustomerFileService().save(ccf);
|
||||
Platform.runLater(() -> {
|
||||
CustomerFileViewModel model = new CustomerFileViewModel();
|
||||
model.update(saved);
|
||||
dataSet.add(model);
|
||||
|
||||
CompanyCustomerEvaluationFormFileWindowController.show(model,
|
||||
getTableView().getScene().getWindow());
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for (File file : files) {
|
||||
File dest = new File(dir, file.getName());
|
||||
if (file.renameTo(dest)) {
|
||||
CustomerFileVo ccf = new CustomerFileVo();
|
||||
ccf.setCustomer(companyCustomer.getId());
|
||||
ccf.setType(CustomerFileType.General);
|
||||
ccf.setFilePath(dest.getAbsolutePath());
|
||||
ccf.setValid(false);
|
||||
getCompanyCustomerFileService().save(ccf);
|
||||
}
|
||||
}
|
||||
loadTableDataSet();
|
||||
}
|
||||
|
||||
public void onFileReBuildingAction(ActionEvent event) {
|
||||
|
||||
CustomerService customerService = getCompanyCustomerService();
|
||||
try {
|
||||
CustomerVo companyCustomer = customerService.findById(viewModel.getId().get());
|
||||
if (customerService.reBuildingFiles(companyCustomer, (level, message) -> setStatus(message))) {
|
||||
loadTableDataSet();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean deleteRow(CustomerFileViewModel row) {
|
||||
String path = row.getFilePath().get();
|
||||
if (super.deleteRow(row)) {
|
||||
File file = new File(path);
|
||||
if (file.exists()) {
|
||||
UITools.showConfirmation("数据记录已经删除,请确认是否删除物理文件", path)
|
||||
.thenAccept(buttonType -> {
|
||||
if (buttonType == ButtonType.OK) {
|
||||
if (file.delete()) {
|
||||
setStatus("删除文件 " + path);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onFileTableMoveToCompanyPathAction(ActionEvent event) {
|
||||
CustomerFileViewModel selectedItem = getSelectedItem();
|
||||
if (selectedItem == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查公司目录设置
|
||||
Integer companyId = viewModel.getCompany().get();
|
||||
CompanyVo company = getCompanyService().findById(companyId);
|
||||
|
||||
if (!StringUtils.hasText(company.getPath())) {
|
||||
setStatus("公司目录未设置");
|
||||
return;
|
||||
}
|
||||
|
||||
File companyPath = new File(company.getPath());
|
||||
if (!companyPath.exists()) {
|
||||
setStatus("公司目录设置异常,无法访问");
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建并启动任务
|
||||
CustomerFileMoveTasker task = new CustomerFileMoveTasker();
|
||||
task.setFileId(selectedItem.getId().get());
|
||||
UITools.showTaskDialogAndWait("移动文件到公司目录", task, null);
|
||||
|
||||
// 刷新表格数据
|
||||
loadTableDataSet();
|
||||
}
|
||||
|
||||
public void onUpdateEvaluationFormAction(ActionEvent event) {
|
||||
CustomerEvaluationFormUpdateTask task = new CustomerEvaluationFormUpdateTask();
|
||||
task.setCustomer(getCompanyCustomerService().findById(viewModel.getId().get()));
|
||||
UITools.showTaskDialogAndWait("更新评价表", task, null);
|
||||
loadTableDataSet();
|
||||
}
|
||||
|
||||
public void onCalcNextSignDateAction(ActionEvent event) {
|
||||
CustomerNextSignDateTask task = new CustomerNextSignDateTask();
|
||||
task.setCustomer(getEntity());
|
||||
UITools.showTaskDialogAndWait("计算客户的下一个评价日期", task, null);
|
||||
}
|
||||
|
||||
private CompanyCustomerFileService getCompanyCustomerFileService() {
|
||||
if (companyCustomerFileService == null) {
|
||||
companyCustomerFileService = SpringApp.getBean(CompanyCustomerFileService.class);
|
||||
}
|
||||
return companyCustomerFileService;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +1,49 @@
|
||||
package com.ecep.contract.manager.ds.customer.controller;
|
||||
package com.ecep.contract.controller.customer;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Map;
|
||||
|
||||
import com.ecep.contract.service.CustomerCatalogService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.controller.project.satisfaction_survey.CustomerSatisfactionSurveyWindowController;
|
||||
import com.ecep.contract.controller.table.cell.EmployeeTableCell;
|
||||
import com.ecep.contract.controller.table.cell.ProjectTableCell;
|
||||
import com.ecep.contract.converter.EntityStringConverter;
|
||||
import com.ecep.contract.vo.CustomerVo;
|
||||
import com.ecep.contract.vo.CustomerCatalogVo;
|
||||
import com.ecep.contract.vo.CustomerSatisfactionSurveyVo;
|
||||
import com.ecep.contract.vo.EmployeeVo;
|
||||
import com.ecep.contract.vo.ProjectVo;
|
||||
import com.ecep.contract.service.CustomerSatisfactionSurveyService;
|
||||
import com.ecep.contract.service.ProjectService;
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import com.ecep.contract.vm.CustomerEntityViewModel;
|
||||
import com.ecep.contract.vm.CustomerSatisfactionSurveyViewModel;
|
||||
|
||||
import com.ecep.contract.manager.ds.company.model.Company;
|
||||
import com.ecep.contract.manager.ds.customer.model.CompanyCustomer;
|
||||
import com.ecep.contract.manager.ds.customer.model.CustomerCatalog;
|
||||
import com.ecep.contract.manager.ds.customer.vo.CustomerEntityViewModel;
|
||||
import com.ecep.contract.manager.ds.other.EntityStringConverter;
|
||||
import com.ecep.contract.manager.ds.project.service.ProjectService;
|
||||
import com.ecep.contract.manager.ui.table.cell.EmployeeTableCell;
|
||||
import com.ecep.contract.manager.ds.other.model.Employee;
|
||||
import com.ecep.contract.manager.ds.project.controller.satisfaction_survey.CustomerSatisfactionSurveyWindowController;
|
||||
import com.ecep.contract.manager.ds.project.model.CustomerSatisfactionSurvey;
|
||||
import com.ecep.contract.manager.ds.project.model.Project;
|
||||
import com.ecep.contract.manager.ds.project.service.CustomerSatisfactionSurveyService;
|
||||
import com.ecep.contract.manager.ds.project.vo.CustomerSatisfactionSurveyViewModel;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.ui.table.cell.ProjectTableCell;
|
||||
import com.ecep.contract.manager.util.SpecificationUtils;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@FxmlPath("/ui/company/customer/customer-tab-satisfaction-survey.fxml")
|
||||
public class CustomerTabSkinSatisfactionSurvey
|
||||
extends AbstCompanyCustomerTableTabSkin<CustomerSatisfactionSurvey, CustomerSatisfactionSurveyViewModel> {
|
||||
extends AbstCompanyCustomerTableTabSkin<CustomerSatisfactionSurveyVo, CustomerSatisfactionSurveyViewModel> {
|
||||
|
||||
// 关联项 tab
|
||||
public TableColumn<CustomerSatisfactionSurveyViewModel, Number> idColumn;
|
||||
public TableColumn<CustomerSatisfactionSurveyViewModel, Project> projectColumn;
|
||||
public TableColumn<CustomerSatisfactionSurveyViewModel, Integer> projectColumn;
|
||||
public TableColumn<CustomerSatisfactionSurveyViewModel, String> codeColumn;
|
||||
public TableColumn<CustomerSatisfactionSurveyViewModel, Number> totalScoreColumn;
|
||||
public TableColumn<CustomerSatisfactionSurveyViewModel, Employee> applicantColumn;
|
||||
public TableColumn<CustomerSatisfactionSurveyViewModel, Integer> applicantColumn;
|
||||
public TableColumn<CustomerSatisfactionSurveyViewModel, LocalDateTime> applyTimeColumn;
|
||||
public TableColumn<CustomerSatisfactionSurveyViewModel, String> descriptionColumn;
|
||||
public TableColumn<CustomerSatisfactionSurveyViewModel, LocalDate> dateColumn;
|
||||
|
||||
public MenuItem entityTable_menu_refresh;
|
||||
public MenuItem entityTable_menu_del;
|
||||
@Setter
|
||||
private ProjectService projectService;
|
||||
@Setter
|
||||
private ProjectService projectService;
|
||||
@Setter
|
||||
private CustomerSatisfactionSurveyService satisfactionSurveyService;
|
||||
|
||||
@@ -55,19 +56,19 @@ private ProjectService projectService;
|
||||
return controller.satisfactionTab;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void initializeTab() {
|
||||
super.initializeTab();
|
||||
|
||||
bindNumberColumn(idColumn, CustomerSatisfactionSurveyViewModel::getId);
|
||||
bindColumn(codeColumn, CustomerSatisfactionSurveyViewModel::getCode);
|
||||
|
||||
projectColumn.setCellValueFactory(param -> param.getValue().getProject());
|
||||
projectColumn.setCellFactory(cell -> new ProjectTableCell<>(getProjectService()));
|
||||
projectColumn.setCellFactory(ProjectTableCell.forTableColumn(getProjectService()));
|
||||
bindLocalDateColumn(dateColumn, CustomerSatisfactionSurveyViewModel::getDate);
|
||||
bindNumberColumn(totalScoreColumn, CustomerSatisfactionSurveyViewModel::getTotalScore);
|
||||
applicantColumn.setCellValueFactory(param -> param.getValue().getApplicant());
|
||||
applicantColumn.setCellFactory(cell -> new EmployeeTableCell<>(getEmployeeService()));
|
||||
applicantColumn.setCellFactory(EmployeeTableCell.forTableColumn(getEmployeeService()));
|
||||
bindLocalDateTimeColumn(applyTimeColumn, CustomerSatisfactionSurveyViewModel::getApplyTime);
|
||||
bindColumn(descriptionColumn, CustomerSatisfactionSurveyViewModel::getDescription);
|
||||
|
||||
@@ -75,13 +76,6 @@ private ProjectService projectService;
|
||||
entityTable_menu_del.setOnAction(this::onTableDeleteAction);
|
||||
}
|
||||
|
||||
|
||||
private void initializeEntityTabCatalogColumn(TableColumn<CustomerEntityViewModel, String> column) {
|
||||
EntityStringConverter<CustomerCatalog> converter = new EntityStringConverter<>();
|
||||
converter.setInitialized(v -> getCompanyCustomerService().findCatalogById(v.getId()));
|
||||
column.setCellValueFactory(param -> param.getValue().getCatalog().map(converter::toString));
|
||||
}
|
||||
|
||||
private CustomerSatisfactionSurveyService getCustomerSatisfactionSurveyService() {
|
||||
if (satisfactionSurveyService == null) {
|
||||
satisfactionSurveyService = getBean(CustomerSatisfactionSurveyService.class);
|
||||
@@ -102,14 +96,10 @@ private ProjectService projectService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Specification<CustomerSatisfactionSurvey> getSpecification(CompanyCustomer parent) {
|
||||
return SpecificationUtils.and(getSpecification(), (root, query, builder) -> {
|
||||
Company company = parent.getCompany();
|
||||
if (company == null) {
|
||||
return null;
|
||||
}
|
||||
return builder.equal(root.get("project").get("customer"), company);
|
||||
});
|
||||
public ParamUtils.Builder getSpecification(CustomerVo parent) {
|
||||
ParamUtils.Builder params = getSpecification();
|
||||
params.equals("project.customer", parent.getId());
|
||||
return params;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.ecep.contract.controller.customer.tasker;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.WebSocketClientTasker;
|
||||
import com.ecep.contract.task.Tasker;
|
||||
import com.ecep.contract.vo.CustomerVo;
|
||||
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 客户评估表更新任务
|
||||
*/
|
||||
public class CustomerEvaluationFormUpdateTask extends Tasker<Object> implements WebSocketClientTasker {
|
||||
@Setter
|
||||
private CustomerVo customer;
|
||||
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
return "CustomerEvaluationFormUpdateTask";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(long current, long total) {
|
||||
super.updateProgress(current, total);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
updateTitle("客户评估表更新任务");
|
||||
return callRemoteTask(holder, getLocale(), customer.getId());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.ecep.contract.controller.customer.tasker;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.WebSocketClientTasker;
|
||||
import com.ecep.contract.task.Tasker;
|
||||
import com.ecep.contract.vo.CustomerVo;
|
||||
|
||||
import lombok.Setter;
|
||||
|
||||
public class CustomerNextSignDateTask extends Tasker<Object> implements WebSocketClientTasker {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CustomerNextSignDateTask.class);
|
||||
|
||||
@Setter
|
||||
private CustomerVo customer;
|
||||
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
return "CustomerNextSignDateTask";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(long current, long total) {
|
||||
super.updateProgress(current, total);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
updateTitle("计算客户下一个评价日期");
|
||||
return callRemoteTask(holder, getLocale(), customer.getId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.ecep.contract.controller.customer.tasker;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.WebSocketClientTasker;
|
||||
import com.ecep.contract.task.Tasker;
|
||||
import com.ecep.contract.vo.CustomerVo;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 客户文件重建任务类
|
||||
* 用于通过WebSocket与服务器通信,重建客户相关文件
|
||||
*/
|
||||
public class CustomerRebuildFilesTasker extends Tasker<Object> implements WebSocketClientTasker {
|
||||
@Getter
|
||||
@Setter
|
||||
private CustomerVo companyCustomer;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
protected boolean filesUpdated = false;
|
||||
|
||||
@Override
|
||||
public String getTaskName() {
|
||||
return "CustomerRebuildFilesTasker";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(long current, long total) {
|
||||
super.updateProgress(current, total);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object execute(MessageHolder holder) throws Exception {
|
||||
updateTitle("重建客户文件");
|
||||
return callRemoteTask(holder, getLocale(), companyCustomer.getId());
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,30 @@
|
||||
package com.ecep.contract.manager.ds.other.controller.department;
|
||||
package com.ecep.contract.controller.department;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
import com.ecep.contract.controller.AbstEntityManagerSkin;
|
||||
import com.ecep.contract.controller.ManagerSkin;
|
||||
import com.ecep.contract.controller.table.EditableEntityTableTabSkin;
|
||||
import com.ecep.contract.controller.table.cell.EmployeeTableCell;
|
||||
import com.ecep.contract.converter.EmployeeStringConverter;
|
||||
import com.ecep.contract.model.Employee;
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import com.ecep.contract.vo.DepartmentVo;
|
||||
import com.ecep.contract.vm.DepartmentViewModel;
|
||||
|
||||
import com.ecep.contract.manager.ds.other.EmployeeStringConverter;
|
||||
import com.ecep.contract.manager.ds.other.model.Department;
|
||||
import com.ecep.contract.manager.ds.other.model.Employee;
|
||||
import com.ecep.contract.manager.ds.other.vo.DepartmentViewModel;
|
||||
import com.ecep.contract.manager.ui.AbstEntityManagerSkin;
|
||||
import com.ecep.contract.manager.ui.table.EditableEntityTableTabSkin;
|
||||
import com.ecep.contract.manager.ui.ManagerSkin;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.scene.control.cell.CheckBoxTableCell;
|
||||
import javafx.scene.control.cell.ComboBoxTableCell;
|
||||
import javafx.scene.control.cell.TextFieldTableCell;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class DepartmentManagerSkin
|
||||
extends AbstEntityManagerSkin<Department, DepartmentViewModel, DepartmentManagerSkin, DepartmentManagerWindowController>
|
||||
implements ManagerSkin, EditableEntityTableTabSkin<Department, DepartmentViewModel> {
|
||||
|
||||
extends
|
||||
AbstEntityManagerSkin<DepartmentVo, DepartmentViewModel, DepartmentManagerSkin, DepartmentManagerWindowController>
|
||||
implements ManagerSkin, EditableEntityTableTabSkin<DepartmentVo, DepartmentViewModel> {
|
||||
|
||||
public DepartmentManagerSkin(DepartmentManagerWindowController controller) {
|
||||
super(controller);
|
||||
@@ -31,10 +34,7 @@ public class DepartmentManagerSkin
|
||||
public void initializeTable() {
|
||||
getTableView().setEditable(true);
|
||||
|
||||
Specification<Employee> spec = (root, query, cb) -> {
|
||||
return cb.equal(root.get("isActive"), true);
|
||||
};
|
||||
List<Employee> employees = controller.getEmployeeService().findAll(spec, Pageable.ofSize(30)).getContent();
|
||||
// 不再需要获取所有员工列表,因为现在使用的是leaderId和leaderName
|
||||
|
||||
controller.idColumn.setCellValueFactory(param -> param.getValue().getId());
|
||||
|
||||
@@ -47,8 +47,7 @@ public class DepartmentManagerSkin
|
||||
controller.codeColumn.setOnEditCommit(this::onCodeColumnEditCommit);
|
||||
|
||||
controller.leaderColumn.setCellValueFactory(param -> param.getValue().getLeader());
|
||||
controller.leaderColumn.setCellFactory(ComboBoxTableCell.forTableColumn(getBean(EmployeeStringConverter.class), FXCollections.observableArrayList(employees)));
|
||||
controller.leaderColumn.setOnEditCommit(this::onLeaderColumnEditCommit);
|
||||
controller.leaderColumn.setCellFactory(param -> new EmployeeTableCell<>(controller.getEmployeeService()));
|
||||
|
||||
controller.activeColumn.setCellValueFactory(param -> param.getValue().getIsActive());
|
||||
controller.activeColumn.setEditable(true);
|
||||
@@ -56,7 +55,6 @@ public class DepartmentManagerSkin
|
||||
controller.activeColumn.setOnEditCommit(this::onActiveColumnEditCommit);
|
||||
}
|
||||
|
||||
|
||||
private void onCodeColumnEditCommit(TableColumn.CellEditEvent<DepartmentViewModel, String> event) {
|
||||
DepartmentViewModel row = event.getRowValue();
|
||||
row.getCode().set(event.getNewValue());
|
||||
@@ -69,9 +67,11 @@ public class DepartmentManagerSkin
|
||||
saveRowData(row);
|
||||
}
|
||||
|
||||
private void onLeaderColumnEditCommit(TableColumn.CellEditEvent<DepartmentViewModel, Employee> event) {
|
||||
private void onLeaderColumnEditCommit(TableColumn.CellEditEvent<DepartmentViewModel, Integer> event) {
|
||||
DepartmentViewModel row = event.getRowValue();
|
||||
row.getLeader().set(event.getNewValue());
|
||||
// 注意:这里我们只设置了leaderName,但没有设置leaderId
|
||||
// 在实际应用中,您可能需要根据leaderName查找对应的leaderId
|
||||
saveRowData(row);
|
||||
}
|
||||
|
||||
@@ -83,14 +83,14 @@ public class DepartmentManagerSkin
|
||||
|
||||
@Override
|
||||
protected void onTableRowDoubleClickedAction(DepartmentViewModel item) {
|
||||
//TODO 显示详情
|
||||
// TODO 显示详情
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onTableCreateNewAction(ActionEvent event) {
|
||||
Department employee = new Department();
|
||||
employee = controller.getViewModelService().save(employee);
|
||||
DepartmentViewModel viewModel = DepartmentViewModel.from(employee);
|
||||
DepartmentVo department = new DepartmentVo();
|
||||
department = controller.getViewModelService().save(department);
|
||||
DepartmentViewModel viewModel = DepartmentViewModel.from(department);
|
||||
dataSet.add(viewModel);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,31 @@
|
||||
package com.ecep.contract.manager.ds.other.controller.department;
|
||||
package com.ecep.contract.controller.department;
|
||||
|
||||
import com.ecep.contract.manager.ds.other.model.Department;
|
||||
import com.ecep.contract.manager.ds.other.model.Employee;
|
||||
import com.ecep.contract.manager.ds.other.service.DepartmentService;
|
||||
import com.ecep.contract.manager.ds.other.vo.DepartmentViewModel;
|
||||
import com.ecep.contract.manager.ui.AbstManagerWindowController;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.ui.ViewModelService;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.stage.Stage;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.controller.AbstManagerWindowController;
|
||||
import com.ecep.contract.service.DepartmentService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.vm.DepartmentViewModel;
|
||||
import com.ecep.contract.vo.DepartmentVo;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
@FxmlPath(value = "/ui/employee/department-manager.fxml")
|
||||
public class DepartmentManagerWindowController
|
||||
extends AbstManagerWindowController<Department, DepartmentViewModel, DepartmentManagerSkin> {
|
||||
extends AbstManagerWindowController<DepartmentVo, DepartmentViewModel, DepartmentManagerSkin> {
|
||||
|
||||
public TableColumn<DepartmentViewModel, Number> idColumn;
|
||||
public TableColumn<DepartmentViewModel, String> nameColumn;
|
||||
public TableColumn<DepartmentViewModel, String> codeColumn;
|
||||
public TableColumn<DepartmentViewModel, Employee> leaderColumn;
|
||||
public TableColumn<DepartmentViewModel, Integer> leaderColumn;
|
||||
public TableColumn<DepartmentViewModel, Boolean> activeColumn;
|
||||
|
||||
@Autowired
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.ecep.contract.controller.employee;
|
||||
|
||||
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.model.Employee;
|
||||
import com.ecep.contract.service.EmployeeRoleService;
|
||||
import com.ecep.contract.service.EmployeeService;
|
||||
import com.ecep.contract.service.PermissionService;
|
||||
import com.ecep.contract.vm.EmployeeViewModel;
|
||||
|
||||
import com.ecep.contract.vo.EmployeeVo;
|
||||
import lombok.Setter;
|
||||
|
||||
public abstract class AbstEmployeeBasedTabSkin
|
||||
extends AbstEntityBasedTabSkin<EmployeeWindowController, EmployeeVo, EmployeeViewModel>
|
||||
implements TabSkin {
|
||||
|
||||
public AbstEmployeeBasedTabSkin(EmployeeWindowController controller) {
|
||||
super(controller);
|
||||
}
|
||||
|
||||
protected EmployeeRoleService getEmployeeRoleService() {
|
||||
return getCachedBean(EmployeeRoleService.class);
|
||||
}
|
||||
|
||||
protected PermissionService getPermissionService() {
|
||||
return getCachedBean(PermissionService.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.ecep.contract.controller.employee;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.controller.table.AbstEntityTableTabSkin;
|
||||
import com.ecep.contract.model.Employee;
|
||||
import com.ecep.contract.model.IdentityEntity;
|
||||
import com.ecep.contract.service.EmployeeService;
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import com.ecep.contract.vm.EmployeeBasedViewModel;
|
||||
import com.ecep.contract.vm.EmployeeViewModel;
|
||||
import com.ecep.contract.vm.IdentityViewModel;
|
||||
import com.ecep.contract.vo.EmployeeVo;
|
||||
|
||||
public abstract class AbstEmployeeTableTabSkin<T extends IdentityEntity, TV extends IdentityViewModel<T>>
|
||||
extends AbstEntityTableTabSkin<EmployeeWindowController, EmployeeVo, EmployeeViewModel, T, TV>
|
||||
implements TabSkin {
|
||||
|
||||
public AbstEmployeeTableTabSkin(EmployeeWindowController controller) {
|
||||
super(controller);
|
||||
}
|
||||
|
||||
public EmployeeService getEmployeeService() {
|
||||
return controller.employeeService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TV createNewViewModel() {
|
||||
TV model = super.createNewViewModel();
|
||||
if (model instanceof EmployeeBasedViewModel m) {
|
||||
m.getEmployee().set(getEntity().getId());
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ParamUtils.Builder getSpecification(EmployeeVo parent) {
|
||||
ParamUtils.Builder params = getSpecification();
|
||||
params.equals("employee", parent.getId());
|
||||
return params;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
package com.ecep.contract.manager.ds.other.controller.employee;
|
||||
package com.ecep.contract.controller.employee;
|
||||
|
||||
import com.ecep.contract.controller.AbstEntityManagerSkin;
|
||||
import com.ecep.contract.controller.ManagerSkin;
|
||||
import com.ecep.contract.controller.table.cell.DepartmentTableCell;
|
||||
import com.ecep.contract.model.Employee;
|
||||
import com.ecep.contract.service.DepartmentService;
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import com.ecep.contract.vm.EmployeeViewModel;
|
||||
import com.ecep.contract.vo.EmployeeVo;
|
||||
|
||||
import com.ecep.contract.manager.ds.other.EntityStringConverter;
|
||||
import com.ecep.contract.manager.ds.other.model.Department;
|
||||
import com.ecep.contract.manager.ds.other.model.Employee;
|
||||
import com.ecep.contract.manager.ds.other.service.DepartmentService;
|
||||
import com.ecep.contract.manager.ds.other.vo.EmployeeViewModel;
|
||||
import com.ecep.contract.manager.ui.AbstEntityManagerSkin;
|
||||
import com.ecep.contract.manager.ui.ManagerSkin;
|
||||
import com.ecep.contract.manager.ui.table.cell.DepartmentTableCell;
|
||||
import com.ecep.contract.manager.util.SpecificationUtils;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.cell.CheckBoxTableCell;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
public class EmployeeManagerSkin
|
||||
extends AbstEntityManagerSkin<Employee, EmployeeViewModel, EmployeeManagerSkin, EmployeeManagerWindowController>
|
||||
extends
|
||||
AbstEntityManagerSkin<EmployeeVo, EmployeeViewModel, EmployeeManagerSkin, EmployeeManagerWindowController>
|
||||
implements ManagerSkin {
|
||||
public EmployeeManagerSkin(EmployeeManagerWindowController controller) {
|
||||
super(controller);
|
||||
@@ -30,14 +30,12 @@ public class EmployeeManagerSkin
|
||||
}
|
||||
|
||||
@Override
|
||||
public Specification<Employee> getSpecification() {
|
||||
Specification<Employee> spec = super.getSpecification();
|
||||
public ParamUtils.Builder getSpecification() {
|
||||
ParamUtils.Builder params = super.getSpecification();
|
||||
if (controller.activeCheckBox.isSelected()) {
|
||||
spec = SpecificationUtils.and(spec, (root, query, builder) -> {
|
||||
return builder.isTrue(root.get("isActive"));
|
||||
});
|
||||
params.equals("isActive", true);
|
||||
}
|
||||
return spec;
|
||||
return params;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -49,8 +47,7 @@ public class EmployeeManagerSkin
|
||||
controller.accountColumn.setCellValueFactory(param -> param.getValue().getAccount());
|
||||
|
||||
controller.departmentColumn.setCellValueFactory(param -> param.getValue().getDepartment());
|
||||
controller.departmentColumn.setCellFactory(param -> new DepartmentTableCell<>(getDepartmentService()));
|
||||
|
||||
controller.departmentColumn.setCellFactory(DepartmentTableCell.forTableColumn(getDepartmentService()));
|
||||
|
||||
controller.emailColumn.setCellValueFactory(param -> param.getValue().getEmail());
|
||||
controller.createdColumn.setCellValueFactory(param -> param.getValue().getCreated());
|
||||
@@ -68,7 +65,7 @@ public class EmployeeManagerSkin
|
||||
|
||||
@Override
|
||||
protected void onTableCreateNewAction(ActionEvent event) {
|
||||
Employee employee = new Employee();
|
||||
EmployeeVo employee = new EmployeeVo();
|
||||
employee = controller.getViewModelService().save(employee);
|
||||
EmployeeViewModel viewModel = EmployeeViewModel.from(employee);
|
||||
dataSet.add(viewModel);
|
||||
@@ -1,39 +1,40 @@
|
||||
package com.ecep.contract.manager.ds.other.controller.employee;
|
||||
package com.ecep.contract.controller.employee;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.constant.CloudServiceConstant;
|
||||
import com.ecep.contract.controller.AbstManagerWindowController;
|
||||
import com.ecep.contract.model.Department;
|
||||
import com.ecep.contract.service.EmployeeService;
|
||||
import com.ecep.contract.task.EmployeesSyncTask;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.EmployeeViewModel;
|
||||
import com.ecep.contract.vo.EmployeeVo;
|
||||
|
||||
import com.ecep.contract.manager.cloud.u8.EmployeesSyncTask;
|
||||
import com.ecep.contract.manager.cloud.u8.YongYouU8Service;
|
||||
import com.ecep.contract.manager.ds.other.model.Department;
|
||||
import com.ecep.contract.manager.ds.other.model.Employee;
|
||||
import com.ecep.contract.manager.ds.other.service.EmployeeService;
|
||||
import com.ecep.contract.manager.ds.other.vo.EmployeeViewModel;
|
||||
import com.ecep.contract.manager.ui.AbstManagerWindowController;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.util.UITools;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import javafx.stage.Stage;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
@FxmlPath("/ui/employee/employee-manager.fxml")
|
||||
public class EmployeeManagerWindowController
|
||||
extends AbstManagerWindowController<Employee, EmployeeViewModel, EmployeeManagerSkin> {
|
||||
extends AbstManagerWindowController<EmployeeVo, EmployeeViewModel, EmployeeManagerSkin> {
|
||||
|
||||
@FXML
|
||||
public TableColumn<EmployeeViewModel, Number> idColumn;
|
||||
@FXML
|
||||
public TableColumn<EmployeeViewModel, String> accountColumn;
|
||||
@FXML
|
||||
public TableColumn<EmployeeViewModel, Department> departmentColumn;
|
||||
public TableColumn<EmployeeViewModel, Integer> departmentColumn;
|
||||
@FXML
|
||||
public TableColumn<EmployeeViewModel, String> nameColumn;
|
||||
@FXML
|
||||
@@ -68,8 +69,8 @@ public class EmployeeManagerWindowController
|
||||
* 从 U8系统 同步员工数据
|
||||
*/
|
||||
public void onSyncFromU8Action(ActionEvent event) {
|
||||
Task<Object> task = new EmployeesSyncTask();
|
||||
UITools.showTaskDialogAndWait("从 " + YongYouU8Service.NAME + " 同步员工数据", task, null);
|
||||
EmployeesSyncTask task = new EmployeesSyncTask();
|
||||
UITools.showTaskDialogAndWait("从 " + CloudServiceConstant.U8_NAME + " 同步员工数据", task, null);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +1,32 @@
|
||||
package com.ecep.contract.manager.ds.other.controller.employee;
|
||||
package com.ecep.contract.controller.employee;
|
||||
|
||||
import com.ecep.contract.manager.ds.other.model.Employee;
|
||||
import com.ecep.contract.manager.ds.other.model.EmployeeAuthBind;
|
||||
import com.ecep.contract.manager.ds.other.service.EmployeeAuthBindService;
|
||||
import com.ecep.contract.manager.ds.other.vo.EmployeeAuthBindViewModel;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.ui.table.cell.EmployeeTableCell;
|
||||
import com.ecep.contract.Desktop;
|
||||
import com.ecep.contract.controller.table.cell.EmployeeTableCell;
|
||||
import com.ecep.contract.model.Employee;
|
||||
import com.ecep.contract.model.EmployeeAuthBind;
|
||||
import com.ecep.contract.service.EmployeeAuthBindService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.vm.EmployeeAuthBindViewModel;
|
||||
import com.ecep.contract.vo.EmployeeAuthBindVo;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TableColumn;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@FxmlPath("/ui/employee/employee-auth-bind.fxml")
|
||||
public class EmployeeTabSkinAuthBind
|
||||
extends AbstEmployeeTableTabSkin<EmployeeAuthBind, EmployeeAuthBindViewModel> {
|
||||
extends AbstEmployeeTableTabSkin<EmployeeAuthBindVo, EmployeeAuthBindViewModel> {
|
||||
public TableColumn<EmployeeAuthBindViewModel, Number> idColumn;
|
||||
public TableColumn<EmployeeAuthBindViewModel, String> ipColumn;
|
||||
public TableColumn<EmployeeAuthBindViewModel, String> macColumn;
|
||||
public TableColumn<EmployeeAuthBindViewModel, LocalDateTime> createTime;
|
||||
public TableColumn<EmployeeAuthBindViewModel, LocalDateTime> updateTimeColumn;
|
||||
public TableColumn<EmployeeAuthBindViewModel, Employee> updaterColumn;
|
||||
public TableColumn<EmployeeAuthBindViewModel, Integer> updaterColumn;
|
||||
public TableColumn<EmployeeAuthBindViewModel, String> descriptionColumn;
|
||||
|
||||
@Setter
|
||||
@@ -79,13 +80,18 @@ public class EmployeeTabSkinAuthBind
|
||||
protected void createContextMenu(ContextMenu contextMenu) {
|
||||
super.createContextMenu(contextMenu);
|
||||
MenuItem menuItem = new MenuItem("导入未关联");
|
||||
int activeEmployeeId = Desktop.instance.getActiveEmployeeId();
|
||||
if (activeEmployeeId <= 0) {
|
||||
logger.warn("未登录员工{}", activeEmployeeId);
|
||||
return;
|
||||
}
|
||||
menuItem.setOnAction(event -> {
|
||||
EmployeeAuthBindService service = getEmployeeAuthBindService();
|
||||
List<EmployeeAuthBind> authBinds = service.findAllByEmployee(null, Sort.unsorted());
|
||||
for (EmployeeAuthBind authBind : authBinds) {
|
||||
authBind.setEmployee(getEntity());
|
||||
List<EmployeeAuthBindVo> authBinds = service.findAllByEmployee(null);
|
||||
for (EmployeeAuthBindVo authBind : authBinds) {
|
||||
authBind.setEmployeeId(getEntity().getId());
|
||||
authBind.setUpdateTime(LocalDateTime.now());
|
||||
authBind.setUpdater(controller.getCurrentUser());
|
||||
authBind.setUpdaterId(activeEmployeeId);
|
||||
service.save(authBind);
|
||||
}
|
||||
});
|
||||
@@ -1,15 +1,14 @@
|
||||
package com.ecep.contract.manager.ds.other.controller.employee;
|
||||
|
||||
import com.ecep.contract.manager.ds.other.EntityStringConverter;
|
||||
import com.ecep.contract.manager.ds.other.model.Department;
|
||||
import com.ecep.contract.manager.ds.other.service.DepartmentService;
|
||||
import com.ecep.contract.manager.ui.tab.TabSkin;
|
||||
import com.ecep.contract.manager.util.UITools;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.util.converter.LocalDateStringConverter;
|
||||
package com.ecep.contract.controller.employee;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.service.DepartmentService;
|
||||
import com.ecep.contract.util.UITools;
|
||||
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.util.converter.LocalDateStringConverter;
|
||||
|
||||
public class EmployeeTabSkinBase
|
||||
extends AbstEmployeeBasedTabSkin
|
||||
implements TabSkin {
|
||||
@@ -24,11 +23,8 @@ public class EmployeeTabSkinBase
|
||||
|
||||
@Override
|
||||
public void initializeTab() {
|
||||
EntityStringConverter<Department> departmentEntityStringConverter = new EntityStringConverter<>();
|
||||
DepartmentService departmentService = getBean(DepartmentService.class);
|
||||
departmentEntityStringConverter.setInitialized(department -> departmentService.findById(department.getId()));
|
||||
UITools.autoCompletion(controller.departmentField, viewModel.getDepartment(),
|
||||
p -> departmentService.search(p.getUserText()), departmentEntityStringConverter);
|
||||
getCachedBean(DepartmentService.class));
|
||||
|
||||
controller.nameField.textProperty().bindBidirectional(viewModel.getName());
|
||||
controller.aliasField.textProperty().bindBidirectional(viewModel.getAlias());
|
||||
@@ -48,23 +44,25 @@ public class EmployeeTabSkinBase
|
||||
|
||||
controller.isActiveField.selectedProperty().bindBidirectional(viewModel.getIsActive());
|
||||
|
||||
// Callback<ListView<EmployeeRole>, ListCell<EmployeeRole>> cellFactory =
|
||||
// controller.rolesField.getCellFactory();
|
||||
// StringConverter<EmployeeRole> employeeRoleStringConverter = new
|
||||
// EntityStringConverter<>();
|
||||
// controller.rolesField.setCellFactory(param -> {
|
||||
// ListCell<EmployeeRole> cell = cellFactory.call(param);
|
||||
// if (cell instanceof CheckBoxListCell<EmployeeRole> list) {
|
||||
// list.setConverter(employeeRoleStringConverter);
|
||||
// }
|
||||
// return cell;
|
||||
// });
|
||||
|
||||
// Callback<ListView<EmployeeRole>, ListCell<EmployeeRole>> cellFactory = controller.rolesField.getCellFactory();
|
||||
// StringConverter<EmployeeRole> employeeRoleStringConverter = new EntityStringConverter<>();
|
||||
// controller.rolesField.setCellFactory(param -> {
|
||||
// ListCell<EmployeeRole> cell = cellFactory.call(param);
|
||||
// if (cell instanceof CheckBoxListCell<EmployeeRole> list) {
|
||||
// list.setConverter(employeeRoleStringConverter);
|
||||
// }
|
||||
// return cell;
|
||||
// });
|
||||
|
||||
|
||||
// controller.rolesField.getCheckModel().getCheckedItems().setAll();
|
||||
// Property<IndexedCheckModel<EmployeeRole>> selectedRoles = new SimpleObjectProperty<>();
|
||||
// controller.rolesField.getCheckModel().getCheckedItems().addListener((ListChangeListener<? super EmployeeRole>) changed -> {
|
||||
// System.out.println("newValue = " + changed);
|
||||
// });
|
||||
// controller.rolesField.getCheckModel().getCheckedItems().setAll();
|
||||
// Property<IndexedCheckModel<EmployeeRole>> selectedRoles = new
|
||||
// SimpleObjectProperty<>();
|
||||
// controller.rolesField.getCheckModel().getCheckedItems().addListener((ListChangeListener<?
|
||||
// super EmployeeRole>) changed -> {
|
||||
// System.out.println("newValue = " + changed);
|
||||
// });
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,20 +1,21 @@
|
||||
package com.ecep.contract.manager.ds.other.controller.employee;
|
||||
package com.ecep.contract.controller.employee;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
|
||||
import com.ecep.contract.service.EmployeeLoginHistoryService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.vo.EmployeeLoginHistoryVo;
|
||||
import com.ecep.contract.vm.EmployeeLoginHistoryViewModel;
|
||||
|
||||
import com.ecep.contract.manager.ds.other.model.EmployeeLoginHistory;
|
||||
import com.ecep.contract.manager.ds.other.service.EmployeeLoginHistoryService;
|
||||
import com.ecep.contract.manager.ds.other.vo.EmployeeLoginHistoryViewModel;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.ui.tab.TabSkin;
|
||||
import com.ecep.contract.manager.ui.table.cell.LocalDateTimeTableCell;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TableColumn;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@FxmlPath("/ui/employee/employee-login-history.fxml")
|
||||
public class EmployeeTabSkinLoginHistory
|
||||
extends AbstEmployeeTableTabSkin<EmployeeLoginHistory, EmployeeLoginHistoryViewModel>
|
||||
extends AbstEmployeeTableTabSkin<EmployeeLoginHistoryVo, EmployeeLoginHistoryViewModel>
|
||||
implements TabSkin {
|
||||
public TableColumn<EmployeeLoginHistoryViewModel, Number> idColumn;
|
||||
public TableColumn<EmployeeLoginHistoryViewModel, String> ipColumn;
|
||||
@@ -1,18 +1,20 @@
|
||||
package com.ecep.contract.manager.ds.other.controller.employee;
|
||||
package com.ecep.contract.controller.employee;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.ecep.contract.util.ParamUtils;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.vo.EmployeeRoleVo;
|
||||
|
||||
import com.ecep.contract.manager.Desktop;
|
||||
import com.ecep.contract.manager.ds.other.model.Employee;
|
||||
import com.ecep.contract.manager.ds.other.model.EmployeeRole;
|
||||
import com.ecep.contract.manager.ui.tab.TabSkin;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.Tab;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EmployeeTabSkinRole
|
||||
extends AbstEmployeeBasedTabSkin
|
||||
@@ -41,24 +43,21 @@ public class EmployeeTabSkinRole
|
||||
}
|
||||
|
||||
private void loadSelectedRoles() {
|
||||
List<EmployeeRole> selectedRoles = getEmployeeService().getRolesByEmployeeId(viewModel.getId().get());
|
||||
List<EmployeeRoleVo> selectedRoles = getEmployeeService().getRolesByEmployeeId(viewModel.getId().get());
|
||||
controller.rolesField.getTargetItems().setAll(selectedRoles);
|
||||
changed.set(false);
|
||||
}
|
||||
|
||||
private void initializeListView() {
|
||||
// 非系统内置账户
|
||||
Specification<EmployeeRole> spec = null;
|
||||
if (!Desktop.instance.getActiveEmployee().isSystemAdministrator()) {
|
||||
spec = (root, query, cb) -> cb.equal(root.get("systemAdministrator"), false);
|
||||
}
|
||||
List<EmployeeRole> roles = getEmployeeRoleService().findAll(spec, Pageable.ofSize(500)).getContent();
|
||||
Map<String, Object> params = ParamUtils.builder().build();
|
||||
List<EmployeeRoleVo> roles = getEmployeeRoleService().findAll(params, Pageable.ofSize(500)).getContent();
|
||||
|
||||
controller.rolesField.getSourceItems().setAll(roles);
|
||||
controller.rolesField.setCellFactory(param -> {
|
||||
return new ListCell<>() {
|
||||
return new ListCell<EmployeeRoleVo>() {
|
||||
@Override
|
||||
protected void updateItem(EmployeeRole item, boolean empty) {
|
||||
protected void updateItem(EmployeeRoleVo item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item == null || empty) {
|
||||
setText(null);
|
||||
@@ -69,10 +68,10 @@ public class EmployeeTabSkinRole
|
||||
};
|
||||
});
|
||||
|
||||
controller.rolesField.getTargetItems().addListener((ListChangeListener<EmployeeRole>) change -> {
|
||||
controller.rolesField.getTargetItems().addListener((ListChangeListener<EmployeeRoleVo>) change -> {
|
||||
while (change.next()) {
|
||||
List<? extends EmployeeRole> added = change.getAddedSubList();
|
||||
List<? extends EmployeeRole> removed = change.getRemoved();
|
||||
List<? extends EmployeeRoleVo> added = change.getAddedSubList();
|
||||
List<? extends EmployeeRoleVo> removed = change.getRemoved();
|
||||
if (!added.isEmpty() || !removed.isEmpty()) {
|
||||
changed.set(true);
|
||||
}
|
||||
@@ -82,9 +81,7 @@ public class EmployeeTabSkinRole
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
Employee entity = getEntity();
|
||||
entity.setRoles(controller.rolesField.getTargetItems());
|
||||
save(entity);
|
||||
getEmployeeService().getUpdateEmployeeRoles(viewModel.getId().get(), controller.rolesField.getTargetItems());
|
||||
loadSelectedRoles();
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,5 @@
|
||||
package com.ecep.contract.manager.ds.other.controller.employee;
|
||||
package com.ecep.contract.controller.employee;
|
||||
|
||||
import com.ecep.contract.manager.ds.other.model.Employee;
|
||||
import com.ecep.contract.manager.ds.other.model.EmployeeRole;
|
||||
import com.ecep.contract.manager.ds.other.service.EmployeeService;
|
||||
import com.ecep.contract.manager.ds.other.vo.EmployeeViewModel;
|
||||
import com.ecep.contract.manager.ui.AbstEntityController;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.ui.ViewModelService;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.Window;
|
||||
import javafx.stage.WindowEvent;
|
||||
import lombok.Getter;
|
||||
import org.controlsfx.control.ListSelectionView;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -21,11 +8,29 @@ import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.controller.AbstEntityController;
|
||||
import com.ecep.contract.service.EmployeeService;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.vm.EmployeeViewModel;
|
||||
import com.ecep.contract.vo.EmployeeRoleVo;
|
||||
import com.ecep.contract.vo.EmployeeVo;
|
||||
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.DatePicker;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TabPane;
|
||||
import javafx.scene.control.TableView;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.stage.Window;
|
||||
import javafx.stage.WindowEvent;
|
||||
import lombok.Getter;
|
||||
|
||||
@Lazy
|
||||
@Scope("prototype")
|
||||
@Component
|
||||
@FxmlPath("/ui/employee/employee.fxml")
|
||||
public class EmployeeWindowController extends AbstEntityController<Employee, EmployeeViewModel> {
|
||||
public class EmployeeWindowController extends AbstEntityController<EmployeeVo, EmployeeViewModel> {
|
||||
private static final Logger logger = LoggerFactory.getLogger(EmployeeWindowController.class);
|
||||
|
||||
/**
|
||||
@@ -55,7 +60,7 @@ public class EmployeeWindowController extends AbstEntityController<Employee, Emp
|
||||
|
||||
*/
|
||||
public Tab rolesTab;
|
||||
public ListSelectionView<EmployeeRole> rolesField;
|
||||
public ListSelectionView<EmployeeRoleVo> rolesField;
|
||||
|
||||
public Tab loginHistoryTab;
|
||||
public Tab authBindTab;
|
||||
@@ -66,7 +71,7 @@ public class EmployeeWindowController extends AbstEntityController<Employee, Emp
|
||||
public TableView<Tab> permissionsTable;
|
||||
|
||||
|
||||
public static void show(Employee employee, Window owner) {
|
||||
public static void show(EmployeeVo employee, Window owner) {
|
||||
EmployeeViewModel model = EmployeeViewModel.from(employee);
|
||||
show(model, owner);
|
||||
}
|
||||
@@ -1,16 +1,18 @@
|
||||
package com.ecep.contract.manager.ds.other.controller.inventory;
|
||||
package com.ecep.contract.controller.inventory;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
import com.ecep.contract.controller.AbstEntityManagerSkin;
|
||||
import com.ecep.contract.controller.table.cell.InventoryCatalogTableCell;
|
||||
import com.ecep.contract.controller.table.cell.LocalDateFieldTableCell;
|
||||
import com.ecep.contract.controller.table.cell.LocalDateTimeTableCell;
|
||||
import com.ecep.contract.converter.EntityStringConverter;
|
||||
import com.ecep.contract.service.InventoryCatalogService;
|
||||
import com.ecep.contract.service.InventoryService;
|
||||
import com.ecep.contract.vm.InventoryViewModel;
|
||||
import com.ecep.contract.vo.InventoryCatalogVo;
|
||||
import com.ecep.contract.vo.InventoryVo;
|
||||
|
||||
import com.ecep.contract.manager.ds.other.EntityStringConverter;
|
||||
import com.ecep.contract.manager.ds.other.model.Inventory;
|
||||
import com.ecep.contract.manager.ds.other.model.InventoryCatalog;
|
||||
import com.ecep.contract.manager.ds.other.service.InventoryCatalogService;
|
||||
import com.ecep.contract.manager.ds.other.service.InventoryService;
|
||||
import com.ecep.contract.manager.ds.other.vo.InventoryViewModel;
|
||||
import com.ecep.contract.manager.ui.AbstEntityManagerSkin;
|
||||
import com.ecep.contract.manager.ui.table.EditableEntityTableTabSkin;
|
||||
import com.ecep.contract.manager.ui.ManagerSkin;
|
||||
import com.ecep.contract.manager.ui.util.LocalDateFieldTableCell;
|
||||
import com.ecep.contract.manager.ui.table.cell.LocalDateTimeTableCell;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.TableColumn;
|
||||
@@ -19,11 +21,8 @@ import javafx.util.converter.CurrencyStringConverter;
|
||||
import javafx.util.converter.NumberStringConverter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public class InventoryManagerSkin
|
||||
extends AbstEntityManagerSkin<Inventory, InventoryViewModel, InventoryManagerSkin, InventoryManagerWindowController>
|
||||
implements ManagerSkin, EditableEntityTableTabSkin<Inventory, InventoryViewModel> {
|
||||
public class InventoryManagerSkin extends
|
||||
AbstEntityManagerSkin<InventoryVo, InventoryViewModel, InventoryManagerSkin, InventoryManagerWindowController> {
|
||||
|
||||
@Setter
|
||||
private InventoryCatalogService catalogService;
|
||||
@@ -58,18 +57,13 @@ public class InventoryManagerSkin
|
||||
controller.codeColumn.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getCode));
|
||||
|
||||
controller.catalogColumn.setCellValueFactory(param -> param.getValue().getCatalog());
|
||||
EntityStringConverter<InventoryCatalog> catalogStringConverter = new EntityStringConverter<>();
|
||||
catalogStringConverter.setInitialized(v -> getInventoryCatalogService().findById(v.getId()));
|
||||
catalogStringConverter.setFormater(InventoryCatalog::getName);
|
||||
catalogStringConverter.setFromString(v -> getInventoryCatalogService().findByName(v));
|
||||
catalogStringConverter.setSuggestion(getInventoryCatalogService()::search);
|
||||
|
||||
controller.catalogColumn.setCellFactory(TextFieldTableCell.forTableColumn(catalogStringConverter));
|
||||
controller.catalogColumn.setCellFactory(param-> new InventoryCatalogTableCell<>(getInventoryCatalogService()));
|
||||
controller.catalogColumn.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getCatalog));
|
||||
|
||||
controller.specificationColumn.setCellValueFactory(param -> param.getValue().getSpecification());
|
||||
controller.specificationColumn.setCellFactory(TextFieldTableCell.forTableColumn());
|
||||
controller.specificationColumn.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getSpecification));
|
||||
controller.specificationColumn
|
||||
.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getSpecification));
|
||||
|
||||
controller.unitColumn.setCellValueFactory(param -> param.getValue().getUnit());
|
||||
controller.unitColumn.setCellFactory(TextFieldTableCell.forTableColumn());
|
||||
@@ -77,44 +71,48 @@ public class InventoryManagerSkin
|
||||
|
||||
controller.purchaseTaxRateColumn.setCellValueFactory(param -> param.getValue().getPurchaseTaxRate());
|
||||
controller.purchaseTaxRateColumn.setCellFactory(TextFieldTableCell.forTableColumn(new NumberStringConverter()));
|
||||
controller.purchaseTaxRateColumn.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getPurchaseTaxRate));
|
||||
controller.purchaseTaxRateColumn
|
||||
.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getPurchaseTaxRate));
|
||||
|
||||
controller.purchasePriceColumn.setCellValueFactory(param -> param.getValue().getPurchasePrice());
|
||||
controller.purchasePriceColumn.setCellFactory(TextFieldTableCell.forTableColumn(new CurrencyStringConverter()));
|
||||
// controller.purchasePriceColumn.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getPurchasePrice));
|
||||
|
||||
// controller.purchasePriceColumn.setOnEditCommit(event ->
|
||||
// onColumnEditCommit(event, InventoryViewModel::getPurchasePrice));
|
||||
|
||||
controller.saleTaxRateColumn.setCellValueFactory(param -> param.getValue().getSaleTaxRate());
|
||||
controller.saleTaxRateColumn.setCellFactory(TextFieldTableCell.forTableColumn(new NumberStringConverter()));
|
||||
controller.saleTaxRateColumn.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getSaleTaxRate));
|
||||
controller.saleTaxRateColumn
|
||||
.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getSaleTaxRate));
|
||||
|
||||
controller.salePriceColumn.setCellValueFactory(param -> param.getValue().getSalePrice());
|
||||
controller.salePriceColumn.setCellFactory(TextFieldTableCell.forTableColumn(new CurrencyStringConverter()));
|
||||
// controller.salePriceColumn.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getSalePrice));
|
||||
// controller.salePriceColumn.setOnEditCommit(event -> onColumnEditCommit(event,
|
||||
// InventoryViewModel::getSalePrice));
|
||||
|
||||
controller.createTimeColumn.setCellValueFactory(param -> param.getValue().getCreateTime());
|
||||
controller.createTimeColumn.setCellFactory(LocalDateFieldTableCell.forTableColumn());
|
||||
controller.createTimeColumn.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getCreateTime));
|
||||
controller.createTimeColumn
|
||||
.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getCreateTime));
|
||||
|
||||
controller.updateDateColumn.setCellValueFactory(param -> param.getValue().getUpdateDate());
|
||||
controller.updateDateColumn.setCellFactory(param -> new LocalDateTimeTableCell<>());
|
||||
controller.updateDateColumn.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getUpdateDate));
|
||||
controller.updateDateColumn
|
||||
.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getUpdateDate));
|
||||
|
||||
controller.descriptionColumn.setCellValueFactory(param -> param.getValue().getDescription());
|
||||
controller.descriptionColumn.setCellFactory(TextFieldTableCell.forTableColumn());
|
||||
controller.descriptionColumn.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getDescription));
|
||||
controller.descriptionColumn
|
||||
.setOnEditCommit(event -> onColumnEditCommit(event, InventoryViewModel::getDescription));
|
||||
}
|
||||
|
||||
private <T> void onColumnEditCommit(
|
||||
TableColumn.CellEditEvent<InventoryViewModel, T> event,
|
||||
Function<InventoryViewModel, Property<T>> supplier
|
||||
) {
|
||||
Function<InventoryViewModel, Property<T>> supplier) {
|
||||
InventoryViewModel row = event.getRowValue();
|
||||
supplier.apply(row).setValue(event.getNewValue());
|
||||
saveRowData(row);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onTableRowDoubleClickedAction(InventoryViewModel item) {
|
||||
showInOwner(InventoryWindowController.class, item);
|
||||
@@ -122,7 +120,7 @@ public class InventoryManagerSkin
|
||||
|
||||
@Override
|
||||
protected void onTableCreateNewAction(ActionEvent event) {
|
||||
Inventory inventory = getService().save(getService().createNewInstance());
|
||||
InventoryVo inventory = getService().save(getService().createNewEntity());
|
||||
InventoryViewModel viewModel = InventoryViewModel.from(inventory);
|
||||
dataSet.add(viewModel);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.ecep.contract.manager.ds.other.controller.inventory;
|
||||
package com.ecep.contract.controller.inventory;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -8,13 +8,15 @@ import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.ecep.contract.manager.ds.other.model.Inventory;
|
||||
import com.ecep.contract.manager.ds.other.model.InventoryCatalog;
|
||||
import com.ecep.contract.manager.ds.other.service.InventoryService;
|
||||
import com.ecep.contract.manager.ds.other.vo.InventoryViewModel;
|
||||
import com.ecep.contract.manager.ui.AbstManagerWindowController;
|
||||
import com.ecep.contract.manager.ui.FxmlPath;
|
||||
import com.ecep.contract.manager.util.UITools;
|
||||
import com.ecep.contract.controller.AbstManagerWindowController;
|
||||
import com.ecep.contract.service.InventoryService;
|
||||
import com.ecep.contract.task.InventoryAllSyncTask;
|
||||
import com.ecep.contract.task.InventorySyncTask;
|
||||
import com.ecep.contract.util.FxmlPath;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.InventoryViewModel;
|
||||
import com.ecep.contract.vo.InventoryCatalogVo;
|
||||
import com.ecep.contract.vo.InventoryVo;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
@@ -26,7 +28,7 @@ import javafx.stage.Stage;
|
||||
@Component
|
||||
@FxmlPath(value = "/ui/inventory/inventory-manager.fxml")
|
||||
public class InventoryManagerWindowController
|
||||
extends AbstManagerWindowController<Inventory, InventoryViewModel, InventoryManagerSkin> {
|
||||
extends AbstManagerWindowController<InventoryVo, InventoryViewModel, InventoryManagerSkin> {
|
||||
|
||||
@FXML
|
||||
public TableColumn<InventoryViewModel, Number> idColumn;
|
||||
@@ -34,8 +36,11 @@ public class InventoryManagerWindowController
|
||||
public TableColumn<InventoryViewModel, String> nameColumn;
|
||||
@FXML
|
||||
public TableColumn<InventoryViewModel, String> codeColumn;
|
||||
/**
|
||||
* InventoryCatalogVo
|
||||
*/
|
||||
@FXML
|
||||
public TableColumn<InventoryViewModel, InventoryCatalog> catalogColumn;
|
||||
public TableColumn<InventoryViewModel, Integer> catalogColumn;
|
||||
@FXML
|
||||
public TableColumn<InventoryViewModel, String> specificationColumn;
|
||||
@FXML
|
||||
@@ -75,7 +80,7 @@ public class InventoryManagerWindowController
|
||||
}
|
||||
|
||||
public void onSyncAction(ActionEvent event) {
|
||||
InventorySyncTask task = new InventorySyncTask();
|
||||
InventoryAllSyncTask task = new InventoryAllSyncTask();
|
||||
UITools.showTaskDialogAndWait("同步数据", task, null);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.ecep.contract.controller.inventory;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.ecep.contract.task.InventorySyncTask;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.ecep.contract.MessageHolder;
|
||||
import com.ecep.contract.MyDateTimeUtils;
|
||||
import com.ecep.contract.controller.tab.AbstEntityBasedTabSkin;
|
||||
import com.ecep.contract.controller.tab.TabSkin;
|
||||
import com.ecep.contract.controller.table.EditableEntityTableTabSkin;
|
||||
import com.ecep.contract.converter.EmployeeStringConverter;
|
||||
import com.ecep.contract.converter.EntityStringConverter;
|
||||
import com.ecep.contract.service.InventoryCatalogService;
|
||||
import com.ecep.contract.service.InventoryService;
|
||||
import com.ecep.contract.util.UITools;
|
||||
import com.ecep.contract.vm.InventoryViewModel;
|
||||
import com.ecep.contract.vo.InventoryCatalogVo;
|
||||
import com.ecep.contract.vo.InventoryVo;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.util.converter.CurrencyStringConverter;
|
||||
import javafx.util.converter.LocalDateStringConverter;
|
||||
import javafx.util.converter.LocalDateTimeStringConverter;
|
||||
import javafx.util.converter.NumberStringConverter;
|
||||
|
||||
public class InventoryTabSkinBase
|
||||
extends AbstEntityBasedTabSkin<InventoryWindowController, InventoryVo, InventoryViewModel>
|
||||
implements TabSkin, EditableEntityTableTabSkin<InventoryVo, InventoryViewModel> {
|
||||
|
||||
public InventoryTabSkinBase(InventoryWindowController controller) {
|
||||
super(controller);
|
||||
}
|
||||
|
||||
InventoryService getService() {
|
||||
return getCachedBean(InventoryService.class);
|
||||
}
|
||||
|
||||
InventoryCatalogService getCatalogService() {
|
||||
return getCachedBean(InventoryCatalogService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tab getTab() {
|
||||
return controller.baseInfoTab;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTab() {
|
||||
controller.syncBtn.setOnAction(this::onSyncAction);
|
||||
|
||||
NumberFormat numberInstance = NumberFormat.getNumberInstance(getLocale());
|
||||
numberInstance.setMaximumFractionDigits(2);
|
||||
numberInstance.setMinimumFractionDigits(2);
|
||||
CurrencyStringConverter currencyStringConverter = new CurrencyStringConverter(numberInstance);
|
||||
|
||||
controller.nameField.textProperty().bindBidirectional(viewModel.getName());
|
||||
controller.nameLockField.selectedProperty().bindBidirectional(viewModel.getNameLock());
|
||||
controller.codeField.textProperty().bind(viewModel.getCode());
|
||||
controller.unitField.textProperty().bindBidirectional(viewModel.getUnit());
|
||||
controller.specificationField.textProperty().bindBidirectional(viewModel.getSpecification());
|
||||
controller.specificationLockField.selectedProperty().bindBidirectional(viewModel.getSpecificationLock());
|
||||
|
||||
UITools.autoCompletion(controller.catalogField, viewModel.getCatalog(), getCatalogService());
|
||||
|
||||
controller.purchaseTaxRateField.textProperty().bindBidirectional(viewModel.getPurchaseTaxRate(),
|
||||
new NumberStringConverter());
|
||||
// 采购价
|
||||
bindPriceField(controller.purchasePriceField, viewModel.getPurchasePrice(), currencyStringConverter,
|
||||
viewModel::updatePurchasePrice);
|
||||
bindPriceField(controller.purchaseTaxPriceField, viewModel.getPurchaseTaxPrice(), currencyStringConverter,
|
||||
viewModel::updatePurchaseTaxPrice);
|
||||
|
||||
controller.saleTaxRateField.textProperty().bindBidirectional(viewModel.getSaleTaxRate(),
|
||||
new NumberStringConverter());
|
||||
// 销售价
|
||||
bindPriceField(controller.salePriceField, viewModel.getSalePrice(), currencyStringConverter,
|
||||
viewModel::updateSalePrice);
|
||||
bindPriceField(controller.saleTaxPriceField, viewModel.getSaleTaxPrice(), currencyStringConverter,
|
||||
viewModel::updateSaleTaxPrice);
|
||||
|
||||
// 采购价不能大于销售价
|
||||
Bindings.greaterThan(viewModel.getPurchasePrice(), viewModel.getSalePrice())
|
||||
.addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
controller.purchasePriceField.getStyleClass().add("error");
|
||||
controller.salePriceField.getStyleClass().add("error");
|
||||
} else {
|
||||
controller.purchasePriceField.getStyleClass().remove("error");
|
||||
controller.salePriceField.getStyleClass().remove("error");
|
||||
}
|
||||
});
|
||||
Bindings.greaterThan(viewModel.getPurchaseTaxPrice(), viewModel.getSaleTaxPrice())
|
||||
.addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
controller.purchaseTaxPriceField.getStyleClass().add("error");
|
||||
controller.saleTaxPriceField.getStyleClass().add("error");
|
||||
} else {
|
||||
controller.purchaseTaxPriceField.getStyleClass().remove("error");
|
||||
controller.saleTaxPriceField.getStyleClass().remove("error");
|
||||
}
|
||||
});
|
||||
|
||||
UITools.autoCompletion(controller.creatorField, viewModel.getCreator(), controller.getEmployeeService());
|
||||
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(MyDateTimeUtils.DEFAULT_DATE_FORMAT_PATTERN);
|
||||
controller.createTimeField.textProperty().bindBidirectional(viewModel.getCreateTime(),
|
||||
new LocalDateStringConverter(dateFormatter, null));
|
||||
|
||||
UITools.autoCompletion(controller.updaterField, viewModel.getUpdater(), controller.getEmployeeService());
|
||||
DateTimeFormatter dateTimeFormatter = DateTimeFormatter
|
||||
.ofPattern(MyDateTimeUtils.DEFAULT_DATETIME_FORMAT_PATTERN);
|
||||
controller.updateDateField.textProperty().bindBidirectional(viewModel.getUpdateDate(),
|
||||
new LocalDateTimeStringConverter(dateTimeFormatter, null));
|
||||
|
||||
controller.weightUnitField.textProperty().bindBidirectional(viewModel.getWeightUnit());
|
||||
controller.sizeUnitField.textProperty().bindBidirectional(viewModel.getSizeUnit());
|
||||
controller.volumeUnitField.textProperty().bindBidirectional(viewModel.getVolumeUnit());
|
||||
controller.weightField.textProperty().bindBidirectional(viewModel.getWeight(), new NumberStringConverter());
|
||||
controller.packagedWeightField.textProperty().bindBidirectional(viewModel.getPackagedWeight(),
|
||||
new NumberStringConverter());
|
||||
controller.sizeLengthField.textProperty().bindBidirectional(viewModel.getSizeLength(),
|
||||
new NumberStringConverter());
|
||||
controller.sizeWidthField.textProperty().bindBidirectional(viewModel.getSizeWidth(),
|
||||
new NumberStringConverter());
|
||||
controller.sizeHeightField.textProperty().bindBidirectional(viewModel.getSizeHeight(),
|
||||
new NumberStringConverter());
|
||||
controller.volumeField.textProperty().bindBidirectional(viewModel.getVolume(), new NumberStringConverter());
|
||||
controller.packagedSizeLengthField.textProperty().bindBidirectional(viewModel.getPackagedSizeLength(),
|
||||
new NumberStringConverter());
|
||||
controller.packagedSizeWidthField.textProperty().bindBidirectional(viewModel.getPackagedSizeWidth(),
|
||||
new NumberStringConverter());
|
||||
controller.packagedSizeHeightField.textProperty().bindBidirectional(viewModel.getPackagedSizeHeight(),
|
||||
new NumberStringConverter());
|
||||
controller.packagedVolumeField.textProperty().bindBidirectional(viewModel.getPackagedVolume(),
|
||||
new NumberStringConverter());
|
||||
|
||||
controller.descriptionField.textProperty().bindBidirectional(viewModel.getDescription());
|
||||
}
|
||||
|
||||
private void bindPriceField(TextField textField, SimpleDoubleProperty property,
|
||||
CurrencyStringConverter stringConverter, Consumer<Double> updater) {
|
||||
textField.setText(stringConverter.toString(property.get()));
|
||||
property.addListener((observable, oldValue, newValue) -> {
|
||||
textField.setText(stringConverter.toString(newValue));
|
||||
});
|
||||
textField.setOnKeyTyped(event -> {
|
||||
Number number = stringConverter.fromString(textField.getText());
|
||||
updater.accept(number.doubleValue());
|
||||
});
|
||||
}
|
||||
|
||||
private void onSyncAction(ActionEvent event) {
|
||||
InventoryVo inventory = getEntity();
|
||||
InventorySyncTask task = new InventorySyncTask();
|
||||
task.setInventory(inventory);
|
||||
UITools.showTaskDialogAndWait("同步数据", task, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteRowData(InventoryVo entity) {
|
||||
getService().delete(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InventoryVo loadRowData(InventoryViewModel row) {
|
||||
return getService().findById(row.getId().get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InventoryVo saveRowData(InventoryVo entity) {
|
||||
return getService().save(entity);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user