package cn.iocoder.yudao.module.hrm.service.employee.contract; import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeContractDO; import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO; import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeContractMapper; import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper; import com.deepoove.poi.XWPFTemplate; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.math.BigDecimal; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.HashMap; import java.util.Map; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.CONTRACT_TEMPLATE_LOAD_FAILED; import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.CONTRACT_TEMPLATE_TYPE_NOT_SUPPORT; /** * 员工合同文档生成 Service 实现类 * * @author 超级管理员 */ @Slf4j @Service public class ContractDocumentServiceImpl implements ContractDocumentService { /** 劳动合同(简版·社保折现) */ public static final int TYPE_LABOR_SIMPLE = 1; /** 劳动合同(标准·官方制式) */ public static final int TYPE_LABOR_STANDARD = 2; /** 劳务合同 */ public static final int TYPE_SERVICE = 3; private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy年MM月dd日"); private static final Map TEMPLATE_PATHS = Map.of( TYPE_LABOR_SIMPLE, "template/contract/contract_labor_simple.docx", TYPE_LABOR_STANDARD, "template/contract/contract_labor_standard.docx", TYPE_SERVICE, "template/contract/contract_service.docx"); private static final Map TEMPLATE_NAMES = Map.of( TYPE_LABOR_SIMPLE, "劳动合同(简版)", TYPE_LABOR_STANDARD, "劳动合同(标准)", TYPE_SERVICE, "劳务合同"); @Resource private HrmEmployeeContractMapper contractMapper; @Resource private HrmEmployeeMapper employeeMapper; @Override public byte[] generateDocx(Long contractId, Integer templateType) { HrmEmployeeContractDO contract = contractMapper.selectById(contractId); if (contract == null) { throw exception(CONTRACT_TEMPLATE_LOAD_FAILED); } HrmEmployeeDO employee = contract.getEmployeeId() == null ? null : employeeMapper.selectById(contract.getEmployeeId()); Map vars = buildVariables(contract, employee); String path = TEMPLATE_PATHS.get(templateType); if (path == null) { throw exception(CONTRACT_TEMPLATE_TYPE_NOT_SUPPORT); } try (InputStream in = getClass().getClassLoader().getResourceAsStream(path)) { if (in == null) { throw exception(CONTRACT_TEMPLATE_LOAD_FAILED); } XWPFTemplate template = XWPFTemplate.compile(in).render(vars); ByteArrayOutputStream out = new ByteArrayOutputStream(); template.write(out); template.close(); return out.toByteArray(); } catch (Exception e) { log.error("[generateDocx][合同({}) 模板({}) 生成失败]", contractId, templateType, e); throw exception(CONTRACT_TEMPLATE_LOAD_FAILED); } } @Override public String buildFileName(Long contractId, Integer templateType) { HrmEmployeeContractDO contract = contractMapper.selectById(contractId); String no = contract != null && contract.getContractNo() != null ? contract.getContractNo() : String.valueOf(contractId); String name = TEMPLATE_NAMES.getOrDefault(templateType, "合同"); return no + "-" + name + ".docx"; } private Map buildVariables(HrmEmployeeContractDO contract, HrmEmployeeDO employee) { Map vars = new HashMap<>(); // 甲方(公司)—— 目前取签约主体,其余留空后续可接入公司配置 vars.put("companyName", nvl(contract.getSignCompany())); vars.put("companyAddress", ""); vars.put("legalPerson", ""); vars.put("companyPhone", ""); vars.put("creditCode", ""); // 乙方(员工) vars.put("employeeName", employee != null ? nvl(employee.getName()) : ""); vars.put("gender", employee != null ? genderText(employee.getGender()) : ""); vars.put("age", employee != null ? nvl(employee.getAge()) : ""); vars.put("idCard", employee != null ? nvl(employee.getIdCard()) : ""); vars.put("householdAddress", employee != null ? nvl(employee.getAddress()) : ""); vars.put("address", employee != null ? nvl(employee.getAddress()) : ""); vars.put("phone", employee != null ? nvl(employee.getPhone()) : ""); vars.put("emergencyPhone", ""); // 合同 vars.put("contractNo", nvl(contract.getContractNo())); vars.put("postName", ""); vars.put("postDuty", ""); vars.put("workPlace", ""); vars.put("contractStart", formatDate(contract.getStartDate())); vars.put("contractEnd", formatDate(contract.getEndDate())); vars.put("contractMonths", monthsBetween(contract.getStartDate(), contract.getEndDate())); vars.put("probationStart", formatDate(contract.getProbationStartDate())); vars.put("probationEnd", formatDate(contract.getProbationEndDate())); vars.put("probationMonths", monthsBetween(contract.getProbationStartDate(), contract.getProbationEndDate())); vars.put("probationDays", daysBetween(contract.getProbationStartDate(), contract.getProbationEndDate())); vars.put("birthday", employee != null ? formatDate(employee.getBirthday()) : ""); // 年月日(供「___年___月___日」逐格填写) vars.put("contractStartY", y(contract.getStartDate())); vars.put("contractStartM", mo(contract.getStartDate())); vars.put("contractStartD", d(contract.getStartDate())); vars.put("contractEndY", y(contract.getEndDate())); vars.put("contractEndM", mo(contract.getEndDate())); vars.put("contractEndD", d(contract.getEndDate())); vars.put("probationStartY", y(contract.getProbationStartDate())); vars.put("probationStartM", mo(contract.getProbationStartDate())); vars.put("probationStartD", d(contract.getProbationStartDate())); vars.put("probationEndY", y(contract.getProbationEndDate())); vars.put("probationEndM", mo(contract.getProbationEndDate())); vars.put("probationEndD", d(contract.getProbationEndDate())); vars.put("probationSalary", nvl(contract.getProbationSalary())); vars.put("regularSalary", nvl(contract.getRegularSalary())); vars.put("baseSalary", nvl(contract.getRegularSalary())); vars.put("performanceSalary", ""); vars.put("socialSecuritySalary", ""); vars.put("signDate", formatDate(contract.getSignDate())); // 劳务合同里的可选项 vars.put("payType", "一"); vars.put("payDay", ""); vars.put("workVolume", ""); vars.put("workAmount", ""); vars.put("piecePrice", ""); vars.put("noticeCompensation", ""); vars.put("copies", "两"); vars.put("eachCopies", "一"); return vars; } private static String nvl(Object o) { return o == null ? "" : o.toString(); } private static String formatDate(LocalDate d) { return d == null ? "" : d.format(DATE_FMT); } private static String monthsBetween(LocalDate start, LocalDate end) { if (start == null || end == null) { return ""; } long months = ChronoUnit.MONTHS.between(start, end); return String.valueOf(Math.max(months, 0)); } private static String daysBetween(LocalDate start, LocalDate end) { if (start == null || end == null) { return ""; } long days = ChronoUnit.DAYS.between(start, end); return String.valueOf(Math.max(days, 0)); } private static String y(LocalDate date) { return date == null ? "" : String.format("%04d", date.getYear()); } private static String mo(LocalDate date) { return date == null ? "" : String.format("%02d", date.getMonthValue()); } private static String d(LocalDate date) { return date == null ? "" : String.format("%02d", date.getDayOfMonth()); } private static String genderText(Integer sex) { if (sex == null) { return ""; } return sex == 1 ? "男" : sex == 2 ? "女" : ""; } // 兼容 BigDecimal 直接 toString private static String nvl(BigDecimal v) { return v == null ? "" : v.toPlainString(); } }