Browse Source

研发费用支出明细账

zhoulisky 10 months ago
parent
commit
13079632ef

BIN
doc/xls导入模板/每月研发工时记录表.xls


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

@@ -17,6 +17,8 @@ import org.sky.scientific.service.IXmTechnicianHoursService;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 import org.springframework.web.multipart.MultipartFile;
 
 
+import java.util.List;
+
 /**
 /**
  * 研发人员研发工时记录表 控制器
  * 研发人员研发工时记录表 控制器
  *
  *
@@ -55,7 +57,7 @@ public class XmTechnicianHoursController extends KdController {
 
 
 	@PostMapping("/clear/batch")
 	@PostMapping("/clear/batch")
 	@ApiOperationSupport(order = 7)
 	@ApiOperationSupport(order = 7)
-	@Operation(summary = "清除工时")
+	@Operation(summary = "清除工时", description = "{\"yearAndMonth\":\"202507\",\"xmAndUnicodeList\":[{\"xmId\":\"1943241918440747010\",\"unicode\":\"ccd86a42-2369-4517-832e-004307e5237e\"}]}")
 	public R clear(@RequestBody JSONObject dto) {
 	public R clear(@RequestBody JSONObject dto) {
 		return R.status(service.clear(dto));
 		return R.status(service.clear(dto));
 	}
 	}
@@ -66,4 +68,11 @@ public class XmTechnicianHoursController extends KdController {
 	public void export(XmTechnicianHoursVO param,  HttpServletResponse response) {
 	public void export(XmTechnicianHoursVO param,  HttpServletResponse response) {
 		service.export(response, param);
 		service.export(response, param);
 	}
 	}
+
+	@GetMapping("/list/print")
+	@ApiOperationSupport(order = 1)
+	@Operation(summary = "自动生成-打印")
+	public R<List<JSONObject>> selectListForPrint(XmTechnicianHoursVO dto) {
+		return R.data(service.selectListForPrint(dto));
+	}
 }
 }

+ 1 - 2
kd-service/kd-scientific/src/main/java/org/sky/scientific/excel/XmTechnicianHoursExcel.java

@@ -34,10 +34,9 @@ public class XmTechnicianHoursExcel implements Serializable {
 	private String xmmc;
 	private String xmmc;
 
 
 	private String number;
 	private String number;
-	private String idCard;
 	private String namme;
 	private String namme;
+	private String idCard;
 	private String personnelType;
 	private String personnelType;
 
 
 	private List<DailyAttendance> dailyAttendances = new ArrayList<>(); // 每日考勤数据
 	private List<DailyAttendance> dailyAttendances = new ArrayList<>(); // 每日考勤数据
-	private Double monthlyHours;   // 使用工时/小时
 }
 }

+ 47 - 30
kd-service/kd-scientific/src/main/java/org/sky/scientific/excel/XmTechnicianHoursListener.java

@@ -4,22 +4,21 @@ import com.alibaba.excel.context.AnalysisContext;
 import com.alibaba.excel.metadata.data.ReadCellData;
 import com.alibaba.excel.metadata.data.ReadCellData;
 import com.alibaba.excel.read.listener.ReadListener;
 import com.alibaba.excel.read.listener.ReadListener;
 import lombok.Data;
 import lombok.Data;
-import org.sky.core.tool.utils.StringUtil;
+import org.sky.core.tool.utils.DateUtil;
 import org.sky.scientific.pojo.vo.DailyAttendance;
 import org.sky.scientific.pojo.vo.DailyAttendance;
 
 
 import java.math.BigDecimal;
 import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 
 @Data
 @Data
 public class XmTechnicianHoursListener implements ReadListener<Map<Integer, String>> {
 public class XmTechnicianHoursListener implements ReadListener<Map<Integer, String>> {
 
 
-	private static final int HEADER_ROW_1 = 1; // 星期行(0-based)
-	private static final int HEADER_ROW_2 = 2; // 日期行
-	private static final int DATA_START_ROW = 3; // 数据开始行
+	private static final int HEADER_ROW_2 = 3; // 日期行
+	private static final int DATA_START_ROW = 4; // 数据开始行
 
 
- 	private List<String> dateHeaders = new ArrayList<>();
+	private static final int DATA_START_COL = 7;
+
+	private List<String> dateHeaders = new ArrayList<>();
 	private List<XmTechnicianHoursExcel> records = new ArrayList<>();
 	private List<XmTechnicianHoursExcel> records = new ArrayList<>();
 	private boolean headersProcessed = false;
 	private boolean headersProcessed = false;
 	private String yearAndMonth;
 	private String yearAndMonth;
@@ -27,10 +26,9 @@ public class XmTechnicianHoursListener implements ReadListener<Map<Integer, Stri
 	@Override
 	@Override
 	public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
 	public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
 		int rowIndex = context.readRowHolder().getRowIndex();
 		int rowIndex = context.readRowHolder().getRowIndex();
-
-		 if (rowIndex == HEADER_ROW_2) {
+		if (rowIndex == HEADER_ROW_2) {
 			// 处理日期行
 			// 处理日期行
-			dateHeaders = extractHeaders(headMap, 8);
+			dateHeaders = extractHeaders(headMap, DATA_START_COL);
 			headersProcessed = true;
 			headersProcessed = true;
 		}
 		}
 	}
 	}
@@ -41,20 +39,39 @@ public class XmTechnicianHoursListener implements ReadListener<Map<Integer, Stri
 			.filter(entry -> entry.getKey() >= startCol)
 			.filter(entry -> entry.getKey() >= startCol)
 			.sorted(Map.Entry.comparingByKey())
 			.sorted(Map.Entry.comparingByKey())
 			.forEach(entry -> {
 			.forEach(entry -> {
-				String stringValue = entry.getValue().getStringValue();
-				if(StringUtil.isBlank(stringValue)){
-					BigDecimal numberValue = entry.getValue().getNumberValue();
-					if(numberValue != null){
-						headers.add(numberValue.intValue()<=9?"0"+numberValue.intValue() : numberValue.intValue()+"");
-					}
-				}else {
-					headers.add(stringValue);
+				BigDecimal numberValue = entry.getValue().getNumberValue();
+				if (numberValue != null) {
+					String str = getDay(numberValue);
+//					int day = Integer.parseInt(str);
+//					headers.add(day<= 9 ? "0" + day : day + "");
+					headers.add(str);
 				}
 				}
-
-
 			});
 			});
 		return headers;
 		return headers;
 	}
 	}
+	public static String getDay(BigDecimal bd) {
+		//如果不是数字
+//		if(!isNumeric(daysDuration)){
+//			return null;
+//		}
+		//如果是数字 小于0则 返回
+//		BigDecimal bd = new BigDecimal(daysDuration);
+		int days = bd.intValue();//天数
+		int mills = (int) Math.round(bd.subtract(new BigDecimal(days)).doubleValue() * 24 * 3600);
+
+		//获取时间
+		Calendar c = Calendar.getInstance();
+		c.set(1900, 0, 0);
+		c.add(Calendar.DATE, days);
+		int hour = mills / 3600;
+		int minute = (mills - hour * 3600) / 60;
+		int second = mills - hour * 3600 - minute * 60;
+		c.set(Calendar.HOUR_OF_DAY, hour);
+		c.set(Calendar.MINUTE, minute);
+		c.set(Calendar.SECOND, second);
+
+		return DateUtil.format(c.getTime(), DateUtil.PATTERN_DATE);
+	}
 
 
 	@Override
 	@Override
 	public void invoke(Map<Integer, String> data, AnalysisContext context) {
 	public void invoke(Map<Integer, String> data, AnalysisContext context) {
@@ -73,14 +90,14 @@ public class XmTechnicianHoursListener implements ReadListener<Map<Integer, Stri
 		record.setSerialNumber(safeParseInt(data.get(0)));
 		record.setSerialNumber(safeParseInt(data.get(0)));
 		record.setXmbh(data.get(1));
 		record.setXmbh(data.get(1));
 		record.setXmmc(data.get(2));
 		record.setXmmc(data.get(2));
-//		record.setNamme(data.get(3));
-//		record.setZcmc(data.get(4));
-//		record.setZclb(data.get(5));
-//		record.setYt(data.get(6));
+		record.setNumber(data.get(3));
+		record.setNamme(data.get(4));
+		record.setIdCard(data.get(5));
+		record.setPersonnelType(data.get(6));
 
 
 		// 解析每日工时数据
 		// 解析每日工时数据
- 		for (int i = 0; i < dateHeaders.size(); i++) {
-			String hoursStr = data.get(i + 8); // 工时数据从第5列开始
+		for (int i = 0; i < dateHeaders.size(); i++) {
+			String hoursStr = data.get(i + DATA_START_COL); // 工时数据从第5列开始
 			//没有填报的日期,跳过
 			//没有填报的日期,跳过
 			if (hoursStr != null && !hoursStr.trim().isEmpty()) {
 			if (hoursStr != null && !hoursStr.trim().isEmpty()) {
 				try {
 				try {
@@ -89,8 +106,8 @@ public class XmTechnicianHoursListener implements ReadListener<Map<Integer, Stri
 					daily.setWorkHours(safeParseDouble(hoursStr));
 					daily.setWorkHours(safeParseDouble(hoursStr));
 					record.getDailyAttendances().add(daily);
 					record.getDailyAttendances().add(daily);
 				} catch (NumberFormatException e) {
 				} catch (NumberFormatException e) {
-//					System.err.printf("解析错误: 资产[%s] 日期[%s] 值[%s]%n",
-//						record.getZcmc(), dateHeaders.get(i), hoursStr);
+					System.err.printf("解析错误:  日期[%s] 值[%s]%n",
+						dateHeaders.get(i), hoursStr);
 				}
 				}
 			}
 			}
 		}
 		}
@@ -100,7 +117,7 @@ public class XmTechnicianHoursListener implements ReadListener<Map<Integer, Stri
 
 
 	@Override
 	@Override
 	public void doAfterAllAnalysed(AnalysisContext context) {
 	public void doAfterAllAnalysed(AnalysisContext context) {
-		System.out.println("科研仪器设备工时数据解析完成,共解析 " + records.size() + " 条记录");
+		System.out.println("每月研发工时数据解析完成,共解析 " + records.size() + " 条记录");
 	}
 	}
 
 
 
 

+ 4 - 1
kd-service/kd-scientific/src/main/java/org/sky/scientific/mapper/XmTechnicianHoursMapper.java

@@ -1,6 +1,7 @@
 
 
 package org.sky.scientific.mapper;
 package org.sky.scientific.mapper;
 
 
+import com.alibaba.fastjson.JSONObject;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Param;
@@ -24,9 +25,11 @@ public interface XmTechnicianHoursMapper extends BaseMapper<XmTechnicianHours> {
 
 
  	List<XmTechnicianHoursVO> selectHoursPage(IPage<XmTechnicianHoursVO> page, @Param("ew") XmTechnicianHoursVO dto);
  	List<XmTechnicianHoursVO> selectHoursPage(IPage<XmTechnicianHoursVO> page, @Param("ew") XmTechnicianHoursVO dto);
 
 
-	List<XmTechnicianHours> selectByYearAndMonth(String yearAndMonth);
+	List<XmTechnicianHours> selectByYearAndMonth(String yearAndMonth, @Param("xmId") Long xmId, @Param("unicode") String unicode);
 	/**
 	/**
 	 * 清除工时数据
 	 * 清除工时数据
 	 */
 	 */
 	int clear(@Param("yearAndMonth") String yearAndMonth, @Param("xmId") Long xmId, @Param("unicode") String unicode);
 	int clear(@Param("yearAndMonth") String yearAndMonth, @Param("xmId") Long xmId, @Param("unicode") String unicode);
+
+    List<JSONObject> selectListForPrint(XmTechnicianHoursVO dto);
 }
 }

+ 6 - 0
kd-service/kd-scientific/src/main/java/org/sky/scientific/mapper/XmTechnicianHoursMapper.xml

@@ -74,6 +74,12 @@
         select id, xm_id, unicode, work_date, work_hours
         select id, xm_id, unicode, work_date, work_hours
         from kd_xm_technician_hours
         from kd_xm_technician_hours
         where left(work_date,6) = #{yearAndMonth}
         where left(work_date,6) = #{yearAndMonth}
+        <if test="xmId!=null">and xm_id = #{xmId}</if>
+        <if test="unicode!=null">and unicode = #{unicode}</if>
+    </select>
+
+    <select id="selectListForPrint" resultType="com.alibaba.fastjson.JSONObject">
+
     </select>
     </select>
 
 
     <delete id="clear">
     <delete id="clear">

+ 9 - 0
kd-service/kd-scientific/src/main/java/org/sky/scientific/service/IXmTechnicianHoursService.java

@@ -9,6 +9,8 @@ import org.sky.scientific.pojo.entity.XmTechnicianHours;
 import org.sky.scientific.pojo.vo.XmTechnicianHoursVO;
 import org.sky.scientific.pojo.vo.XmTechnicianHoursVO;
 import org.springframework.web.multipart.MultipartFile;
 import org.springframework.web.multipart.MultipartFile;
 
 
+import java.util.List;
+
 /**
 /**
  * 研发人员研发工时记录表 服务类
  * 研发人员研发工时记录表 服务类
  *
  *
@@ -47,4 +49,11 @@ public interface IXmTechnicianHoursService extends BaseService<XmTechnicianHours
 	 * @param param
 	 * @param param
 	 */
 	 */
 	void export(HttpServletResponse response, XmTechnicianHoursVO param);
 	void export(HttpServletResponse response, XmTechnicianHoursVO param);
+
+	/**
+	 * 列表,自动生成-打印
+	 * @param dto
+	 * @return
+	 */
+	List<JSONObject> selectListForPrint(XmTechnicianHoursVO dto);
 }
 }

+ 2 - 0
kd-service/kd-scientific/src/main/java/org/sky/scientific/service/impl/XmCostDetailsServiceImpl.java

@@ -57,6 +57,8 @@ public class XmCostDetailsServiceImpl extends BaseServiceImpl<XmCostDetailsMappe
 	@Override
 	@Override
 	public boolean submit(XmCostDetailsEntity xmCostDetails) {
 	public boolean submit(XmCostDetailsEntity xmCostDetails) {
 		boolean result = false;
 		boolean result = false;
+		//日期,必须为当月日期
+		judgeDate(xmCostDetails.getRecordDate(), xmCostDetails.getYearAndMonth());
 		if (Func.isEmpty(xmCostDetails.getId())) {
 		if (Func.isEmpty(xmCostDetails.getId())) {
 			//新增
 			//新增
 			result = this.save(xmCostDetails);
 			result = this.save(xmCostDetails);

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

@@ -17,6 +17,7 @@ import org.sky.core.tool.utils.Func;
 import org.sky.core.tool.utils.StringUtil;
 import org.sky.core.tool.utils.StringUtil;
 import org.sky.scientific.excel.XmTechnicianHoursExcel;
 import org.sky.scientific.excel.XmTechnicianHoursExcel;
 import org.sky.scientific.excel.XmTechnicianHoursListener;
 import org.sky.scientific.excel.XmTechnicianHoursListener;
+import org.sky.scientific.mapper.AttendanceMapper;
 import org.sky.scientific.mapper.XmMapper;
 import org.sky.scientific.mapper.XmMapper;
 import org.sky.scientific.mapper.XmTechnicianHoursMapper;
 import org.sky.scientific.mapper.XmTechnicianHoursMapper;
 import org.sky.scientific.mapper.XmTechnicianMapper;
 import org.sky.scientific.mapper.XmTechnicianMapper;
@@ -30,6 +31,7 @@ import org.springframework.web.multipart.MultipartFile;
 
 
 import java.io.BufferedInputStream;
 import java.io.BufferedInputStream;
 import java.util.*;
 import java.util.*;
+import java.util.stream.Collectors;
 
 
 /**
 /**
  * 研发人员研发工时记录表 服务实现类
  * 研发人员研发工时记录表 服务实现类
@@ -44,6 +46,7 @@ public class XmTechnicianHoursServiceImpl extends BaseServiceImpl<XmTechnicianHo
 
 
 	private final XmMapper xmMapper;
 	private final XmMapper xmMapper;
 	private final XmTechnicianMapper xmTechnicianMapper;
 	private final XmTechnicianMapper xmTechnicianMapper;
+	private final AttendanceMapper attendanceMapper;
 
 
 	@SneakyThrows
 	@SneakyThrows
 	@Transactional(rollbackFor = Exception.class)
 	@Transactional(rollbackFor = Exception.class)
@@ -54,7 +57,7 @@ public class XmTechnicianHoursServiceImpl extends BaseServiceImpl<XmTechnicianHo
 		EasyExcel.read(new BufferedInputStream(file.getInputStream()))
 		EasyExcel.read(new BufferedInputStream(file.getInputStream()))
 			.registerReadListener(listener)
 			.registerReadListener(listener)
 			.sheet(0)
 			.sheet(0)
-			.headRowNumber(3) // 重要:设置表头行数(0-based)
+			.headRowNumber(4) // 重要:设置表头行数(0-based)
 			.doRead();
 			.doRead();
 		List<XmTechnicianHoursExcel> records = listener.getRecords();
 		List<XmTechnicianHoursExcel> records = listener.getRecords();
 		if (CollectionUtil.isEmpty(records)) {
 		if (CollectionUtil.isEmpty(records)) {
@@ -67,26 +70,67 @@ public class XmTechnicianHoursServiceImpl extends BaseServiceImpl<XmTechnicianHo
 		List<XmTechnicianHours> updateList = new ArrayList<>();
 		List<XmTechnicianHours> updateList = new ArrayList<>();
 
 
 		int batchCount = 20;
 		int batchCount = 20;
+		//月度工时列表
+		List<XmTechnicianHours> dbList = baseMapper.selectByYearAndMonth(yearAndMonth, null, null);
+
+		//将研发项目信息缓存起来
+		Map<String, Long> xmMap = new HashMap<>();
+		//将研发人员信息缓存起来
+		Map<String, String> technicianMap = new HashMap<>();
 
 
-		List<XmTechnicianHours> dbList = baseMapper.selectByYearAndMonth(yearAndMonth);
 		for (XmTechnicianHoursExcel record : records) {
 		for (XmTechnicianHoursExcel record : records) {
 			//保存
 			//保存
 			if (StringUtil.isAllBlank(record.getXmmc(), record.getXmbh())) {
 			if (StringUtil.isAllBlank(record.getXmmc(), record.getXmbh())) {
 				throw new ServiceException("研发项目编号、研发项目名称都为空,请检查数据");
 				throw new ServiceException("研发项目编号、研发项目名称都为空,请检查数据");
 			}
 			}
-			Long xmId = xmMapper.getXmIdByXmmcOrXmbh(yearAndMonth, record.getXmmc(), record.getXmbh());
-			if (xmId == null) {
-				throw new ServiceException("序号为[" + record.getSerialNumber() + "]的研发项目编号和研发项目名称都不存在");
+			String xmKey = record.getXmmc() + ":" + record.getXmbh();
+			Long xmId = null;
+			if (xmMap.containsKey(xmKey)) {
+				xmId = xmMap.get(xmKey);
+			} else {
+				xmId = xmMapper.getXmIdByXmmcOrXmbh(yearAndMonth, record.getXmmc(), record.getXmbh());
+				if (xmId == null) {
+					throw new ServiceException("序号为[" + record.getSerialNumber() + "]的研发项目编号和研发项目名称都不存在");
+				}
+				xmMap.put(xmKey, xmId);
 			}
 			}
 
 
-			String unicode = xmTechnicianMapper.countByXmIdAndNumberAndIdCardAndYearMonth(xmId, record.getNumber(), record.getIdCard(), yearAndMonth);
-			if (unicode == null) {
-				throw new ServiceException("序号为[" + record.getSerialNumber() + "]的研发人员不存在");
+			String unicode = null;
+			String techniciankey = record.getNumber() + ":" + record.getIdCard();
+			if (technicianMap.containsKey(techniciankey)) {
+				unicode = technicianMap.get(techniciankey);
+			} else {
+				unicode = xmTechnicianMapper.countByXmIdAndNumberAndIdCardAndYearMonth(xmId, record.getNumber(), record.getIdCard(), yearAndMonth);
+				if (unicode == null) {
+					throw new ServiceException("序号为[" + record.getSerialNumber() + "]的研发人员不存在");
+				}
+				technicianMap.put(techniciankey, unicode);
 			}
 			}
-			List<XmTechnicianHours> byXmAndUnicodeList = dbList.stream().filter(e -> e.getXmId().equals(xmId) && e.getUnicode().equals(unicode)).toList();
+
+			//根据人员和项目获取工时列表,用于后续决定是新增还是修改
+			Long finalXmId = xmId;
+			String finalUnicode = unicode;
+			List<XmTechnicianHours> byXmAndUnicodeList = dbList.stream().filter(e -> e.getXmId().equals(finalXmId) && e.getUnicode().equals(finalUnicode)).toList();
+
+			//根据unicode、yearAndMonth获取考勤明细
+			List<DailyAttendance> dailyAttendanceList = attendanceMapper.selectDailyAttendanceByUnicodeAndMonth(unicode, yearAndMonth);
+			if (CollectionUtil.isEmpty(dailyAttendanceList)) {
+				throw new ServiceException("未设置考勤数据");
+			}
+			//每天的考勤总时长
+			Map<String, Double> hoursByDateMap = dailyAttendanceList.stream().collect(Collectors.groupingBy(DailyAttendance::getWorkDate, Collectors.summingDouble(DailyAttendance::getWorkHours)));
+
+			//按人员筛选、根据日期分组,再对工时求和
+ 			Map<String, Double> usedByUnicodeAndDateMap = dbList.stream().filter(e -> e.getUnicode().equals(finalUnicode)).collect(Collectors.groupingBy(XmTechnicianHours::getWorkDate, Collectors.summingDouble(XmTechnicianHours::getWorkHours)));
+
 			for (DailyAttendance dailyAttendance : record.getDailyAttendances()) {
 			for (DailyAttendance dailyAttendance : record.getDailyAttendances()) {
 
 
-				List<Long> primaryKeyList = byXmAndUnicodeList.stream().filter(e -> Objects.equals(e.getWorkDate(), dailyAttendance.getWorkDate())).map(XmTechnicianHours::getId).toList();
+				Double usedHours = usedByUnicodeAndDateMap.get(dailyAttendance.getWorkDate());
+				if (hoursByDateMap.get(dailyAttendance.getWorkDate()) - usedHours < dailyAttendance.getWorkHours()) {
+					//当 考勤时长 - 当天以用工时 < 新的工时 ,则提示“考勤时长不足”
+					throw new ServiceException("考勤总时长为" + hoursByDateMap.get(dailyAttendance.getWorkDate()) + "小时,工时已用" + usedHours + "小时,请核对数据");
+				}
+ 				List<Long> primaryKeyList = byXmAndUnicodeList.stream().filter(e -> Objects.equals(e.getWorkDate(), dailyAttendance.getWorkDate()) && Objects.equals(e.getXmId(), finalXmId) && Objects.equals(e.getUnicode(), finalUnicode)).map(XmTechnicianHours::getId).toList();
 				Long primaryKey = CollectionUtil.isEmpty(primaryKeyList) ? null : primaryKeyList.get(0);
 				Long primaryKey = CollectionUtil.isEmpty(primaryKeyList) ? null : primaryKeyList.get(0);
 				XmTechnicianHours entity = new XmTechnicianHours();
 				XmTechnicianHours entity = new XmTechnicianHours();
 				if (primaryKey == null) {
 				if (primaryKey == null) {
@@ -98,10 +142,17 @@ public class XmTechnicianHoursServiceImpl extends BaseServiceImpl<XmTechnicianHo
 					entity.setCreateUser(SecureUtil.getUserId());
 					entity.setCreateUser(SecureUtil.getUserId());
 					entity.setCreateDept(Func.toLong(SecureUtil.getDeptId()));
 					entity.setCreateDept(Func.toLong(SecureUtil.getDeptId()));
 					insertList.add(entity);
 					insertList.add(entity);
+
+					//已用工时 加上新增的工时
+					usedByUnicodeAndDateMap.put(dailyAttendance.getWorkDate(), usedHours + dailyAttendance.getWorkHours());
 				} else {
 				} else {
 					entity.setId(primaryKey);
 					entity.setId(primaryKey);
 					entity.setWorkHours(dailyAttendance.getWorkHours());
 					entity.setWorkHours(dailyAttendance.getWorkHours());
 					updateList.add(entity);
 					updateList.add(entity);
+
+					//查询数据库中的原有的工时,需要加上旧值,在减去新值
+					double oldHours = byXmAndUnicodeList.stream().filter(e -> Objects.equals(e.getWorkDate(), dailyAttendance.getWorkDate())).mapToDouble(XmTechnicianHours::getWorkHours).sum();
+					usedByUnicodeAndDateMap.put(dailyAttendance.getWorkDate(), usedHours - oldHours + dailyAttendance.getWorkHours());
 				}
 				}
 				if (!insertList.isEmpty() && insertList.size() % batchCount == 0) {
 				if (!insertList.isEmpty() && insertList.size() % batchCount == 0) {
 					if (this.saveBatch(insertList)) {
 					if (this.saveBatch(insertList)) {
@@ -150,7 +201,15 @@ public class XmTechnicianHoursServiceImpl extends BaseServiceImpl<XmTechnicianHo
 		if (dto.getUnicode() == null) {
 		if (dto.getUnicode() == null) {
 			throw new ServiceException("参数unicode不能为空");
 			throw new ServiceException("参数unicode不能为空");
 		}
 		}
-		List<XmTechnicianHours> dbList = baseMapper.selectByYearAndMonth(dto.getYearAndMonth());
+
+		//获取考勤数据,具体人员在某个月的考勤明细
+		List<DailyAttendance> dailyAttendanceList = attendanceMapper.selectDailyAttendanceByUnicodeAndMonth(dto.getUnicode(), dto.getYearAndMonth());
+		if (CollectionUtil.isEmpty(dailyAttendanceList)) {
+			throw new ServiceException("未设置考勤数据");
+		}
+
+		//获取工时数据,具体项目、具体人员在某月的工时数据
+		List<XmTechnicianHours> dbList = baseMapper.selectByYearAndMonth(dto.getYearAndMonth(), null, dto.getUnicode());
 
 
 		//需要新增的,以便后续批量新增
 		//需要新增的,以便后续批量新增
 		List<XmTechnicianHours> insertList = new ArrayList<>();
 		List<XmTechnicianHours> insertList = new ArrayList<>();
@@ -158,11 +217,22 @@ public class XmTechnicianHoursServiceImpl extends BaseServiceImpl<XmTechnicianHo
 		List<XmTechnicianHours> updateList = new ArrayList<>();
 		List<XmTechnicianHours> updateList = new ArrayList<>();
 
 
 		for (DailyAttendance dailyAttendance : dto.getDailyAttendances()) {
 		for (DailyAttendance dailyAttendance : dto.getDailyAttendances()) {
+			//查询一天的考勤时长
+			double attendanceHours = dailyAttendanceList.stream().filter(e -> Objects.equals(e.getWorkDate(), dailyAttendance.getWorkDate())).mapToDouble(DailyAttendance::getWorkHours).sum();
+			//查询一天所有项目的工时总和
+			double hoursTotal = dbList.stream().filter(e -> Objects.equals(e.getWorkDate(), dailyAttendance.getWorkDate())).mapToDouble(XmTechnicianHours::getWorkHours).sum();
+
+			List<XmTechnicianHours> tempList = dbList.stream().filter(e -> Objects.equals(e.getWorkDate(), dailyAttendance.getWorkDate()) && Objects.equals(e.getXmId(), dto.getXmId())).toList();
+			Long primaryKey = CollectionUtil.isEmpty(tempList) ? null : tempList.get(0).getId();
 
 
-			List<Long> primaryKeyList = dbList.stream().filter(e -> Objects.equals(e.getWorkDate(), dailyAttendance.getWorkDate())).map(XmTechnicianHours::getId).toList();
-			Long primaryKey = CollectionUtil.isEmpty(primaryKeyList) ? null : primaryKeyList.get(0);
 			XmTechnicianHours entity = new XmTechnicianHours();
 			XmTechnicianHours entity = new XmTechnicianHours();
 			if (primaryKey == null) {
 			if (primaryKey == null) {
+
+				//当 考勤时长 - 项目工时总和 - 新的工时 < 0,则提示“考勤时长不足”
+				if (attendanceHours - hoursTotal - dailyAttendance.getWorkHours() < 0) {
+					throw new ServiceException(dailyAttendance.getWorkDate() + "的考勤时长为" + attendanceHours + "小时,可填写的工时不能超过" + (attendanceHours - hoursTotal) + "小时,请核对数据");
+				}
+
 				entity.setXmId(dto.getXmId());
 				entity.setXmId(dto.getXmId());
 				entity.setUnicode(dto.getUnicode());
 				entity.setUnicode(dto.getUnicode());
 				entity.setWorkDate(dailyAttendance.getWorkDate());
 				entity.setWorkDate(dailyAttendance.getWorkDate());
@@ -172,6 +242,10 @@ public class XmTechnicianHoursServiceImpl extends BaseServiceImpl<XmTechnicianHo
 				entity.setCreateDept(Func.toLong(SecureUtil.getDeptId()));
 				entity.setCreateDept(Func.toLong(SecureUtil.getDeptId()));
 				insertList.add(entity);
 				insertList.add(entity);
 			} else {
 			} else {
+//当 			考勤时长 - 项目工时总和 + 旧工时 - 新的工时 < 0,则提示“考勤时长不足”
+				if (attendanceHours + tempList.get(0).getWorkHours() - hoursTotal - dailyAttendance.getWorkHours() < 0) {
+					throw new ServiceException(dailyAttendance.getWorkDate() + "的考勤时长为" + attendanceHours + "小时,可填写的工时不能超过" + (attendanceHours + tempList.get(0).getWorkHours() - hoursTotal) + "小时,请核对数据");
+				}
 				entity.setId(primaryKey);
 				entity.setId(primaryKey);
 				entity.setWorkHours(dailyAttendance.getWorkHours());
 				entity.setWorkHours(dailyAttendance.getWorkHours());
 				updateList.add(entity);
 				updateList.add(entity);
@@ -202,8 +276,14 @@ public class XmTechnicianHoursServiceImpl extends BaseServiceImpl<XmTechnicianHo
 		return true;
 		return true;
 	}
 	}
 
 
+	//
 	@Override
 	@Override
 	public void export(HttpServletResponse response, XmTechnicianHoursVO param) {
 	public void export(HttpServletResponse response, XmTechnicianHoursVO param) {
 
 
 	}
 	}
+
+	@Override
+	public List<JSONObject> selectListForPrint(XmTechnicianHoursVO dto) {
+		return baseMapper.selectListForPrint(dto);
+	}
 }
 }