package com.goi.erp.service; import com.goi.erp.dto.EmployeeRequestDto; import com.goi.erp.dto.EmployeeResponseDto; import com.goi.erp.entity.Employee; import com.goi.erp.entity.EntityChangeLog; import com.goi.erp.repository.EmployeeRepository; import com.goi.erp.repository.EntityChangeLogRepository; import lombok.RequiredArgsConstructor; import org.springframework.beans.BeanUtils; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.lang.reflect.Field; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.stream.Collectors; @Service @RequiredArgsConstructor @Transactional public class EmployeeService { private final EmployeeRepository employeeRepository; private final PasswordEncoder passwordEncoder; // 비밀번호 암호화 (auth-service처럼) private final EntityChangeLogRepository entityChangeLogRepository; /** * CREATE */ public EmployeeResponseDto createEmployee(EmployeeRequestDto dto) { Employee employee = Employee.builder() .empUuid(UUID.randomUUID()) .empNo(dto.getEmpNo()) .empLoginId(dto.getEmpLoginId()) .empLoginPassword(passwordEncoder.encode(dto.getEmpLoginPassword())) .empFirstName(dto.getEmpFirstName()) .empMiddleName(dto.getEmpMiddleName()) .empLastName(dto.getEmpLastName()) .empPreferredFirstName(dto.getEmpPreferredFirstName()) .empPreferredLastName(dto.getEmpPreferredLastName()) .empStatus(dto.getEmpStatus()) .empFulltime(dto.getEmpFulltime()) .empEmploymentType(dto.getEmpEmploymentType()) .empLeaveStatus(dto.getEmpLeaveStatus()) .empOfferAcceptedDate(dto.getEmpOfferAcceptedDate()) .empNationality(dto.getEmpNationality()) .empCitizenshipStatus(dto.getEmpCitizenshipStatus()) .empPassportNo(dto.getEmpPassportNo()) .empPassportCountry(dto.getEmpPassportCountry()) .empPassportIssueDate(dto.getEmpPassportIssueDate()) .empPassportExpiryDate(dto.getEmpPassportExpiryDate()) .empVisaType(dto.getEmpVisaType()) .empVisaIssueDate(dto.getEmpVisaIssueDate()) .empVisaExpiryDate(dto.getEmpVisaExpiryDate()) .empVisaCountry(dto.getEmpVisaCountry()) .empDateOfBirth(dto.getEmpDateOfBirth()) .empSex(dto.getEmpSex()) .empSin(dto.getEmpSin()) .empAccountNo(dto.getEmpAccountNo()) .empDriverLicenseNo(dto.getEmpDriverLicenseNo()) .empPersonalEmail(dto.getEmpPersonalEmail()) .empPersonalPhone(dto.getEmpPersonalPhone()) .empAddress1(dto.getEmpAddress1()) .empAddress2(dto.getEmpAddress2()) .empPostalCode(dto.getEmpPostalCode()) .empCity(dto.getEmpCity()) .empProvince(dto.getEmpProvince()) .empContractStartDate(dto.getEmpContractStartDate()) .empProbationStartDate(dto.getEmpProbationStartDate()) .empProbationEndDate(dto.getEmpProbationEndDate()) .empProbationStatus(dto.getEmpProbationStatus()) .empJobStartDate(dto.getEmpJobStartDate()) .empJobEndDate(dto.getEmpJobEndDate()) .empTerminationDate(dto.getEmpTerminationDate()) .empTerminationReason(dto.getEmpTerminationReason()) .empDeptId(dto.getEmpDeptId()) .empRate(dto.getEmpRate()) .empRateOt(dto.getEmpRateOt()) .empRateDot(dto.getEmpRateDot()) .empOfficeEmail(dto.getEmpOfficeEmail()) .empOfficeMobile(dto.getEmpOfficeMobile()) .empOfficePhone(dto.getEmpOfficePhone()) .empExtensionNo(dto.getEmpExtensionNo()) .build(); employeeRepository.save(employee); return mapToDto(employee); } /** * READ - detail */ @Transactional(readOnly = true) public EmployeeResponseDto getEmployeeByUuid(UUID empUuid) { Employee employee = employeeRepository.findByEmpUuid(empUuid) .orElseThrow(() -> new RuntimeException("Employee not found")); return mapToDto(employee); } /** * READ - list */ @Transactional(readOnly = true) public List getAllEmployees() { return employeeRepository.findAll().stream() .map(this::mapToDto) .collect(Collectors.toList()); } /** * READ - active */ @Transactional(readOnly = true) public List getActiveEmployees() { return employeeRepository.findAll().stream() .filter(e -> "A".equals(e.getEmpStatus())) .map(this::mapToDto) .collect(Collectors.toList()); } /** * READ - inactive */ @Transactional(readOnly = true) public List getInactiveEmployees() { return employeeRepository.findAll().stream() .filter(e -> "I".equals(e.getEmpStatus())) .map(this::mapToDto) .collect(Collectors.toList()); } /** * UPDATE */ public EmployeeResponseDto updateEmployee(UUID empUuid, EmployeeRequestDto newEmp) { Employee oldEmployee = employeeRepository.findByEmpUuid(empUuid) .orElseThrow(() -> new RuntimeException("Employee not found")); // 기존 값 복사본 생성 Employee beforeUpdate = new Employee(); BeanUtils.copyProperties(oldEmployee, beforeUpdate); // PATCH: 값이 있는 것만 적용 if (newEmp.getEmpNo() != null) oldEmployee.setEmpNo(newEmp.getEmpNo()); if (newEmp.getEmpLoginId() != null) oldEmployee.setEmpLoginId(newEmp.getEmpLoginId()); if (newEmp.getEmpLoginPassword() != null && !newEmp.getEmpLoginPassword().isEmpty()) oldEmployee.setEmpLoginPassword(passwordEncoder.encode(newEmp.getEmpLoginPassword())); if (newEmp.getEmpFirstName() != null) oldEmployee.setEmpFirstName(newEmp.getEmpFirstName()); if (newEmp.getEmpMiddleName() != null) oldEmployee.setEmpMiddleName(newEmp.getEmpMiddleName()); if (newEmp.getEmpLastName() != null) oldEmployee.setEmpLastName(newEmp.getEmpLastName()); if (newEmp.getEmpPreferredFirstName() != null) oldEmployee.setEmpPreferredFirstName(newEmp.getEmpPreferredFirstName()); if (newEmp.getEmpPreferredLastName() != null) oldEmployee.setEmpPreferredLastName(newEmp.getEmpPreferredLastName()); if (newEmp.getEmpStatus() != null) oldEmployee.setEmpStatus(newEmp.getEmpStatus()); if (newEmp.getEmpFulltime() != null) oldEmployee.setEmpFulltime(newEmp.getEmpFulltime()); if (newEmp.getEmpEmploymentType() != null) oldEmployee.setEmpEmploymentType(newEmp.getEmpEmploymentType()); if (newEmp.getEmpLeaveStatus() != null) oldEmployee.setEmpLeaveStatus(newEmp.getEmpLeaveStatus()); if (newEmp.getEmpOfferAcceptedDate() != null) oldEmployee.setEmpOfferAcceptedDate(newEmp.getEmpOfferAcceptedDate()); if (newEmp.getEmpNationality() != null) oldEmployee.setEmpNationality(newEmp.getEmpNationality()); if (newEmp.getEmpCitizenshipStatus() != null) oldEmployee.setEmpCitizenshipStatus(newEmp.getEmpCitizenshipStatus()); if (newEmp.getEmpPassportNo() != null) oldEmployee.setEmpPassportNo(newEmp.getEmpPassportNo()); if (newEmp.getEmpPassportCountry() != null) oldEmployee.setEmpPassportCountry(newEmp.getEmpPassportCountry()); if (newEmp.getEmpPassportIssueDate() != null) oldEmployee.setEmpPassportIssueDate(newEmp.getEmpPassportIssueDate()); if (newEmp.getEmpPassportExpiryDate() != null) oldEmployee.setEmpPassportExpiryDate(newEmp.getEmpPassportExpiryDate()); if (newEmp.getEmpVisaType() != null) oldEmployee.setEmpVisaType(newEmp.getEmpVisaType()); if (newEmp.getEmpVisaIssueDate() != null) oldEmployee.setEmpVisaIssueDate(newEmp.getEmpVisaIssueDate()); if (newEmp.getEmpVisaExpiryDate() != null) oldEmployee.setEmpVisaExpiryDate(newEmp.getEmpVisaExpiryDate()); if (newEmp.getEmpVisaCountry() != null) oldEmployee.setEmpVisaCountry(newEmp.getEmpVisaCountry()); if (newEmp.getEmpDateOfBirth() != null) oldEmployee.setEmpDateOfBirth(newEmp.getEmpDateOfBirth()); if (newEmp.getEmpSex() != null) oldEmployee.setEmpSex(newEmp.getEmpSex()); if (newEmp.getEmpSin() != null) oldEmployee.setEmpSin(newEmp.getEmpSin()); if (newEmp.getEmpAccountNo() != null) oldEmployee.setEmpAccountNo(newEmp.getEmpAccountNo()); if (newEmp.getEmpDriverLicenseNo() != null) oldEmployee.setEmpDriverLicenseNo(newEmp.getEmpDriverLicenseNo()); if (newEmp.getEmpPersonalEmail() != null) oldEmployee.setEmpPersonalEmail(newEmp.getEmpPersonalEmail()); if (newEmp.getEmpPersonalPhone() != null) oldEmployee.setEmpPersonalPhone(newEmp.getEmpPersonalPhone()); if (newEmp.getEmpAddress1() != null) oldEmployee.setEmpAddress1(newEmp.getEmpAddress1()); if (newEmp.getEmpAddress2() != null) oldEmployee.setEmpAddress2(newEmp.getEmpAddress2()); if (newEmp.getEmpPostalCode() != null) oldEmployee.setEmpPostalCode(newEmp.getEmpPostalCode()); if (newEmp.getEmpCity() != null) oldEmployee.setEmpCity(newEmp.getEmpCity()); if (newEmp.getEmpProvince() != null) oldEmployee.setEmpProvince(newEmp.getEmpProvince()); if (newEmp.getEmpContractStartDate() != null) oldEmployee.setEmpContractStartDate(newEmp.getEmpContractStartDate()); if (newEmp.getEmpProbationStartDate() != null) oldEmployee.setEmpProbationStartDate(newEmp.getEmpProbationStartDate()); if (newEmp.getEmpProbationEndDate() != null) oldEmployee.setEmpProbationEndDate(newEmp.getEmpProbationEndDate()); if (newEmp.getEmpProbationStatus() != null) oldEmployee.setEmpProbationStatus(newEmp.getEmpProbationStatus()); if (newEmp.getEmpJobStartDate() != null) oldEmployee.setEmpJobStartDate(newEmp.getEmpJobStartDate()); if (newEmp.getEmpJobEndDate() != null) oldEmployee.setEmpJobEndDate(newEmp.getEmpJobEndDate()); if (newEmp.getEmpTerminationDate() != null) oldEmployee.setEmpTerminationDate(newEmp.getEmpTerminationDate()); if (newEmp.getEmpTerminationReason() != null) oldEmployee.setEmpTerminationReason(newEmp.getEmpTerminationReason()); if (newEmp.getEmpDeptId() != null) oldEmployee.setEmpDeptId(newEmp.getEmpDeptId()); if (newEmp.getEmpRate() != null) oldEmployee.setEmpRate(newEmp.getEmpRate()); if (newEmp.getEmpRateOt() != null) oldEmployee.setEmpRateOt(newEmp.getEmpRateOt()); if (newEmp.getEmpRateDot() != null) oldEmployee.setEmpRateDot(newEmp.getEmpRateDot()); if (newEmp.getEmpOfficeEmail() != null) oldEmployee.setEmpOfficeEmail(newEmp.getEmpOfficeEmail()); if (newEmp.getEmpOfficeMobile() != null) oldEmployee.setEmpOfficeMobile(newEmp.getEmpOfficeMobile()); if (newEmp.getEmpOfficePhone() != null) oldEmployee.setEmpOfficePhone(newEmp.getEmpOfficePhone()); if (newEmp.getEmpExtensionNo() != null) oldEmployee.setEmpExtensionNo(newEmp.getEmpExtensionNo()); // 변경 로그 기록 //compareAndLogChanges(oldData, oldEmployee, changedBy); return mapToDto(oldEmployee); } /** * INACTIVATE (Soft Delete) * 직원 삭제하지 않고 emp_status = 'I' 로 비활성화 */ public EmployeeResponseDto inactivateEmployee(UUID empUuid) { Employee employee = employeeRepository.findByEmpUuid(empUuid) .orElseThrow(() -> new RuntimeException("Employee not found")); employee.setEmpStatus("I"); // Inactive return mapToDto(employee); } /** * ENTITY → DTO 변환 */ private EmployeeResponseDto mapToDto(Employee e) { EmployeeResponseDto dto = new EmployeeResponseDto(); dto.setEmpId(e.getEmpId()); dto.setEmpUuid(e.getEmpUuid()); dto.setEmpNo(e.getEmpNo()); dto.setEmpLoginId(e.getEmpLoginId()); dto.setEmpFirstName(e.getEmpFirstName()); dto.setEmpMiddleName(e.getEmpMiddleName()); dto.setEmpLastName(e.getEmpLastName()); dto.setEmpPreferredFirstName(e.getEmpPreferredFirstName()); dto.setEmpPreferredLastName(e.getEmpPreferredLastName()); dto.setEmpStatus(e.getEmpStatus()); dto.setEmpFulltime(e.getEmpFulltime()); dto.setEmpEmploymentType(e.getEmpEmploymentType()); dto.setEmpLeaveStatus(e.getEmpLeaveStatus()); dto.setEmpOfferAcceptedDate(e.getEmpOfferAcceptedDate()); dto.setEmpCreatedAt(e.getEmpCreatedAt()); dto.setEmpUpdatedAt(e.getEmpUpdatedAt()); dto.setEmpCreatedBy(e.getEmpCreatedBy()); dto.setEmpUpdatedBy(e.getEmpUpdatedBy()); dto.setEmpNationality(e.getEmpNationality()); dto.setEmpCitizenshipStatus(e.getEmpCitizenshipStatus()); dto.setEmpPassportNo(e.getEmpPassportNo()); dto.setEmpPassportCountry(e.getEmpPassportCountry()); dto.setEmpPassportIssueDate(e.getEmpPassportIssueDate()); dto.setEmpPassportExpiryDate(e.getEmpPassportExpiryDate()); dto.setEmpVisaType(e.getEmpVisaType()); dto.setEmpVisaIssueDate(e.getEmpVisaIssueDate()); dto.setEmpVisaExpiryDate(e.getEmpVisaExpiryDate()); dto.setEmpVisaCountry(e.getEmpVisaCountry()); dto.setEmpDateOfBirth(e.getEmpDateOfBirth()); dto.setEmpSex(e.getEmpSex()); dto.setEmpSin(e.getEmpSin()); dto.setEmpAccountNo(e.getEmpAccountNo()); dto.setEmpDriverLicenseNo(e.getEmpDriverLicenseNo()); dto.setEmpPersonalEmail(e.getEmpPersonalEmail()); dto.setEmpPersonalPhone(e.getEmpPersonalPhone()); dto.setEmpAddress1(e.getEmpAddress1()); dto.setEmpAddress2(e.getEmpAddress2()); dto.setEmpPostalCode(e.getEmpPostalCode()); dto.setEmpCity(e.getEmpCity()); dto.setEmpProvince(e.getEmpProvince()); dto.setEmpContractStartDate(e.getEmpContractStartDate()); dto.setEmpProbationStartDate(e.getEmpProbationStartDate()); dto.setEmpProbationEndDate(e.getEmpProbationEndDate()); dto.setEmpProbationStatus(e.getEmpProbationStatus()); dto.setEmpJobStartDate(e.getEmpJobStartDate()); dto.setEmpJobEndDate(e.getEmpJobEndDate()); dto.setEmpTerminationDate(e.getEmpTerminationDate()); dto.setEmpTerminationReason(e.getEmpTerminationReason()); dto.setEmpDeptId(e.getEmpDeptId()); dto.setEmpRate(e.getEmpRate()); dto.setEmpRateOt(e.getEmpRateOt()); dto.setEmpRateDot(e.getEmpRateDot()); dto.setEmpOfficeEmail(e.getEmpOfficeEmail()); dto.setEmpOfficeMobile(e.getEmpOfficeMobile()); dto.setEmpOfficePhone(e.getEmpOfficePhone()); dto.setEmpExtensionNo(e.getEmpExtensionNo()); return dto; } private void compareAndLogChanges(Employee oldData, Employee newData, String changedBy) { Map fieldToColumn = Map.ofEntries( Map.entry("empNo", "emp_no"), Map.entry("empLoginId", "emp_login_id"), Map.entry("empFirstName", "emp_first_name"), Map.entry("empMiddleName", "emp_middle_name"), Map.entry("empLastName", "emp_last_name"), Map.entry("empPreferredFirstName", "emp_preferred_first_name"), Map.entry("empPreferredLastName", "emp_preferred_last_name"), Map.entry("empStatus", "emp_status"), Map.entry("empFulltime", "emp_fulltime"), Map.entry("empEmploymentType", "emp_employment_type"), Map.entry("empLeaveStatus", "emp_leave_status"), Map.entry("empOfferAcceptedDate", "emp_offer_accepted_date"), Map.entry("empNationality", "emp_nationality"), Map.entry("empCitizenshipStatus", "emp_citizenship_status"), Map.entry("empPassportNo", "emp_passport_no"), Map.entry("empPassportCountry", "emp_passport_country"), Map.entry("empPassportIssueDate", "emp_passport_issue_date"), Map.entry("empPassportExpiryDate", "emp_passport_expiry_date"), Map.entry("empVisaType", "emp_visa_type"), Map.entry("empVisaIssueDate", "emp_visa_issue_date"), Map.entry("empVisaExpiryDate", "emp_visa_expiry_date"), Map.entry("empVisaCountry", "emp_visa_country"), Map.entry("empDateOfBirth", "emp_date_of_birth"), Map.entry("empSex", "emp_sex"), Map.entry("empSin", "emp_sin"), Map.entry("empAccountNo", "emp_account_no"), Map.entry("empDriverLicenseNo", "emp_driver_license_no"), Map.entry("empPersonalEmail", "emp_personal_email"), Map.entry("empPersonalPhone", "emp_personal_phone"), Map.entry("empAddress1", "emp_address1"), Map.entry("empAddress2", "emp_address2"), Map.entry("empPostalCode", "emp_postal_code"), Map.entry("empCity", "emp_city"), Map.entry("empProvince", "emp_province"), Map.entry("empContractStartDate", "emp_contract_start_date"), Map.entry("empProbationStartDate", "emp_probation_start_date"), Map.entry("empProbationEndDate", "emp_probation_end_date"), Map.entry("empProbationStatus", "emp_probation_status"), Map.entry("empJobStartDate", "emp_job_start_date"), Map.entry("empJobEndDate", "emp_job_end_date"), Map.entry("empTerminationDate", "emp_termination_date"), Map.entry("empTerminationReason", "emp_termination_reason"), Map.entry("empDeptId", "emp_dept_id"), Map.entry("empRate", "emp_rate"), Map.entry("empRateOt", "emp_rate_ot"), Map.entry("empRateDot", "emp_rate_dot"), Map.entry("empOfficeEmail", "emp_office_email"), Map.entry("empOfficeMobile", "emp_office_mobile"), Map.entry("empOfficePhone", "emp_office_phone"), Map.entry("empExtensionNo", "emp_extension_no") ); Class clazz = Employee.class; for (var entry : fieldToColumn.entrySet()) { String fieldName = entry.getKey(); String columnName = entry.getValue(); try { Field field = clazz.getDeclaredField(fieldName); field.setAccessible(true); Object oldVal = field.get(oldData); Object newVal = field.get(newData); if (valuesAreDifferent(oldVal, newVal)) { entityChangeLogRepository.save( EntityChangeLog.builder() .eclEntityType("Employee") .eclEntityId(newData.getEmpId()) .eclFieldName(fieldName) .eclColumnName(columnName) .eclOldValue(oldVal == null ? null : oldVal.toString()) .eclNewValue(newVal == null ? null : newVal.toString()) .eclChangedBy(changedBy) .eclChangedAt(LocalDateTime.now()) .eclEffectiveDate(LocalDate.now()) .build() ); } } catch (Exception e) { throw new RuntimeException("Failed to compare field: " + fieldName, e); } } } private boolean valuesAreDifferent(Object oldVal, Object newVal) { if (oldVal == null && newVal == null) return false; if (oldVal == null || newVal == null) return true; // BigDecimal (numeric 비교) if (oldVal instanceof BigDecimal oldNum && newVal instanceof BigDecimal newNum) { return oldNum.compareTo(newNum) != 0; } // LocalDate if (oldVal instanceof LocalDate oldDate && newVal instanceof LocalDate newDate) { return !oldDate.isEqual(newDate); } // LocalDateTime if (oldVal instanceof LocalDateTime oldDt && newVal instanceof LocalDateTime newDt) { return !oldDt.equals(newDt); } // Boolean if (oldVal instanceof Boolean && newVal instanceof Boolean) { return !oldVal.equals(newVal); } // 기본 equals return !oldVal.equals(newVal); } }