
package org.sky.scientific.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.alibaba.excel.write.metadata.fill.FillWrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.util.CellRangeAddress;
import org.sky.core.log.exception.ServiceException;
import org.sky.core.mp.base.BaseServiceImpl;
import org.sky.core.mp.support.Condition;
import org.sky.core.mp.support.Query;
import org.sky.core.secure.utils.SecureUtil;
import org.sky.core.tool.utils.*;
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;
import org.sky.scientific.pojo.dto.XmSalaryDto;
import org.sky.scientific.pojo.dto.XmTechnicianDto;
import org.sky.scientific.pojo.entity.*;
import org.sky.scientific.pojo.vo.*;
import org.sky.scientific.service.ITechnicianService;
import org.sky.scientific.service.IXmService;
import org.sky.system.cache.DictCache;
import org.sky.system.pojo.enums.DictEnum;
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.io.OutputStream;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 技术人员花名册 服务实现类
 *
 * @author sky
 * @since 2025-06-10
 */
@Slf4j
@Service
@AllArgsConstructor
public class TechnicianServiceImpl extends BaseServiceImpl<TechnicianMapper, TechnicianEntity> implements ITechnicianService {

	private final YsRyrgfyMapper ysRyrgfyMapper;
	private final XmTechnicianMapper xmTechnicianMapper;
	private final XmMapper xmMapper;
	private final XmTechnicianHoursMapper hoursMapper;
	private final DepartmentMapper departmentMapper;
	private final SalaryMapper salaryMapper;
	private final AttendanceMapper attendanceMapper;
	private final IXmService xmService;

	@Override
	public boolean insertTechnician(TechnicianEntity technician) {
		if (StringUtil.isBlank(technician.getYearAndMonth())) {
			throw new ServiceException("参数yearAndMonth不能为空");
		}
		if (StringUtil.isAllBlank(technician.getNumber(),technician.getIdCard())) {
			throw new ServiceException("工号和身份证号码不能同时为空");
		}
		technician.setNumber(Func.isBlank(technician.getNumber()) ? null : technician.getNumber().trim());
		technician.setIdCard(Func.isBlank(technician.getIdCard()) ? null : technician.getIdCard().trim());
		String unicode = baseMapper.getUnicodeByNumberAndIdentityCard(technician.getNumber(), technician.getIdCard());
		if(Func.isBlank(unicode)){
			unicode = generateUnicode();
		}else {
			if(baseMapper.selectCount(Wrappers.<TechnicianEntity>lambdaQuery().eq(TechnicianEntity::getUnicode, unicode).eq(TechnicianEntity::getYearAndMonth, technician.getYearAndMonth())) > 0){
				StringBuilder sb = new StringBuilder();
				if(Func.isNotBlank(technician.getNumber()) && Func.isBlank(technician.getIdCard())){
					sb.append("工号已存在");
				}else if(Func.isNotBlank(technician.getIdCard()) && Func.isBlank(technician.getNumber())){
					sb.append("身份证号已存在");
				}else {
					sb.append("工号/身份证号已存在");
				}
				throw new ServiceException(sb.toString());
			}
		}
		technician.setUnicode(unicode);
		return this.save(technician);
	}

	@Override
	public boolean updateTechnician(TechnicianEntity technician) {
		if (StringUtil.isBlank(technician.getYearAndMonth())) {
			throw new ServiceException("参数yearAndMonth不能为空");
		}
		if (StringUtil.isBlank(technician.getNumber()) && StringUtil.isBlank(technician.getIdCard())) {
			throw new ServiceException("工号和身份证号码不能同时为空");
		}

		if (Func.isBlank(technician.getUnicode())) {
			throw new ServiceException("参数unicode不能为空");
		}
		technician.setNumber(Func.isBlank(technician.getNumber()) ? null : technician.getNumber().trim());
		technician.setIdCard(Func.isBlank(technician.getIdCard()) ? null : technician.getIdCard().trim());
		//修改technician.getYearAndMonth()及之后月份的数据
		LambdaQueryWrapper<TechnicianEntity> updateWrapper = Wrappers.<TechnicianEntity>lambdaQuery().eq(TechnicianEntity::getUnicode, technician.getUnicode()).ge(TechnicianEntity::getYearAndMonth, technician.getYearAndMonth());
		technician.setId(null);
		technician.setTenantId(null);
		technician.setUnicode(null);
		technician.setYearAndMonth(null);
		technician.setCreateTime(null);
		technician.setCreateUser(null);
		technician.setCreateDept(null);
		return this.update(technician, updateWrapper);
	}

	@Override
	public IPage<TechnicianVO> selectTechnicianPage(IPage<TechnicianVO> page, TechnicianDTO technician) {
		if (StringUtil.isBlank(technician.getYearAndMonth())) {
			throw new ServiceException("参数yearAndMonth不能为空");
		}
		List<TechnicianVO> voList = null;
		if (technician.getYearAndMonth().length() == 4) {
			voList = baseMapper.selectByYear(page, technician);
		} else if (technician.getYearAndMonth().length() == 6) {
			voList = baseMapper.selectTechnicianPage(page, technician);
		} else {
			throw new ServiceException("参数yearAndMonth只能是YYYY或YYYYmm格式");
		}
		return page.setRecords(voList);
	}

	@SneakyThrows
	@Override
	public void exportTechnician(HttpServletResponse response, TechnicianDTO technician) {
		List<TechnicianVO> technicianList = this.selectTechnicianPage(Condition.getPage(new Query().setCurrent(1).setSize(Integer.MAX_VALUE)), technician).getRecords();

		AtomicInteger i = new AtomicInteger(1);
		technicianList.forEach(temp -> {
			temp.setGenderName(DictCache.getValue(DictEnum.SEX, temp.getGender()));
			temp.setXh(i.getAndIncrement() + "");
		});

		InputStream templateFileName = this.getClass().getClassLoader().getResourceAsStream("export-template/基础资源管理/企业技术人员名单.xls");
		response.setContentType("application/vnd.ms-excel");
		response.setCharacterEncoding("utf-8");
		response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("企业技术人员名单.xls", Charsets.UTF_8));
		ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
			.withTemplate((templateFileName))
			.excelType(ExcelTypeEnum.XLS)
			.build();

		WriteSheet writeSheet = EasyExcel.writerSheet().build();
		//设置list集合数据
		excelWriter.fill(new FillWrapper("list", technicianList), writeSheet);
		excelWriter.finish();
		if (templateFileName != null) {
			templateFileName.close();
		}
	}

	public synchronized String generateUnicode() {
		String maxUnicode = baseMapper.getMaxUnicode();
		StringBuilder unicode = null;
		if (Func.isNull(maxUnicode)) {
			unicode = new StringBuilder("00001");
		} else {
			unicode = new StringBuilder(String.valueOf(Integer.parseInt(maxUnicode) + 1));
			while (unicode.length() < 5) {
				unicode.insert(0, "0");
			}
		}
		return unicode.toString();
	}

	@SneakyThrows
	@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<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 (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("数据已存在，导入失败！");
			}
			identifierList.add(identifier);
			technician.setNumber(Func.isBlank(excel.getNumber()) ? null : technician.getNumber().trim());
			technician.setIdCard(Func.isBlank(technician.getIdCard()) ? null : technician.getIdCard().trim());

			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 = baseMapper.getUnicodeByNumberAndIdentityCard(technician.getNumber(), technician.getIdCard());
			if (Func.isNull(unicode)) {
				unicode = generateUnicode();
			}
			technician.setUnicode(unicode);
			this.save(technician);
		}
	}


	@Transactional
	@Override
	public boolean fetchLastMonthData(TechnicianEntity technician) {
		//查询上个月的数据
		//得到上个月的年月
		String lastDate = DateUtil.format(DateUtil.minusMonths(DateUtil.parse(technician.getYearAndMonth(), DateUtil.PATTERN_YYYYMM), 1), DateUtil.PATTERN_YYYYMM);
		List<TechnicianEntity> lastMonthList = this.baseMapper.selectList(Wrappers.<TechnicianEntity>lambdaQuery().eq(TechnicianEntity::getYearAndMonth, lastDate));
		if (CollectionUtil.isEmpty(lastMonthList)) {
			throw new ServiceException("上个月人员名单为空");
		}

		List<TechnicianEntity> currentMonthList = this.baseMapper.selectList(Wrappers.<TechnicianEntity>lambdaQuery().eq(TechnicianEntity::getYearAndMonth, technician.getYearAndMonth()));
		if (Func.isEmpty(currentMonthList)) {
			//本月无数据
			lastMonthList.forEach(temp -> {
				temp.setId(null);
				temp.setYearAndMonth(technician.getYearAndMonth());
			});
			return this.saveBatch(lastMonthList);
		} else {
			//与本月数据对比，去重后保存至数据库
			List<String> unicodeList_curr = currentMonthList.stream().map(TechnicianEntity::getUnicode).toList();

			List<TechnicianEntity> insertList = new ArrayList<>();
			for (TechnicianEntity temp : lastMonthList) {
				temp.setYearAndMonth(technician.getYearAndMonth());
				temp.setId(null);

				if (!unicodeList_curr.contains(temp.getUnicode())) {
					insertList.add(temp);
				}
				if (insertList.size() % 20 == 0) {
					this.saveBatch(insertList);
					insertList.clear();
				}
			}
			if (Func.isNotEmpty(insertList)) {
				this.saveBatch(insertList);
			}
		}
		return true;
	}

	@Override
	@Transactional
	public boolean batchDelete(List<TechnicianEntity> list) {
		for (TechnicianEntity technician : list) {
			TechnicianEntity dbTechnician = baseMapper.selectById(technician.getId());
			if (Func.isNull(dbTechnician)) {
				throw new ServiceException("删除失败，技术人员不存在");
			}
			technician.setUnicode(dbTechnician.getUnicode());
			//删除人员花名册时需判断数据是否被引入
			canDeleteTechnician(dbTechnician.getUnicode(), dbTechnician.getYearAndMonth(), dbTechnician.getName());
		}
		return baseMapper.batchDelete(list.stream().map(TechnicianEntity::getUnicode).toList(), list.get(0).getYearAndMonth()) > 0;
	}

	@Transactional
	@Override
	public void zhijieyinyongyusuanbiaomingdan(String yearAndMonth) {

		int affectNum = 0;
//		直接引用预算表名单 改成引入数据，逻辑：
//		1、先选择月份，
//		2、先根据项目开发周期查项目列表，（选择月份在项目开发周期内）
//		3、根据项目引入上个月的参研人员名单（基础信息），若无数据，则走第4步
//		4、再引入预算表的人员人工费用。（只有基础信息时，附属字段为空）
//		5、回第3步。

		//先根据项目开发周期查项目列表，（选择月份在项目开发周期内）
		List<XmEntity> xmList = xmMapper.listByYearAndMonth(yearAndMonth);
		Date lastDate = DateUtil.minusMonths(DateUtil.parse(yearAndMonth, DateUtil.PATTERN_YYYYMM), 1);
		for (XmEntity xm : xmList) {

			if (Func.equals(xm.getStatus(), XmStatusEnum.已结题.getCode())) {
				continue;
			}
			//根据项目引入上个月的参研人员名单
			LambdaQueryWrapper<XmTechnicianEntity> wrapper = Wrappers.<XmTechnicianEntity>lambdaQuery()
				.eq(XmTechnicianEntity::getXmId, xm.getId())
				.eq(XmTechnicianEntity::getYearAndMonth, DateUtil.format(lastDate, DateUtil.PATTERN_YYYYMM));
			List<XmTechnicianEntity> lastMonthTechnicianList = xmTechnicianMapper.selectList(wrapper);
			//若无数据，则引入预算表的人员人工费用
			List<String> unicodeList = null;
			if (CollectionUtil.isEmpty(lastMonthTechnicianList)) {
				Query query = new Query();
				query.setCurrent(1);
				query.setSize(Integer.MAX_VALUE);

				YsRyrgfyVO vo = new YsRyrgfyVO();
				vo.setXmId(xm.getId());

				List<YsRyrgfyVO> ysRyrgfyList = ysRyrgfyMapper.selectYsRyrgfyPage(Condition.getPage(query), vo);
				unicodeList = ysRyrgfyList.stream().map(YsRyrgfyVO::getUnicode).toList();
			} else {
				unicodeList = lastMonthTechnicianList.stream().map(XmTechnicianEntity::getUnicode).toList();
			}
			affectNum += insertXmTechnician(unicodeList, xm.getId(), yearAndMonth);
		}
		if(affectNum==0){
			throw new ServiceException("项目预算表中无数据");
		}
	}

	private int insertXmTechnician(List<String> unicodeList, Long xmId, String yearAndMonth) {
		int affectNum = 0;
		for (String unicode : unicodeList) {
			XmTechnicianEntity monthlyEntity = new XmTechnicianEntity();
			monthlyEntity.setXmId(xmId);
			monthlyEntity.setUnicode(unicode);
			monthlyEntity.setYearAndMonth(yearAndMonth);
			monthlyEntity.setCreateUser(SecureUtil.getUserId());
			monthlyEntity.setUpdateUser(SecureUtil.getUserId());

			LambdaQueryWrapper<XmTechnicianEntity> queryWrapper = Wrappers.<XmTechnicianEntity>lambdaQuery().
				eq(XmTechnicianEntity::getYearAndMonth, yearAndMonth).
				eq(XmTechnicianEntity::getUnicode, unicode).
				eq(XmTechnicianEntity::getXmId, xmId);
			if (xmTechnicianMapper.selectCount(queryWrapper) == 0) {
				affectNum += xmTechnicianMapper.insert(monthlyEntity);
			}
		}
		return affectNum;
	}

	@Override
	public IPage<XmTechnicianEntity> meiyueshijicanyuanrenyuanmingdan(IPage<XmTechnicianEntity> page, XmTechnicianDto entity) {
		return xmTechnicianMapper.meiyueshijicanyuanrenyuanmingdan(page, entity);
	}

	@SneakyThrows
	@Override
	public void exportXmTechnicianMonthly(HttpServletResponse response, XmTechnicianDto dto) {
		Query query = new Query().setCurrent(1).setSize(Integer.MAX_VALUE);
		List<XmTechnicianEntity> list = this.meiyueshijicanyuanrenyuanmingdan(Condition.getPage(query), dto).getRecords();

		AtomicInteger i = new AtomicInteger(1);
		list.forEach(temp -> {
			temp.setXh(i.getAndIncrement() + "");
		});

		InputStream templateFileName = this.getClass().getClassLoader().getResourceAsStream("export-template/科技人员管理/每月实际参研人员名单.xls");
		response.setContentType("application/vnd.ms-excel");
		response.setCharacterEncoding("utf-8");
		response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("每月实际参研人员名单.xls", Charsets.UTF_8));
		ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
			.withTemplate((templateFileName))
			.excelType(ExcelTypeEnum.XLS)
			.build();

		WriteSheet writeSheet = EasyExcel.writerSheet().build();
		//设置list集合数据
		excelWriter.fill(list, writeSheet);
		Map<String, Object> map = new HashMap<>();
		map.put("year", dto.getYearAndMonth().substring(0, 4));
		map.put("month", dto.getYearAndMonth().substring(4));
		excelWriter.fill(map, writeSheet);
		excelWriter.finish();
		if (templateFileName != null) {
			templateFileName.close();
		}
	}

	@Override
	public boolean submitXmTechnicianMonthly(XmTechnicianEntity entity) {
		xmService.judgeXmIsOver(entity.getXmId(), true);

 		entity.setId(null);
		entity.setYearAndMonth(entity.getYearAndMonth());
		entity.setCreateUser(SecureUtil.getUserId());
		entity.setUpdateUser(SecureUtil.getUserId());
		entity.setTenantId(SecureUtil.getTenantId());
		LambdaQueryWrapper<XmTechnicianEntity> wrapper = Wrappers.<XmTechnicianEntity>lambdaQuery().
			eq(XmTechnicianEntity::getYearAndMonth, entity.getYearAndMonth()).
			eq(XmTechnicianEntity::getUnicode, entity.getUnicode()).
			eq(XmTechnicianEntity::getXmId, entity.getXmId());
		if (xmTechnicianMapper.selectCount(wrapper) > 0) {
			throw new ServiceException("科技人员已是项目在[" + entity.getYearAndMonth() + "]的参研人员，请勿重复新增");
		}

		return xmTechnicianMapper.insert(entity) > 0;
	}

	@Override
	public boolean physicalDeleteXmTechnicianMonthlyById(Long id) {
		if (Func.isNull(id)) {
			throw new ServiceException("参数不能为空");
		}
		XmTechnicianEntity dbXmTechnician = xmTechnicianMapper.selectById(id);
		if (Func.isNull(dbXmTechnician)) {
			throw new ServiceException("数据不存在");
		}

		xmService.judgeXmIsOver(dbXmTechnician.getXmId(), true);

		//每月研发人员工资
		if (salaryMapper.selectCount(Wrappers.<SalaryEntity>lambdaQuery().eq(SalaryEntity::getYearAndMonth, dbXmTechnician.getYearAndMonth()).eq(SalaryEntity::getUnicode, dbXmTechnician.getUnicode())) > 0) {
			throw new ServiceException("每月研发人员工资引用了该数据，无法删除");
		}
		//每月研发人员出勤
		if (attendanceMapper.countByUnicodeAndMonth(dbXmTechnician.getUnicode(), dbXmTechnician.getYearAndMonth()) > 0) {
			throw new ServiceException("每月研发人员出勤引用了该数据，无法删除");
		}
		//每月研发人员工时
		if (hoursMapper.countByUnicodeAndMonth(dbXmTechnician.getUnicode(), dbXmTechnician.getYearAndMonth()) > 0) {
			throw new ServiceException("每月研发人员工时引用了该数据，无法删除");
		}
		return xmTechnicianMapper.physicalDeleteXmTechnicianMonthlyById(id) == 1;
	}

	private void canDeleteTechnician(String unicode, String yeaAndMonth, String name) {

		if (xmMapper.countXmfzrByUnicodeAndYearMonth(unicode, yeaAndMonth) > 0) {
			throw new ServiceException("删除失败，[" + name + "]是项目负责人");
		}
		if (xmTechnicianMapper.selectCount(Wrappers.<XmTechnicianEntity>lambdaQuery().eq(XmTechnicianEntity::getUnicode, unicode).ge(XmTechnicianEntity::getYearAndMonth, yeaAndMonth)) > 0) {
			throw new ServiceException("删除失败，每月参研人员名单引用了[" + name + "]");
		}
		if (ysRyrgfyMapper.countByUnicodeAndYearMonth(unicode, yeaAndMonth) > 0) {
			throw new ServiceException("删除失败，研发项目费用预算-人员人工费用引用了[" + name + "]");
		}
		if (salaryMapper.selectCount(Wrappers.<SalaryEntity>lambdaQuery().eq(SalaryEntity::getUnicode, unicode).ge(SalaryEntity::getYearAndMonth, yeaAndMonth)) > 0) {
			throw new ServiceException("删除失败，每月研发人员工资引用了[" + name + "]");
		}
		if (attendanceMapper.selectCount(Wrappers.<AttendanceEntity>lambdaQuery().eq(AttendanceEntity::getUnicode, unicode).apply("LEFT(work_date,6) >= {0}", yeaAndMonth)) > 0) {
			throw new ServiceException("删除失败，每月研发人员出勤引用了[" + name + "]");
		}
		if (hoursMapper.selectCount(Wrappers.<XmTechnicianHours>lambdaQuery().eq(XmTechnicianHours::getUnicode, unicode).apply("LEFT(work_date,6) >= {0}", yeaAndMonth)) > 0) {
			throw new ServiceException("删除失败，每月研发工时打卡引用了[" + name + "]");
		}
	}

	@Override
	public IPage<TechnicianEntity> selectTechnicianPageByDateInterval(IPage<TechnicianEntity> page, HashMap<String, String> param) {
		if (!param.containsKey("startDate") || !param.containsKey("endDate")) {
			throw new ServiceException("startDate或者endDate为空");
		}
		String startDate = param.get("startDate");
		String endDate = param.get("endDate");
		if (startDate.contains("-")) {
			startDate = startDate.replace("-", "");
		}
		if (endDate.contains("-")) {
			endDate = endDate.replace("-", "");
		}

		if (startDate.length() == 8) {
			startDate = startDate.substring(0, 6);
		}
		if (endDate.length() == 8) {
			endDate = endDate.substring(0, 6);
		}
		param.put("startDate", startDate);
		param.put("endDate", endDate);
		List<TechnicianEntity> list = baseMapper.selectByDateInterval(page, param);
		return page.setRecords(list);
	}

	@Override
	public IPage<XmTechnicianSummary> xiangmucanyanrenyuanhuizongbiao(IPage<XmTechnicianSummary> page, XmTechnicianEntity entity) {
		if (StringUtil.isAllBlank(entity.getYearAndMonth())) {
			throw new ServiceException("参数yearAndMonth不能为空");
		}
		List<XmTechnicianSummary> summary = null;
		if (entity.getYearAndMonth().length() == 6) {
			summary = xmTechnicianMapper.xmTechnicianSummaryByMonthly(page, entity);
		} else {
			summary = xmTechnicianMapper.xmTechnicianSummaryByYear(page, entity);
		}
		return page.setRecords(summary);
	}

	@Override
	public IPage<XmTechnicianVO> canyanrenyuanhuamingce(IPage<XmTechnicianVO> page, XmTechnicianDto entity) {
		List<XmTechnicianVO> list = xmTechnicianMapper.canyanrenyuanhuamingce(page, entity);
		for (XmTechnicianVO vo : list) {
			// 参研项目列表
			vo.setXmList(xmTechnicianMapper.selectXmListByUnicodeAndYear(vo.getUnicode(), entity.getYearAndMonth()));
			// 参研时长列表
			List<DailyAttendance> attendances = hoursMapper.selectHoursListByUnicodeAndYear(vo.getUnicode(), entity.getYearAndMonth());
			vo.setHoursList(attendances);
		}
		return page.setRecords(list);
	}

	@Override
	public IPage<TechnicianVO> niandugaokejirenyuanhuizongbiao(IPage<TechnicianVO> page, TechnicianDTO dto) {
		dto.setTenantId(SecureUtil.getTenantId());
		List<TechnicianVO> list = baseMapper.niandugaokejirenyuanhuizongbiao(page, dto);
		for (TechnicianVO vo : list) {
			vo.setGenderName(DictCache.getValue(DictEnum.SEX, vo.getGender()));
		}
		return page.setRecords(list);
	}

	@Override
	public IPage<XmTechnicianSalaryVO> meiyueyanfarengongfeiyongfeifenpeibiao(IPage<XmTechnicianSalaryVO> page, XmTechnicianDto dto) {
		if (StringUtil.isBlank(dto.getYearAndMonth())) {
			throw new ServiceException("参数yearAndMonth不能为空");
		}
		dto.setTenantId(SecureUtil.getTenantId());
		List<XmTechnicianSalaryVO> list = null;
		if (dto.getYearAndMonth().length() == 6) {
			list = xmTechnicianMapper.meiyueyanfarengongfeiyongfeifenpeibiao(page, dto);
		} else if (dto.getYearAndMonth().length() == 4) {
			list = xmTechnicianMapper.meiyueyanfarengongfeiyongfeifenpeibiaoYear(page, dto);
		} else {
			throw new ServiceException("参数yearAndMonth格式错误，yyyyMM或yyyy");
		}
		return page.setRecords(list);
	}

	@SneakyThrows
	@Override
	public void exportMeiyueyanfarengongfeiyongfeifenpeibiao(OutputStream os, List<XmTechnicianSalaryVO> list, String yearAndMonth) {

		AtomicInteger index = new AtomicInteger(1);
		list.forEach(temp -> {
			temp.setXh(index.getAndIncrement() + "");
			//研发占比（%）
			temp.setYanfazhanbi(((temp.getWorkHours() <= 0 || temp.getAttendanceHours() <= 0) ? BigDecimal.ZERO : BigDecimal.valueOf(temp.getWorkHours() / temp.getAttendanceHours())));
			//社保合计
			temp.setTotalInsurance(temp.getPensionInsurance().add(temp.getMedicalInsurance()).add(temp.getUnemploymentInsurance()).add(temp.getInjuryInsurance()).add(temp.getMaternityInsurance()));
			//小计=工资+社保合计+公积金+奖金+福利费+补充养老保险+补充医疗保险
			temp.setSubTotal(temp.getAverageMonthlySalary().add(temp.getTotalInsurance()).add(temp.getProvidentFund()).add(temp.getBonus()).add(temp.getBenefit()).add(temp.getPensionInsuranceBc()).add(temp.getMedicalInsuranceBc()));
			temp.setAverageMonthlySalaryRate(temp.getAverageMonthlySalary().multiply(temp.getYanfazhanbi()));
			temp.setPensionInsuranceRate(temp.getPensionInsurance().multiply(temp.getYanfazhanbi()));
			temp.setMedicalInsuranceRate(temp.getMedicalInsurance().multiply(temp.getYanfazhanbi()));
			temp.setUnemploymentInsuranceRate(temp.getUnemploymentInsurance().multiply(temp.getYanfazhanbi()));
			temp.setInjuryInsuranceRate(temp.getInjuryInsurance().multiply(temp.getYanfazhanbi()));
			temp.setMaternityInsuranceRate(temp.getMaternityInsurance().multiply(temp.getYanfazhanbi()));
			temp.setTotalInsuranceRate(temp.getTotalInsurance().multiply(temp.getYanfazhanbi()));
			temp.setProvidentFundRate(temp.getProvidentFund().multiply(temp.getYanfazhanbi()));
			temp.setBonusRate(temp.getBonus().multiply(temp.getYanfazhanbi()));
			temp.setBenefitRate(temp.getBenefit().multiply(temp.getYanfazhanbi()));
			temp.setPensionInsuranceBcRate(temp.getPensionInsuranceBc().multiply(temp.getYanfazhanbi()));
			temp.setMedicalInsuranceBcRate(temp.getMedicalInsuranceBc().multiply(temp.getYanfazhanbi()));
			temp.setTotal(temp.getAverageMonthlySalaryRate().add(temp.getTotalInsuranceRate()).add(temp.getProvidentFundRate()).add(temp.getBonusRate()).add(temp.getBenefitRate()).add(temp.getPensionInsuranceBcRate()).add(temp.getMedicalInsuranceBcRate()));
		});

		InputStream templateFileName = this.getClass().getClassLoader().getResourceAsStream("export-template/研发费用管理/人员人工费用/每月研发人员人工分配表.xls");
		ExcelWriter excelWriter = EasyExcel.write(os)
			.withTemplate((templateFileName))
			.excelType(ExcelTypeEnum.XLS)
			.build();

		WriteSheet writeSheet = EasyExcel.writerSheet().build();
		FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
		//设置list集合数据
		excelWriter.fill(list, fillConfig, writeSheet);
		Map<String, Object> map = new HashMap<>();
		map.put("attendanceHoursSum", list.stream().mapToDouble(XmTechnicianSalaryVO::getAttendanceHours).sum());
		map.put("workHoursSum", list.stream().mapToDouble(XmTechnicianSalaryVO::getWorkHours).sum());
		map.put("averageMonthlySalaryRateSum", list.stream().map(XmTechnicianSalaryVO::getAverageMonthlySalaryRate).reduce(BigDecimal.ZERO, BigDecimal::add));
		map.put("totalInsuranceRateSum", list.stream().map(XmTechnicianSalaryVO::getTotalInsuranceRate).reduce(BigDecimal.ZERO, BigDecimal::add));
		map.put("providentFundRateSum", list.stream().map(XmTechnicianSalaryVO::getProvidentFundRate).reduce(BigDecimal.ZERO, BigDecimal::add));
		map.put("bonusRateSum", list.stream().map(XmTechnicianSalaryVO::getBonusRate).reduce(BigDecimal.ZERO, BigDecimal::add));
		map.put("benefitRateSum", list.stream().map(XmTechnicianSalaryVO::getBenefitRate).reduce(BigDecimal.ZERO, BigDecimal::add));
		map.put("pensionInsuranceBcRateSum", list.stream().map(XmTechnicianSalaryVO::getPensionInsuranceBcRate).reduce(BigDecimal.ZERO, BigDecimal::add));
		map.put("medicalInsuranceBcRateSum", list.stream().map(XmTechnicianSalaryVO::getMedicalInsuranceBcRate).reduce(BigDecimal.ZERO, BigDecimal::add));
		map.put("totalSum", list.stream().map(XmTechnicianSalaryVO::getTotal).reduce(BigDecimal.ZERO, BigDecimal::add));
		map.put("year", yearAndMonth.substring(0, 4));
		map.put("month", yearAndMonth.substring(4));
		excelWriter.fill(map, writeSheet);
		excelWriter.finish();
		if (templateFileName != null) {
			templateFileName.close();
		}
	}

	@Override
	public IPage<XmSalaryVO> meiyueyanfaxiangmurengongfenpeihuizongbiao(IPage<XmSalaryVO> page, XmSalaryDto dto) {
		if (StringUtil.isBlank(dto.getYearAndMonth())) {
			throw new ServiceException("参数yearAndMonth不能为空");
		}
		dto.setTenantId(SecureUtil.getTenantId());
		List<XmSalaryVO> list = null;
		if (dto.getYearAndMonth().length() == 6) {
			list = xmTechnicianMapper.meiyueyanfaxiangmurengongfenpeihuizongbiao(page, dto);
		} else if (dto.getYearAndMonth().length() == 4) {
			list = xmTechnicianMapper.meiyueyanfaxiangmurengongfenpeihuizongbiaoByYear(page, dto);
		} else {
			throw new ServiceException("参数yearAndMonth格式错误，yyyyMM或yyyy");
		}
		for (XmSalaryVO vo : list) {
			vo.setTotal(vo.getAverageMonthlySalary().add(vo.getTotalInsurance()).add(vo.getProvidentFund()).add(vo.getBonus()).add(vo.getBenefit()).add(vo.getPensionInsuranceBc()).add(vo.getMedicalInsuranceBc()));
		}
		return page.setRecords(list);
	}

	@SneakyThrows
	@Override
	public void exportMeiyueyanfaxiangmurengongfenpeihuizongbiao(HttpServletResponse response, XmSalaryDto dto) {
		List<XmSalaryVO> list = this.meiyueyanfaxiangmurengongfenpeihuizongbiao(Condition.getPage(new Query().setCurrent(1).setSize(Integer.MAX_VALUE)), dto).getRecords();
		AtomicInteger index = new AtomicInteger(1);
		list.forEach(temp -> {
			temp.setXh(index.getAndIncrement() + "");
			//社保合计
//			temp.setTotalInsurance(temp.getPensionInsurance().add(temp.getMedicalInsurance()).add(temp.getUnemploymentInsurance()).add(temp.getInjuryInsurance()).add(temp.getMaternityInsurance()));
			//合计=工资+社保合计+公积金+奖金+福利费+补充养老保险+补充医疗保险
//			temp.setTotal(temp.getAverageMonthlySalary().add(temp.getTotalInsurance()).add(temp.getProvidentFund()).add(temp.getBonus()).add(temp.getBenefit()).add(temp.getPensionInsuranceBc()).add(temp.getMedicalInsuranceBc()));
		});

		InputStream templateFileName = this.getClass().getClassLoader().getResourceAsStream("export-template/研发费用管理/人员人工费用/每月研发项目人工分配汇总表.xls");
		response.setContentType("application/vnd.ms-excel");
		response.setCharacterEncoding("utf-8");
		response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("每月研发项目人工分配汇总表.xls", Charsets.UTF_8));
		ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
			.withTemplate((templateFileName))
			.excelType(ExcelTypeEnum.XLS)
			.build();

		WriteSheet writeSheet = EasyExcel.writerSheet().build();
		FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
		//设置list集合数据
		excelWriter.fill(list, fillConfig, writeSheet);
		Map<String, Object> map = new HashMap<>();
		map.put("averageMonthlySalarySum", list.stream().map(XmSalaryVO::getAverageMonthlySalary).reduce(BigDecimal.ZERO, BigDecimal::add));
		map.put("insuranceSum", list.stream().map(XmSalaryVO::getTotalInsurance).reduce(BigDecimal.ZERO, BigDecimal::add));
		map.put("providentFundSum", list.stream().map(XmSalaryVO::getProvidentFund).reduce(BigDecimal.ZERO, BigDecimal::add));
		map.put("bonusSum", list.stream().map(XmSalaryVO::getBonus).reduce(BigDecimal.ZERO, BigDecimal::add));
		map.put("benefitSum", list.stream().map(XmSalaryVO::getBenefit).reduce(BigDecimal.ZERO, BigDecimal::add));
		map.put("pensionInsuranceBcSum", list.stream().map(XmSalaryVO::getPensionInsuranceBc).reduce(BigDecimal.ZERO, BigDecimal::add));
		map.put("medicalInsuranceBcSum", list.stream().map(XmSalaryVO::getMedicalInsuranceBc).reduce(BigDecimal.ZERO, BigDecimal::add));
		map.put("totalSum", list.stream().map(XmSalaryVO::getTotal).reduce(BigDecimal.ZERO, BigDecimal::add));

		String yearAndMonth = dto.getYearAndMonth();
		map.put("yearAndMonth", yearAndMonth.length() == 6 ? yearAndMonth.substring(0, 4) + "年" + yearAndMonth.substring(4) + "月" : yearAndMonth + "年");
		excelWriter.fill(map, writeSheet);
		excelWriter.finish();
		if (templateFileName != null) {
			templateFileName.close();
		}
	}

	@SneakyThrows
	@Override
	public void exportCyryhmc(OutputStream os, List<XmTechnicianVO> list, String year) {
		List<Map<String, Object>> resultList = new ArrayList<>(list.size());
		AtomicInteger i = new AtomicInteger(1);
		list.forEach(temp -> {
			temp.setXh(i.getAndIncrement() + "");
			temp.setXmmcJoin(temp.getXmList().stream().map(XmEntity::getXmmc).collect(Collectors.joining(";")));
			temp.setXmCount(temp.getXmList().size());

			List<BigDecimal> hours1 = new ArrayList<>(temp.getHoursList().stream().map(x -> BigDecimal.valueOf(x.getWorkHours())).toList());
			Map<String, Object> map = BeanUtil.beanToMap(temp);
			for (int k = 0; k < 12; k++) {
				String month = k < 10 ? "0" + (k + 1) : (k + 1) + "";
				if (temp.getHoursList().stream().noneMatch(a -> Func.equals(a.getWorkDate(), year + month))) {
					hours1.add(k, new BigDecimal(0));
				}
				map.put("month" + (k + 1), hours1.get(k));
			}
			map.put("hoursByMonth", hours1.stream().reduce(BigDecimal.ZERO, BigDecimal::add));
			resultList.add(map);
		});
		InputStream templateFileName = this.getClass().getClassLoader().getResourceAsStream("export-template/科技人员管理/参研人员花名册.xls");
		ExcelWriter excelWriter = EasyExcel.write(os)
			.withTemplate((templateFileName))
			.excelType(ExcelTypeEnum.XLS)
			.build();

		WriteSheet writeSheet = EasyExcel.writerSheet().build();
		//设置list集合数据
		excelWriter.fill(resultList, writeSheet);
		Map<String, Object> map = new HashMap<>();
		map.put("year", year);
		excelWriter.fill(map, writeSheet);
		excelWriter.finish();
		if (templateFileName != null) {
			templateFileName.close();
		}
	}

	@SneakyThrows
	@Override
	public void exportXmcyryhzb(OutputStream os, List<XmTechnicianSummary> list, String yearAndMonth) {
		InputStream templateFileName = this.getClass().getClassLoader().getResourceAsStream("export-template/科技人员管理/项目参研人员汇总表.xls");

		List<XmcyryhzmExcel> dataList = new ArrayList<>();
		AtomicInteger i = new AtomicInteger(1);
		for (XmTechnicianSummary temp : list) {
			List<XmTechnicianEntity> technicianList = temp.getTechnicianList();
			for (XmTechnicianEntity technician : technicianList) {
				XmcyryhzmExcel data = new XmcyryhzmExcel();
				data.setXh(i.getAndIncrement() + "");
				data.setXmbh(temp.getXmbh());
				data.setXmmc(temp.getXmmc());
				data.setName(technician.getName());
				data.setNumber(technician.getNumber());
				data.setIdCard(technician.getIdCard());
				data.setDepartment(technician.getDepartment());
				data.setPost(technician.getPost());
				data.setPostLocation(technician.getPostLocation());
				data.setNature(technician.getNature());
				data.setSituation(technician.getSituation());
				data.setPersonnelType(technician.getPersonnelType());
				dataList.add(data);
			}
		}

		List<CellRangeAddress> rangeCellList = createCellRange(dataList, new int[]{1, 2}, 2, XmcyryhzmExcel::getXmbh);
		ExcelWriter excelWriter = EasyExcel.write(os)
			.withTemplate((templateFileName))
			.registerWriteHandler(new MergeCellRangeWriteHandler(rangeCellList))
			.excelType(ExcelTypeEnum.XLS)
			.build();
		WriteSheet writeSheet = EasyExcel.writerSheet().build();
		//设置list集合数据
		excelWriter.fill(dataList, writeSheet);
		Map<String, Object> map = new HashMap<>();
		map.put("yearAndMonth", yearAndMonth);
		excelWriter.fill(map, writeSheet);
		excelWriter.finish();
		if (templateFileName != null) {
			templateFileName.close();
		}
	}

	/**
	 * 生成合并区域
	 *
	 * @param detailList       数据列表
	 * @param mergeColumnIndex 要合并的列索引
	 * @param startRowIndex    起始行（含表头时，表头行数）
	 * @param groupFunction    分组函数，如 e -> e.get某字段()
	 * @return 合并区域集合
	 */
	public static <T> List<CellRangeAddress> createCellRange(List<T> detailList, int[] mergeColumnIndex, int startRowIndex, Function<T, String> groupFunction) {
		if (detailList == null || detailList.isEmpty()) {
			return Collections.emptyList();
		}
		// 计算每个key下的数量
		Map<String, Long> groupMap = new LinkedHashMap<>();
		for (T item : detailList) {
			String key = groupFunction.apply(item);
			groupMap.put(key, groupMap.getOrDefault(key, 0L) + 1);
		}

		List<CellRangeAddress> rangeCellList = new ArrayList<>();
		// 当前行数
		int lineCount = startRowIndex;

		for (Map.Entry<String, Long> entry : groupMap.entrySet()) {
			int count = entry.getValue().intValue();
			if (count > 1) {
				int endRowIndex = lineCount + count - 1;
				for (int columnIndex : mergeColumnIndex) {
					rangeCellList.add(new CellRangeAddress(lineCount, endRowIndex, columnIndex, columnIndex));
				}
			}
			lineCount += count;
		}

		return rangeCellList;
	}

	@SneakyThrows
	@Override
	public void exportNdgkjryhzb(OutputStream os, List<TechnicianVO> list, String yearAndMonth) {
		AtomicInteger i = new AtomicInteger(1);
		list.forEach(temp -> {
			temp.setXh(i.getAndIncrement() + "");
		});
		InputStream templateFileName = this.getClass().getClassLoader().getResourceAsStream("export-template/科技人员管理/年度高新科技人员汇总表.xls");
		ExcelWriter excelWriter = EasyExcel.write(os)
			.withTemplate((templateFileName))
			.excelType(ExcelTypeEnum.XLS)
			.build();
		WriteSheet writeSheet = EasyExcel.writerSheet().build();
		//设置list集合数据
		excelWriter.fill(list, writeSheet);
		Map<String, Object> map = new HashMap<>();
		map.put("year", yearAndMonth);
		excelWriter.fill(map, writeSheet);
		excelWriter.finish();
		if (templateFileName != null) {
			templateFileName.close();
		}
	}

	@SneakyThrows
	@Override
	public boolean importXmTechnicianMonthly(MultipartFile file, String yearAndMonth) {
		XmTechnicianImportListener listener = new XmTechnicianImportListener();
		EasyExcel.read(new BufferedInputStream(file.getInputStream()))
			.registerReadListener(listener)
			.sheet(0)
			.headRowNumber(2) // 重要：设置表头行数(0-based)
			.doRead();
		List<XmTechnicianEntity> dataList = listener.getDataList();
		if (CollectionUtil.isEmpty(dataList)) {
			throw new ServiceException("未能从模板中读取数据，请检查模板格式是否正确");
		}
		for (XmTechnicianEntity xmTechnician : dataList) {
			String unicode = baseMapper.getUnicodeByNumberAndIdentityCard(xmTechnician.getNumber(), xmTechnician.getIdCard());
			if (Func.isBlank(unicode)) {
				throw new ServiceException(getTechnicianNotExistTips(xmTechnician.getNumber(), xmTechnician.getIdCard()));
			}
			Long xmId = xmMapper.getXmIdByXmmcOrXmbh(null, null, xmTechnician.getXmbh());
			if (Func.isNull(xmId)) {
				throw new ServiceException("项目不存在，请检查数据");
			}
			xmService.judgeXmIsOver(xmId, true);

			LambdaQueryWrapper<XmTechnicianEntity> wrapper = Wrappers.<XmTechnicianEntity>lambdaQuery().
				eq(XmTechnicianEntity::getYearAndMonth, yearAndMonth).
				eq(XmTechnicianEntity::getUnicode, unicode).
				eq(XmTechnicianEntity::getXmId, xmId);
			if (xmTechnicianMapper.selectCount(wrapper) == 0) {
				xmTechnician.setYearAndMonth(yearAndMonth);
				xmTechnician.setXmId(xmId);
				xmTechnician.setUnicode(unicode);
				xmTechnician.setCreateDept(Long.valueOf(SecureUtil.getDeptId()));
				xmTechnician.setCreateUser(SecureUtil.getUserId());
				xmTechnicianMapper.insert(xmTechnician);
			} else {
				throw new ServiceException("科技人员已是参研人员，请勿重复添加");
			}
		}
		return true;
	}

	public static String getTechnicianNotExistTips(String number, String idCard){
		StringBuilder builder = new StringBuilder();
		if(Func.isNotBlank(number)){
			builder.append("工号[").append(number).append("]");
		}
		if(Func.isNotBlank(idCard)){
			builder.append("身份证[").append(idCard).append("]");
		}
 		return builder.append("不存在").toString();
	}

}
