Browse Source

000000016

zhoulisky 6 months ago
parent
commit
6d1621544b
17 changed files with 399 additions and 346 deletions
  1. 4 0
      kd-service-api/kd-scientific-api/src/main/java/org/sky/scientific/pojo/entity/AttendanceEntity.java
  2. 2 2
      kd-service/kd-scientific/src/main/java/org/sky/scientific/controller/XmController.java
  3. 1 1
      kd-service/kd-scientific/src/main/java/org/sky/scientific/controller/XmJtbController.java
  4. 1 1
      kd-service/kd-scientific/src/main/java/org/sky/scientific/e/AssetStatusEnum.java
  5. 19 48
      kd-service/kd-scientific/src/main/java/org/sky/scientific/excel/AssetExcel.java
  6. 35 44
      kd-service/kd-scientific/src/main/java/org/sky/scientific/excel/TechnicianExcel.java
  7. 62 0
      kd-service/kd-scientific/src/main/java/org/sky/scientific/excel/listener/AssetImportListener.java
  8. 97 167
      kd-service/kd-scientific/src/main/java/org/sky/scientific/excel/listener/TechnicianImportListener.java
  9. 2 0
      kd-service/kd-scientific/src/main/java/org/sky/scientific/mapper/AttendanceMapper.java
  10. 9 1
      kd-service/kd-scientific/src/main/java/org/sky/scientific/mapper/AttendanceMapper.xml
  11. 4 0
      kd-service/kd-scientific/src/main/java/org/sky/scientific/mapper/TechnicianMapper.java
  12. 14 12
      kd-service/kd-scientific/src/main/java/org/sky/scientific/mapper/XmMapper.xml
  13. 52 39
      kd-service/kd-scientific/src/main/java/org/sky/scientific/service/impl/AssetServiceImpl.java
  14. 59 15
      kd-service/kd-scientific/src/main/java/org/sky/scientific/service/impl/HoursSyncServiceImpl.java
  15. 13 13
      kd-service/kd-scientific/src/main/java/org/sky/scientific/service/impl/TechnicianServiceImpl.java
  16. 12 3
      kd-service/kd-scientific/src/main/java/org/sky/scientific/service/impl/XmServiceImpl.java
  17. 13 0
      kd-service/kd-scientific/src/main/java/org/sky/scientific/utils/ValidationUtils.java

+ 4 - 0
kd-service-api/kd-scientific-api/src/main/java/org/sky/scientific/pojo/entity/AttendanceEntity.java

@@ -1,6 +1,7 @@
 
 package org.sky.scientific.pojo.entity;
 
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableName;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.Max;
@@ -32,6 +33,9 @@ public class AttendanceEntity extends TenantEntity {
 	@NotBlank(message = "unicode不能为空")
 	private String unicode;
 
+	@TableField(exist = false)
+	private String userName;
+
 	@Schema(description="考勤时间,yyyyMMdd")
 	@NotNull(message = "请选择考勤时间")
 	private String workDate;

+ 2 - 2
kd-service/kd-scientific/src/main/java/org/sky/scientific/controller/XmController.java

@@ -112,7 +112,7 @@ public class XmController extends KdController {
 	@PostMapping("/save")
 	@ApiOperationSupport(order = 4)
 	@Operation(summary = "项目立项", description = "传入xm,暂存时status=0,确认立项时status=1")
-	public R<JSONObject> save(@Valid @RequestBody XmDTO xm) {
+	public R<JSONObject> save(@RequestBody XmDTO xm) {
 		Long xmId = service.approval(xm);
 		JSONObject result = new JSONObject();
 		result.put("xmId", xmId);
@@ -122,7 +122,7 @@ public class XmController extends KdController {
 	@PostMapping("/update")
 	@ApiOperationSupport(order = 5)
 	@Operation(summary = "项目立项-修改", description = "传入xm")
-	public R<Boolean> update(@Valid @RequestBody XmUpdateDto xm) {
+	public R<Boolean> update(@RequestBody XmUpdateDto xm) {
 		return R.status(service.updateXm(xm));
 	}
 

+ 1 - 1
kd-service/kd-scientific/src/main/java/org/sky/scientific/controller/XmJtbController.java

@@ -39,7 +39,7 @@ public class XmJtbController extends KdController {
 	@PostMapping("/submit")
 	@ApiOperationSupport(order = 6)
 	@Operation(summary = "暂存/确定结题/取消结题", description  = "status,0:暂存,1:确定结题.2:取消结题")
-	public R submit(@Valid @RequestBody XmJtbEntity xmJtb) {
+	public R submit(@RequestBody XmJtbEntity xmJtb) {
 		return R.status(service.submit(xmJtb));
 	}
 

+ 1 - 1
kd-service/kd-scientific/src/main/java/org/sky/scientific/e/AssetStatusEnum.java

@@ -14,7 +14,7 @@ public enum AssetStatusEnum {
 	停用("停用", 1),
 	已足额折旧("已足额折旧", 2),
 	改造("改造", 3),
-	资产处置("处置", 4);
+	资产处置("资产处置", 4);
 
 	final String name;
 	final int code;

+ 19 - 48
kd-service/kd-scientific/src/main/java/org/sky/scientific/excel/AssetExcel.java

@@ -3,11 +3,10 @@ package org.sky.scientific.excel;
 
 
 import com.alibaba.excel.annotation.ExcelProperty;
-import com.alibaba.excel.annotation.format.DateTimeFormat;
-import com.alibaba.excel.annotation.write.style.ColumnWidth;
-import com.alibaba.excel.annotation.write.style.ContentRowHeight;
-import com.alibaba.excel.annotation.write.style.HeadRowHeight;
 import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 import org.sky.core.tool.utils.DateUtil;
 
@@ -24,65 +23,37 @@ import java.util.Date;
  * @since 2025-06-29
  */
 @Data
-@ColumnWidth(25)
-@HeadRowHeight(20)
-@ContentRowHeight(18)
 public class AssetExcel implements Serializable {
 
 	@Serial
 	private static final long serialVersionUID = 1L;
-
-	@ColumnWidth(20)
-	@ExcelProperty(index = 1, value = "资产编码")
+	@Schema(description = "序号")
+	private String xh;
+	@NotBlank(message = "资产编码不能为空")
 	private String zcbm;
-
-	@ColumnWidth(20)
-	@ExcelProperty(index = 2, value = "资产名称")
+	@NotBlank(message = "资产名称不能为空")
 	private String zcmc;
-
-	@ColumnWidth(20)
-	@ExcelProperty("所属年月")
+ 	@ExcelProperty("所属年月")
 	private String yearAndMonth;
-
-	@ColumnWidth(20)
-	@ExcelProperty(index = 4, value = "资产类别")
+	@NotBlank(message = "资产类别不能为空")
 	private String zclb;
-
-	@ColumnWidth(20)
-	@ExcelProperty(index = 3, value = "功率")
+ 	@Schema(description = "功率")
 	private String gl;
-
-	@ColumnWidth(20)
-	@ExcelProperty(index = 5, value = "用途")
+	@NotBlank(message = "用途不能为空")
 	private String yt;
-
-	@ColumnWidth(20)
-	@ExcelProperty(index = 6, value = "开始日期")
-	@DateTimeFormat(DateUtil.PATTERN_DATE)
-	@JsonFormat(pattern = DateUtil.PATTERN_DATE)
+ 	@JsonFormat(pattern = DateUtil.PATTERN_DATE)
+	@NotNull(message = "开始日期不能为空")
 	private Date kssj;
-
-	@ColumnWidth(20)
-	@ExcelProperty(index = 7, value = "使用寿命/月")
+	@NotNull(message = "使用寿命不能为空")
 	private Integer sysm;
-
-	@ColumnWidth(20)
-	@ExcelProperty(index = 8, value = "已使用月数")
+	@Schema(description = "已使用月数")
 	private Integer ysysm;
-
-	@ColumnWidth(20)
-	@ExcelProperty(index = 9, value = "资产原值")
+	@NotNull(message = "资产原值不能为空")
 	private BigDecimal zcyz;
-
-	@ColumnWidth(20)
-	@ExcelProperty(index = 10, value = "净残值")
+	@NotNull(message = "净残值不能为空")
 	private Double jcz;
-
-	@ColumnWidth(20)
-	@ExcelProperty(index = 11, value = "月折旧额/月摊销额")
+	@NotNull(message = "月折旧额/月摊销额不能为空")
 	private BigDecimal yzje;
-
-	@ExcelProperty(index = 12, value = "状态")
+	@Schema(description = "状态")
 	private String statusName;
-
 }

+ 35 - 44
kd-service/kd-scientific/src/main/java/org/sky/scientific/excel/TechnicianExcel.java

@@ -2,10 +2,10 @@
 package org.sky.scientific.excel;
 
 
-import com.alibaba.excel.annotation.ExcelIgnore;
 import com.alibaba.excel.annotation.ExcelProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
 import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 import org.hibernate.validator.constraints.Length;
 
@@ -25,91 +25,82 @@ public class TechnicianExcel implements Serializable {
 	@Serial
 	private static final long serialVersionUID = 1L;
 
-	@ExcelProperty(index = 0, value = "序号")
+	@ExcelProperty(index = 0 )
 	private String xh;
 
-	@ExcelProperty(index = 1, value = "姓名")
-	@NotBlank(message = "姓名不能为空")
+ 	@NotBlank(message = "请填写姓名")
 	@Length(max = 20, message = "姓名长度不能超过20个字符")
 	private String name;
 
 	@Schema(description = "工号")
-	@ExcelProperty(index = 2)
-	private String number;
+ 	private String number;
 
 	@Schema(description = "身份证号")
-	@ExcelProperty(index = 3)
-	private String idCard;
+ 	private String idCard;
 
-	@ExcelIgnore
-	private Integer gender;
+	@NotNull(message = "请选择性别")
+ 	private Integer gender;
 
 	@Schema(description = "性别")
-	@ExcelProperty(index = 4)
-	private String genderName;
+ 	private String genderName;
 
 	@Schema(description = "出生日期")
-	@ExcelProperty(index = 5)
-	private String birthday;
+	@NotBlank(message = "请选择出生日期")
+ 	private String birthday;
 
 	@Schema(description = "民族")
-	@ExcelProperty(index = 6)
-	private String ethnic;
+	@NotBlank(message = "请填写民族")
+ 	private String ethnic;
 
 	@Schema(description = "所属二级单位/部门")
-	@ExcelProperty(index = 7)
-	private String department;
+ 	private String department;
 
 	@Schema(description = "岗位职务")
-	@ExcelProperty(index = 8)
-	private String post;
+	@NotBlank(message = "岗位职务不能为空")
+ 	private String post;
 
 	@Schema(description = "岗位所在地")
-	@ExcelProperty(index = 9)
-	private String postLocation;
+	@NotBlank(message = "岗位所在地不能为空")
+ 	private String postLocation;
 
 	@Schema(description = "用工性质")
-	@ExcelProperty(index = 10)
-	private String nature;
+	@NotBlank(message = "请选择用工性质")
+ 	private String nature;
 
 	@Schema(description = "入职时间")
-	@ExcelProperty(index = 11)
-	private String hireDate;
+	@NotBlank(message = "请填写入职时间")
+ 	private String hireDate;
 
 	@Schema(description = "最高学历")
-	@ExcelProperty(index = 12)
-	private String education;
+	@NotBlank(message = "请选择最高学历")
+ 	private String education;
 
 	@Schema(description = "最高学位")
-	@ExcelProperty(index = 13)
-	private String degree;
+ 	private String degree;
 
 	@Schema(description = "最高学历学校")
-	@ExcelProperty(index = 14)
-	private String schoolName;
+	@NotBlank(message = "请填写最高学历学校")
+ 	private String schoolName;
 
 	@Schema(description = "最高学历专业名称")
-	@ExcelProperty(index = 15)
-	private String major;
+	@NotBlank(message = "请填写最高学历专业名称")
+ 	private String major;
 
 	@Schema(description = "职称")
-	@ExcelProperty(index = 16)
-	private String rank;
+  	private String rank;
 
 	@Schema(description = "执业资格")
-	@ExcelProperty(index = 17)
-	private String practicingRequirement;
+ 	private String practicingRequirement;
 
 	@Schema(description = "情况说明")
-	@ExcelProperty(index = 18)
-	private String remark;
+ 	private String remark;
 
 	@Schema(description = "参研情况")
-	@ExcelProperty(index = 19)
-	private String situation;
+	@NotBlank(message = "请选择参研情况")
+ 	private String situation;
 
 	@Schema(description = "人员类型")
-	@ExcelProperty(index = 20)
-	private String personnelType;
+	@NotBlank(message = "请选择人员类型")
+ 	private String personnelType;
 
 }

+ 62 - 0
kd-service/kd-scientific/src/main/java/org/sky/scientific/excel/listener/AssetImportListener.java

@@ -0,0 +1,62 @@
+package org.sky.scientific.excel.listener;
+
+import com.alibaba.excel.context.AnalysisContext;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.extern.slf4j.Slf4j;
+import org.sky.core.tool.utils.DateUtil;
+import org.sky.scientific.excel.AssetExcel;
+import org.sky.scientific.utils.ImportResult;
+import org.sky.scientific.utils.ValidationUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@EqualsAndHashCode(callSuper = true)
+@Data
+@Slf4j
+public class AssetImportListener extends BaseReadListener {
+
+	@Override
+	public void invoke(Map<Integer, String> map, AnalysisContext context) {
+		int rowIndex = context.readRowHolder().getRowIndex() + 1;
+
+		ImportResult<Object> result = convertAndValidate(map, rowIndex);
+		resultList.add(result);
+		if (result.isSuccess()) {
+			dataList.add(result.getData());
+			incrementSuccessCount();
+		} else {
+			incrementErrorCount();
+		}
+	}
+
+	private ImportResult<Object> convertAndValidate(Map<Integer, String> data, int rowIndex) {
+
+		List<String> errors = new ArrayList<>();
+
+		AssetExcel excel = new AssetExcel();
+		excel.setXh(data.get(0));
+		excel.setZcbm(data.get(1));
+		excel.setZcmc(data.get(2));
+		excel.setGl(data.get(3));
+		excel.setZclb(data.get(4));
+		excel.setYt(data.get(5));
+		excel.setKssj(ValidationUtils.safeParseDate(data.get(6), DateUtil.PATTERN_DATE));
+		if (excel.getKssj() == null) {
+			errors.add("申请日为空或格式错误,yyyy-MM-dd");
+		}
+ 		excel.setSysm(ValidationUtils.safeParseInteger(data.get(7)));
+ 		excel.setZcyz(ValidationUtils.safeParseBigDecimal(data.get(9)));
+		excel.setJcz(ValidationUtils.safeParseDoubleWithPercentage(data.get(10), "净残值"));
+		excel.setYzje(ValidationUtils.safeParseBigDecimal(data.get(11)));
+		excel.setStatusName(data.get(12));
+
+		errors.addAll(ValidationUtils.validate(excel));
+		if (!errors.isEmpty()) {
+			return ImportResult.error(rowIndex, errors, data.toString());
+		}
+		return ImportResult.success(rowIndex, excel);
+	}
+}

+ 97 - 167
kd-service/kd-scientific/src/main/java/org/sky/scientific/excel/listener/TechnicianImportListener.java

@@ -1,184 +1,114 @@
 package org.sky.scientific.excel.listener;
 
 import com.alibaba.excel.context.AnalysisContext;
-import com.alibaba.excel.read.listener.ReadListener;
 import lombok.extern.slf4j.Slf4j;
+import org.sky.core.tool.utils.DateUtil;
+import org.sky.core.tool.utils.Func;
 import org.sky.scientific.excel.TechnicianExcel;
+import org.sky.scientific.utils.ImportResult;
+import org.sky.scientific.utils.ValidationUtils;
+import org.sky.system.cache.DictCache;
+import org.sky.system.pojo.entity.Dict;
+import org.sky.system.pojo.enums.DictEnum;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
 
 @Slf4j
-public class TechnicianImportListener implements ReadListener<Map<Integer, String>> {
-
-//	private static final int BATCH_COUNT = 100;
-//	private List<TechnicianEntity> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
-//	private List<ImportError> errorList = new ArrayList<>();
-//	private final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
-//
-//	List<String> identifierList = null;
-//
-//	private final ITechnicianService technicianService;
-//	private final TechnicianMapper technicianMapper;
-//	private final DepartmentMapper departmentMapper;
-//	private final String yearAndMonth;
-
-//	public TechnicianImportListener(ITechnicianService technicianService, TechnicianMapper technicianMapper, String yearAndMonth, DepartmentMapper departmentMapper) {
-//		this.technicianService = technicianService;
-//		this.technicianMapper = technicianMapper;
-//		this.yearAndMonth = yearAndMonth;
-//		this.departmentMapper = departmentMapper;
-//
-//		List<TechnicianEntity> dbTechnicianList = technicianMapper.selectList(Wrappers.<TechnicianEntity>lambdaQuery().eq(TechnicianEntity::getYearAndMonth, yearAndMonth));
-//		identifierList = new ArrayList<>(dbTechnicianList.stream().map(tmp -> tmp.getNumber() + ":" + tmp.getIdCard()).toList());
-//	}
+public class TechnicianImportListener extends BaseReadListener {
 
 	@Override
-	public void invoke(Map<Integer, String> data, AnalysisContext context) {
-		log.info("解析到一条数据:{}", data);
-		// 获取当前行号
-//		Integer rowIndex = context.readRowHolder().getRowIndex() + 1;
-//		// 数据验证
-//		Set<ConstraintViolation<TechnicianExcel>> violations = validator.validate(data);
-//		if (!violations.isEmpty()) {
-//			// 收集错误信息
-//			for (ConstraintViolation<TechnicianExcel> violation : violations) {
-//				errorList.add(new ImportError(rowIndex, violation.getMessage()));
-//			}
-//			return;
-//		}
-//
-//		validateBusiness(data, rowIndex);
-//
-////		cachedDataList.add(data);
-//
-//		// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
-//		if (cachedDataList.size() >= BATCH_COUNT) {
-//			technicianService.saveBatch(cachedDataList);
-//			cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
-//		}
-	}
+	public void invoke(Map<Integer, String> map, AnalysisContext context) {
+		int rowIndex = context.readRowHolder().getRowIndex() + 1;
 
-	private void validateBusiness(TechnicianExcel excel, Integer rowIndex) {
-		// 示例:验证部门是否存在
-//		if (!importService.isDepartmentValid(data.getDepartment())) {
-//			errorList.add(new ImportError(rowIndex, "部门不存在: " + data.getDepartment()));
-//		}
-//
-//		// 示例:验证工资范围
-//		if (data.getSalary().compareTo(new BigDecimal("1000000")) > 0) {
-//			errorList.add(new ImportError(rowIndex, "工资不能超过1000000"));
-//		}
-
-//		TechnicianEntity technician = Objects.requireNonNull(BeanUtil.copyProperties(excel, TechnicianDTO.class));
-//
-//		String identifier = technician.getNumber() + ":" + technician.getIdCard();
-//		if (identifierList.contains(identifier)) {
-//			throw new ServiceException("数据已存在,导入失败!");
-//		}
-//		identifierList.add(identifier);
-//		technician.setNumber(Func.isBlank(technician.getNumber()) ? null : technician.getNumber().trim());
-//		technician.setIdCard(Func.isBlank(technician.getIdCard()) ? null : technician.getIdCard().trim());
-//		//转换 性别字典
-//		technician.setGender(Integer.valueOf(DictCache.getKey(DictEnum.SEX, excel.getGenderName())));
-//		technician.setYearAndMonth(yearAndMonth);
-//
-//		if (Func.isNotBlank(excel.getDepartment())) {
-//			List<String> tmpList = Arrays.asList(excel.getDepartment().split("/"));
-//			String department = null;
-//			if (tmpList.size() == 1 || tmpList.size() == 2) {
-//				department = tmpList.get(tmpList.size() - 1);
-//			} else {
-//				throw new ServiceException("所属二级单位/部门最多只能有两级,用'/'分隔");
-//			}
-//			LambdaQueryWrapper<DepartmentEntity> eq = Wrappers.<DepartmentEntity>lambdaQuery()
-//				.eq(DepartmentEntity::getName, department)
-//				.eq(DepartmentEntity::getYearAndMonth, StringUtil.sub(yearAndMonth, 0, 4));
-//			DepartmentEntity dbDepartment = departmentMapper.selectOne(eq);
-//			if (Func.isNull(dbDepartment)) {
-//				throw new ServiceException("序号为[" + excel.getXh() + "]的部门/科室/项目部[" + excel.getDepartment() + "]不存在");
-//			}
-//			technician.setDepartmentId(dbDepartment.getId());
-//		}
-//		String unicode = technicianMapper.getUnicodeByNumberAndIdentityCard(technician.getNumber(), technician.getIdCard());
-//		if (unicode == null) {
-//			unicode = technicianService.generateUnicode();
-//		}
-//		technician.setUnicode(unicode);
-//
-//		cachedDataList.add(technician);
+		ImportResult<Object> result = convertAndValidate(map, rowIndex);
+		resultList.add(result);
+		if (result.isSuccess()) {
+			dataList.add(result.getData());
+			incrementSuccessCount();
+		} else {
+			incrementErrorCount();
+		}
 	}
 
-//	 public List<ImportError> getErrorList() {
-//		 return errorList;
-//	 }
-//
-//	 /**
-//	 * 是否有错误
-//	 */
-//	public boolean hasErrors() {
-//		return !errorList.isEmpty();
-//	}
-
-
-	//	private void saveData() {
-//
-//		List<TechnicianEntity> dbTechnicianList = technicianMapper.selectList(Wrappers.<TechnicianEntity>lambdaQuery().eq(TechnicianEntity::getYearAndMonth, yearAndMonth));
-//		List<String> identifierList = new ArrayList<>(dbTechnicianList.stream().map(tmp -> tmp.getNumber() + ":" + tmp.getIdCard()).toList());
-//
-//		List<TechnicianEntity> insertList = new ArrayList<>();
-//
-//		for (TechnicianExcel excel : cachedDataList) {
-//			TechnicianEntity technician = Objects.requireNonNull(BeanUtil.copyProperties(excel, TechnicianDTO.class));
-//
-//			String identifier = technician.getNumber() + ":" + technician.getIdCard();
-//			if(identifierList.contains(identifier)){
-//				throw new ServiceException("数据已存在,导入失败!");
-//			}
-//			identifierList.add(identifier);
-//			technician.setNumber(Func.isBlank(technician.getNumber()) ? null : technician.getNumber().trim());
-//			technician.setIdCard(Func.isBlank(technician.getIdCard()) ? null : technician.getIdCard().trim());
-//			//转换 性别字典
-// 			technician.setGender(Integer.valueOf(DictCache.getKey(DictEnum.SEX, excel.getGenderName())));
-//			technician.setYearAndMonth(yearAndMonth);
-//
-//			if (Func.isNotBlank(excel.getDepartment())) {
-//				List<String> tmpList = Arrays.asList(excel.getDepartment().split("/"));
-//				String department = null;
-//				if (tmpList.size() == 1 || tmpList.size() == 2) {
-//					department = tmpList.get(tmpList.size() - 1);
-//				} else {
-//					throw new ServiceException("所属二级单位/部门最多只能有两级,用'/'分隔");
-//				}
-//				LambdaQueryWrapper<DepartmentEntity> eq = Wrappers.<DepartmentEntity>lambdaQuery()
-//					.eq(DepartmentEntity::getName, department)
-//					.eq(DepartmentEntity::getYearAndMonth, StringUtil.sub(yearAndMonth, 0, 4));
-//				DepartmentEntity dbDepartment = departmentMapper.selectOne(eq);
-//				if (Func.isNull(dbDepartment)) {
-//					throw new ServiceException("序号为[" + excel.getXh() + "]的部门/科室/项目部[" + excel.getDepartment() + "]不存在");
-//				}
-//				technician.setDepartmentId(dbDepartment.getId());
-//			}
-//			String unicode = technicianMapper.getUnicodeByNumberAndIdentityCard(technician.getNumber(), technician.getIdCard());
-//			if (unicode == null) {
-//				unicode = technicianService.generateUnicode();
-//			}
-//			technician.setUnicode(unicode);
-//
-//			insertList.add(technician);
-//			if (insertList.size() % 20 == 0) {
-//				technicianService.saveBatch(insertList);
-//				insertList.clear();
-//			}
-//		}
-//		if (Func.isNotEmpty(insertList)) {
-//			this.saveBatch(insertList);
-//		}
-//	}
-	@Override
-	public void doAfterAllAnalysed(AnalysisContext analysisContext) {
-//		if (Func.isNotEmpty(cachedDataList)) {
-//			technicianService.saveBatch(cachedDataList);
-//		}
+	private ImportResult<Object> convertAndValidate(Map<Integer, String> data, int rowIndex) {
+
+		List<String> errors = new ArrayList<>();
+
+		TechnicianExcel excel = new TechnicianExcel();
+		excel.setXh(data.get(0));
+		excel.setName(data.get(1));
+		excel.setNumber(data.get(2));
+		excel.setIdCard(data.get(3));
+		String sexName = data.get(4);
+		List<String> sexList = DictCache.getList(DictEnum.SEX.getName()).stream().map(Dict::getDictValue).toList();
+		if(Func.isNotEmpty(sexList) && !sexList.contains(sexName)){
+			errors.add("性别错误,请重新选择");
+		}
+		excel.setGender(Integer.valueOf(DictCache.getKey(DictEnum.SEX, sexName)));
+
+		if (!ValidationUtils.isValidDateFormat(data.get(5), DateUtil.PATTERN_DATE)) {
+			errors.add("出生日期为空或格式错误,yyyy-MM-dd");
+		}
+		excel.setBirthday(data.get(5));
+		excel.setEthnic(data.get(6));
+		excel.setDepartment(data.get(7));
+		excel.setPost(data.get(8));
+		excel.setPostLocation(data.get(9));
+		String nature = data.get(10);
+		List<String> natureList = DictCache.getList("yonggongxingzhi").stream().map(Dict::getDictValue).toList();
+		if(Func.isNotEmpty(natureList) && !natureList.contains(nature)){
+			errors.add("用工性质错误,请重新选择");
+		}
+		excel.setNature(nature);
+		if (!ValidationUtils.isValidDateFormat(data.get(11), DateUtil.PATTERN_DATE)) {
+			errors.add("入职时间为空或格式错误,yyyy-MM-dd");
+		}
+		excel.setHireDate(data.get(11));
+
+		String education = data.get(12);
+		List<String> educationList = DictCache.getList("xueli").stream().map(Dict::getDictValue).toList();
+		if(Func.isNotEmpty(educationList) && !educationList.contains(education)){
+			errors.add("最高学历错误,请重新选择");
+		}
+		excel.setEducation(education);
+
+		excel.setDegree(data.get(13));
+		excel.setSchoolName(data.get(14));
+		excel.setMajor(data.get(15));
+
+		String rank = data.get(16);
+		if(Func.isNotBlank(rank)){
+			List<String> rankList = DictCache.getList("zhicheng").stream().map(Dict::getDictValue).toList();
+			if(Func.isNotEmpty(rankList) && !rankList.contains(rank.trim())){
+				errors.add("职称错误,请重新选择");
+			}
+			excel.setRank(rank);
+		}
+
+		excel.setPracticingRequirement(data.get(17));
+		excel.setRemark(data.get(18));
+
+		String situation = data.get(19);
+		List<String> situationList = DictCache.getList("canyanqingkuang").stream().map(Dict::getDictValue).toList();
+		if(Func.isNotEmpty(situationList) && !situationList.contains(situation)){
+			errors.add("参研情况错误,请重新选择");
+		}
+ 		excel.setSituation(situation);
+
+		String personnelType = data.get(20);
+		List<String> personnelTypeList = DictCache.getList("renyuanleixing").stream().map(Dict::getDictValue).toList();
+		if(Func.isNotEmpty(personnelTypeList) && !personnelTypeList.contains(personnelType)){
+			errors.add("人员类型错误,请重新选择");
+		}
+		excel.setPersonnelType(personnelType);
+
+		errors.addAll(ValidationUtils.validate(excel));
+		if (!errors.isEmpty()) {
+			return ImportResult.error(rowIndex, errors, data.toString());
+		}
+		return ImportResult.success(rowIndex, excel);
 	}
 }

+ 2 - 0
kd-service/kd-scientific/src/main/java/org/sky/scientific/mapper/AttendanceMapper.java

@@ -36,4 +36,6 @@ public interface AttendanceMapper extends BaseMapper<AttendanceEntity> {
 	int deleteByUnicodeAndMonthly(@Param("unicode") String unicode, @Param("yearAndMonth") String yearAndMonth);
 
 	Long countByUnicodeAndMonth(@Param("unicode") String unicode, @Param("yearAndMonth") String yearAndMonth);
+
+	AttendanceEntity selectInfoByUnicodeAndDate(AttendanceEntity entity);
 }

+ 9 - 1
kd-service/kd-scientific/src/main/java/org/sky/scientific/mapper/AttendanceMapper.xml

@@ -6,7 +6,8 @@
         <result column="id" property="id"/>
         <result column="tenant_id" property="tenantId"/>
         <result column="unicode" property="unicode"/>
-        <result column="work_date" property="workDate"/>
+        <result column="userName" property="userName"/>
+         <result column="work_date" property="workDate"/>
         <result column="work_hours" property="workHours"/>
 
         <result column="create_user" property="createUser"/>
@@ -101,4 +102,11 @@
         from kd_attendance_records
         where unicode = #{unicode} and left(work_date,6) = #{yearAndMonth}
     </delete>
+
+    <select id="selectInfoByUnicodeAndDate" resultMap="ResultMap" parameterType="org.sky.scientific.pojo.entity.AttendanceEntity">
+        SELECT xt.unicode, t.name as userName ,xt.work_date, xt.work_hours
+        FROM kd_attendance_records xt
+        LEFT JOIN kd_technician t on xt.unicode = t.unicode and t.year_and_month = left(#{workDate},6)
+        WHERE xt.work_date = #{workDate} and xt.unicode = #{unicode}
+    </select>
 </mapper>

+ 4 - 0
kd-service/kd-scientific/src/main/java/org/sky/scientific/mapper/TechnicianMapper.java

@@ -4,6 +4,7 @@ package org.sky.scientific.mapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
 import org.sky.core.tenant.annotation.TenantIgnore;
 import org.sky.scientific.pojo.dto.TechnicianDTO;
@@ -64,4 +65,7 @@ public interface TechnicianMapper extends BaseMapper<TechnicianEntity> {
 
 	@Update("update kd_technician set department = #{departmentName} where department_id = #{departmentId}")
 	int updateDepartment(@Param("departmentId") Long departmentId, @Param("departmentName") String departmentName);
+
+	@Select("select name from kd_technician where unicode = #{unicode} and year_and_month = #{yearAndMonth} ")
+	String getNameByUnicodeAndMonth(@Param("unicode") String unicode, @Param("yearAndMonth") String yearAndMonth);
 }

+ 14 - 12
kd-service/kd-scientific/src/main/java/org/sky/scientific/mapper/XmMapper.xml

@@ -38,21 +38,23 @@
     </resultMap>
 
     <update id="updateXm" parameterType="org.sky.scientific.pojo.dto.XmUpdateDto">
-        update kd_xm
-        <trim prefix="set" suffixOverrides=",">
-            <if test="yjlx != null and yjlx!=''">yjlx = #{yjlx},</if>
-            <if test="xmly != null and xmly!=''">xmly = #{xmly},</if>
-            <if test="xmkzxs != null and xmkzxs!=''">xmkzxs = #{xmkzxs},</if>
-            <if test="yqcgxs != null and yqcgxs!=''">yqcgxs = #{yqcgxs},</if>
-            <if test="xmjsjjmb != null and xmjsjjmb!=''">xmjsjjmb = #{xmjsjjmb},</if>
-            <if test="lxpw != null and lxpw!=''">lxpw = #{lxpw},</if>
-            <if test="lxbg != null and lxbg!=''">lxbg = #{lxbg},</if>
-            <if test="qtzl != null and qtzl!=''">qtzl = #{qtzl},</if>
-            <if test="remark != null and remark!=''">remark = #{remark},</if>
-        </trim>
+        update kd_xm set yjlx = #{yjlx},xmly = #{xmly},xmkzxs = #{xmkzxs},yqcgxs = #{yqcgxs},xmjsjjmb = #{xmjsjjmb},
+        lxpw = #{lxpw},lxbg = #{lxbg},qtzl = #{qtzl},remark = #{remark} where id = #{xmId}
         where id = #{xmId}
     </update>
 
+    <!--        <trim prefix="set" suffixOverrides=",">-->
+    <!--            <if test="yjlx != null and yjlx!=''">yjlx = #{yjlx},</if>-->
+    <!--            <if test="xmly != null and xmly!=''">xmly = #{xmly},</if>-->
+    <!--            <if test="xmkzxs != null and xmkzxs!=''">xmkzxs = #{xmkzxs},</if>-->
+    <!--            <if test="yqcgxs != null and yqcgxs!=''">yqcgxs = #{yqcgxs},</if>-->
+    <!--            <if test="xmjsjjmb != null and xmjsjjmb!=''">xmjsjjmb = #{xmjsjjmb},</if>-->
+    <!--            <if test="lxpw != null and lxpw!=''">lxpw = #{lxpw},</if>-->
+    <!--            <if test="lxbg != null and lxbg!=''">lxbg = #{lxbg},</if>-->
+    <!--            <if test="qtzl != null and qtzl!=''">qtzl = #{qtzl},</if>-->
+    <!--            <if test="remark != null and remark!=''">remark = #{remark},</if>-->
+    <!--        </trim>-->
+
     <select id="selectXmPage" resultMap="ResultMap">
         SELECT
             xm.id,

+ 52 - 39
kd-service/kd-scientific/src/main/java/org/sky/scientific/service/impl/AssetServiceImpl.java

@@ -14,7 +14,6 @@ import jakarta.servlet.http.HttpServletResponse;
 import lombok.AllArgsConstructor;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
-import org.sky.core.excel.util.ExcelUtil;
 import org.sky.core.log.exception.ServiceException;
 import org.sky.core.mp.base.BaseServiceImpl;
 import org.sky.core.mp.support.Condition;
@@ -23,6 +22,7 @@ import org.sky.core.secure.utils.SecureUtil;
 import org.sky.core.tool.utils.*;
 import org.sky.scientific.e.AssetStatusEnum;
 import org.sky.scientific.excel.AssetExcel;
+import org.sky.scientific.excel.listener.AssetImportListener;
 import org.sky.scientific.mapper.*;
 import org.sky.scientific.pojo.dto.AssetDTO;
 import org.sky.scientific.pojo.dto.CostSummaryDTO;
@@ -34,10 +34,13 @@ import org.sky.scientific.pojo.vo.AssetVO;
 import org.sky.scientific.pojo.vo.CostSummaryVO;
 import org.sky.scientific.pojo.vo.XmAssetCostSummary;
 import org.sky.scientific.service.IAssetService;
+import org.sky.system.cache.DictCache;
+import org.sky.system.pojo.entity.Dict;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.multipart.MultipartFile;
 
+import java.io.BufferedInputStream;
 import java.io.InputStream;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
@@ -80,62 +83,73 @@ public class AssetServiceImpl extends BaseServiceImpl<AssetMapper, AssetEntity>
 			throw new ServiceException("资产【" + asset.getZcbm() + "】已存在");
 		}
 
-		if (!isFixedAsset(asset.getZclb())) {
-			asset.setGl("/");
-		}
-
-		//使用寿命-已使用月份数=0时,即已足额折旧
-//			仪器设置的算法:当前时间为2025年01月-2024年5月,差额为9个月,因为折旧从次月开始,所以已使用月数为8个月(即9-1=8),再将使用寿命-已使用月份数=0时,即已足额折旧。
-//			无形资产:当月即摊销,不需要-1
-//
-//		long ysysm = cn.hutool.core.date.DateUtil.betweenMonth(DateUtil.parse(asset.getYearAndMonth(), DateUtil.PATTERN_YYYYMM), asset.getKssj(), true);
-////			这里
-//		if (!isFixedAsset(asset.getZclb())) {
-//			ysysm++;
-//		}
-		asset.setYsysm(getYsysm(asset.getYearAndMonth(), asset.getKssj(), asset.getZclb()));
+		validateAsset(asset);
 
 		asset.setStatus((asset.getSysm() - asset.getYsysm() <= 0) ? AssetStatusEnum.已足额折旧.getCode() : AssetStatusEnum.正常.getCode());
 		return this.save(asset);
 	}
 
+
+	@SneakyThrows
 	@Override
+	@Transactional
 	public void importAsset(MultipartFile file, String yearAndMonth) {
-
-		List<AssetExcel> data = ExcelUtil.read(file, 0, 2, AssetExcel.class);
+		AssetImportListener listener = new AssetImportListener();
+		EasyExcel.read(new BufferedInputStream(file.getInputStream()))
+			.registerReadListener(listener)
+			.sheet(0)
+			.headRowNumber(2) // 重要:设置表头行数(0-based)
+			.doRead();
+		List<Object> data = listener.getDataList();
 
 		List<AssetEntity> dbAssetList = baseMapper.selectList(Wrappers.<AssetEntity>lambdaQuery().eq(AssetEntity::getYearAndMonth, yearAndMonth));
 		List<String> idenfifierList = new ArrayList<>(dbAssetList.stream().map(tmp -> tmp.getZcbm() + ":" + tmp.getZcmc()).toList());
 
-		for (AssetExcel excel : data) {
+		for (Object excel : data) {
+			AssetExcel excel1 = (AssetExcel) excel;
 			AssetEntity asset = Objects.requireNonNull(BeanUtil.copyProperties(excel, AssetDTO.class));
-			String identifier = excel.getZcbm() + ":" + excel.getZcmc();
+			String identifier = asset.getZcbm() + ":" + asset.getZcmc();
 			if (idenfifierList.contains(identifier)) {
-				throw new ServiceException("数据已存在,导入失败!");
+				throw new ServiceException("资产【" + asset.getZcbm() + "," + asset.getZcmc() + "】已存在,导入失败!");
 			}
 			idenfifierList.add(identifier);
 			asset.setYearAndMonth(yearAndMonth);
-			//yearAndMonth 是否存在该资产
-			LambdaQueryWrapper<AssetEntity> queryWrapper = Wrappers.lambdaQuery();
-			queryWrapper.eq(AssetEntity::getZcbm, excel.getZcbm());
-			queryWrapper.eq(AssetEntity::getYearAndMonth, yearAndMonth);
-			if (this.count(queryWrapper) > 0) {
-//				throw new ServiceException("资产编号【" + excel.getZcbm() + "】已存在");
-				continue;
-			}
-			if (Func.notNull(asset.getJcz())) {
-				asset.setJcz(Math.min(asset.getJcz(), 1.0));
+
+			validateAsset(asset);
+
+			Optional<AssetStatusEnum> optional = Arrays.stream(AssetStatusEnum.values()).filter(temp -> temp.getName().equalsIgnoreCase(excel1.getStatusName())).findFirst();
+			if (optional.isEmpty()) {
+				throw new ServiceException("状态错误,请重新选择");
 			}
-			asset.setYsysm(getYsysm(yearAndMonth, asset.getKssj(), asset.getZclb()));
-//			if(asset.getYsysm() <=0){
-//				continue;
-//			}
-			AssetStatusEnum assetStatusEnum = Arrays.stream(AssetStatusEnum.values()).filter(temp -> temp.getName().equalsIgnoreCase(excel.getStatusName())).findFirst().orElse(AssetStatusEnum.正常);
-			asset.setStatus(assetStatusEnum.getCode());
+			asset.setStatus(optional.get().getCode());
+
 			this.save(asset);
 		}
 	}
 
+	private void validateAsset(AssetEntity asset) {
+		List<String> zclbList = DictCache.getList("zclb").stream().map(Dict::getDictValue).toList();
+		List<String> ytList = DictCache.getList("zcyt").stream().map(Dict::getDictValue).toList();
+		if (Func.isNotEmpty(zclbList) && !zclbList.contains(asset.getZclb())) {
+			throw new ServiceException("资产类别错误,请重新选择");
+		}
+		if (Func.isNotEmpty(ytList) && !ytList.contains(asset.getYt())) {
+			throw new ServiceException("用途错误,请重新选择");
+		}
+		if (!isFixedAsset(asset.getZclb())) {
+			asset.setGl("/");
+		}
+		asset.setYsysm(getYsysm(asset.getYearAndMonth(), asset.getKssj(), asset.getZclb()));
+		if (Func.notNull(asset.getJcz())) {
+			asset.setJcz(Math.min(asset.getJcz(), 1.0));
+		}
+		Date selectDate = DateUtil.parse(asset.getYearAndMonth(), DateUtil.PATTERN_YYYYMM);
+
+		if ((selectDate.compareTo(asset.getKssj()) < 0 && !cn.hutool.core.date.DateUtil.isSameMonth(selectDate, asset.getKssj()))) {
+			throw new ServiceException("开始时间可以添加当前或是前面的时间,不能添加未来的时间");
+		}
+	}
+
 	@Transactional
 	@Override
 	public boolean fetchLastMonthData(AssetDTO zc) {
@@ -152,8 +166,7 @@ public class AssetServiceImpl extends BaseServiceImpl<AssetMapper, AssetEntity>
 		for (AssetEntity temp : lastMonthDataList) {
 			LambdaQueryWrapper<AssetEntity> qw = Wrappers.<AssetEntity>lambdaQuery().eq(AssetEntity::getZcbm, temp.getZcbm()).eq(AssetEntity::getYearAndMonth, zc.getYearAndMonth());
 			if (this.baseMapper.selectCount(qw) > 0) {
-//				throw new ServiceException("资产【" + temp.getZcbm() + "】已存在");
-				continue;
+ 				continue;
 			}
 
 			if (Arrays.asList(AssetStatusEnum.停用.getCode(), AssetStatusEnum.已足额折旧.getCode()).contains(temp.getStatus())) {
@@ -172,7 +185,7 @@ public class AssetServiceImpl extends BaseServiceImpl<AssetMapper, AssetEntity>
 				count.incrementAndGet();
 			}
 		}
-		if(count.get()==0){
+		if (count.get() == 0) {
 			throw new ServiceException("上个月无资产数据可读取");
 		}
 		return true;

+ 59 - 15
kd-service/kd-scientific/src/main/java/org/sky/scientific/service/impl/HoursSyncServiceImpl.java

@@ -11,10 +11,12 @@ import org.sky.core.secure.utils.SecureUtil;
 import org.sky.core.tool.api.R;
 import org.sky.core.tool.utils.DateUtil;
 import org.sky.core.tool.utils.Func;
+import org.sky.scientific.mapper.AttendanceMapper;
 import org.sky.scientific.mapper.TechnicianMapper;
 import org.sky.scientific.mapper.XmMapper;
 import org.sky.scientific.mapper.XmTechnicianHoursMapper;
 import org.sky.scientific.pojo.dto.HoursSyncDto;
+import org.sky.scientific.pojo.entity.AttendanceEntity;
 import org.sky.scientific.pojo.entity.TechnicianEntity;
 import org.sky.scientific.pojo.entity.XmTechnicianHours;
 import org.sky.scientific.pojo.vo.XmVO;
@@ -24,6 +26,7 @@ import org.sky.system.feign.ISysClient;
 import org.sky.system.pojo.entity.Tenant;
 import org.springframework.http.*;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.client.RestClientException;
 import org.springframework.web.client.RestTemplate;
 import org.springframework.web.util.UriComponentsBuilder;
@@ -43,6 +46,7 @@ public class HoursSyncServiceImpl implements IHoursSyncService {
 	private final XmMapper xmMapper;
 	private final IXmService xmService;
 	private final TechnicianMapper technicianMapper;
+	private final AttendanceMapper attendanceMapper;
 	private static final String BASE_URL = "https://yanfa.jishutao.com/prod-api";
 	private static final String URL_LOGIN = BASE_URL + "/login";
 	private static final String URL_SYNC_HOURS = BASE_URL + "/api/project/pushCompanyData";
@@ -169,6 +173,7 @@ public class HoursSyncServiceImpl implements IHoursSyncService {
 	}
 
 	@Override
+	@Transactional
 	public boolean acceptRdData(HoursSyncDto dto) {
 		//先登录
 		String token = this.login();
@@ -194,8 +199,13 @@ public class HoursSyncServiceImpl implements IHoursSyncService {
 					List<JSONObject> dbHoursList = hoursMapper.selectListForSync(dto.getYearAndMonth());
 					List<String> identifierList = dbHoursList.stream().map(t ->
 						t.getString("tenantId") + ":" + t.getString("xmId") + ":" + t.getString("unicode") + ":" + t.getString("workDate")).toList();
+
+					Map<String, List<JSONObject>> mapByUnicode = dbHoursList.stream().collect(Collectors.groupingBy(t -> t.getString("unicode"), Collectors.toList()));
+
 					List<XmTechnicianHours> insertList = new ArrayList<>();
 					List<XmTechnicianHours> updateList = new ArrayList<>();
+
+					Map<String, String> userNameMap = new HashMap<>();
 					for (Object o : rdList) {
 						JSONObject rd = (JSONObject) o;
 						Double duration = rd.getDouble("duration");
@@ -203,6 +213,8 @@ public class HoursSyncServiceImpl implements IHoursSyncService {
 						String adminKyId = rd.getString("adminKyId");
 						List<String> tenantAndUnicode = Arrays.asList(adminKyId.split("_"));
 
+						String unicode = tenantAndUnicode.get(1);
+
 						String recordTime = rd.getString("recordTime");
 						recordTime = DateUtil.format(DateUtil.parse(recordTime, DateUtil.PATTERN_YYYY_MM_DD), DateUtil.PATTERN_YYYYMMDD);
 
@@ -222,40 +234,72 @@ public class HoursSyncServiceImpl implements IHoursSyncService {
 //							continue;
 //						}
 
-						String identifier = SecureUtil.getTenantId() + ":" + xmId + ":" + tenantAndUnicode.get(1) + ":" + recordTime;
+						String identifier = SecureUtil.getTenantId() + ":" + xmId + ":" + unicode + ":" + recordTime;
+
+						List<JSONObject> hourListByUnicode = mapByUnicode.get(unicode);
+						if (hourListByUnicode == null) {
+							hourListByUnicode = new ArrayList<>();
+						}
+						mapByUnicode.put(unicode, hourListByUnicode);
+
+						for (JSONObject t : hourListByUnicode) {
+							if (Func.equals(t.getLong("xmId"), xmId) && Func.equals(t.getString("unicode"), unicode) && Func.equals(t.getString("workDate"), recordTime)) {
+								t.put("workHours", duration);
+								break;
+							}
+						}
+
+						String userName = userNameMap.get(unicode);
+						if (userName == null) {
+							userName = technicianMapper.getNameByUnicodeAndMonth(unicode, recordTime.substring(0, 6));
+							userNameMap.put(unicode, userName);
+						}
+
+						//判断用户当天是否有考勤数据,且用户的总工时不能超过出勤时长
+						AttendanceEntity entity = new AttendanceEntity();
+						entity.setUnicode(unicode);
+						entity.setWorkDate(recordTime);
+						AttendanceEntity one = attendanceMapper.selectInfoByUnicodeAndDate(entity);
+						if (Func.isNull(one)) {
+							throw new ServiceException(userName + "在" + recordTime + "这天没有考勤数据");
+						}
+
+						List<JSONObject> list = mapByUnicode.get(unicode);
+						String finalRecordTime = recordTime;
+						double sum = list.stream().filter(t -> Func.equals(finalRecordTime, t.getString("workDate"))).mapToDouble(t -> t.getDouble("workHours")).sum();
+						if (sum > one.getWorkHours()) {
+							throw new ServiceException(one.getUserName() + "在" + recordTime + "这天的考勤时长为" + one.getWorkHours() + "小时,填报的工时为" + sum + "小时,同步失败");
+						}
 						if (identifierList.contains(identifier)) {
 							//现有的,做修改
 							XmTechnicianHours updateEntity = new XmTechnicianHours();
 							updateEntity.setTenantId(SecureUtil.getTenantId());
 							updateEntity.setXmId(xmId);
-							updateEntity.setUnicode(tenantAndUnicode.get(1));
+							updateEntity.setUnicode(unicode);
 							updateEntity.setWorkDate(recordTime);
 							updateEntity.setWorkHours(duration);
 							updateList.add(updateEntity);
+
 						} else {
 							//新的,做插入
 							XmTechnicianHours insertEntity = new XmTechnicianHours();
 							insertEntity.setTenantId(SecureUtil.getTenantId());
 							insertEntity.setXmId(xmId);
-							insertEntity.setUnicode(tenantAndUnicode.get(1));
+							insertEntity.setUnicode(unicode);
 							insertEntity.setWorkDate(recordTime);
 							insertEntity.setWorkHours(duration);
 							insertList.add(insertEntity);
 						}
-						if (Func.isNotEmpty(insertList) && insertList.size() % BATCH_SIZE == 0) {
-							hoursMapper.insert(insertList);
-							insertList.clear();
-						}
-						if (Func.isNotEmpty(updateList) && updateList.size() % BATCH_SIZE == 0) {
-							hoursMapper.batchUpdateHours(updateList);
-							updateList.clear();
-						}
 					}
-					if (Func.isNotEmpty(insertList)) {
-						hoursMapper.insert(insertList);
+
+					for (int i = 0; i < insertList.size(); i +=BATCH_SIZE) {
+						int end = Math.min(insertList.size(), i + BATCH_SIZE);
+  						hoursMapper.insert(insertList.subList(i, end));
 					}
-					if (Func.isNotEmpty(updateList)) {
-						hoursMapper.batchUpdateHours(updateList);
+
+					for (int i = 0; i < updateList.size(); i += BATCH_SIZE) {
+						int end = Math.min(updateList.size(), i + BATCH_SIZE);
+						hoursMapper.batchUpdateHours(updateList.subList(i, end));
 					}
 					log.info("同步至本系统,响应数据:{}", data);
 				} else {

+ 13 - 13
kd-service/kd-scientific/src/main/java/org/sky/scientific/service/impl/TechnicianServiceImpl.java

@@ -16,7 +16,6 @@ import lombok.AllArgsConstructor;
 import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.poi.ss.util.CellRangeAddress;
-import org.sky.core.excel.util.ExcelUtil;
 import org.sky.core.log.exception.ServiceException;
 import org.sky.core.mp.base.BaseServiceImpl;
 import org.sky.core.mp.support.Condition;
@@ -27,6 +26,7 @@ import org.sky.scientific.e.XmStatusEnum;
 import org.sky.scientific.excel.TechnicianExcel;
 import org.sky.scientific.excel.handle.MergeCellRangeWriteHandler;
 import org.sky.scientific.excel.handle.XmcyryhzmExcel;
+import org.sky.scientific.excel.listener.TechnicianImportListener;
 import org.sky.scientific.excel.listener.XmTechnicianImportListener;
 import org.sky.scientific.mapper.*;
 import org.sky.scientific.pojo.dto.TechnicianDTO;
@@ -177,20 +177,21 @@ public class TechnicianServiceImpl extends BaseServiceImpl<TechnicianMapper, Tec
 	@Transactional
 	@Override
 	public void importTechnician(MultipartFile file, String yearAndMonth) {
-//		TechnicianImportListener listener = new TechnicianImportListener();
-//		EasyExcel.read(new BufferedInputStream(file.getInputStream()))
-//			.registerReadListener(listener)
-//			.sheet(0)
-//			.headRowNumber(2) // 重要:设置表头行数(0-based)
-//			.doRead();
-		List<TechnicianExcel> data = ExcelUtil.read(file, 0, 2, TechnicianExcel.class);
+		TechnicianImportListener listener = new TechnicianImportListener();
+		EasyExcel.read(new BufferedInputStream(file.getInputStream()))
+			.registerReadListener(listener)
+			.sheet(0)
+			.headRowNumber(2) // 重要:设置表头行数(0-based)
+			.doRead();
+		List<Object> data = listener.getDataList();
 //
 		List<TechnicianEntity> dbTechnicianList = baseMapper.selectList(Wrappers.<TechnicianEntity>lambdaQuery().eq(TechnicianEntity::getYearAndMonth, yearAndMonth));
 		List<String> identifierList = new ArrayList<>(dbTechnicianList.stream().map(tmp -> tmp.getNumber() + ":" + tmp.getIdCard()).toList());
 
-		for (TechnicianExcel excel : data) {
+		for (Object t : data) {
+			TechnicianExcel excel = (TechnicianExcel) t;
 			TechnicianEntity technician = Objects.requireNonNull(BeanUtil.copyProperties(excel, TechnicianDTO.class));
-
+			technician.setYearAndMonth(yearAndMonth);
 			String identifier = technician.getNumber() + ":" + technician.getIdCard();
 			if (identifierList.contains(identifier)) {
 				throw new ServiceException("数据已存在,导入失败!");
@@ -198,9 +199,7 @@ public class TechnicianServiceImpl extends BaseServiceImpl<TechnicianMapper, Tec
 			identifierList.add(identifier);
 			technician.setNumber(Func.isBlank(excel.getNumber()) ? null : technician.getNumber().trim());
 			technician.setIdCard(Func.isBlank(technician.getIdCard()) ? null : technician.getIdCard().trim());
-			//转换 性别字典
-			technician.setGender(Integer.valueOf(DictCache.getKey(DictEnum.SEX, excel.getGenderName())));
-			technician.setYearAndMonth(yearAndMonth);
+
 
 			if (Func.isNotBlank(excel.getDepartment())) {
 				List<String> tmpList = Arrays.asList(excel.getDepartment().split("/"));
@@ -228,6 +227,7 @@ public class TechnicianServiceImpl extends BaseServiceImpl<TechnicianMapper, Tec
 		}
 	}
 
+
 	@Transactional
 	@Override
 	public boolean fetchLastMonthData(TechnicianEntity technician) {

+ 12 - 3
kd-service/kd-scientific/src/main/java/org/sky/scientific/service/impl/XmServiceImpl.java

@@ -32,6 +32,7 @@ import org.sky.scientific.pojo.vo.XmVO;
 import org.sky.scientific.pojo.vo.XmYszbVO;
 import org.sky.scientific.service.IXmService;
 import org.sky.scientific.utils.TemplateService;
+import org.sky.scientific.utils.ValidationUtils;
 import org.sky.system.feign.ISysClient;
 import org.sky.system.pojo.entity.Tenant;
 import org.springframework.stereotype.Service;
@@ -79,20 +80,21 @@ public class XmServiceImpl extends BaseServiceImpl<XmMapper, XmEntity> implement
 
 	/**
 	 * 判断项目是否已结题
+	 *
 	 * @param xmId
 	 */
 	public boolean judgeXmIsOver(Long xmId, boolean isThrowEx) {
 		JSONObject byXmId = baseMapper.getXmStatusByXmId(xmId);
-		if(Func.isNull(byXmId)){
+		if (Func.isNull(byXmId)) {
 			return false;
 		}
 		Integer status = byXmId.getInteger("status");
 //		Date xmjssj = byXmId.getDate("xmjssj");
 //		if (Func.equals(status, XmStatusEnum.已结题.getCode()) || xmjssj.compareTo(new Date()) < 0) {
 		if (Func.equals(status, XmStatusEnum.已结题.getCode())) {
-			if(isThrowEx){
+			if (isThrowEx) {
 				throw new ServiceException("项目已结题,不可操作");
-			}else {
+			} else {
 				return true;
 			}
 		}
@@ -102,6 +104,13 @@ public class XmServiceImpl extends BaseServiceImpl<XmMapper, XmEntity> implement
 	@Override
 	public Long approval(XmDTO xm) {
 
+		if(Func.equals(xm.getStatus(), XmStatusEnum.暂存.getCode())){
+
+		}
+		List<String> validate = ValidationUtils.validate(xm);
+		if (Func.isEmpty(validate)) {
+			throw new ServiceException(String.join(",", validate));
+		}
 		this.judgeXmmcAvailable(xm.getXmmc());
 
 		judgeXmIsOver(xm.getId(), true);

+ 13 - 0
kd-service/kd-scientific/src/main/java/org/sky/scientific/utils/ValidationUtils.java

@@ -4,6 +4,7 @@ import jakarta.validation.ConstraintViolation;
 import jakarta.validation.Validation;
 import jakarta.validation.Validator;
 import jakarta.validation.ValidatorFactory;
+import org.sky.core.log.exception.ServiceException;
 import org.sky.core.tool.utils.StringUtil;
 
 import java.math.BigDecimal;
@@ -75,6 +76,18 @@ public class ValidationUtils {
 		}
 	}
 
+	public static Double safeParseDoubleWithPercentage(String numberStr ,String key) {
+		if (StringUtil.isBlank(numberStr)) {
+			return null;
+		}
+		try {
+			String numberPart = numberStr.trim().split("%")[0];
+			return Double.parseDouble(numberPart) / 100;
+		}catch (Exception e) {
+			throw new ServiceException(key + "不是一个有效的百分比");
+		}
+	}
+
 	public static BigDecimal safeParseBigDecimal(String numberStr) {
 		if (StringUtil.isBlank(numberStr)) {
 			return null;