feat(contract): 新增合同余额功能及重构文件管理

重构合同文件管理逻辑,增加错误处理和日志记录
新增ContractBalance实体、Repository和VO类
完善Voable接口文档和实现规范
更新项目架构文档和数据库设计
修复SmbFileService的连接问题
移动合同相关TabSkin类到contract包
添加合同文件重建任务的WebSocket支持
This commit is contained in:
2025-11-19 00:50:16 +08:00
parent 87290f15b0
commit 02afa189f8
49 changed files with 7577 additions and 441 deletions

View File

@@ -17,13 +17,19 @@ import com.fasterxml.jackson.databind.JsonNode;
/**
* 实体服务基类
* 提供基础的CRUD操作和查询方法
*
* @param <T> 实体类型
* @param <VO> VO类型
* @param <ID> 主键类型
*/
public abstract class EntityService<T extends Voable<VO>, VO, ID> {
/**
* 获取实体数据访问层接口
* 子类必须实现此方法,提供具体的实体数据访问层实例
*
* @return 实体数据访问层接口
*/
protected abstract MyRepository<T, ID> getRepository();
public T getById(ID id) {

View File

@@ -384,5 +384,11 @@ public class YongYouU8Repository {
return getJdbcTemplate().queryForList("select * from SaleBillVouchs where SBVID=?", sbvid);
}
/**
* 通过合同号查询相关余额记录
*/
public List<Map<String, Object>> findAllBalanceByContractCode(String code) {
return getJdbcTemplate().queryForList("select * from CM_Balance where cContractID=?", code);
}
}

View File

@@ -448,7 +448,7 @@ public class ContractCtx extends AbstractYongYouU8Ctx {
}
private VendorEntityVo updateVendorEntityDetailByCode(VendorEntityVo entity, String unitCode,
MessageHolder holder) {
MessageHolder holder) {
if (vendorEntityUpdateDelayDays > 0) {
LocalDateTime today = LocalDateTime.now();
if (entity.getFetchedTime() != null) {
@@ -672,7 +672,7 @@ public class ContractCtx extends AbstractYongYouU8Ctx {
* 客户数据要通过 CompanyCustomerEntity 表关联来从用友数据库中读取
*/
public boolean syncByCustomerEntity(CompanyCustomer companyCustomer, CompanyCustomerEntity entity,
MessageHolder holder) {
MessageHolder holder) {
String code = entity.getCode();
holder.debug("同步客户相关项 " + code + "," + entity.getName() + " 的合同");
if (!StringUtils.hasText(code)) {
@@ -1205,6 +1205,7 @@ public class ContractCtx extends AbstractYongYouU8Ctx {
public boolean syncContractFiles(ContractVo contract, MessageHolder holder) {
String contractPath = contract.getPath();
if (!StringUtils.hasText(contractPath)) {
holder.warn("合同没有指定目录");
return false;
}
SmbFileService smbFileService = getCachedBean(SmbFileService.class);
@@ -1216,6 +1217,7 @@ public class ContractCtx extends AbstractYongYouU8Ctx {
}
ContractFileService fileService = getContractFileService();
List<ContractFileVo> dbFiles = fileService.findAllByContract(contract);
holder.debug("合同下已有记录:" + dbFiles.size() + "");
List<ContractFile> retrieveFiles = new ArrayList<>();
boolean modfied = false;
@@ -1223,9 +1225,11 @@ public class ContractCtx extends AbstractYongYouU8Ctx {
// 排除掉数据库中重复的
for (ContractFileVo dbFile : dbFiles) {
String fileName = dbFile.getFileName();
holder.debug("记录 #" + dbFile.getId() + " " + fileName);
// 没有文件信息,无效记录,删除
if (!StringUtils.hasText(fileName)) {
fileService.delete(fileService.getById(dbFile.getId()));
holder.warn(" - 记录无效:删除");
modfied = true;
continue;
}
@@ -1234,6 +1238,7 @@ public class ContractCtx extends AbstractYongYouU8Ctx {
File file = new File(dir, fileName);
if (!file.exists()) {
fileService.delete(fileService.getById(dbFile.getId()));
holder.warn(" - 文件不存在:删除");
modfied = true;
continue;
}
@@ -1242,6 +1247,7 @@ public class ContractCtx extends AbstractYongYouU8Ctx {
ContractFileVo old = map.put(fileName, dbFile);
if (old != null) {
fileService.delete(fileService.getById(old.getId()));
holder.warn(" - 文件重复记录:删除 #" + old.getId());
modfied = true;
}
}
@@ -1249,7 +1255,9 @@ public class ContractCtx extends AbstractYongYouU8Ctx {
// 遍历合同目录下的文件,如果未创建,创建
try {
List<File> files = smbFileService.listFiles(dir);
holder.debug("目录下有文件:" + files.size() + "");
for (File file : files) {
holder.debug("文件:" + file.getName());
// 只处理文件
if (!smbFileService.isFile(file)) {
continue;
@@ -1265,6 +1273,7 @@ public class ContractCtx extends AbstractYongYouU8Ctx {
contractFile.setFileName(file.getName());
syncContractFile(contractFile, file, holder);
retrieveFiles.add(contractFile);
holder.info("找到新文件:" + fileName);
}
} catch (IOException e) {
holder.error("遍历合同目录下的文件失败:" + contractPath + ",错误:" + e.getMessage());

View File

@@ -2,11 +2,18 @@ package com.ecep.contract.ds.company.repository;
import java.util.List;
import org.springframework.stereotype.Repository;
import com.ecep.contract.CompanyFileType;
import com.ecep.contract.ds.MyRepository;
import com.ecep.contract.ds.company.model.Company;
import com.ecep.contract.ds.company.model.CompanyFile;
/**
* 公司文件数据访问层接口
* 提供公司文件相关的数据访问操作
*/
@Repository
public interface CompanyFileRepository extends MyRepository<CompanyFile, Integer> {
List<CompanyFile> findByCompany(Company company);

View File

@@ -0,0 +1,248 @@
package com.ecep.contract.ds.contract.model;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.UUID;
import com.ecep.contract.model.Employee;
import com.ecep.contract.model.IdentityEntity;
import com.ecep.contract.model.Voable;
import com.ecep.contract.vo.ContractBalanceVo;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* 合同余额实体类
*/
@Getter
@Setter
@Entity
@Table(name = "CONTRACT_BALANCE", schema = "supplier_ms")
@ToString
public class ContractBalance implements IdentityEntity, ContractBasedEntity, Voable<ContractBalanceVo> {
/**
* 主键
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ID", nullable = false)
private Integer id;
/**
* 余额ID
*/
@Column(name = "REF_ID")
private String refId;
/**
* GUID余额在系统中的唯一标识对应 BalanceGuid 字段
*/
@Column(name = "GUID", nullable = false)
private UUID guid;
/**
* 关联合同
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CONTRACT_ID")
@ToString.Exclude
private Contract contract;
/**
* 业务员, 对应 cFunctionaryID
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "BM_EMPLOYEE_ID")
@ToString.Exclude
private Employee employee;
/**
* 发票号码,对应 balanceDetails 字段
*/
@Column(name = "INVOICE_NUMBER")
private String invoiceNumber;
/**
* 创建人, 对应 cProducer 字段
*/
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "SETUP_PERSON_ID")
@ToString.Exclude
private Employee setupPerson;
/**
* 创建日期,对应 dtCreateTime 字段
*/
@Column(name = "SETUP_DATE_TIME")
private LocalDateTime setupDate;
/**
* 审核人,对应 cAuditer 字段
*/
@JoinColumn(name = "AUDITER_ID")
@ManyToOne(fetch = FetchType.LAZY)
@ToString.Exclude
private Employee auditer;
/**
* 审核日期,对应 dtAuditeDate 字段
*/
@Column(name = "AUDITE_DATE")
private LocalDate auditeDate;
/**
* 修改人
*/
@JoinColumn(name = "MODIFER_ID")
@ManyToOne(fetch = FetchType.LAZY)
@ToString.Exclude
private Employee modifer;
/**
* 修改时间
*/
@Column(name = "MODIFY_DATE_TIME")
private LocalDateTime modifyTime;
/**
* 管理员,对应 cAdmin 字段
*/
@JoinColumn(name = "ADMIN_ID")
@ManyToOne(fetch = FetchType.LAZY)
@ToString.Exclude
private Employee admin;
/**
* 管理员日期
*/
@Column(name = "ADMIN_DATE")
private LocalDate adminDate;
/**
* 凭证ID
*/
@Column(name = "PZ_ID")
private String pzId;
/**
* 凭证编号
*/
@Column(name = "PZ_NUM")
private String pzNum;
/**
* JSD类型
*/
@Column(name = "JSD_TYPE", nullable = false)
private String jsdType;
/**
* 生效时间
*/
@Column(name = "EFFECT_DATE_TIME")
private LocalDateTime effectTime;
/**
* 将当前 ContractBalance 实体对象转换为 ContractBalanceVo 视图对象
*
* <p>该方法实现了 Voable<T> 接口的 toVo() 方法负责将实体对象转换为轻量级的VO对象
* 用于前端数据展示和WebSocket通信。
*
* <p><strong>转换规则:</strong></p>
* <ul>
* <li>基本字段id, refId, guid 直接映射</li>
* <li>业务字段balanceTypeId, exchangeRate, balanceDetails 直接映射</li>
* <li>关联对象只映射ID避免加载整个关联对象</li>
* <li>日期处理LocalDateTime 转换为适当的日期类型</li>
* <li>空值防护所有关联对象访问前进行null检查</li>
* </ul>
*
* <p><strong>映射明细:</strong></p>
* <ul>
* <li>contract.getId() → contractId (带null检查)</li>
* <li>employee.getId() → functionaryId (转换为String)</li>
* <li>auditer.getId() → auditer (转换为String带null检查)</li>
* <li>admin.getId() → admin (转换为String带null检查)</li>
* <li>setupPerson.getId() → producer (转换为String带null检查)</li>
* <li>modifer.getId() → modifer (转换为String带null检查)</li>
* <li>contract.getCompany().getName() → superviseDept (级联null检查)</li>
* <li>setupDate → produceDate (LocalDateTime 转换为 LocalDate)</li>
* </ul>
*
* <p><strong>注意事项:</strong></p>
* <ul>
* <li>所有Employee关联对象的ID都转换为String类型</li>
* <li>setupDate为LocalDateTime类型转换为produceDate时取日期部分</li>
* <li>supervisorDept通过contract.getCompany()获取需级联null检查</li>
* <li>关联对象可能为null转换时需要防护</li>
* </ul>
*
* @return 转换后的 ContractBalanceVo 对象实例
* @throws IllegalStateException 如果转换过程中发生不可恢复的状态错误
* @see Voable#toVo()
* @see ContractBalanceVo
*/
@Override
public ContractBalanceVo toVo() {
ContractBalanceVo vo = new ContractBalanceVo();
// 基本字段直接映射
vo.setId(id);
vo.setRefId(refId);
vo.setGuid(guid);
vo.setExchangeRate(1.0);
vo.setInvoiceNumber(invoiceNumber);
// 关联对象只映射ID进行null检查
if (contract != null) {
vo.setContractId(contract.getId());
}
if (employee != null) {
vo.setEmployeeId(employee.getId());
}
// 审核人和审核日期
if (auditer != null) {
vo.setAuditerId(auditer.getId());
}
vo.setAuditeDate(auditeDate);
// 管理员信息
if (admin != null) {
vo.setAdminId(admin.getId());
}
vo.setAdminDate(adminDate);
// 创建人信息
if (setupPerson != null) {
vo.setSetupPersonId(setupPerson.getId());
}
vo.setSetupDate(setupDate != null ? setupDate.toLocalDate() : null);
// 修改人信息
if (modifer != null) {
vo.setModiferId(modifer.getId());
}
vo.setModifyTime(modifyTime);
// 凭证信息
vo.setPzId(pzId);
vo.setPzNum(pzNum);
// 时间相关字段
vo.setCreateTime(setupDate);
vo.setEffectTime(effectTime);
vo.setJsdType(jsdType); // 已在上面设置
return vo;
}
}

View File

@@ -0,0 +1,64 @@
package com.ecep.contract.ds.contract.repository;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.ecep.contract.ds.MyRepository;
import com.ecep.contract.ds.contract.model.ContractBalance;
/**
* 合同余额数据访问层
*/
@Repository
public interface ContractBalanceRepository extends MyRepository<ContractBalance, Integer> {
/**
* 根据合同ID查询余额列表
*
* @param contractId 合同ID
* @return 合同余额列表
*/
List<ContractBalance> findByContractId(Integer contractId);
/**
* 根据合同ID和关联ID查询余额
*
* @param contractId 合同ID
* @param refId 关联ID
* @return 合同余额
*/
ContractBalance findByContractIdAndRefId(Integer contractId, Integer refId);
/**
* 根据GUID查询余额
*
* @param guid GUID
* @return 合同余额
*/
ContractBalance findByGuid(String guid);
/**
* 根据合同ID分页查询余额
*
* @param contractId 合同ID
* @param pageable 分页参数
* @return 分页结果
*/
@Query("SELECT cb FROM ContractBalance cb WHERE cb.contract.id = :contractId")
Page<ContractBalance> findByContractId(@Param("contractId") Integer contractId, Pageable pageable);
/**
* 统计指定合同的余额记录数
*
* @param contractId 合同ID
* @return 余额记录数
*/
@Query("SELECT COUNT(cb) FROM ContractBalance cb WHERE cb.contract.id = :contractId")
long countByContractId(@Param("contractId") Integer contractId);
}

View File

@@ -2,20 +2,19 @@ package com.ecep.contract.ds.contract.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
import com.ecep.contract.ContractFileType;
import com.ecep.contract.ds.MyRepository;
import com.ecep.contract.ds.contract.model.Contract;
import com.ecep.contract.ds.contract.model.ContractFile;
@Repository
public interface ContractFileRepository extends JpaRepository<ContractFile, Integer>, JpaSpecificationExecutor<ContractFile> {
public interface ContractFileRepository extends MyRepository<ContractFile, Integer> {
List<ContractFile> findByContract(Contract contract);
List<ContractFile> findAllByContract(Contract contract);
List<ContractFile> findAllByContractId(Integer contractId);
List<ContractFile> findAllByContractAndType(Contract contract, ContractFileType type);

View File

@@ -1,15 +1,15 @@
package com.ecep.contract.ds.contract.repository;
import com.ecep.contract.ds.contract.model.ContractItem;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Repository;
import com.ecep.contract.ds.MyRepository;
import com.ecep.contract.ds.contract.model.ContractItem;
@Repository
public interface ContractItemRepository extends JpaRepository<ContractItem, Integer>, JpaSpecificationExecutor<ContractItem> {
public interface ContractItemRepository extends MyRepository<ContractItem, Integer> {
Optional<ContractItem> findByRowId(String rowId);
@@ -19,5 +19,4 @@ public interface ContractItemRepository extends JpaRepository<ContractItem, Inte
List<ContractItem> findByInventoryId(int inventoryId);
}

View File

@@ -3,27 +3,21 @@ package com.ecep.contract.ds.contract.repository;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
import com.ecep.contract.ds.MyRepository;
import com.ecep.contract.ds.contract.model.Contract;
import com.ecep.contract.ds.vendor.model.PurchaseOrder;
@Repository
public interface PurchaseOrderRepository extends
// JDBC interfaces
CrudRepository<PurchaseOrder, Integer>, PagingAndSortingRepository<PurchaseOrder, Integer>,
// JPA interfaces
JpaRepository<PurchaseOrder, Integer>, JpaSpecificationExecutor<PurchaseOrder> {
public interface PurchaseOrderRepository extends MyRepository<PurchaseOrder, Integer> {
Optional<PurchaseOrder> findByCode(String code);
Optional<PurchaseOrder> findByRefId(Integer refId);
List<PurchaseOrder> findAllByContract(Contract contract);
List<PurchaseOrder> findAllByContractId(Integer contractId);
List<PurchaseOrder> findByCodeStartsWith(String code);

View File

@@ -0,0 +1,193 @@
package com.ecep.contract.ds.contract.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import com.ecep.contract.IEntityService;
import com.ecep.contract.QueryService;
import com.ecep.contract.SpringApp;
import com.ecep.contract.ds.contract.model.ContractBalance;
import com.ecep.contract.ds.contract.repository.ContractBalanceRepository;
import com.ecep.contract.service.VoableService;
import com.ecep.contract.util.SpecificationUtils;
import com.ecep.contract.vo.ContractBalanceVo;
import com.fasterxml.jackson.databind.JsonNode;
@Lazy
@Service
@CacheConfig(cacheNames = "contract-balance")
public class ContractBalanceService implements IEntityService<ContractBalance>, QueryService<ContractBalanceVo>,
VoableService<ContractBalance, ContractBalanceVo> {
@Lazy
@Autowired
private ContractBalanceRepository repository;
@Override
public ContractBalance getById(Integer id) {
return repository.findById(id).orElse(null);
}
@Cacheable(key = "#p0")
@Override
public ContractBalanceVo findById(Integer id) {
ContractBalance entity = getById(id);
if (entity != null) {
return entity.toVo();
}
return null;
}
@Override
public Specification<ContractBalance> getSpecification(String searchText) {
if (!StringUtils.hasText(searchText)) {
return null;
}
return (root, query, builder) -> {
return builder.or(
builder.like(root.get("refId"), "%" + searchText + "%"),
builder.like(root.get("guid"), "%" + searchText + "%"));
};
}
@Override
public Page<ContractBalance> findAll(Specification<ContractBalance> spec, Pageable pageable) {
return repository.findAll(spec, pageable);
}
@Caching(evict = {
@CacheEvict(key = "#p0.id"),
@CacheEvict(key = "'contract-'+#p0.contract.id")
})
@Override
public ContractBalance save(ContractBalance contractBalance) {
return repository.save(contractBalance);
}
@Caching(evict = {
@CacheEvict(key = "#p0.id"),
@CacheEvict(key = "'contract-'+#p0.contract.id")
})
@Override
public void delete(ContractBalance contractBalance) {
repository.delete(contractBalance);
}
@Override
public Page<ContractBalanceVo> findAll(JsonNode paramsNode, Pageable pageable) {
Specification<ContractBalance> spec = null;
if (paramsNode.has("searchText")) {
spec = getSpecification(paramsNode.get("searchText").asText());
}
// 字段等值查询 - 只包含ContractBalanceVo中存在的字段
spec = SpecificationUtils.andFieldEqualParam(spec, paramsNode, "contract", "employee");
return findAll(spec, pageable).map(ContractBalance::toVo);
}
@Override
public void updateByVo(ContractBalance model, ContractBalanceVo vo) {
// 参数校验
if (model == null) {
throw new IllegalArgumentException("实体对象不能为空");
}
if (vo == null) {
throw new IllegalArgumentException("VO对象不能为空");
}
// 映射基本属性 - 只映射ContractBalanceVo中存在的字段
model.setRefId(vo.getRefId());
model.setGuid(vo.getGuid());
// 设置汇率默认值1.0
if (vo.getExchangeRate() != null) {
// ContractBalance实体可能没有exchangeRate字段这里使用invoiceNumber存储
model.setInvoiceNumber(vo.getInvoiceNumber());
}
// 处理关联对象 - Contract
if (vo.getContractId() == null) {
model.setContract(null);
} else {
ContractService contractService = SpringApp.getBean(ContractService.class);
if (model.getContract() == null || !model.getContract().getId().equals(vo.getContractId())) {
model.setContract(contractService.getById(vo.getContractId()));
}
}
com.ecep.contract.ds.other.service.EmployeeService employeeService = SpringApp
.getBean(com.ecep.contract.ds.other.service.EmployeeService.class);
// 处理关联对象 - Employee (业务员)
if (vo.getEmployeeId() == null) {
model.setEmployee(null);
} else {
if (model.getEmployee() == null
|| !model.getEmployee().getId().equals(Integer.valueOf(vo.getEmployeeId()))) {
model.setEmployee(employeeService.getById(Integer.valueOf(vo.getEmployeeId())));
}
}
// 处理关联对象 - Employee (审核人)
if (vo.getAuditerId() == null) {
model.setAuditer(null);
} else {
if (model.getAuditer() == null || !model.getAuditer().getId().equals(Integer.valueOf(vo.getAuditerId()))) {
model.setAuditer(employeeService.getById(Integer.valueOf(vo.getAuditerId())));
}
}
model.setAuditeDate(vo.getAuditeDate());
// 处理关联对象 - Employee (管理员)
if (vo.getAdminId() == null) {
model.setAdmin(null);
} else {
if (model.getAdmin() == null || !model.getAdmin().getId().equals(Integer.valueOf(vo.getAdminId()))) {
model.setAdmin(employeeService.getById(Integer.valueOf(vo.getAdminId())));
}
}
model.setAdminDate(vo.getAdminDate());
// 处理关联对象 - Employee (创建人)
if (vo.getSetupPersonId() == null) {
model.setSetupPerson(null);
} else {
if (model.getSetupPerson() == null
|| !model.getSetupPerson().getId().equals(Integer.valueOf(vo.getSetupPersonId()))) {
model.setSetupPerson(employeeService.getById(Integer.valueOf(vo.getSetupPersonId())));
}
}
// 设置创建日期
if (vo.getSetupDate() != null) {
model.setSetupDate(vo.getSetupDate().atStartOfDay());
}
// 处理关联对象 - Employee (修改人)
if (vo.getModiferId() == null) {
model.setModifer(null);
} else {
if (model.getModifer() == null || !model.getModifer().getId().equals(Integer.valueOf(vo.getModiferId()))) {
model.setModifer(employeeService.getById(Integer.valueOf(vo.getModiferId())));
}
}
model.setModifyTime(vo.getModifyTime());
// 凭证信息
model.setPzId(vo.getPzId());
model.setPzNum(vo.getPzNum());
// JSD类型和生效时间
model.setJsdType(vo.getJsdType());
model.setEffectTime(vo.getEffectTime());
}
}

View File

@@ -4,22 +4,22 @@ import com.ecep.contract.MessageHolder;
import com.ecep.contract.cloud.u8.ctx.ContractCtx;
import com.ecep.contract.ds.contract.service.ContractService;
import com.ecep.contract.ds.contract.model.Contract;
import com.ecep.contract.service.tasker.WebSocketServerTasker;
import com.ecep.contract.ui.Tasker;
import com.ecep.contract.vo.ContractVo;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Getter;
import lombok.Setter;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
/**
* 对合同的文件进行重置
* 继承Tasker<Object>并实现WebSocketServerTasker接口支持与客户端的实时通信
*/
public class ContractFilesRebuildTasker extends Tasker<Object> {
@Setter
private ContractService contractService;
@Setter
private Contract contract;
@Getter
@Slf4j
@Data
public class ContractFilesRebuildTasker extends Tasker<Object> implements WebSocketServerTasker {
private ContractVo contract;
private boolean repaired = false;
public ContractFilesRebuildTasker() {
@@ -27,20 +27,54 @@ public class ContractFilesRebuildTasker extends Tasker<Object> {
}
@Override
protected Object execute(MessageHolder holder) {
updateTitle("遍历合同的文件进行“重置”操作");
protected Object execute(MessageHolder holder) throws Exception {
log.info("开始执行合同文件重建任务: {}", contract != null ? contract.getCode() : "未知合同");
ContractCtx contractCtx = new ContractCtx();
if (contractCtx.syncContractFiles(contract.toVo(), holder)) {
repaired = true;
try {
// 检查合同信息
if (contract == null) {
throw new IllegalArgumentException("合同信息不能为空");
}
updateProgress(25, 100);
updateMessage("正在同步合同文件...");
// 执行文件重建逻辑
ContractCtx contractCtx = new ContractCtx();
boolean success = contractCtx.syncContractFiles(contract, holder);
updateProgress(75, 100);
if (success) {
repaired = true;
updateMessage("合同文件重建成功");
log.info("合同文件重建成功: {}", contract.getCode());
} else {
updateMessage("合同文件重建失败");
log.warn("合同文件重建失败: {}", contract.getCode());
}
updateProperty("repaired", repaired);
updateProgress(100, 100);
updateMessage("任务完成");
return success;
} catch (Exception e) {
log.error("合同文件重建任务执行失败: {}", contract != null ? contract.getCode() : "未知合同", e);
updateMessage("任务执行失败: " + e.getMessage());
throw e;
}
return null;
}
private ContractService getContractService() {
if (contractService == null) {
contractService = getBean(ContractService.class);
@Override
public void init(JsonNode argsNode) {
log.info("初始化合同文件重建任务,参数: {}", argsNode);
// 从JSON参数中提取合同信息
if (argsNode != null && argsNode.size() > 0) {
ContractService contractService = getCachedBean(ContractService.class);
int contractId = argsNode.get(0).asInt();
this.contract = contractService.findById(contractId);
}
return contractService;
}
}

View File

@@ -2,11 +2,18 @@ package com.ecep.contract.ds.project.repository;
import java.util.List;
import org.springframework.stereotype.Repository;
import com.ecep.contract.ProjectFileType;
import com.ecep.contract.ds.MyRepository;
import com.ecep.contract.ds.project.model.Project;
import com.ecep.contract.ds.project.model.ProjectFile;
/**
* 项目文件数据访问层接口
* 提供项目文件相关的数据访问操作
*/
@Repository
public interface ProjectFileRepository extends MyRepository<ProjectFile, Integer> {
List<ProjectFile> findByProject(Project project);

View File

@@ -2,16 +2,14 @@ package com.ecep.contract.ds.project.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
import com.ecep.contract.ds.MyRepository;
import com.ecep.contract.ds.project.model.Project;
import com.ecep.contract.ds.project.model.ProjectQuotation;
@Repository
public interface ProjectQuotationRepository
extends JpaRepository<ProjectQuotation, Integer>, JpaSpecificationExecutor<ProjectQuotation> {
public interface ProjectQuotationRepository extends MyRepository<ProjectQuotation, Integer> {
/**
* 根据项目查询

View File

@@ -2,17 +2,19 @@ package com.ecep.contract.ds.vendor.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
import com.ecep.contract.VendorFileType;
import com.ecep.contract.ds.MyRepository;
import com.ecep.contract.ds.vendor.model.Vendor;
import com.ecep.contract.ds.vendor.model.VendorFile;
/**
* 供应商文件数据访问层接口
* 提供供应商文件相关的数据访问操作
*/
@Repository
public interface VendorFileRepository
extends JpaRepository<VendorFile, Integer>, JpaSpecificationExecutor<VendorFile> {
public interface VendorFileRepository extends MyRepository<VendorFile, Integer> {
List<VendorFile> findAllByVendorId(int vendorId);

View File

@@ -399,6 +399,10 @@ public class SmbFileService implements DisposableBean {
try {
// 获取连接
connectionInfo = getConnectionInfo(hostname);
if (connectionInfo == null) {
log.error("Failed to get SMB connection for host: {}", hostname);
break;
}
// 从session池获取session
sessionInfo = connectionInfo.peekSession();
@@ -407,8 +411,13 @@ public class SmbFileService implements DisposableBean {
// 获取认证上下文
AuthenticationContext authContext = getAuthenticationContext(hostname);
// 创建新session并添加到池中
sessionInfo = connectionInfo.createSession(authContext);
log.debug("Created new SMB session for host: {}", hostname);
try {
sessionInfo = connectionInfo.createSession(authContext);
log.debug("Created new SMB session for host: {}", hostname);
} catch (SMBRuntimeException ex) {
log.error("Failed to create SMB session for host: {}, maxTrys:{}", hostname, maxTrys, ex);
continue;
}
} else {
log.debug("Reusing SMB session for host: {}", hostname);
}
@@ -421,22 +430,16 @@ public class SmbFileService implements DisposableBean {
log.debug("Returned SMB session to pool for host: {}", hostname);
} catch (SMBRuntimeException e) {
sessionInfo.close();
throw e;
try {
sessionInfo.close();
} catch (IOException ignored) {
}
log.error("Failed to execute SMB operation for host: {}, maxTrys:{}", hostname, maxTrys, e);
continue;
} finally {
}
break;
} catch (TransportException e) {
log.warn("TransportException occurred while trying to connect to host: {}. Retrying...", hostname);
// 延迟1秒
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
continue;
} catch (IOException e) {
// 如果操作失败且连接信息存在,检查连接状态
if (connectionInfo != null) {
@@ -501,7 +504,7 @@ public class SmbFileService implements DisposableBean {
* @throws IOException 如果检查失败
*/
public boolean exists(SmbPath smbPath) throws IOException {
return executeSmbOperation(smbPath, (share, path) -> {
Object result = executeSmbOperation(smbPath, (share, path) -> {
try {
FileAllInformation info = share.getFileInformation(path);
if (info.getStandardInformation().isDirectory()) {
@@ -516,6 +519,10 @@ public class SmbFileService implements DisposableBean {
throw e;
}
});
if (result != null) {
return (boolean) result;
}
return false;
}
/**