feat: 实现节假日服务功能并更新项目版本
refactor(CloudRkManagerWindowController): 简化任务初始化逻辑 feat(HolidayService): 添加节假日调整和检查功能 feat(HolidayTable): 实现Voable接口并添加toVo方法 feat(HolidayTableRepository): 添加节假日查询方法 docs: 添加服务器端Service类规则文档 build: 更新项目版本至0.0.101-SNAPSHOT
This commit is contained in:
@@ -60,7 +60,7 @@
|
||||
- `customer/`: 客户相关业务
|
||||
- `project/`: 项目相关业务
|
||||
- `vendor/`: 供应商相关业务
|
||||
- 每个业务领域包含:`model/`(实体类)、`repository/`(数据访问接口)、`service/`(业务逻辑)、`vo/`(视图对象)
|
||||
- 每个业务领域包含:`model/`(实体类)、`repository/`(数据访问接口)、`service/`(业务逻辑, 详细规范见 `.trae\rules\server_service_rules.md`)、`tasker/`(任务处理器)、`controller/`(控制器)
|
||||
- `handler/`: WebSocket处理器
|
||||
- `service/`: 服务层,包含一些通用服务和任务处理器
|
||||
- `ui/`: UI相关组件
|
||||
|
||||
260
.trae/rules/server_service_rules.md
Normal file
260
.trae/rules/server_service_rules.md
Normal file
@@ -0,0 +1,260 @@
|
||||
# 服务器端 Service 类规则文档
|
||||
|
||||
## 1. 概述
|
||||
|
||||
本规则文档定义了 Contract-Manager 项目服务器端(server模块)Service 类的设计规范、实现标准和最佳实践。所有服务器端 Service 类必须严格遵循本规则,以确保代码的一致性、可维护性和性能。
|
||||
|
||||
## 2. 目录结构
|
||||
|
||||
Service 类按业务领域组织,位于 `server/src/main/java/com/ecep/contract/ds/{业务领域}/service/` 目录下。其中 `{业务领域}` 对应具体的业务模块,如 `customer`、`contract`、`company`、`project`、`other` 等。
|
||||
|
||||
**示例:**
|
||||
- 客户分类服务:`server/src/main/java/com/ecep/contract/ds/customer/service/CustomerCatalogService.java`
|
||||
- 员工服务:`server/src/main/java/com/ecep/contract/ds/other/service/EmployeeService.java`
|
||||
|
||||
## 3. 命名规范
|
||||
|
||||
- **类名**:采用驼峰命名法,首字母大写,以 `Service` 结尾,表示这是一个服务类。
|
||||
**示例**:`CustomerCatalogService`、`EmployeeService`
|
||||
|
||||
## 4. 接口实现
|
||||
|
||||
所有业务领域的 Service 类必须实现以下三个核心接口:
|
||||
|
||||
### 4.1 IEntityService<T>
|
||||
|
||||
提供实体类的基本 CRUD 操作。泛型 `T` 表示实体类类型。
|
||||
|
||||
**主要方法:**
|
||||
- `T getById(Integer id)`:根据 ID 查询实体对象
|
||||
- `Page<T> findAll(Specification<T> spec, Pageable pageable)`:根据条件和分页参数查询实体列表
|
||||
- `Specification<T> getSpecification(String searchText)`:构建搜索条件
|
||||
- `void delete(T entity)`:删除实体
|
||||
- `T save(T entity)`:保存实体
|
||||
|
||||
### 4.2 QueryService<Vo>
|
||||
|
||||
提供 VO 对象的查询能力。泛型 `Vo` 表示视图对象类型。
|
||||
|
||||
**主要方法:**
|
||||
- `Vo findById(Integer id)`:根据 ID 查询 VO 对象
|
||||
- `Page<Vo> findAll(JsonNode paramsNode, Pageable pageable)`:根据 JSON 查询参数和分页条件查询 VO 列表
|
||||
- `default long count(JsonNode paramsNode)`:根据查询参数统计数据总数
|
||||
|
||||
### 4.3 VoableService<M, Vo>
|
||||
|
||||
提供从 VO 对象更新实体对象的能力。泛型 `M` 表示实体类类型,`Vo` 表示视图对象类型。
|
||||
|
||||
**主要方法:**
|
||||
- `void updateByVo(M model, Vo vo)`:根据 VO 对象更新实体对象
|
||||
|
||||
**实现示例:**
|
||||
```java
|
||||
@Lazy
|
||||
@Service
|
||||
@CacheConfig(cacheNames = "customer-catalog")
|
||||
public class CustomerCatalogService implements IEntityService<CustomerCatalog>, QueryService<CustomerCatalogVo>,
|
||||
VoableService<CustomerCatalog, CustomerCatalogVo> {
|
||||
// 实现方法...
|
||||
}
|
||||
```
|
||||
|
||||
## 5. 注解规范
|
||||
|
||||
Service 类必须使用以下注解:
|
||||
|
||||
### 5.1 类级别注解
|
||||
|
||||
- `@Service`:标记这是一个 Spring 服务类,使其能够被自动扫描和管理
|
||||
- `@Lazy`:延迟加载服务类,提高应用启动性能
|
||||
- `@CacheConfig(cacheNames = "缓存名称")`:配置缓存名称,缓存名称通常与业务领域相关
|
||||
**示例**:`@CacheConfig(cacheNames = "customer-catalog")`、`@CacheConfig(cacheNames = "employee")`
|
||||
|
||||
### 5.2 方法级别注解
|
||||
|
||||
#### 5.2.1 查询方法注解
|
||||
|
||||
- `@Cacheable(key = "缓存键")`:将查询结果缓存起来,下次相同查询可以直接从缓存获取
|
||||
**示例**:`@Cacheable(key = "#p0")`(使用方法第一个参数作为缓存键)、`@Cacheable(key = "'code-'+#p0")`(使用前缀+参数值作为缓存键)
|
||||
|
||||
#### 5.2.2 数据修改方法注解
|
||||
|
||||
- `@Caching(evict = { ... })`:在保存或删除操作时清除相关缓存
|
||||
**示例**:
|
||||
```java
|
||||
@Caching(evict = {
|
||||
@CacheEvict(key = "#p0.id"),
|
||||
@CacheEvict(key = "'code-'+#p0.code"),
|
||||
@CacheEvict(key = "'name-'+#p0.name"),
|
||||
@CacheEvict(key = "'all'")
|
||||
})
|
||||
```
|
||||
|
||||
- `@Transactional`:标记方法需要在事务中执行,确保数据一致性
|
||||
**示例**:用于包含多个数据操作的复杂业务方法
|
||||
|
||||
## 6. 缓存策略
|
||||
|
||||
Service 类必须遵循以下缓存策略:
|
||||
|
||||
### 6.1 查询缓存
|
||||
|
||||
- 所有 `findById`、`findByCode`、`findByName` 等单条查询方法都应使用 `@Cacheable` 注解缓存结果
|
||||
- 缓存键设计应具有唯一性和可读性,通常包含参数值和适当的前缀
|
||||
- 列表查询(如 `findAll`)可以考虑使用 `@Cacheable`,但需谨慎管理缓存失效
|
||||
|
||||
### 6.2 缓存清理
|
||||
|
||||
- 所有 `save`、`delete` 等修改数据的方法都应使用 `@Caching` 和 `@CacheEvict` 注解清理相关缓存
|
||||
- 清理缓存时应考虑所有可能影响的查询,确保缓存数据的一致性
|
||||
|
||||
## 7. 方法实现规范
|
||||
|
||||
### 7.1 IEntityService 方法实现
|
||||
|
||||
- `getById`:直接调用 Repository 的 `findById` 方法,返回实体对象
|
||||
```java
|
||||
@Override
|
||||
public CustomerCatalog getById(Integer id) {
|
||||
return repository.findById(id).orElse(null);
|
||||
}
|
||||
```
|
||||
|
||||
- `findAll(Specification<T>, Pageable)`:直接调用 Repository 的 `findAll` 方法,返回实体分页对象
|
||||
```java
|
||||
@Override
|
||||
public Page<CustomerCatalog> findAll(Specification<CustomerCatalog> spec, Pageable pageable) {
|
||||
return repository.findAll(spec, pageable);
|
||||
}
|
||||
```
|
||||
|
||||
- `save/delete`:调用 Repository 的相应方法,并添加缓存清理注解
|
||||
```java
|
||||
@Caching(evict = { ... })
|
||||
@Override
|
||||
public CustomerCatalog save(CustomerCatalog catalog) {
|
||||
return repository.save(catalog);
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 QueryService 方法实现
|
||||
|
||||
- `findById`:调用 Repository 的 `findById` 方法,然后调用实体类的 `toVo` 方法转换为 VO 对象
|
||||
```java
|
||||
@Cacheable(key = "#p0")
|
||||
@Override
|
||||
public CustomerCatalogVo findById(Integer id) {
|
||||
return repository.findById(id).map(CustomerCatalog::toVo).orElse(null);
|
||||
}
|
||||
```
|
||||
|
||||
- `findAll(JsonNode, Pageable)`:构建 Specification,调用 IEntityService 的 `findAll` 方法,然后使用 Stream API 的 `map` 方法将结果转换为 VO 对象
|
||||
```java
|
||||
@Override
|
||||
public Page<CustomerCatalogVo> findAll(JsonNode paramsNode, Pageable pageable) {
|
||||
Specification<CustomerCatalog> spec = null;
|
||||
if (paramsNode.has(ServiceConstant.KEY_SEARCH_TEXT)) {
|
||||
spec = getSpecification(paramsNode.get(ServiceConstant.KEY_SEARCH_TEXT).asText());
|
||||
}
|
||||
|
||||
// 字段等值查询
|
||||
spec = SpecificationUtils.andFieldEqualParam(spec, paramsNode, "code", "name", "description");
|
||||
return repository.findAll(spec, pageable).map(CustomerCatalog::toVo);
|
||||
}
|
||||
```
|
||||
|
||||
### 7.3 VoableService 方法实现
|
||||
|
||||
- `updateByVo`:将 VO 对象的属性逐个复制到实体对象,实现 VO 到实体的转换
|
||||
```java
|
||||
@Override
|
||||
public void updateByVo(CustomerCatalog model, CustomerCatalogVo vo) {
|
||||
// 参数校验
|
||||
if (model == null) {
|
||||
throw new ServiceException("实体对象不能为空");
|
||||
}
|
||||
if (vo == null) {
|
||||
throw new ServiceException("VO对象不能为空");
|
||||
}
|
||||
|
||||
// 映射基本属性
|
||||
model.setCode(vo.getCode());
|
||||
model.setName(vo.getName());
|
||||
model.setDescription(vo.getDescription());
|
||||
}
|
||||
```
|
||||
|
||||
## 8. 依赖注入规范
|
||||
|
||||
- Service 类应使用 `@Autowired` 注解注入所需的 Repository 和其他依赖
|
||||
- 为了避免循环依赖,所有注入的依赖都应使用 `@Lazy` 注解标记为延迟加载
|
||||
```java
|
||||
@Lazy
|
||||
@Autowired
|
||||
private CustomerCatalogRepository repository;
|
||||
```
|
||||
|
||||
## 9. 查询条件构建
|
||||
|
||||
- 使用 `SpecificationUtils` 工具类辅助构建查询条件
|
||||
- 实现 `getSpecification(String searchText)` 方法,提供基于搜索文本的模糊查询能力
|
||||
```java
|
||||
@Override
|
||||
public Specification<CustomerCatalog> getSpecification(String searchText) {
|
||||
if (!StringUtils.hasText(searchText)) {
|
||||
return null;
|
||||
}
|
||||
String likeText = "%" + searchText + "%";
|
||||
return (root, query, builder) -> {
|
||||
return builder.or(
|
||||
builder.like(root.get("code"), likeText),
|
||||
builder.like(root.get("name"), likeText),
|
||||
builder.like(root.get("description"), likeText));
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## 10. 异常处理
|
||||
|
||||
- Service 类应适当处理异常,特别是对输入参数的校验
|
||||
- 对于业务逻辑异常,应抛出 `ServiceException`
|
||||
```java
|
||||
if (model == null) {
|
||||
throw new ServiceException("实体对象不能为空");
|
||||
}
|
||||
```
|
||||
|
||||
## 11. 最佳实践
|
||||
|
||||
1. **VO优先原则**:QueryService 接口应使用 VO 类型作为泛型参数,返回 VO 对象而不是实体对象
|
||||
2. **缓存粒度**:缓存键应设计得足够细粒度,避免缓存过大或频繁失效
|
||||
3. **事务管理**:包含多个数据操作的方法应使用 `@Transactional` 注解确保事务一致性
|
||||
4. **延迟加载**:所有依赖都应使用 `@Lazy` 注解,避免循环依赖问题
|
||||
5. **参数校验**:方法开始时应进行参数校验,确保输入数据的合法性
|
||||
6. **文档注释**:关键方法应有清晰的 JavaDoc 注释,说明其功能、参数和返回值
|
||||
7. **代码复用**:尽量使用 SpecificationUtils 等工具类辅助构建查询条件,提高代码复用性
|
||||
|
||||
## 12. 不符合规范的Service类示例
|
||||
|
||||
以下是不符合规范的Service类实现,应避免:
|
||||
|
||||
```java
|
||||
// 错误:QueryService泛型参数使用实体类型而非VO类型
|
||||
@Service
|
||||
@CacheConfig(cacheNames = "company")
|
||||
public class CompanyService extends EntityService<Company, Integer>
|
||||
implements IEntityService<Company>, QueryService<Company>, VoableService<Company, CompanyVo> {
|
||||
// 实现方法...
|
||||
}
|
||||
|
||||
// 错误:未使用缓存注解
|
||||
@Service
|
||||
public class ExampleService implements IEntityService<Example>, QueryService<ExampleVo>,
|
||||
VoableService<Example, ExampleVo> {
|
||||
// 未使用@Cacheable、@CacheEvict等缓存注解
|
||||
}
|
||||
```
|
||||
|
||||
## 13. 总结
|
||||
|
||||
本规则文档定义了服务器端 Service 类的完整规范,包括目录结构、命名规范、接口实现、注解规范、缓存策略、方法实现、依赖注入等方面。所有服务器端开发人员都必须严格遵循这些规则,以确保代码的一致性、可维护性和性能。
|
||||
@@ -6,12 +6,12 @@
|
||||
<parent>
|
||||
<groupId>com.ecep.contract</groupId>
|
||||
<artifactId>Contract-Manager</artifactId>
|
||||
<version>0.0.100-SNAPSHOT</version>
|
||||
<version>0.0.101-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.ecep.contract</groupId>
|
||||
<artifactId>client</artifactId>
|
||||
<version>0.0.100-SNAPSHOT</version>
|
||||
<version>0.0.101-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
@@ -22,7 +22,7 @@
|
||||
<dependency>
|
||||
<groupId>com.ecep.contract</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>0.0.100-SNAPSHOT</version>
|
||||
<version>0.0.101-SNAPSHOT</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@@ -65,32 +65,12 @@ public class CloudRkManagerWindowController
|
||||
getTitle().set("数据源:集团相关方");
|
||||
}
|
||||
|
||||
private void initializeTask(Task<Object> task, String prefix, Consumer<String> consumer) {
|
||||
task.setOnScheduled(e -> {
|
||||
consumer.accept("正在从相关方平台同步" + prefix + ",请稍后...");
|
||||
});
|
||||
task.setOnRunning(e -> {
|
||||
consumer.accept("开始" + prefix + "...");
|
||||
});
|
||||
task.setOnSucceeded(e -> {
|
||||
consumer.accept(prefix + "完成...");
|
||||
});
|
||||
task.exceptionProperty().addListener((observable, oldValue, newValue) -> {
|
||||
consumer.accept(newValue.getMessage());
|
||||
logger.error("{} 发生异常", prefix, newValue);
|
||||
});
|
||||
SpringApp.getBean(ScheduledExecutorService.class).submit(task);
|
||||
consumer.accept("任务已创建...");
|
||||
}
|
||||
|
||||
public void onDataRepairAction(ActionEvent event) {
|
||||
}
|
||||
|
||||
public void onSyncAction(ActionEvent event) {
|
||||
CloudRkSyncTask task = new CloudRkSyncTask();
|
||||
UITools.showTaskDialogAndWait("同步数据", task, consumer -> {
|
||||
initializeTask(task, "同步数据", msg -> consumer.accept(Message.info(msg)));
|
||||
});
|
||||
UITools.showTaskDialogAndWait("同步数据", task, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,15 +1,71 @@
|
||||
package com.ecep.contract.service;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import com.ecep.contract.WebSocketClientService;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 节假日服务类
|
||||
*
|
||||
* @author System
|
||||
*/
|
||||
@Lazy
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class HolidayService {
|
||||
@Autowired
|
||||
private WebSocketClientService webSocketClientService;
|
||||
@Autowired
|
||||
protected ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* 调整日期到工作日
|
||||
*
|
||||
* @param date 要调整的日期
|
||||
* @return 调整的日期
|
||||
*/
|
||||
public LocalDate adjustToWorkDay(LocalDate date) {
|
||||
throw new RuntimeException("Not implemented");
|
||||
if (date == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
JsonNode json = webSocketClientService.invoke("holidayService", "adjustToWorkDay", date).get();
|
||||
if (json != null && !json.isEmpty()) {
|
||||
return objectMapper.convertValue(json, LocalDate.class);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("调整日期到工作日失败: {}", e.getMessage(), e);
|
||||
}
|
||||
|
||||
// 如果调用失败,使用客户端本地逻辑作为后备
|
||||
return adjustToWorkDayLocally(date);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 客户端本地日期调整逻辑,作为服务器调用失败的后备方案
|
||||
*
|
||||
* @param date 要调整的日期
|
||||
* @return 调整的日期
|
||||
*/
|
||||
private LocalDate adjustToWorkDayLocally(LocalDate date) {
|
||||
while (date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY) {
|
||||
date = date.plusDays(1);
|
||||
}
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.ecep.contract.service;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
@@ -7,6 +8,8 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.ecep.contract.SpringApp;
|
||||
import com.ecep.contract.vo.ContractVo;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -22,13 +25,62 @@ import com.ecep.contract.vo.VendorVo;
|
||||
@Service
|
||||
public class VendorFileService extends QueryService<VendorFileVo, CompanyVendorFileViewModel> {
|
||||
|
||||
public LocalDate getNextSignDate(VendorVo companyVendor, Consumer<String> state) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getNextSignDate'");
|
||||
}
|
||||
public Map<VendorFileType, VendorFileTypeLocal> getFileTypeLocalMap(Locale locale) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'getFileTypeLocalMap'");
|
||||
public LocalDate getNextSignDate(VendorVo vendor, Consumer<String> state) {
|
||||
LocalDate miniContractDate = LocalDate.of(2022, 1, 1);
|
||||
|
||||
// 检索全部合同
|
||||
ContractService contractService = SpringApp.getBean(ContractService.class);
|
||||
List<ContractVo> contractList = contractService.findAllByCompanyVendor(vendor, null, null);
|
||||
if (contractList.isEmpty()) {
|
||||
state.accept("未发现已登记的合同");
|
||||
return null;
|
||||
}
|
||||
// 检索评价表
|
||||
List<VendorFileVo> files = findAllByVendorAndType(vendor, VendorFileType.EvaluationForm);
|
||||
VendorFileVo latestFile = files.stream()
|
||||
.filter(v -> v.getSignDate() != null && v.isValid())
|
||||
.max(Comparator.comparing(VendorFileVo::getSignDate))
|
||||
.orElse(null);
|
||||
|
||||
// 没有有效的评价表的评价日期
|
||||
if (latestFile == null) {
|
||||
state.accept("未发现有效的评价表");
|
||||
// 返回最早的合同日期
|
||||
ContractVo firstContract = contractList.stream()
|
||||
.filter(v -> v.getSetupDate() != null && !v.getSetupDate().isBefore(miniContractDate))
|
||||
.min(Comparator.comparing(ContractVo::getSetupDate))
|
||||
.orElse(null);
|
||||
if (firstContract == null) {
|
||||
state.accept("最早的合同不存在?");
|
||||
return null;
|
||||
}
|
||||
|
||||
LocalDate setupDate = firstContract.getSetupDate();
|
||||
state.accept("依据合同 " + firstContract.getCode() + " 的日期 " + setupDate + " 推算");
|
||||
return SpringApp.getBean(HolidayService.class).adjustToWorkDay(setupDate.plusDays(-7));
|
||||
}
|
||||
|
||||
// 检查失效日期起的第一个合同
|
||||
LocalDate nextInValidDate = latestFile.getSignDate().plusYears(1);
|
||||
File file = new File(latestFile.getFilePath());
|
||||
state.accept("依据 " + file.getName() + " 的失效期 " + nextInValidDate + " 检索合同");
|
||||
List<ContractVo> matchedContracts = contractList.stream()
|
||||
.filter(v -> v.getSetupDate().isAfter(nextInValidDate)).toList();
|
||||
// 没有在失效日期后的合同时,使用失效日期
|
||||
if (matchedContracts.isEmpty()) {
|
||||
state.accept("未发现失效期 " + nextInValidDate + " 后的合同");
|
||||
return null;
|
||||
}
|
||||
state.accept("发现匹配合同 " + matchedContracts.size() + " 个");
|
||||
|
||||
// 按时间取最早一个
|
||||
ContractVo firstContract = matchedContracts.stream()
|
||||
.min(Comparator.comparing(ContractVo::getSetupDate))
|
||||
.orElse(null);
|
||||
LocalDate setupDate = firstContract.getSetupDate();
|
||||
state.accept("匹配失效期 " + nextInValidDate + " 后的第一个合同 " + firstContract.getCode());
|
||||
state.accept("依据合同 " + firstContract.getCode() + " 的日期 " + setupDate + " 推算");
|
||||
return SpringApp.getBean(HolidayService.class).adjustToWorkDay(setupDate.plusDays(-7));
|
||||
}
|
||||
|
||||
public void verify(VendorVo companyVendor, LocalDate verifyDate, MessageHolder holder) {
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
<parent>
|
||||
<groupId>com.ecep.contract</groupId>
|
||||
<artifactId>Contract-Manager</artifactId>
|
||||
<version>0.0.100-SNAPSHOT</version>
|
||||
<version>0.0.101-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.ecep.contract</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>0.0.100-SNAPSHOT</version>
|
||||
<version>0.0.101-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.hibernate.annotations.JdbcTypeCode;
|
||||
import org.hibernate.type.SqlTypes;
|
||||
|
||||
import com.ecep.contract.util.HibernateProxyUtils;
|
||||
import com.ecep.contract.vo.HolidayTableVo;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
@@ -21,7 +22,7 @@ import lombok.ToString;
|
||||
@Entity
|
||||
@Table(name = "HOLIDAY_TABLE")
|
||||
@ToString
|
||||
public class HolidayTable {
|
||||
public class HolidayTable implements Voable<HolidayTableVo> {
|
||||
@Id
|
||||
@Column(name = "ID", nullable = false)
|
||||
@JdbcTypeCode(SqlTypes.DATE)
|
||||
@@ -47,4 +48,12 @@ public class HolidayTable {
|
||||
public final int hashCode() {
|
||||
return HibernateProxyUtils.hashCode(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HolidayTableVo toVo() {
|
||||
HolidayTableVo vo = new HolidayTableVo();
|
||||
vo.setId(this.id);
|
||||
vo.setHoliday(this.holiday);
|
||||
return vo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.ecep.contract.vo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Objects;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 节假日表视图对象
|
||||
*
|
||||
* @author System
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class HolidayTableVo implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private LocalDate id;
|
||||
private boolean holiday;
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
HolidayTableVo that = (HolidayTableVo) o;
|
||||
return Objects.equals(id, that.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id);
|
||||
}
|
||||
}
|
||||
2
pom.xml
2
pom.xml
@@ -10,7 +10,7 @@
|
||||
</parent>
|
||||
<groupId>com.ecep.contract</groupId>
|
||||
<artifactId>Contract-Manager</artifactId>
|
||||
<version>0.0.100-SNAPSHOT</version>
|
||||
<version>0.0.101-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>server</module>
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
<parent>
|
||||
<groupId>com.ecep.contract</groupId>
|
||||
<artifactId>Contract-Manager</artifactId>
|
||||
<version>0.0.100-SNAPSHOT</version>
|
||||
<version>0.0.101-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.ecep.contract</groupId>
|
||||
<artifactId>server</artifactId>
|
||||
<version>0.0.100-SNAPSHOT</version>
|
||||
<version>0.0.101-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||
@@ -22,7 +22,7 @@
|
||||
<dependency>
|
||||
<groupId>com.ecep.contract</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>0.0.100-SNAPSHOT</version>
|
||||
<version>0.0.101-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@@ -21,7 +21,6 @@ import com.ecep.contract.CustomerFileType;
|
||||
import com.ecep.contract.VendorFileType;
|
||||
import com.ecep.contract.SpringApp;
|
||||
import com.ecep.contract.ds.company.CompanyFileUtils;
|
||||
import com.ecep.contract.ds.other.repository.HolidayTableRepository;
|
||||
import com.ecep.contract.ds.company.model.Company;
|
||||
import com.ecep.contract.model.CompanyBasicFile;
|
||||
import com.ecep.contract.ds.customer.model.CompanyCustomerFile;
|
||||
@@ -46,11 +45,11 @@ public abstract class CompanyBasicService {
|
||||
date = date.plusDays(-2);
|
||||
}
|
||||
|
||||
HolidayTableRepository holidayTableRepository = SpringApp.getBean(HolidayTableRepository.class);
|
||||
//TODO 跳过节假日
|
||||
HolidayService holidayTableRepository = SpringApp.getBean(HolidayService.class);
|
||||
// TODO 跳过节假日
|
||||
int tryDays = 15;
|
||||
while (tryDays-- > 0) {
|
||||
HolidayTable holidayTable = holidayTableRepository.findById(date).orElse(null);
|
||||
HolidayTable holidayTable = holidayTableRepository.getById(date);
|
||||
if (holidayTable == null) {
|
||||
// 没有节假日定义,检查是否是工作日
|
||||
DayOfWeek dayOfWeek = date.getDayOfWeek();
|
||||
@@ -78,7 +77,6 @@ public abstract class CompanyBasicService {
|
||||
@Autowired
|
||||
protected CompanyService companyService;
|
||||
|
||||
|
||||
protected boolean isEditableFile(String fileName) {
|
||||
return FileUtils.withExtensions(fileName,
|
||||
FileUtils.XLS, FileUtils.XLSX,
|
||||
@@ -93,7 +91,8 @@ public abstract class CompanyBasicService {
|
||||
|
||||
public abstract <T, F extends CompanyBasicFile<T>, ID> void deleteFile(F file);
|
||||
|
||||
protected <T, F extends CompanyBasicFile<T>, ID> boolean fetchDbFiles(List<F> dbFiles, Map<String, F> map, Consumer<String> status) {
|
||||
protected <T, F extends CompanyBasicFile<T>, ID> boolean fetchDbFiles(List<F> dbFiles, Map<String, F> map,
|
||||
Consumer<String> status) {
|
||||
boolean modified = false;
|
||||
List<File> editFiles = new ArrayList<>();
|
||||
// 排除掉数据库中重复的
|
||||
@@ -166,8 +165,8 @@ public abstract class CompanyBasicService {
|
||||
* @see CompanyCustomerFile
|
||||
* @see CustomerFileType
|
||||
*/
|
||||
protected abstract <T, F extends CompanyBasicFile<T>> boolean fillFileAsDefaultType(F dbFile, File file, Consumer<String> status);
|
||||
|
||||
protected abstract <T, F extends CompanyBasicFile<T>> boolean fillFileAsDefaultType(F dbFile, File file,
|
||||
Consumer<String> status);
|
||||
|
||||
protected void moveFileToCompany(Company company, List<File> needMoveToCompanyPath) {
|
||||
if (needMoveToCompanyPath.isEmpty()) {
|
||||
@@ -219,8 +218,7 @@ public abstract class CompanyBasicService {
|
||||
List<File> needMoveToCompanyPath,
|
||||
List<F> retrieveFiles,
|
||||
Map<String, F> map,
|
||||
Consumer<String> status
|
||||
) {
|
||||
Consumer<String> status) {
|
||||
if (!StringUtils.hasText(path)) {
|
||||
return;
|
||||
}
|
||||
@@ -281,7 +279,8 @@ public abstract class CompanyBasicService {
|
||||
* @param <T> 类型类
|
||||
* @param <F> 文件类
|
||||
*/
|
||||
protected abstract <T, F extends CompanyBasicFile<T>> F fillFileType(File file, List<File> fileList, Consumer<String> status);
|
||||
protected abstract <T, F extends CompanyBasicFile<T>> F fillFileType(File file, List<File> fileList,
|
||||
Consumer<String> status);
|
||||
|
||||
/**
|
||||
* @param customerFile 文件对象
|
||||
@@ -292,7 +291,8 @@ public abstract class CompanyBasicService {
|
||||
* @param <F> 文件类
|
||||
* @return true 有修改
|
||||
*/
|
||||
protected <T, F extends CompanyBasicFile<T>> boolean fillFile(F customerFile, File file, List<File> fileList, Consumer<String> status) {
|
||||
protected <T, F extends CompanyBasicFile<T>> boolean fillFile(F customerFile, File file, List<File> fileList,
|
||||
Consumer<String> status) {
|
||||
String fileName = file.getName();
|
||||
boolean modified = CompanyFileUtils.fillApplyDateAbsent(file, customerFile, F::getSignDate, F::setSignDate);
|
||||
// 评估表
|
||||
@@ -320,7 +320,8 @@ public abstract class CompanyBasicService {
|
||||
* @param <F> 文件类
|
||||
* @return true:文件对象有修改,否则返回false
|
||||
*/
|
||||
protected <T, F extends CompanyBasicFile<T>> boolean fillFileAsEvaluationFile(F customerFile, File file, List<File> fileList, Consumer<String> status) {
|
||||
protected <T, F extends CompanyBasicFile<T>> boolean fillFileAsEvaluationFile(F customerFile, File file,
|
||||
List<File> fileList, Consumer<String> status) {
|
||||
boolean modified = setFileTypeAsEvaluationForm(customerFile);
|
||||
String fileName = file.getName();
|
||||
|
||||
@@ -353,7 +354,8 @@ public abstract class CompanyBasicService {
|
||||
return modified;
|
||||
}
|
||||
|
||||
private <T, F extends CompanyBasicFile<T>> boolean useAsEditableFile(F customerFile, File file, List<File> fileList, Consumer<String> status) {
|
||||
private <T, F extends CompanyBasicFile<T>> boolean useAsEditableFile(F customerFile, File file, List<File> fileList,
|
||||
Consumer<String> status) {
|
||||
if (fileList == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -403,7 +405,8 @@ public abstract class CompanyBasicService {
|
||||
return false;
|
||||
}
|
||||
|
||||
private <T, F extends CompanyBasicFile<T>> boolean useAsArchiveFile(F customerFile, File file, List<File> fileList, Consumer<String> status) {
|
||||
private <T, F extends CompanyBasicFile<T>> boolean useAsArchiveFile(F customerFile, File file, List<File> fileList,
|
||||
Consumer<String> status) {
|
||||
if (fileList == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -449,7 +452,6 @@ public abstract class CompanyBasicService {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置文件类型为表单文件
|
||||
*
|
||||
@@ -468,5 +470,4 @@ public abstract class CompanyBasicService {
|
||||
*/
|
||||
protected abstract boolean isEvaluationFile(String fileName);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,254 @@
|
||||
package com.ecep.contract.ds.company.service;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.CacheConfig;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.ecep.contract.IEntityService;
|
||||
import com.ecep.contract.QueryService;
|
||||
import com.ecep.contract.ds.other.repository.HolidayTableRepository;
|
||||
import com.ecep.contract.model.HolidayTable;
|
||||
import com.ecep.contract.service.VoableService;
|
||||
import com.ecep.contract.vo.HolidayTableVo;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 节假日表服务类
|
||||
*
|
||||
* @author System
|
||||
*/
|
||||
@Service
|
||||
@CacheConfig(cacheNames = "HolidayTable")
|
||||
@Slf4j
|
||||
public class HolidayService implements IEntityService<HolidayTable>, QueryService<HolidayTableVo>, VoableService<HolidayTable, HolidayTableVo> {
|
||||
|
||||
@Autowired
|
||||
private HolidayTableRepository holidayTableRepository;
|
||||
|
||||
/**
|
||||
* 调整日期到工作日
|
||||
*
|
||||
* @param date 要调整的日期
|
||||
* @return 调整的日期
|
||||
*/
|
||||
public LocalDate adjustToWorkDay(LocalDate date) {
|
||||
if (date == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
while (date.getDayOfWeek() == DayOfWeek.SATURDAY || date.getDayOfWeek() == DayOfWeek.SUNDAY || isHoliday(date)) {
|
||||
date = date.plusDays(1);
|
||||
}
|
||||
return date;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查日期是否为节假日
|
||||
*
|
||||
* @param date 日期
|
||||
* @return 是否为节假日
|
||||
*/
|
||||
public boolean isHoliday(LocalDate date) {
|
||||
if (date == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HolidayTable holidayTable = holidayTableRepository.findById(date).orElse(null);
|
||||
return holidayTable != null && holidayTable.isHoliday();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HolidayTable getById(Integer id) {
|
||||
throw new UnsupportedOperationException("HolidayTable uses LocalDate as ID, please use getById(LocalDate id) method instead");
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据LocalDate类型的ID查询实体
|
||||
* @param id 实体ID
|
||||
* @return 实体对象
|
||||
*/
|
||||
public HolidayTable getById(LocalDate id) {
|
||||
if (id == null) {
|
||||
return null;
|
||||
}
|
||||
return holidayTableRepository.findById(id).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HolidayTable save(HolidayTable entity) {
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
return holidayTableRepository.save(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(HolidayTable entity) {
|
||||
if (entity != null && entity.getId() != null) {
|
||||
holidayTableRepository.deleteById(entity.getId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据LocalDate类型的ID删除实体
|
||||
* @param id 实体ID
|
||||
*/
|
||||
public void deleteById(LocalDate id) {
|
||||
if (id != null) {
|
||||
holidayTableRepository.deleteById(id);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有节假日
|
||||
* @param pageable 分页参数
|
||||
* @return 分页结果
|
||||
*/
|
||||
public Page<HolidayTable> findAll(Pageable pageable) {
|
||||
return holidayTableRepository.findAll(pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<HolidayTable> findAll(Specification<HolidayTable> spec, Pageable pageable) {
|
||||
// 由于HolidayTableRepository不支持Specification查询,返回所有节假日
|
||||
return findAll(pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Specification<HolidayTable> getSpecification(String searchText) {
|
||||
// 实现根据搜索文本构建规格化查询
|
||||
return (Root<HolidayTable> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) -> {
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
|
||||
if (searchText != null && !searchText.trim().isEmpty()) {
|
||||
try {
|
||||
// 尝试将搜索文本解析为日期
|
||||
LocalDate date = LocalDate.parse(searchText.trim());
|
||||
predicates.add(criteriaBuilder.equal(root.get("id"), date));
|
||||
} catch (DateTimeParseException e) {
|
||||
// 如果不是日期格式,尝试其他搜索方式
|
||||
// 由于HolidayTable只有id和holiday字段,这里无法进行其他字段的模糊搜索
|
||||
log.warn("Search text '{}' is not a valid date format", searchText);
|
||||
}
|
||||
}
|
||||
|
||||
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public HolidayTableVo findById(Integer id) {
|
||||
throw new UnsupportedOperationException("HolidayTable uses LocalDate as ID, please use findById(Object id) instead");
|
||||
}
|
||||
|
||||
@Cacheable(key = "#id", unless = "#result == null")
|
||||
public HolidayTableVo findById(LocalDate id) {
|
||||
HolidayTable entity = getById(id);
|
||||
return entity != null ? entity.toVo() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<HolidayTableVo> findAll(JsonNode paramsNode, Pageable pageable) {
|
||||
// 实现根据JSON参数过滤节假日
|
||||
Page<HolidayTable> entityPage;
|
||||
|
||||
if (paramsNode == null || paramsNode.isEmpty()) {
|
||||
// 如果没有参数,返回所有节假日
|
||||
entityPage = findAll(pageable);
|
||||
} else {
|
||||
// 处理参数过滤
|
||||
Boolean isHoliday = null;
|
||||
LocalDate startDate = null;
|
||||
LocalDate endDate = null;
|
||||
|
||||
if (paramsNode.has("isHoliday")) {
|
||||
isHoliday = paramsNode.get("isHoliday").asBoolean();
|
||||
}
|
||||
|
||||
if (paramsNode.has("startDate")) {
|
||||
try {
|
||||
startDate = LocalDate.parse(paramsNode.get("startDate").asText());
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to parse startDate: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (paramsNode.has("endDate")) {
|
||||
try {
|
||||
endDate = LocalDate.parse(paramsNode.get("endDate").asText());
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to parse endDate: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 根据参数组合选择合适的查询方法
|
||||
if (isHoliday != null && startDate != null && endDate != null) {
|
||||
// 组合条件:是否为节假日 + 日期范围
|
||||
entityPage = holidayTableRepository.findByHolidayAndIdBetween(isHoliday, startDate, endDate, pageable);
|
||||
} else if (isHoliday != null) {
|
||||
// 单个条件:是否为节假日
|
||||
entityPage = holidayTableRepository.findByHoliday(isHoliday, pageable);
|
||||
} else if (startDate != null && endDate != null) {
|
||||
// 单个条件:日期范围
|
||||
entityPage = holidayTableRepository.findByIdBetween(startDate, endDate, pageable);
|
||||
} else {
|
||||
// 不满足上述条件,返回所有节假日
|
||||
entityPage = findAll(pageable);
|
||||
}
|
||||
}
|
||||
|
||||
return entityPage.map(HolidayTable::toVo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count(JsonNode paramsNode) {
|
||||
// 简单实现,返回所有节假日数量
|
||||
return holidayTableRepository.count();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateByVo(HolidayTable model, HolidayTableVo vo) {
|
||||
if (model == null || vo == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新实体属性
|
||||
model.setHoliday(vo.isHoliday());
|
||||
save(model);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建节假日
|
||||
*
|
||||
* @param vo 节假日VO对象
|
||||
* @return 创建后的节假日VO对象
|
||||
*/
|
||||
public HolidayTableVo createByVo(HolidayTableVo vo) {
|
||||
if (vo == null || vo.getId() == null) {
|
||||
throw new IllegalArgumentException("HolidayTableVo or ID cannot be null");
|
||||
}
|
||||
|
||||
HolidayTable entity = new HolidayTable();
|
||||
entity.setId(vo.getId());
|
||||
entity.setHoliday(vo.isHoliday());
|
||||
|
||||
HolidayTable savedEntity = save(entity);
|
||||
return savedEntity.toVo();
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,47 @@
|
||||
package com.ecep.contract.ds.other.repository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import com.ecep.contract.model.HolidayTable;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
@Repository
|
||||
public interface HolidayTableRepository // curd
|
||||
extends CrudRepository<HolidayTable, LocalDate>, PagingAndSortingRepository<HolidayTable, LocalDate> {
|
||||
public interface HolidayTableRepository // curd
|
||||
extends CrudRepository<HolidayTable, LocalDate>, PagingAndSortingRepository<HolidayTable, LocalDate> {
|
||||
|
||||
/**
|
||||
* 根据是否为节假日过滤
|
||||
*
|
||||
* @param isHoliday 是否为节假日
|
||||
* @param pageable 分页参数
|
||||
* @return 分页结果
|
||||
*/
|
||||
Page<HolidayTable> findByHoliday(boolean isHoliday, Pageable pageable);
|
||||
|
||||
/**
|
||||
* 根据日期范围过滤
|
||||
*
|
||||
* @param startDate 开始日期
|
||||
* @param endDate 结束日期
|
||||
* @param pageable 分页参数
|
||||
* @return 分页结果
|
||||
*/
|
||||
Page<HolidayTable> findByIdBetween(LocalDate startDate, LocalDate endDate, Pageable pageable);
|
||||
|
||||
/**
|
||||
* 根据是否为节假日和日期范围组合过滤
|
||||
*
|
||||
* @param isHoliday 是否为节假日
|
||||
* @param startDate 开始日期
|
||||
* @param endDate 结束日期
|
||||
* @param pageable 分页参数
|
||||
* @return 分页结果
|
||||
*/
|
||||
Page<HolidayTable> findByHolidayAndIdBetween(boolean isHoliday, LocalDate startDate, LocalDate endDate,
|
||||
Pageable pageable);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user