From 4e9436edbe0f3b2bb2a1f7e902008f77406d8c19 Mon Sep 17 00:00:00 2001 From: GUARDiA AutoDeploy Date: Sun, 5 Jul 2026 08:23:08 +0900 Subject: [PATCH] =?UTF-8?q?feat(ux):=20=EB=82=A0=EC=A7=9C=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EC=A0=84=EB=A9=B4=20=EC=BA=98=EB=A6=B0=EB=8D=94=20?= =?UTF-8?q?=EC=A0=84=ED=99=98=20=E2=80=94=20=EC=9B=B9=20datetime-local/dat?= =?UTF-8?q?e/time=20+=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20=EC=88=9C=EC=88=98JS?= =?UTF-8?q?=20CalendarPicker=20[auto-sync]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/pom.xml | 6 - .../zioinfo/hrm/admin/AdminController.java | 25 - .../com/zioinfo/hrm/admin/AdminMapper.java | 2 - .../java/com/zioinfo/hrm/ai/AiService.java | 44 +- .../com/zioinfo/hrm/auth/AuthController.java | 81 +-- .../com/zioinfo/hrm/auth/AuthService.java | 194 +------ .../java/com/zioinfo/hrm/auth/HrmUser.java | 16 - .../java/com/zioinfo/hrm/auth/JwtFilter.java | 5 +- .../java/com/zioinfo/hrm/auth/JwtUtil.java | 41 -- .../zioinfo/hrm/auth/mapper/UserMapper.java | 114 ---- .../zioinfo/hrm/config/SecurityConfig.java | 8 - backend/src/main/resources/application.yml | 35 +- .../src/main/resources/mapper/AdminMapper.xml | 4 - .../src/main/resources/mapper/UserMapper.xml | 23 +- backend/target/classes/application.yml | 53 ++ .../com/zioinfo/hrm/HrmApplication.class | Bin 0 -> 857 bytes .../zioinfo/hrm/admin/AdminController.class | Bin 0 -> 6657 bytes .../com/zioinfo/hrm/admin/AdminMapper.class | Bin 0 -> 1638 bytes .../com/zioinfo/hrm/ai/AiController.class | Bin 0 -> 3145 bytes .../com/zioinfo/hrm/ai/AiService.class | Bin 0 -> 10722 bytes .../hrm/attendance/AttendanceController.class | Bin 0 -> 7002 bytes .../hrm/attendance/AttendanceMapper.class | Bin 0 -> 2042 bytes .../hrm/attendance/AttendanceService.class | Bin 0 -> 4723 bytes .../auth/AuthController$LoginRequest.class | Bin 0 -> 1553 bytes .../com/zioinfo/hrm/auth/AuthController.class | Bin 0 -> 2606 bytes .../com/zioinfo/hrm/auth/AuthService.class | Bin 0 -> 2744 bytes .../com/zioinfo/hrm/auth/HrmUser.class | Bin 0 -> 5274 bytes .../com/zioinfo/hrm/auth/JwtFilter.class | Bin 0 -> 3010 bytes .../com/zioinfo/hrm/auth/JwtUtil.class | Bin 0 -> 3628 bytes .../zioinfo/hrm/auth/mapper/UserMapper.class | Bin 0 -> 503 bytes .../com/zioinfo/hrm/common/ApiResponse.class | Bin 0 -> 1702 bytes .../com/zioinfo/hrm/common/AuthSupport.class | Bin 0 -> 2022 bytes .../com/zioinfo/hrm/common/CryptoUtil.class | Bin 0 -> 4215 bytes .../hrm/common/GlobalExceptionHandler.class | Bin 0 -> 2891 bytes .../zioinfo/hrm/config/MyBatisConfig.class | Bin 0 -> 561 bytes .../zioinfo/hrm/config/SecurityConfig.class | Bin 0 -> 9865 bytes .../hrm/dashboard/DashboardController.class | Bin 0 -> 2429 bytes .../hrm/dashboard/DashboardMapper.class | Bin 0 -> 1042 bytes .../com/zioinfo/hrm/employee/Employee.class | Bin 0 -> 14368 bytes .../hrm/employee/EmployeeController.class | Bin 0 -> 5860 bytes .../zioinfo/hrm/employee/EmployeeMapper.class | Bin 0 -> 1904 bytes .../hrm/employee/EmployeeService.class | Bin 0 -> 6685 bytes .../integration/IntegrationController.class | Bin 0 -> 2250 bytes .../hrm/integration/IntegrationService.class | Bin 0 -> 4263 bytes .../hrm/organization/OrgController.class | Bin 0 -> 4333 bytes .../zioinfo/hrm/organization/OrgMapper.class | Bin 0 -> 863 bytes .../zioinfo/hrm/organization/OrgService.class | Bin 0 -> 4765 bytes .../hrm/payroll/PayrollController.class | Bin 0 -> 6059 bytes .../zioinfo/hrm/payroll/PayrollMapper.class | Bin 0 -> 1964 bytes .../zioinfo/hrm/payroll/PayrollService.class | Bin 0 -> 4202 bytes .../performance/PerformanceController.class | Bin 0 -> 6122 bytes .../hrm/performance/PerformanceMapper.class | Bin 0 -> 1685 bytes .../hrm/performance/PerformanceService.class | Bin 0 -> 4736 bytes .../recruitment/RecruitmentController.class | Bin 0 -> 7291 bytes .../hrm/recruitment/RecruitmentMapper.class | Bin 0 -> 2118 bytes .../hrm/recruitment/RecruitmentService.class | Bin 0 -> 5146 bytes .../hrm/training/TrainingController.class | Bin 0 -> 5895 bytes .../zioinfo/hrm/training/TrainingMapper.class | Bin 0 -> 1947 bytes .../hrm/training/TrainingService.class | Bin 0 -> 4475 bytes backend/target/classes/db/schema.sql | 515 ++++++++++++++++++ backend/target/classes/mapper/AdminMapper.xml | 80 +++ .../classes/mapper/AttendanceMapper.xml | 99 ++++ .../target/classes/mapper/DashboardMapper.xml | 71 +++ .../target/classes/mapper/EmployeeMapper.xml | 129 +++++ backend/target/classes/mapper/OrgMapper.xml | 54 ++ .../target/classes/mapper/PayrollMapper.xml | 85 +++ .../classes/mapper/PerformanceMapper.xml | 86 +++ .../classes/mapper/RecruitmentMapper.xml | 104 ++++ .../target/classes/mapper/TrainingMapper.xml | 88 +++ backend/target/classes/mapper/UserMapper.xml | 27 + frontend/index.html | 2 +- frontend/src/App.tsx | 63 +-- frontend/src/api/client.ts | 27 - frontend/src/index.css | 42 -- frontend/src/pages/AdminPage.tsx | 31 +- frontend/src/pages/LoginPage.tsx | 233 +------- frontend/src/pages/uiws/ScheduleCalendar.tsx | 16 +- 77 files changed, 1479 insertions(+), 924 deletions(-) create mode 100644 backend/target/classes/application.yml create mode 100644 backend/target/classes/com/zioinfo/hrm/HrmApplication.class create mode 100644 backend/target/classes/com/zioinfo/hrm/admin/AdminController.class create mode 100644 backend/target/classes/com/zioinfo/hrm/admin/AdminMapper.class create mode 100644 backend/target/classes/com/zioinfo/hrm/ai/AiController.class create mode 100644 backend/target/classes/com/zioinfo/hrm/ai/AiService.class create mode 100644 backend/target/classes/com/zioinfo/hrm/attendance/AttendanceController.class create mode 100644 backend/target/classes/com/zioinfo/hrm/attendance/AttendanceMapper.class create mode 100644 backend/target/classes/com/zioinfo/hrm/attendance/AttendanceService.class create mode 100644 backend/target/classes/com/zioinfo/hrm/auth/AuthController$LoginRequest.class create mode 100644 backend/target/classes/com/zioinfo/hrm/auth/AuthController.class create mode 100644 backend/target/classes/com/zioinfo/hrm/auth/AuthService.class create mode 100644 backend/target/classes/com/zioinfo/hrm/auth/HrmUser.class create mode 100644 backend/target/classes/com/zioinfo/hrm/auth/JwtFilter.class create mode 100644 backend/target/classes/com/zioinfo/hrm/auth/JwtUtil.class create mode 100644 backend/target/classes/com/zioinfo/hrm/auth/mapper/UserMapper.class create mode 100644 backend/target/classes/com/zioinfo/hrm/common/ApiResponse.class create mode 100644 backend/target/classes/com/zioinfo/hrm/common/AuthSupport.class create mode 100644 backend/target/classes/com/zioinfo/hrm/common/CryptoUtil.class create mode 100644 backend/target/classes/com/zioinfo/hrm/common/GlobalExceptionHandler.class create mode 100644 backend/target/classes/com/zioinfo/hrm/config/MyBatisConfig.class create mode 100644 backend/target/classes/com/zioinfo/hrm/config/SecurityConfig.class create mode 100644 backend/target/classes/com/zioinfo/hrm/dashboard/DashboardController.class create mode 100644 backend/target/classes/com/zioinfo/hrm/dashboard/DashboardMapper.class create mode 100644 backend/target/classes/com/zioinfo/hrm/employee/Employee.class create mode 100644 backend/target/classes/com/zioinfo/hrm/employee/EmployeeController.class create mode 100644 backend/target/classes/com/zioinfo/hrm/employee/EmployeeMapper.class create mode 100644 backend/target/classes/com/zioinfo/hrm/employee/EmployeeService.class create mode 100644 backend/target/classes/com/zioinfo/hrm/integration/IntegrationController.class create mode 100644 backend/target/classes/com/zioinfo/hrm/integration/IntegrationService.class create mode 100644 backend/target/classes/com/zioinfo/hrm/organization/OrgController.class create mode 100644 backend/target/classes/com/zioinfo/hrm/organization/OrgMapper.class create mode 100644 backend/target/classes/com/zioinfo/hrm/organization/OrgService.class create mode 100644 backend/target/classes/com/zioinfo/hrm/payroll/PayrollController.class create mode 100644 backend/target/classes/com/zioinfo/hrm/payroll/PayrollMapper.class create mode 100644 backend/target/classes/com/zioinfo/hrm/payroll/PayrollService.class create mode 100644 backend/target/classes/com/zioinfo/hrm/performance/PerformanceController.class create mode 100644 backend/target/classes/com/zioinfo/hrm/performance/PerformanceMapper.class create mode 100644 backend/target/classes/com/zioinfo/hrm/performance/PerformanceService.class create mode 100644 backend/target/classes/com/zioinfo/hrm/recruitment/RecruitmentController.class create mode 100644 backend/target/classes/com/zioinfo/hrm/recruitment/RecruitmentMapper.class create mode 100644 backend/target/classes/com/zioinfo/hrm/recruitment/RecruitmentService.class create mode 100644 backend/target/classes/com/zioinfo/hrm/training/TrainingController.class create mode 100644 backend/target/classes/com/zioinfo/hrm/training/TrainingMapper.class create mode 100644 backend/target/classes/com/zioinfo/hrm/training/TrainingService.class create mode 100644 backend/target/classes/db/schema.sql create mode 100644 backend/target/classes/mapper/AdminMapper.xml create mode 100644 backend/target/classes/mapper/AttendanceMapper.xml create mode 100644 backend/target/classes/mapper/DashboardMapper.xml create mode 100644 backend/target/classes/mapper/EmployeeMapper.xml create mode 100644 backend/target/classes/mapper/OrgMapper.xml create mode 100644 backend/target/classes/mapper/PayrollMapper.xml create mode 100644 backend/target/classes/mapper/PerformanceMapper.xml create mode 100644 backend/target/classes/mapper/RecruitmentMapper.xml create mode 100644 backend/target/classes/mapper/TrainingMapper.xml create mode 100644 backend/target/classes/mapper/UserMapper.xml diff --git a/backend/pom.xml b/backend/pom.xml index 808494f..fa16c7c 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -42,17 +42,11 @@ org.postgresqlpostgresql${postgresql.version} - - org.duckdbduckdb_jdbc1.1.3 - io.jsonwebtokenjjwt-api${jjwt.version} io.jsonwebtokenjjwt-impl${jjwt.version}runtime io.jsonwebtokenjjwt-jackson${jjwt.version}runtime - - dev.samstevens.totptotp1.7.1 - org.springdoc diff --git a/backend/src/main/java/com/zioinfo/hrm/admin/AdminController.java b/backend/src/main/java/com/zioinfo/hrm/admin/AdminController.java index 60e4e71..ca94dea 100644 --- a/backend/src/main/java/com/zioinfo/hrm/admin/AdminController.java +++ b/backend/src/main/java/com/zioinfo/hrm/admin/AdminController.java @@ -51,31 +51,6 @@ public class AdminController { return ApiResponse.ok(null); } - /** - * 관리자 OTP 초기화(SUPERADMIN). 대상 사용자의 OTP 시크릿을 폐기하고 등록을 해제한다. - * 사용자는 다음 로그인 시 OTP_SETUP(QR 재등록) 플로우를 탄다. 시크릿은 응답/로그에 노출하지 않는다. - */ - @PostMapping("/users/{id}/otp-reset") - public ApiResponse resetOtp(@PathVariable Long id, Authentication auth) { - HrmUser user = adminMapper.findUserById(id); - if (user == null) { - throw new RuntimeException("ERR-HRM-404: 대상 사용자를 찾을 수 없습니다"); - } - adminMapper.clearOtp(id); - - Map log = new HashMap<>(); - log.put("actor", AuthSupport.actor(auth)); - log.put("action", "USER_OTP_RESET"); - log.put("targetType", "auth.otp"); - log.put("targetId", user.getUsername()); - log.put("detail", "OTP 초기화(다음 로그인 재등록)"); - log.put("ipAddr", null); - adminMapper.insertAuditLog(log); - - user.setPasswordHash(null); // 비밀번호 해시 미노출 - return ApiResponse.ok(user); - } - @GetMapping("/audit") public ApiResponse> audit( @RequestParam(required = false) String actor, diff --git a/backend/src/main/java/com/zioinfo/hrm/admin/AdminMapper.java b/backend/src/main/java/com/zioinfo/hrm/admin/AdminMapper.java index 1c2c54e..e69d8a8 100644 --- a/backend/src/main/java/com/zioinfo/hrm/admin/AdminMapper.java +++ b/backend/src/main/java/com/zioinfo/hrm/admin/AdminMapper.java @@ -14,8 +14,6 @@ public interface AdminMapper { int insertUser(HrmUser user); int updateUser(HrmUser user); int updateUserActive(@Param("id") Long id, @Param("active") boolean active); - /** 관리자 OTP 초기화: 시크릿 폐기 + otp_enabled=false(다음 로그인 시 재등록 유도). 시크릿 미노출. */ - int clearOtp(@Param("id") Long id); List> findAuditLogs(@Param("actor") String actor, @Param("action") String action, @Param("offset") int offset, diff --git a/backend/src/main/java/com/zioinfo/hrm/ai/AiService.java b/backend/src/main/java/com/zioinfo/hrm/ai/AiService.java index 9325dcc..cb2df98 100644 --- a/backend/src/main/java/com/zioinfo/hrm/ai/AiService.java +++ b/backend/src/main/java/com/zioinfo/hrm/ai/AiService.java @@ -1,27 +1,37 @@ package com.zioinfo.hrm.ai; -import com.zioinfo.hrm.ai.service.AiTextRouter; -import com.zioinfo.hrm.common.ai.TextAiClient.GenResult; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.client.WebClient; +import java.time.Duration; import java.util.HashMap; import java.util.List; import java.util.Map; /** - * AI 인사 분석 서비스. 텍스트 생성은 {@link AiTextRouter} 경유(provider 선택: Claude ↔ Ollama). - * 외부 AI API 는 소유자 승인 예외인 Claude(api.anthropic.com) 만 허용, 그 외 온프레미스 Ollama 전용. - * Claude/Ollama 응답 실패 시 Java 폴백 로직으로 대체(무회귀). + * Ollama 온프레미스 AI 인사 분석 서비스. + * 외부 AI API 절대 금지 — localhost:11434 만 사용. + * Ollama 응답 실패 시 Java 폴백 로직으로 대체. */ @Slf4j @Service @RequiredArgsConstructor public class AiService { - /** provider 라우터(Claude→Ollama 폴백 + 추론 로그). 기존 Ollama 직접호출을 대체. */ - private final AiTextRouter aiTextRouter; + @Value("${guardia.ollama-url:http://localhost:11434}") + private String ollamaUrl; + + @Value("${guardia.ollama-text-model:llama3}") + private String textModel; + + private WebClient ollamaClient() { + return WebClient.builder().baseUrl(ollamaUrl) + .codecs(c -> c.defaultCodecs().maxInMemorySize(4 * 1024 * 1024)) + .build(); + } public Map predictTurnover(Long empId) { try { @@ -107,12 +117,16 @@ public class AiService { } private Map callOllama(String prompt) { - // provider 라우터 경유: provider=claude·키설정·활성 → Claude, 실패/그외 → Ollama(선택모델) 폴백. - GenResult gr = aiTextRouter.generate(prompt); - if (gr.degraded() || gr.text() == null || gr.text().isBlank()) { - return fallbackTurnover(); - } - String response = gr.text(); + Map body = Map.of("model", textModel, "prompt", prompt, "stream", false); + @SuppressWarnings("unchecked") + Map resp = ollamaClient().post().uri("/api/generate") + .bodyValue(body) + .retrieve() + .bodyToMono(Map.class) + .timeout(Duration.ofSeconds(30)) + .block(); + if (resp == null) return fallbackTurnover(); + String response = (String) resp.getOrDefault("response", "{}"); // JSON 추출 int start = response.indexOf('{'); int end = response.lastIndexOf('}'); @@ -120,10 +134,10 @@ public class AiService { // 간단 파싱: 실제로는 Jackson 사용 Map r = new HashMap<>(); r.put("raw", response.substring(start, end + 1)); - r.put("source", "ai"); + r.put("source", "ollama"); return r; } - return Map.of("response", response, "source", "ai"); + return Map.of("response", response, "source", "ollama"); } private Map fallbackTurnover() { diff --git a/backend/src/main/java/com/zioinfo/hrm/auth/AuthController.java b/backend/src/main/java/com/zioinfo/hrm/auth/AuthController.java index a32e872..3345745 100644 --- a/backend/src/main/java/com/zioinfo/hrm/auth/AuthController.java +++ b/backend/src/main/java/com/zioinfo/hrm/auth/AuthController.java @@ -1,54 +1,22 @@ package com.zioinfo.hrm.auth; -import com.zioinfo.hrm.auth.dto.ChangePasswordRequest; -import com.zioinfo.hrm.auth.dto.OtpConfirmRequest; -import com.zioinfo.hrm.auth.dto.OtpSetupResponse; -import com.zioinfo.hrm.auth.dto.OtpVerifyRequest; import com.zioinfo.hrm.common.ApiResponse; -import com.zioinfo.hrm.uiws.auth.OtpAuthService; -import com.zioinfo.hrm.uiws.auth.TwoFactorService; -import com.zioinfo.hrm.uiws.common.UiwsApiException; -import com.zioinfo.hrm.uiws.common.UiwsErrorCode; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.Map; -/** - * HRM 인증 컨트롤러. - * - /login: 2FA off 면 { token, type, twofa:"false" }. - * OTP on 이면 { twofa:"true", verifyToken, verifyMethod:(OTP|OTP_SETUP), secret?, otpAuthUri?, qrImage? }. - * 이메일 2FA on 이면 { twofa:"true", verifyToken, step, maskedEmail }. - * - /verify-otp: (UIMS 방식) verify-token + 6자리 → access/refresh(최초 로그인이면 등록 확정). - * - /verify: (이메일 2FA) verify-token + 인증코드 → access/refresh. - * 기존 클라이언트(2FA off)는 응답에 token 보존 → 회귀 0. - */ @RestController @RequestMapping("/api/hrm/auth") @RequiredArgsConstructor public class AuthController { private final AuthService authService; - private final TwoFactorService twoFactorService; - private final OtpAuthService otpAuthService; - private final JwtUtil jwtUtil; @PostMapping("/login") public ApiResponse> login(@RequestBody LoginRequest req) { - return ApiResponse.ok(authService.login(req.username(), req.password())); - } - - /** 이메일 2FA 이식: 2차 인증 코드 검증 → access/refresh 발급. */ - @PostMapping("/verify") - public ApiResponse> verify(@RequestBody VerifyRequest req) { - return ApiResponse.ok(twoFactorService.verify(req.verifyToken(), req.code())); - } - - /** TOTP 이식: 로그인 2단계 6자리 검증 → access/refresh 발급(최초 로그인이면 등록 확정). */ - @PostMapping("/verify-otp") - public ApiResponse> verifyOtp(@Valid @RequestBody OtpVerifyRequest req) { - return ApiResponse.ok(otpAuthService.verifyOtp(req.verifyToken(), req.code())); + String token = authService.login(req.username(), req.password()); + return ApiResponse.ok(Map.of("token", token, "type", "Bearer")); } @GetMapping("/me") @@ -57,50 +25,5 @@ public class AuthController { return ApiResponse.ok(authService.me(token)); } - // ── 마이페이지: OTP 등록/재설정/해제 + 비밀번호 변경 (본인, access 토큰 필요) ────────── - - /** 마이페이지 OTP 등록/재설정 시작 → { secret, otpAuthUri, qrImage }(이 응답에서만 시크릿/QR 노출). */ - @PostMapping("/otp/setup") - public ApiResponse otpSetup(@RequestHeader("Authorization") String header) { - return ApiResponse.ok(otpAuthService.setup(requireUser(header))); - } - - /** 마이페이지 OTP 확인·활성화(앱 6자리 코드). */ - @PostMapping("/otp/confirm") - public ApiResponse> otpConfirm(@RequestHeader("Authorization") String header, - @Valid @RequestBody OtpConfirmRequest req) { - otpAuthService.confirm(requireUser(header), req.code()); - return ApiResponse.ok(Map.of("result", "ok")); - } - - /** 마이페이지 OTP 해제. */ - @PostMapping("/otp/disable") - public ApiResponse> otpDisable(@RequestHeader("Authorization") String header) { - otpAuthService.disable(requireUser(header)); - return ApiResponse.ok(Map.of("result", "ok")); - } - - /** 마이페이지 비밀번호 변경(현재 비번 검증 + BCrypt). 새 비번은 응답/로그 미포함. */ - @PostMapping("/change-password") - public ApiResponse> changePassword(@RequestHeader("Authorization") String header, - @Valid @RequestBody ChangePasswordRequest req) { - authService.changePassword(requireUser(header), req); - return ApiResponse.ok(Map.of("result", "ok")); - } - - /** - * Authorization 헤더에서 인증 사용자를 해석한다. verify-token(2fa 단계용)은 거부. - * (/api/hrm/auth/** 는 permitAll 이라 필터 인증이 없으므로 여기서 access 토큰을 명시 검증.) - */ - private String requireUser(String header) { - String token = header == null ? "" : header.replace("Bearer ", "").trim(); - if (token.isEmpty() || jwtUtil.isVerifyToken(token) || !jwtUtil.isValid(token)) { - throw new UiwsApiException(UiwsErrorCode.UNAUTHORIZED); - } - return jwtUtil.getUsername(token); - } - record LoginRequest(String username, String password) {} - - record VerifyRequest(String verifyToken, String code) {} } diff --git a/backend/src/main/java/com/zioinfo/hrm/auth/AuthService.java b/backend/src/main/java/com/zioinfo/hrm/auth/AuthService.java index 14043d6..e9a4bd1 100644 --- a/backend/src/main/java/com/zioinfo/hrm/auth/AuthService.java +++ b/backend/src/main/java/com/zioinfo/hrm/auth/AuthService.java @@ -1,35 +1,13 @@ package com.zioinfo.hrm.auth; -import com.zioinfo.hrm.auth.dto.FindIdRequest; -import com.zioinfo.hrm.auth.dto.FindIdResponse; -import com.zioinfo.hrm.auth.dto.ResetPwRequest; -import com.zioinfo.hrm.auth.dto.SignupRequest; -import com.zioinfo.hrm.auth.dto.SignupResponse; -import com.zioinfo.hrm.auth.dto.ChangePasswordRequest; import com.zioinfo.hrm.auth.mapper.UserMapper; -import com.zioinfo.hrm.uiws.auth.OtpAuthService; -import com.zioinfo.hrm.uiws.auth.TwoFactorService; -import com.zioinfo.hrm.uiws.common.UiwsApiException; -import com.zioinfo.hrm.uiws.common.UiwsErrorCode; -import com.zioinfo.hrm.uiws.common.mail.MailSender; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import java.security.SecureRandom; import java.util.HashMap; import java.util.Map; -/** - * HRM 인증 서비스. - * - 기존 단일 JWT 로그인 보존(2FA off 또는 미설정 시 회귀 0). - * - UIWS 2FA 레이어(이메일 코드) 추가: 2FA on 이면 1차 통과 후 verify-token + 이메일코드 발급. - * 실패 누적 max-login-fail 회 시 계정 잠금. - * - UIWS 로그인 보조 3종(회원가입·아이디찾기·비밀번호 초기화) 이식. - */ -@Slf4j @Service @RequiredArgsConstructor public class AuthService { @@ -37,66 +15,16 @@ public class AuthService { private final UserMapper userMapper; private final PasswordEncoder passwordEncoder; private final JwtUtil jwtUtil; - private final TwoFactorService twoFactorService; - private final OtpAuthService otpAuthService; - private final MailSender mailSender; - private static final SecureRandom RANDOM = new SecureRandom(); - // 혼동 문자(0/O/1/l/I) 제외 — 임시비번 가독성. - private static final String TMP_PW_CHARS = "ABCDEFGHJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789"; - - /** - * 1차 로그인. 2FA 활성 시 verify-token + 이메일코드 흐름으로 분기, - * 비활성 시 기존처럼 access 토큰 즉시 발급. - * - * @return 2FA off: { token, type, twofa:"false" } - * 2FA on : { verifyToken, step:"EMAIL", maskedEmail, twofa:"true" } - */ - public Map login(String username, String password) { + public String login(String username, String password) { HrmUser user = userMapper.findByUsername(username); - - // 잠금 우선 차단(존재하는 계정에 한해 잠금 메시지 — 존재 여부 누설 최소화) - if (user != null && twoFactorService.isLocked(user)) { - throw new UiwsApiException(UiwsErrorCode.ACCOUNT_LOCKED); - } if (user == null || !user.isActive()) { throw new RuntimeException("ERR-AUTH-001: 존재하지 않거나 비활성 계정"); } - // 회원가입 승인 게이트(UIWS 로그인 보조 이식): 가입 신청자(approved=false)는 관리자 승인 전 로그인 차단. - // 기존 계정은 approved=true(91_uiws_port.sql 멱등 보정)라 회귀 없음. - if (Boolean.FALSE.equals(user.getApproved())) { - throw new RuntimeException("ERR-AUTH-003: 승인 대기 중인 계정입니다. 관리자 승인 후 로그인하세요."); - } if (!passwordEncoder.matches(password, user.getPasswordHash())) { - // 2FA(OTP 또는 이메일) 활성 시 실패 누적/잠금. 비활성 시 기존 동작(메시지만) 유지. - if (otpAuthService.isEnabled() || twoFactorService.isEnabled()) { - twoFactorService.recordLoginFailure(username); - HrmUser after = userMapper.findByUsername(username); - if (after != null && Boolean.TRUE.equals(after.getLocked())) { - throw new UiwsApiException(UiwsErrorCode.ACCOUNT_LOCKED); - } - } throw new RuntimeException("ERR-AUTH-002: 비밀번호 불일치"); } - - // 비밀번호 검증 통과 — 2단계 우선순위: OTP > 이메일코드 > 단일 로그인 - if (otpAuthService.isEnabled()) { - // { twofa:true, verifyToken, verifyMethod:(OTP|OTP_SETUP), secret?, otpAuthUri?, qrImage? } - return otpAuthService.beginOtp(user); - } - if (twoFactorService.isEnabled()) { - Map step1 = twoFactorService.beginTwoFactor(user); - return Map.of( - "twofa", "true", - "verifyToken", step1.get("verifyToken"), - "step", step1.get("step"), - "maskedEmail", step1.getOrDefault("maskedEmail", "")); - } - - // 2FA 비활성 — 기존 단일 로그인 흐름(회귀 0) - userMapper.resetLoginFail(username); - String token = jwtUtil.generate(username, user.getRole()); - return Map.of("twofa", "false", "token", token, "type", "Bearer"); + return jwtUtil.generate(username, user.getRole()); } public Map me(String token) { @@ -109,122 +37,4 @@ public class AuthService { m.put("displayName", u != null ? u.getDisplayName() : username); return m; } - - /** - * 마이페이지 비밀번호 변경(현재 비밀번호 검증 + BCrypt 저장). UIMS changePassword 미러. - * 현재 비번 불일치 → PASSWORD_MISMATCH, 기존과 동일 → PASSWORD_SAME_AS_OLD. - * 새 비밀번호 평문은 로그/응답/감사에 절대 기록하지 않는다(보안 불변규칙). - */ - @Transactional - public void changePassword(String username, ChangePasswordRequest req) { - HrmUser user = userMapper.findByUsername(username); - if (user == null) { - throw new UiwsApiException(UiwsErrorCode.USER_NOT_FOUND); - } - if (!passwordEncoder.matches(req.currentPassword(), user.getPasswordHash())) { - throw new UiwsApiException(UiwsErrorCode.PASSWORD_MISMATCH); - } - if (passwordEncoder.matches(req.newPassword(), user.getPasswordHash())) { - throw new UiwsApiException(UiwsErrorCode.PASSWORD_SAME_AS_OLD); - } - userMapper.updatePasswordByUsername(username, passwordEncoder.encode(req.newPassword())); - log.info("[auth] password changed: username={}", username); - } - - // ── 로그인 보조 3종 (UIWS auth 패턴 이식) ──────────────────────────────────── - - /** - * 운영자 회원가입(승인 대기 INSERT). username/email 중복 차단, 비번 BCrypt, approved=false. - * 보안: 응답에 자격증명·임시정보 미포함, 일반 메시지만. - */ - public SignupResponse signup(SignupRequest req) { - if (req.username() == null || req.username().isBlank() - || req.password() == null || req.password().length() < 4 - || req.email() == null || req.email().isBlank()) { - return new SignupResponse(false, "아이디·비밀번호(4자 이상)·이메일은 필수입니다."); - } - if (userMapper.countByUsername(req.username()) > 0) { - return new SignupResponse(false, "이미 사용 중인 아이디입니다."); - } - if (userMapper.countByEmail(req.email()) > 0) { - return new SignupResponse(false, "이미 등록된 이메일입니다."); - } - HrmUser u = new HrmUser(); - u.setUsername(req.username()); - u.setPasswordHash(passwordEncoder.encode(req.password())); - u.setDisplayName(req.displayName() != null && !req.displayName().isBlank() - ? req.displayName() : req.username()); - u.setEmail(req.email()); - // insertSignup: role='VIEWER', approved=false, locked=false, login_fail_count=0 (XML 고정) - userMapper.insertSignup(u); - log.info("[auth-helper] signup pending approval: username={}", req.username()); - return new SignupResponse(true, "가입 신청이 접수되었습니다. 관리자 승인 후 로그인할 수 있습니다."); - } - - /** - * 아이디 찾기: 표시명+이메일 동시 일치 계정 1건 조회. username 은 부분 마스킹 후 반환. - * 미발견 시 found=false(원문 username 절대 미노출). - */ - public FindIdResponse findId(FindIdRequest req) { - if (req.displayName() == null || req.displayName().isBlank() - || req.email() == null || req.email().isBlank()) { - return new FindIdResponse(false, ""); - } - HrmUser u = userMapper.findByDisplayNameAndEmail(req.displayName(), req.email()); - if (u == null) { - return new FindIdResponse(false, ""); - } - return new FindIdResponse(true, maskUsername(u.getUsername())); - } - - /** - * 비밀번호 초기화: username+email 일치 검증 → 임시비번 생성·BCrypt 저장·잠금/실패카운트 해제·변경유도. - * 임시비번은 메일(미설정 시 LogMailSender 로그)로만 전달. API 응답·로그 메시지에 비번 미노출. - * 대상 미존재여도 success=true(계정 열거 방지). - */ - @Transactional - public SignupResponse resetPassword(ResetPwRequest req) { - final String okMsg = "임시 비밀번호를 등록된 이메일로 발송했습니다. 메일을 확인하세요."; - if (req.username() == null || req.username().isBlank() - || req.email() == null || req.email().isBlank()) { - return new SignupResponse(false, "아이디와 이메일을 모두 입력하세요."); - } - HrmUser u = userMapper.findByUsernameAndEmail(req.username(), req.email()); - if (u == null) { - // 존재 여부 누설 방지 — 동일 성공 메시지 반환(실제 발송 없음). - log.info("[auth-helper] reset-password no match (suppressed): username={}", req.username()); - return new SignupResponse(true, okMsg); - } - String tempPw = generateTempPassword(); - // applyTempPassword: BCrypt 저장 + pw_change_yn=true + locked=false + login_fail_count=0 (멱등) - userMapper.applyTempPassword(req.username(), passwordEncoder.encode(tempPw)); - - String subject = "[GUARDiA HRM] 임시 비밀번호 안내"; - String body = String.format( - "안녕하세요 %s 님,\n임시 비밀번호는 [%s] 입니다.\n로그인 후 즉시 비밀번호를 변경하세요.", - u.getDisplayName() != null ? u.getDisplayName() : u.getUsername(), tempPw); - // 메일 본문에만 임시비번 포함. mailSender 미설정 환경은 LogMailSender 폴백(서버 로그). - mailSender.send(u.getEmail(), subject, body); - log.info("[auth-helper] reset-password issued temp pw (sent via mail/log): username={}", req.username()); - return new SignupResponse(true, okMsg); - } - - private static String generateTempPassword() { - StringBuilder sb = new StringBuilder(10); - for (int i = 0; i < 10; i++) { - sb.append(TMP_PW_CHARS.charAt(RANDOM.nextInt(TMP_PW_CHARS.length()))); - } - return sb.toString(); - } - - /** username 부분 마스킹: 앞 2자만 노출(예: admin → ad***). 2자 이하는 첫 글자만. */ - private static String maskUsername(String username) { - if (username == null || username.isBlank()) { - return ""; - } - if (username.length() <= 2) { - return username.charAt(0) + "*"; - } - return username.substring(0, 2) + "*".repeat(Math.max(1, username.length() - 2)); - } } diff --git a/backend/src/main/java/com/zioinfo/hrm/auth/HrmUser.java b/backend/src/main/java/com/zioinfo/hrm/auth/HrmUser.java index ef06201..ad98500 100644 --- a/backend/src/main/java/com/zioinfo/hrm/auth/HrmUser.java +++ b/backend/src/main/java/com/zioinfo/hrm/auth/HrmUser.java @@ -17,20 +17,4 @@ public class HrmUser { private String role; private boolean active; private LocalDateTime createdAt; - - // UIWS 2FA 이식 컬럼 (멱등 ALTER — db/91_uiws_port.sql). - private String emailVerifyCode; - private LocalDateTime emailVerifyExpire; - private Integer loginFailCount; - private Boolean locked; - /** TOTP 시크릿(보류/확정 공용). API 응답·로그에 절대 미노출. (db/91_uiws_port.sql ALTER) */ - private String otpSecret; - /** OTP 등록 확정 여부(기본 false). 최초 로그인 verify 성공 시 true 로 확정(멱등). (db/93_auth_otp.sql ALTER) */ - private Boolean otpEnabled; - - // UIWS 로그인 보조기능 이식 컬럼 (멱등 ALTER — db/91_uiws_port.sql). - // approved: 가입 신청자는 false(관리자 승인 전 로그인 차단), 기존 계정은 true 보정. - // pwChangeYn: 임시비번 발급 시 true(다음 로그인 시 변경 유도). - private Boolean approved; - private Boolean pwChangeYn; } diff --git a/backend/src/main/java/com/zioinfo/hrm/auth/JwtFilter.java b/backend/src/main/java/com/zioinfo/hrm/auth/JwtFilter.java index e60bf2c..6e597ea 100644 --- a/backend/src/main/java/com/zioinfo/hrm/auth/JwtFilter.java +++ b/backend/src/main/java/com/zioinfo/hrm/auth/JwtFilter.java @@ -26,10 +26,7 @@ public class JwtFilter extends OncePerRequestFilter { String header = req.getHeader("Authorization"); if (header != null && header.startsWith("Bearer ")) { String token = header.substring(7); - // 보안(UIWS 2FA): purpose=2fa verify-token 은 access 토큰이 아니다. - // 동일 서명키라 isValid()는 통과하므로 별도 차단하지 않으면 2차 인증 전 보호 API 접근(2FA 우회)이 가능. - // → verify-token 은 인증 컨텍스트를 세우지 않고 무시한다(/api/hrm/auth/verify 에서만 사용). - if (jwtUtil.isValid(token) && !jwtUtil.isVerifyToken(token)) { + if (jwtUtil.isValid(token)) { String username = jwtUtil.getUsername(token); String role = jwtUtil.getRole(token); var auth = new UsernamePasswordAuthenticationToken( diff --git a/backend/src/main/java/com/zioinfo/hrm/auth/JwtUtil.java b/backend/src/main/java/com/zioinfo/hrm/auth/JwtUtil.java index 6df8619..2656647 100644 --- a/backend/src/main/java/com/zioinfo/hrm/auth/JwtUtil.java +++ b/backend/src/main/java/com/zioinfo/hrm/auth/JwtUtil.java @@ -34,47 +34,6 @@ public class JwtUtil { .compact(); } - /** - * UIWS 2FA 이식: 1차 로그인 통과 후 발급하는 단기 verify-token. - * purpose=2fa 클레임으로 access 토큰과 구분(verify-token 으로는 보호 API 접근 불가). - */ - public String generateVerifyToken(String username, long validitySeconds) { - return Jwts.builder() - .subject(username) - .claim("purpose", "2fa") - .issuedAt(new Date()) - .expiration(new Date(System.currentTimeMillis() + validitySeconds * 1000L)) - .signWith(key()) - .compact(); - } - - /** verify-token 검증 후 username 반환. 유효하지 않거나 purpose!=2fa 면 null. */ - public String parseVerifyTokenUsername(String token) { - try { - Claims c = parse(token); - if (!"2fa".equals(c.get("purpose", String.class))) { - return null; - } - return c.getSubject(); - } catch (JwtException | IllegalArgumentException e) { - log.debug("verify-token 검증 실패: {}", e.getMessage()); - return null; - } - } - - /** - * 보안: 토큰이 2FA verify-token(purpose=2fa)인지 판별. - * JwtFilter 가 access 토큰만 인증 컨텍스트로 인정하도록 verify-token 을 걸러내는 데 사용 - * (2차 인증 전 verify-token 으로 보호 API 에 접근하는 2FA 우회 차단). - */ - public boolean isVerifyToken(String token) { - try { - return "2fa".equals(parse(token).get("purpose", String.class)); - } catch (JwtException | IllegalArgumentException e) { - return false; - } - } - public Claims parse(String token) { return Jwts.parser().verifyWith(key()).build() .parseSignedClaims(token).getPayload(); diff --git a/backend/src/main/java/com/zioinfo/hrm/auth/mapper/UserMapper.java b/backend/src/main/java/com/zioinfo/hrm/auth/mapper/UserMapper.java index ecb7c17..28bdf7c 100644 --- a/backend/src/main/java/com/zioinfo/hrm/auth/mapper/UserMapper.java +++ b/backend/src/main/java/com/zioinfo/hrm/auth/mapper/UserMapper.java @@ -3,123 +3,9 @@ package com.zioinfo.hrm.auth.mapper; import com.zioinfo.hrm.auth.HrmUser; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; -import org.apache.ibatis.annotations.Select; -import org.apache.ibatis.annotations.Update; -import java.time.LocalDateTime; - -/** - * HRM 운영자 계정 매퍼. - * 기존 메서드(findByUsername/insert)에 UIWS 2FA·로그인 보조기능 이식 메서드를 추가. - * 대상 테이블 hrm_users (컬럼 active — pms_user.is_active 와 다름). - */ @Mapper public interface UserMapper { - HrmUser findByUsername(@Param("username") String username); - int insert(HrmUser user); - - /** 회원가입(승인 대기): username·password_hash·display_name·email + role='VIEWER'·approved=false. */ - int insertSignup(HrmUser user); - - // ── UIWS 로그인 보조기능 이식(회원가입·아이디찾기·비번초기화) ─────────────── - - /** 가입 중복 검사: username 존재 여부. */ - @Select("SELECT COUNT(1) FROM hrm_users WHERE username = #{username}") - int countByUsername(@Param("username") String username); - - /** 가입 중복 검사: email 존재 여부(enumeration 최소화 — 내부 사용). */ - @Select("SELECT COUNT(1) FROM hrm_users WHERE email = #{email}") - int countByEmail(@Param("email") String email); - - /** 아이디찾기: 이름(display_name)·이메일 일치 사용자(존재 시 마스킹 반환). */ - @Select(""" - SELECT id, username, password_hash, role, active, created_at, - display_name, email, - email_verify_code, email_verify_expire, login_fail_count, locked, otp_secret, - approved, pw_change_yn - FROM hrm_users - WHERE display_name = #{displayName} AND email = #{email} - ORDER BY id LIMIT 1 - """) - HrmUser findByDisplayNameAndEmail(@Param("displayName") String displayName, - @Param("email") String email); - - /** 비번초기화: username·email 동시 일치 검증용. */ - @Select(""" - SELECT id, username, password_hash, role, active, created_at, - display_name, email, - email_verify_code, email_verify_expire, login_fail_count, locked, otp_secret, - approved, pw_change_yn - FROM hrm_users - WHERE username = #{username} AND email = #{email} - """) - HrmUser findByUsernameAndEmail(@Param("username") String username, - @Param("email") String email); - - /** 비번초기화: 임시비번 적용 + 변경유도 + 잠금/실패카운트 해제(멱등 UPDATE). */ - @Update(""" - UPDATE hrm_users - SET password_hash = #{passwordHash}, pw_change_yn = true, - locked = false, login_fail_count = 0 - WHERE username = #{username} - """) - int applyTempPassword(@Param("username") String username, - @Param("passwordHash") String passwordHash); - - // ── UIWS 2FA 이식: 실패 카운트/잠금/인증코드 갱신 (멱등 UPDATE) ─────────────── - - /** 로그인 성공 시 실패 카운트 초기화. */ - @Update("UPDATE hrm_users SET login_fail_count = 0 WHERE username = #{username}") - int resetLoginFail(@Param("username") String username); - - /** 로그인 실패 누적(+1) 및 임계 도달 시 잠금. */ - @Update(""" - UPDATE hrm_users - SET login_fail_count = COALESCE(login_fail_count, 0) + 1, - locked = (COALESCE(login_fail_count, 0) + 1 >= #{maxFail}) - WHERE username = #{username} - """) - int incrementLoginFail(@Param("username") String username, @Param("maxFail") int maxFail); - - /** 1차 통과 시 이메일 인증코드/만료 저장(verify 단계에서 검증). */ - @Update(""" - UPDATE hrm_users - SET email_verify_code = #{code}, email_verify_expire = #{expire}, login_fail_count = 0 - WHERE username = #{username} - """) - int saveEmailCode(@Param("username") String username, - @Param("code") String code, - @Param("expire") LocalDateTime expire); - - /** 2차 검증 성공 시 코드 폐기. */ - @Update("UPDATE hrm_users SET email_verify_code = NULL, email_verify_expire = NULL WHERE username = #{username}") - int clearEmailCode(@Param("username") String username); - - /** 관리자 잠금 해제(실패 카운트/잠금 초기화). */ - @Update("UPDATE hrm_users SET locked = false, login_fail_count = 0 WHERE username = #{username}") - int unlock(@Param("username") String username); - - /** - * BCrypt 비밀번호 해시 갱신(username 기준). 평문은 절대 저장하지 않는다. - * 마이페이지 비밀번호 변경 + AdminPasswordSeeder(env 재시드) 공용. - */ - @Update("UPDATE hrm_users SET password_hash = #{passwordHash} WHERE username = #{username}") - int updatePasswordByUsername(@Param("username") String username, - @Param("passwordHash") String passwordHash); - - // ── TOTP(OTP 2차 인증) 이식 (멱등 UPDATE) ──────────────────────────────────── - - /** 보류 시크릿 저장(등록 확정 전). otp_enabled 은 건드리지 않는다(verify/confirm 에서 확정). */ - @Update("UPDATE hrm_users SET otp_secret = #{secret} WHERE username = #{username}") - int updateOtpSecret(@Param("username") String username, @Param("secret") String secret); - - /** 등록 확정: otp_enabled=true (시크릿은 유지). */ - @Update("UPDATE hrm_users SET otp_enabled = true WHERE username = #{username}") - int enableOtp(@Param("username") String username); - - /** 마이페이지 해제: 시크릿 폐기 + otp_enabled=false. */ - @Update("UPDATE hrm_users SET otp_secret = NULL, otp_enabled = false WHERE username = #{username}") - int disableOtp(@Param("username") String username); } diff --git a/backend/src/main/java/com/zioinfo/hrm/config/SecurityConfig.java b/backend/src/main/java/com/zioinfo/hrm/config/SecurityConfig.java index 981f824..73bba0c 100644 --- a/backend/src/main/java/com/zioinfo/hrm/config/SecurityConfig.java +++ b/backend/src/main/java/com/zioinfo/hrm/config/SecurityConfig.java @@ -41,18 +41,10 @@ public class SecurityConfig { .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/hrm/auth/**").permitAll() - // UIWS 로그인 보조 3종(회원가입·아이디찾기·비번초기화) — 로그인 전 무인증 접근. - .requestMatchers("/api/auth/**").permitAll() - // UIWS system: 가입 화면 공개 조회(부서/회사 GET) — permitAll. - .requestMatchers(HttpMethod.GET, "/api/public/**").permitAll() .requestMatchers("/actuator/health").permitAll() .requestMatchers("/swagger-ui/**", "/v3/api-docs/**", "/api/hrm/docs/**", "/api/hrm/swagger/**").permitAll() .requestMatchers("/", "/index.html", "/assets/**", "/favicon.ico").permitAll() - // UIWS system(권한관리): 조회는 MANAGER 이상, 변경은 SUPERADMIN. - .requestMatchers(HttpMethod.GET, "/api/system/**").hasAnyRole("SUPERADMIN", "MANAGER") - .requestMatchers("/api/system/**").hasRole("SUPERADMIN") - .requestMatchers("/api/hrm/admin/users/**").hasRole("SUPERADMIN") .requestMatchers(HttpMethod.GET, "/api/hrm/admin/settings").hasAnyRole("SUPERADMIN", "MANAGER") .requestMatchers("/api/hrm/admin/settings/**").hasRole("SUPERADMIN") diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 4f1a60a..5bd7742 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -15,14 +15,8 @@ spring: minimum-idle: 1 sql: init: - # schema.sql(전부 IF NOT EXISTS + ON CONFLICT DO NOTHING)·UIWS 이식 파일(전부 멱등)만 재적용. - # 91_uiws_port.sql: 업무 모듈 tb_uiws_* + hrm_users 2FA/로그인보조 컬럼 ALTER(멱등). - # 92_uiws_system.sql: UIWS system(권한관리) tb_uiws_* 10테이블(부서/회사/코드/사용자/역할/메뉴/프로그램, 멱등). - mode: ${SQL_INIT_MODE:always} - # 104_seed_ai_config.sql: AI 플랫폼(provider/모델) 설정 시드 hrm_settings ai.* (멱등 ON CONFLICT DO NOTHING). - # 93_auth_otp.sql: hrm_users otp_enabled 멱등 ALTER(TOTP 2차 인증). ops_otp_reset_all.sql 은 미등재(운영 1회 수동). - schema-locations: classpath:db/schema.sql,classpath:db/91_uiws_port.sql,classpath:db/92_uiws_system.sql,classpath:db/93_auth_otp.sql,classpath:db/104_seed_ai_config.sql - continue-on-error: true + mode: ${SQL_INIT_MODE:never} + schema-locations: classpath:db/schema.sql servlet: multipart: max-file-size: 20MB @@ -44,38 +38,15 @@ guardia: erp-url: ${ERP_URL:http://localhost:8003} itsm-url: ${ITSM_URL:http://localhost:9001} groupware-url: ${GROUPWARE_URL:http://localhost:8009} - # 보안 불변 규칙: Ollama 온프레미스만 허용. 외부 AI API 절대 금지(Claude 는 소유자 승인 예외). + # 보안 불변 규칙: Ollama 온프레미스만 허용. 외부 AI API 절대 금지. ollama-url: ${OLLAMA_URL:http://localhost:11434} ollama-text-model: ${OLLAMA_TEXT_MODEL:llama3} - # 중앙 guardia-rag 연동(피드백 전달). base-url 은 서버 내부 루프백 전용 — 외부 URL 금지. - rag: - base-url: ${RAG_URL:http://127.0.0.1:8020} - enabled: ${RAG_ENABLED:true} - # 로컬 임베디드 DuckDB 학습 저장소 파일 경로(솔루션별 격리). - learning: - duckdb-path: ${HRM_LEARNING_DUCKDB:/opt/guardia-hrm/data/hrm_learning.duckdb} crypto: secret: ${HRM_CRYPTO_SECRET:guardia-hrm-aes-256-gcm-master-key-2026-zioinfo} jwt: secret: ${JWT_SECRET:guardia-hrm-jwt-secret-2026-minimum-256bit-key-zioinfo} expiration: 86400000 -# UIWS 이식 모듈 설정 (worklog·schedule·message·stats + 2FA 레이어 + 로그인 보조). -# 2FA 토글: hrm.uiws.auth.twofa-enabled=false 면 기존 단일 JWT 로그인 회귀 0. -hrm: - uiws: - auth: - twofa-enabled: ${UIWS_2FA:true} - # TOTP(OTP 2차 인증, UIMS 방식) on/off. 기본 on. 우선순위: otp → 이메일코드 → 단일. off 시 이메일/단일 경로로 폴백. - otp-enabled: ${UIWS_OTP:true} - verify-token-validity-seconds: 300 # verify-token 5분 - email-code-validity-seconds: 300 # 이메일 코드 5분 - max-login-fail: 5 # 실패 5회 잠금 - mail: - mode: ${UIWS_MAIL_MODE:log} # log 폴백(외부 API 금지). smtp 운영 시 별도 구현 - upload: - upload-dir: ${UIWS_UPLOAD_DIR:./uploads/uiws} - logging: level: com.zioinfo.hrm: DEBUG diff --git a/backend/src/main/resources/mapper/AdminMapper.xml b/backend/src/main/resources/mapper/AdminMapper.xml index 08480f0..1459b2c 100644 --- a/backend/src/main/resources/mapper/AdminMapper.xml +++ b/backend/src/main/resources/mapper/AdminMapper.xml @@ -39,10 +39,6 @@ UPDATE hrm_users SET active=#{active} WHERE id=#{id} - - UPDATE hrm_users SET otp_secret=NULL, otp_enabled=false WHERE id=#{id} - - - SELECT id, username, password_hash, display_name, email, role, active, created_at, - email_verify_code, email_verify_expire, login_fail_count, locked, otp_secret, otp_enabled, - approved, pw_change_yn + SELECT id, username, password_hash, display_name, email, role, active, created_at FROM hrm_users WHERE username = #{username} @@ -36,13 +24,4 @@ VALUES (#{username}, #{passwordHash}, #{displayName}, #{email}, #{role}, #{active}) - - - INSERT INTO hrm_users (username, password_hash, role, display_name, email, - active, approved, login_fail_count, locked) - VALUES (#{username}, #{passwordHash}, 'VIEWER', #{displayName}, #{email}, - true, false, 0, false) - - diff --git a/backend/target/classes/application.yml b/backend/target/classes/application.yml new file mode 100644 index 0000000..5bd7742 --- /dev/null +++ b/backend/target/classes/application.yml @@ -0,0 +1,53 @@ +server: + port: 8014 + +spring: + application: + name: guardia-hrm + datasource: + url: ${DB_URL:jdbc:postgresql://localhost:5432/hrm_db} + username: ${DB_USER:hrm_user} + password: ${DB_PASS:hrm_pass2026} + driver-class-name: org.postgresql.Driver + hikari: + # 운영 함정 준수: 다중 솔루션 동시 기동 시 PG max_connections 보호 — 풀 3개 캡. + maximum-pool-size: ${DB_POOL_MAX:3} + minimum-idle: 1 + sql: + init: + mode: ${SQL_INIT_MODE:never} + schema-locations: classpath:db/schema.sql + servlet: + multipart: + max-file-size: 20MB + max-request-size: 20MB + +mybatis: + mapper-locations: classpath:mapper/**/*.xml + configuration: + map-underscore-to-camel-case: true + log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl + +springdoc: + api-docs: + path: /api/hrm/docs + swagger-ui: + path: /api/hrm/swagger + +guardia: + erp-url: ${ERP_URL:http://localhost:8003} + itsm-url: ${ITSM_URL:http://localhost:9001} + groupware-url: ${GROUPWARE_URL:http://localhost:8009} + # 보안 불변 규칙: Ollama 온프레미스만 허용. 외부 AI API 절대 금지. + ollama-url: ${OLLAMA_URL:http://localhost:11434} + ollama-text-model: ${OLLAMA_TEXT_MODEL:llama3} + crypto: + secret: ${HRM_CRYPTO_SECRET:guardia-hrm-aes-256-gcm-master-key-2026-zioinfo} + jwt: + secret: ${JWT_SECRET:guardia-hrm-jwt-secret-2026-minimum-256bit-key-zioinfo} + expiration: 86400000 + +logging: + level: + com.zioinfo.hrm: DEBUG + org.mybatis: WARN diff --git a/backend/target/classes/com/zioinfo/hrm/HrmApplication.class b/backend/target/classes/com/zioinfo/hrm/HrmApplication.class new file mode 100644 index 0000000000000000000000000000000000000000..bbaf961659ce196bd4b988ba30bf1ddb56d575f9 GIT binary patch literal 857 zcmb7CO>fgc5Ph2_O=?105@;zOE!@%^*c%*DDzy=agZU6a;lOF_Y@99jZnRzl?Z4s# z;=m8!N1@J|sx^{@#KAM(nVmOp-t4d6KYjvuioG@(47;((_?a?F&kbMLjK8qi(Q=up zSU6>L8%>7I6Y)v#ROp1ij!tCk7*-FIR_>5tWw1YGXpYTH23Wyb7cHzZY=lb7S49>{ z`%XluWaxz^7U@)2<^lQLMDt&5wuMHAS9fTKYLcbtkmAJugV>)>Ofo&4hX*yy=R z3hM`~^r5g%`yqr)Xxx4cGO7Ghx`mm&5uUb_mM#VpQ`lHOS6;yW-P&1*1ceZHnKfuSGA9Tmw6$y@R{t*NNjj^7rZ6 G1Meilf|i$xN$IG=rWeZ_QaA#%#55m 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 literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/admin/AdminMapper.class b/backend/target/classes/com/zioinfo/hrm/admin/AdminMapper.class new file mode 100644 index 0000000000000000000000000000000000000000..ca10db514034ce8896ca46f3190905d9fb64df60 GIT binary patch literal 1638 zcmbVMO>fgc5FLl4q3H){DJ}g1+R`=w+O0Str67d^A}Cc65gfQ}lTEV4_D1$PQu%2d z_yPPV#M_M%J4)0>9Bl85XWqVrnBU_c?R9-(i|@{=%*s0e*iDS}{-)M)=&Ur@!xE`0zf&pSj^WV$m&n)B z6W?SguUzq&2iy}{1XbTxnro?ikUy-epu>2NH@kw#E4V|(lK^J3KquGdZiHGg5@d=O zLU&bbgb191^ubjd!f0*jc5daGL)plN7NTB@Yca?xCPuZXWgq*kP1OC}WY6*V|B}@r zYDryJ9q@RQj&JBdqEo5P#hzMiie&0PQ_oZCV{l^A3N=Y!Fhfw-Xq-b83jMOxC?5;e zZifORmUE>i@m+2zh*(YPJD!PT`4dtHiVSnjoQ@N%m_`o!WoLJG_9pe7t2*(VHiXvb zH2PngGV96Vn0*7&o&}?A4DHatl*-d80vkECyQ5|l2nGHSHx4aDJu}%O9(f34ip9(~ zR1`GD2kBx^i>Zs_)Ku)y=J){|J9kHSoI6y58>lkp5P0C$B3i&@l%qM=c~}{`4||>p zSS`R_G=YnFmO@&l2k_)D0%UJ6T7f@ytyVGkTYPj uKdkq_!aYy*?GT|ybUY$nn&@A7m|V8WFQ(zX#O@h-W#1=simy?I5&Z*y=becF literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/ai/AiController.class b/backend/target/classes/com/zioinfo/hrm/ai/AiController.class new file mode 100644 index 0000000000000000000000000000000000000000..4fb4726fb86b34e2ceb70d69e17950a1bcbab541 GIT binary patch literal 3145 zcmbVOZC4vb7=0$Z1j1Gz7PL|eR9ktGt}g|vP%KajXebyEwbc)kWJsoDcj9D15B2yb z{3UvhwjMwD1N>2*-q|e4CIs1Xa&~uScJ`S&_ul83{QKYE{}9n#`XNaPM&o(qxzD7M z{(*8!+H<*dSLB-VjaKEd&`IiMH1vcYaktFyqa~n3pw)N0xQrj8E7_WFq$l=eRp!cK#rKurc0W}{w=+r? z-D<@KIMCb^PnAA&pNgEDlYYVFjk)f+Fj%%?7h=?N#LG2o<*Iwa(p;rMll!yqDw~C0 z7+r5&d57B-jLVy-;OFlO1FU#6F>XF-2qac9O1QW9=)P7Tuw1G|niY4jJ8vh7PrfX`I1~^u@!P zmlJx=?iv3y=mVua z?&n1`6UN=hiw@#jHD(-|wOpq|u6LTD;7Tp>x+aY$e3KQHKj9YwTQZst`D!p9ldNCc zzKfY8aiU*5{pREQTU8@DHs(ecnnSylqS#Th;-{LZnpIUehJ#1wV~1|jJfq8vkf8rW z2}Tob9HPRtW&xwVyq3sf$&p5dcmK7J;)n``zKM8#|t7+nhAp{&Sqf|+6`c+vh86?xqsW?^xz#n5xytpU(T^WM1ptl@)NZZ(8 zQJ9Sg++m<-*S>d2`qC^1<+8m92DRqo1fAYzG#1wXP5-FF*^|zcm6ub8I9hz6xe*2U zvx~lQXov7|*OU#B+Eq237Z0Q@eZ$dH{+5M?+WHEKP&Lgz31InEJ`HlHc>oT$yduN@ z9yEzF$T|s?&K^W)CDBe&AA0@hC19}uJYU18gX_{`Vpa48?M#M&AvA_@-NV!bp3cl4 zl%9P~m;a)-enUS&6S((cCebLCv-e;gU8i@@!x!`}-N5^sX!kI5ESmSuK`?bbg6R$j z#^NJ*pXM4I+-h*(0$ZJf51I%*1Z2xWwvB_2LIhnA4kpe(ut1+6^c_U8G~4?EeTr7d z$^wQMeRd8jivcFb>av9E?ju_6D9+~{sJU?lu+^wIs{xF)bEvt0J~dx-pl0R_1nZ}$ zS&ve)9$?y_O&Gau*)JP%H7)y9K*T&)vFr|5mp-RV2Oiu2MO}n}(4-7${WZd8@u{`) zE}mBK?8 G+vAtxh89!+ literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/ai/AiService.class b/backend/target/classes/com/zioinfo/hrm/ai/AiService.class new file mode 100644 index 0000000000000000000000000000000000000000..c54330306d22c796e031bd1c84af44bbc4a6ee43 GIT binary patch literal 10722 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 Y>o;+ojO({>orUXrxX#73CuiLM1F!cCqW}N^ literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/attendance/AttendanceController.class b/backend/target/classes/com/zioinfo/hrm/attendance/AttendanceController.class new file mode 100644 index 0000000000000000000000000000000000000000..5e2de596b311917e4947623d6e714d39c1ec9bf1 GIT binary patch literal 7002 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 literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/auth/AuthController$LoginRequest.class b/backend/target/classes/com/zioinfo/hrm/auth/AuthController$LoginRequest.class new file mode 100644 index 0000000000000000000000000000000000000000..d922dbcbf62db6601b94468cea963d99d79170aa GIT binary patch literal 1553 zcmbVMT~pIQ6g^vL)7IG10t$YB3P@X!pom`uXS5E&OhpG69`q?~R~ksOb(7-gZ}LHA zWSrr_AK;I2yql&rNMV$R>?U{bx##Y^_vGiVqaOg4v8*A&Fkcl7Ug&=ermyj>|fRFv75S9*#AJ z7}eCYU54RYs3NZ`yS(ZZ^KTW!H65e4&hYXa6un{+>5}DG-cyF@Flo@J5G1hVx0C3@ zO&w#n<==dp`u~c1by0g`N@}>rCEXyoDO$41S1i@1!Q%ror{;lB&xP<@Pnyj(+)W~f zf`&Z9cz0rC%kiuRH`-MQJax=qmLXB;L|`}wXKy+CVvieb?^V;O+1#bE6??5_@#K|4 zmH@c*smWh(w<@isCuA5>vT1I^hM+9(P6i3H9~+^dq3IZ=<1iR99jz2QqDWH#=}9r@ zYmURE&&pj5_ZcSsVSEX9T%->frn)sfqdXcOGmM?>N;A{0B}r_Z{sF7P^~lziKITb* zeKe^NWR-4}^pqTFkI_jKaat4fHAOq4JzDsNi&i*72KXDq{_tc-y2IqEQVWAF0bl%7y2*t>qqq`)a>NuN&v;?x2v^TSfwVJD zzKY7AT~#Yx_>LQenL{KCvxi6>oJu)CCy5Bvk?urK6qCN|6mHX=qFnBvpVm3*!(H4X gTb39Y7O;pBJisG7p&v-m$1GsU$EawkKYo{f1FNZS)c^nh literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/auth/AuthController.class b/backend/target/classes/com/zioinfo/hrm/auth/AuthController.class new file mode 100644 index 0000000000000000000000000000000000000000..f6df1f102a972ccb686afbf76ca2034b9b4f504a GIT binary patch literal 2606 zcmcImZFdtz6n>^nn>5`*O({}QEG=l?WD!BoK(#GWs!1sg7Vwo#GHsS_cEaqY9QYId z6+K5Q#}EDhf0V~(Hrq5IsVDUuKWujH&7HZ=edfOGUw{AcCxBacl0=`tbV*h1m#%XC zZDm(<)pqJ(#a`miitRl;L*&{)`yNSY?k|d8ZbO5jllWyZ%nLVbH2s%q|k$#Ae+O-KyMj1GnhOCEr(}6S~AM za4o0wjvds@0o&TC%I8Y&+RtUtF1mi%cAB>Kh6+OZtr<@wb4vf-G+ zLaG0wxA+2H)cAI?$mVgpTaab4%M#oQ@BV1 z!WT7}!uuqASvp$kVa#Gai8+ChwzROYfDZ)v)V9FmR_~1hYR!>jXkvs1xtC4D#)nB< zW(B^^L}z_ixPp%iq}|sDi2?LjV<&U2FW2kUqSOx@rm;XKr%H~uT;}z~ zj>7HIWSCj~T0RrF(u;L_XE5#$+f!VDY*~33R#dr}{)EzJvaDMs^cc8my~fy4FI$h* zFygytG>oe~H-%N%(i8e^6Y2GyH^()8S2|^>c^4yQ1SsvkjG9vlw*``oY@bZwjsViQ zi!YN{Ynu!WMl5`V9PPENd5#$kw_90TsW`e|TDo77ttHT=?^1@Q;@2(Q#63=})&rZ@ z&{lkmVXzWK!^KV4cf&%0=>g8O>-Y2Hf%RruKUe)+Cy}BwM5&KiG0gV~YArq^ z-$DeGPEwZ?RE%=*2A?NLLs+-f3RY$E)O|1=PO9wD?0 z!X={&upGgBNH-?l@%hY3=8Ju-<_+G3edN2KJ3|U4TF{L*(T!t+nD}KvQrggMMCk5g zlU@p357OuX--e<^24R9u&;5?C=jMMy<`+s0I*c6{hoRzh!>Z{l9o>vLAYg!;CGaTP h`Zx+b!wuh1=H0~Me~0fWrzwx~`GHgUN6HB!{{sh_*~c5AQce=8=?}xAGNm2W=R&3-MHCc>66oO zrcU3Tp`EtVKKarIXc0^8v~NE2Pm%Z^bZXDtY#~& z&cYJlVTdS3PWnPKG=0vHGGgx*qfINg}a8C8qpOa#hnM^hMATMc~jHVbEceANxL8w zRHbNY){>-{OL@zXDsIx1o&L0?Fn`yYwzMq65%)MQmGxv=lQq(s&Tyt9z6~j9@uT~9 zOFDOp7<|JNb}a~rh#Ksv!#=zvAjHuAdN~jg#(su|IZaQUU*fsZ2{S{lOGusWc*e{e z_v;Vf0Gb3GWH{`4wqqxv8HX7BT46}Bv_(40+tGPbz}pPX+gX?_>Xw#MM?P27yrmhs zA4eHZk4#R+hNh>+Vy8~^^hHWPt(AWG`NhNK(xZEk(!;NxudZxQ*E@T+O z9WKSFLldKlWO64l$s zGqSmIZ}XQ2FP49cY_2~jeg8*kW0`|}M?@_G9PDWk?{TmKft@t6Y5-@@C*XruRnzHW z5&aOUnrT&6P1&Mq?u~1@nkeRGRdY(7&5|S(HxxNLBbyrkc7#4FqY=0k_nxYvJn_WO z*|hxaljYS7}v-`lKZ*3p6pDaDo%6l`&FRWKMZYH7PU?=Rs?A@*QeA zwJ#FMCE9@ktW9cZUABrQd5r(R$G~n7Rg&^d>mMXSok3j01QHB&RF-Xaw!iuedn`JE z@(r%=x+VlnGVHVIWrxk%m_iTWDyBF!rfn9^_;Icd*FkK&K`GicCsPq0;}%)vi!^R` z3@j(!-3Y2V;u!gHn`&fIfBEqlp-O3md{$mcP||r5&WccY6E+aq3)?E1l$Noe(xhZX z`pfYq+Yua3tdW~F7Nm;~F;mn|UQE$dnKX)~qK;~un!zffo#ZKHXma+Q zaoM%Q9Tf3JH7rWGlq_G<{WwO$#Y;E47a@Kx(JI8ps{N*kV{54X5aB^ReXWtDkKSX^ zH8gZ>Am~9N_6&j8IvT%4{Zkyi5jye&k-tda)=kp(4Me@Ds7^Vm-Y!zTyN>r)>=Op) z*=(O4rnB~=k$xgLh#oZ2+&V=6cL34yxfsNU^hFWi9EK=*zB*4U4|$%&FfLGpBP69X z{)0vV2Wp!!O5e2}asZivoGy}+7vr?{;1UPMy6Bt3zfOVocCF*131@LR!J&5BQ2TsO zarMR%B>QTbYCg%ppz1%+OgP zVJ%BElJK+)!}Age>=D2O%vXL72sBPS^dP}YvT{><%WAT)^I5|v9-hkz6QD%9ggriSjM7tiNA=>>IZPD0c^sbWJW9Nc<@F=$n9Nxkkwi}Y`!*)x0>=kcH cuN^?WmkffW3Q{VAv|--f_6|&X^NkC@e;!KK!2kdN literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/auth/HrmUser.class b/backend/target/classes/com/zioinfo/hrm/auth/HrmUser.class new file mode 100644 index 0000000000000000000000000000000000000000..51f52f380e48530f899d05535c94872f792c8750 GIT binary patch literal 5274 zcmb_gTW}j!8U9wcT}dljcAB(H+|Yn%eTxO*+I3t=oVYf~jtS z|NqXn=lth8|G&;3|MTi=0A}!#ikO0tvR5rW<#>*J#Vc;tszs~eZx`ol)ko@fO+`XM z{+hLC6)To|wRmy!nqBr4#GEbqOYK0(bFWS-&>EcNT2))Ya3s3w*BlwwcddH;Nw2mw zZ`HRIWVf99ZpGSPk~5Na)p9Bd5;duQcNM5!51+Hy)bS(1zaCOI58m^eN;O;qfXR~6TbzRT5e8+R^V!;)!y6No{ zAGTe)CSFFq#H_bvr=j3(9Z94V43-?%UTRb~?b@0}3NcTtVcn`ZVjMaX{#?5>VghglPEf2$4^8sAR%awLT!_BI`95gTCad^?{a^bQ>v5z-jCAd)Mb zqdKy}snWT^Obg$H4qXDB`q?n+@zJpj5i07)VL*XS_-y+SjyIRuxxbSx#tYkf>-PxP5ve+vld~kelXK zX^O2ASXpo{rpGoi-1>PP7jTXXC|m9Y zffmk(bxh)vuoiR-VK{?0KBD6+&I#Y5juG4|tcyDC3#^aocza;2>bO6!9?>xtSdZ#> zC*H-&;dNfMa3xH<=Wp9J?p-LcAnvOsJ<`lcXZx}a1fjNLNCG-<>u><-bmk?n;~ozM zxDH=N!omxl4FKUNEknVb&jgWRFdzewi$54dBc9xs$ZeiuZN zt2(Y>hxu4uSy;Tlge~w8Yj(X+VJV5rY@`eAjpquT`_l^@`$!eSRVNGKiVKnDXucO{ zG=rz`85N)ID4sz`$7k_59?PCpY1kL9D7Zf=@urX>mnBsFFl+Ybb$kKOuozXX9edVu z%a;GB<8#Aq-M3tys(dj@7S24#_5bp093+aC-SGX)_Zi1^{DB5K{R!vuTx`p>p^t7 zzZJa)@1-j5)R9T@m=MnR#7!LgW!EJg3lO1@gbszO6}*q=Bs&VFs^IWMW2=QXL{3*_e(Dd zQHx%e^fP$q(4#$m6I1=ti~820&q?|Z;zNhhQ<$gwr59zhMIQ|KX?c)7woXkCu_{hY zjQE01bqooG}mqt($`ct($pcP=N9wXwb+v zK?zZ^;s+YdjUbcF`k+kV5+36*990Z#u=&JT!fxYn`r<4T|H8*;C9sCS;p4QD#QX)H zpq0WtZsL=)R6LEJ<1#G`FXIJRw9@z~p2sGw41R|llxb!02iVx6rQ;u%qx~3i_$LK* zh1P(QV=ul+Yfw4Db!^kh!{Ht-!BDk-V?c$X-^MW&asJbk+qfq%)Bmg4tcjQSqJ;SK z=b-fPAX6hsZc=EjdlLvMZnBwCnInQA!6F*8RQv>|u}4e8Yq%Rv z(n<^ED9<4O20zQF7e(P0nDPeS6AofxbOZ?*qK;%V9vMr(Q6!syCMMD83Sp@Ttc|?W z8-mqNa2wwx5Khq!Oli;R9t$U?yG?A*ebaC_J@Y2h+YE;xNWzSNs_SRkx@=v5sNVCx z#y%e<{+9S#=5K|+HU8%KTW9!H78xA&?KBxGN)dmg9lUChZ}3;}T=x#u`3)Y&mw7W| bl-XDL6n*wv{Jntha3ucI<3)TAKY;OHy7rE$ literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/auth/JwtFilter.class b/backend/target/classes/com/zioinfo/hrm/auth/JwtFilter.class new file mode 100644 index 0000000000000000000000000000000000000000..86dffb98dccf55a88c5604b32aeef451c3526d99 GIT binary patch literal 3010 zcmbVOX;%|h7=A8Koi+1HwCAsl8vL2Wt+eQW8Fb^LFGz|j<15KrPbdJJ?6?AvS@Qs~uz7Qg!4F-{L5T7DILiuiV)bKGy;7typE5^;|}w zD@}@JcU6$Y0KQD&90mpA=WW+k7dPm_R4dNoqJawn+rw7Yc#q7S9jFv8;j%!JH^aiO zZs6$joGhrN8CjOkByk!;+G$qc!a548W-2mK=Y$rrp5xFehnlx1iEFrF;JU!2_t&f7 zu{=+dnU7k-BIxt5=Wx7J7{&;vk&TV`I=0^22b;~b;w#)TkP|rf!A4TJjZuM}fmDry zB5<~63$ZmSv0$yZgS>%pfzGu+unLz_n82h!VhJJzuIe^5LgV`oaru1&gc|F1SPotsuo# zfP^8n<&{+^-?FMvW!rB_u7%>5rFSa#9!qhkXTvgwt5Wx^bG$|ftH=>|-VSUw>Wb@n zDnv9|=*T%4|lix|W!-i(UA;!L=?CtP!RujUsdE5?ksFE9I3x>5q^$idV) z=KI#K3!|5wTC7Sl>s7dGSsNzYKID0d3&N__GbO^+uwY=G*tF6V+nr}Kq9w-rPFTEd z6_n>MYP@_R@Y`xwilplXhb_10$lz$s^UAfVUhiua$h^dr z!Ne754fE90w|GQ9NzCCpT8Pp52&PH3(Eb1l zq~i3_jUp+7za%7PSgl!VCm{Ys*H?pGP1AFv<2;r5WKnUEjSRITDMpIX`HGvJSrVUfS5 P_<^S+sUPtSzaafT_#1CI literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/auth/JwtUtil.class b/backend/target/classes/com/zioinfo/hrm/auth/JwtUtil.class new file mode 100644 index 0000000000000000000000000000000000000000..34bfbbff047cc1498c73d8349b8fae6a4be6f56c GIT binary patch literal 3628 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 literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/common/CryptoUtil.class b/backend/target/classes/com/zioinfo/hrm/common/CryptoUtil.class new file mode 100644 index 0000000000000000000000000000000000000000..0cd8f3e0d1732256ca1be1db8a9aa1b3020f0e94 GIT binary patch literal 4215 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 literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/dashboard/DashboardController.class b/backend/target/classes/com/zioinfo/hrm/dashboard/DashboardController.class new file mode 100644 index 0000000000000000000000000000000000000000..8aee888b665f514127f57d36c6b8da4c4d747483 GIT binary patch literal 2429 zcmb7FZC4vb6n-WNB&6H7AW-O=tuIY@TVH6crc$v;!G@LsrmfZLBs-9WWOuteX%GGZ z|AFWD#owUks4aSqKfoX5@tI8sr0^j?D@UXE#K3jU$3h$jg-KtE%{8^b?I%|_bOYess&DVyq0AU zs8C>{)I;DFxF|5t@fWy~Un(4OX0V3u*>i!rs-nR6B4xbR+)YpFW~c<7^yYP=6q9di zw{COy_B76AO^2?xS!@9gWLKQ!pTOv9)6;H4mEFj#)YY8l`C4k1R0L*9ez<8zfeEk? z%7%LGhuijZRk16s=h(7?wQnnJk~SL&T<#9JB8{t7VOQ*HJ;z>EPn#;zaTrFo9JL{v zbzPSACJQ9438ZfMj!I)t;PieO7TIQU$|8p$3&R)@=)d85uAUW0<_qNvMmg9H+yMxj zwa|zDV>pkCXcsU^6o)2%M-X=~R6-bUjp_W73(hFo`MhA-}~gtH7W+Tl-u} zZlsGD*d*wvU>|M5yl>$He8@Y(F!KkE0=ceC+d-}hWTn<5A-$?H5k9hT-4rUv2y-3F zy6?!HEIz@FG}zn0n2kA(cFC{G`n=T2;+-}v^Ze%mBYT^5V~QrkXBOu0xj>pbJW`UE zm2F-!mU#jArGB15CK06uQX2}A%V>F=MIKyoZW9vK$v7QQl$gLGVZ z#h%3jJWAssuiC5U^st~<_!^G|68`ql$E!zCI$2^ycbkoh3fCotf}M<8RFg1?gyPT@42$BFKv<(I#ad-MWtEKlan zjQ@_YU7Qox#pNU(Oy=@t_9#R$ij~D;Xy74gXKaISnz(VT@q}Q#ga07{z_g9^x#% z$2l~4lYhho{EUnEmOeAB4DkftamHMUk}N_7KcRvu>-G=}aJZJFWfqE~6gG&oI;c&e zYFz0PgqWfa!zFx+V^iR5epyaBC-?_s$6ug6KJf>}e&Z|=Q#Ux%219AMh3ZhslA^J< bx9DSc5@^J(UYv1`bD#5opr?-^B82|~&UBZE literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/dashboard/DashboardMapper.class b/backend/target/classes/com/zioinfo/hrm/dashboard/DashboardMapper.class new file mode 100644 index 0000000000000000000000000000000000000000..c9b5fcfd4722bc8ec5cc2b1f83e93bd1069cd3d6 GIT binary patch literal 1042 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 literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/employee/EmployeeController.class b/backend/target/classes/com/zioinfo/hrm/employee/EmployeeController.class new file mode 100644 index 0000000000000000000000000000000000000000..eb30507329d3d62d90058d2da55c0b4dda561b13 GIT binary patch literal 5860 zcmd5=TXP#l8a-`G78VmbF*ZpEup#E+_`*PlL%_zwF-b6y!Nj0t6K-RvV^1v2D5H^$ z6GGSo_QGu+_7CjazCsmvQ~R)w`>;Q1i_<;h(I_)(Pf|s(<&xCh)BSztJKynkDJ2+Z8D=TH4iYICn}QSF@Y6^8QGvZF6oq1Y2R(rI$hQcHd5b=W?U+4co_wI!RrHbd(VcQMnTo=i+@vI`%4(vD1bV8n;m`Bw-q=%JQGC{<%3Ac9Ygj8PZS!n4?Q3lA zU6B5&Ta9GHNre^ZpWalaPQAn_D@6L#>qWpTGzPx$euHGQb%<5jW{MGGj zP$CC#!hnev1tv29PNauP>9R>vPsNj#PcpBMWQZUYY*SWRp6zd%71xvIY|CGj+$bt~ zZaSX+O2Lf!LMSrfKxl{0VH~<)o8e$Z{RfM z1p2}aZ^3d{DxOwG+P=JV!ph3^83Si=P9Sepe3zEaj-*Dvv!z%uhwm08+j#>o;SGU{ znNptEEs}yt({?up1%@+7n!AZbCIyb&dxR{XR->xR=j%ifX7GLQ9+S~0-v})2@Be?$ zWG%1kM~KB%R9#9>?-+Os?+UzkKQSlyOjA>jCw(3y1g<50Bn0}4CH0$M&3XKkSa$8I zev=vc4Q2+^6mbb36!5;lLz)nEP&D-c;x(3sfe-N!HsKCyGFc!FGqBBNDq@z(STHSxCYk1 zt?)<+G|ja>bt<=9-D_2=+p(NUEC>jRPN`W!qs<$;*okXvhZ{&1Z*fzhOcez!16Q%0 zM#0??Q3^c)b-Xp+^^n(t&I9pgWBX7%**P^1V~2=%K~d6?p3igZgF!J>R8L|4%)oV( zwt~tC2i4f`4ct_5u*FC%#z#kD)OwYE(-h4B(CDmDrxaPlDZeB9hrC&_S-jk%I02{ z>RVs?Rykl=4ZGu?1x-A`>z%p=@&cmncx)BfF5m(7dfDq@|3UutaMa-M9)3>d+3Dk_ z|04G8*xc8o*60lf)}DqC7LH z5xj(#BLq_s)zf^YsP66J{8!ire3u!cigIxL4rT)W?%=g8yxxJKsQo@g7M|8P4hL%@ z9D_KI674FR3wV>W6-RV+o<9rx2p$|3BUEokyjBO0ord!L5dt)cGe*5tL}mhsyN?t2m3=?L!cG?{~KxRH7T zUcV#eJe>?H%;Wz?k^El#J9_TmO28un!9cNaf2^8BG zQP>wz*oO^#LJBu&O@&UI9J$uku&SyJet2=;g|AO{=j%oaUuU!Pb#oWK0t8w3`aF%V z^W>|vD_^$~eBHX6uUne0+xUV>`jTxWj6d*KrEZ4QD#aE@|A9Y_j{St8AJ~g;)BQ0* mic!jnREn~rq|D7O&o@OEzS2v-*3ubZ{~Pwd<)mO!`R-?+Z4adY literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/employee/EmployeeMapper.class b/backend/target/classes/com/zioinfo/hrm/employee/EmployeeMapper.class new file mode 100644 index 0000000000000000000000000000000000000000..563784bcf9bc055e7445a662ae107ae28803fd93 GIT binary patch literal 1904 zcmb_cYj4vq6us`&cI(=$>;>aZVO`5(;WaVQfiwn)q6`p~kof4OZs(E2mE#PJpT-A% z06z+G?W9Y(7PTNgM1JJ@oO_SY_0M15e*nNU*vdnOz=k6{>l+u`?+EKqdKU9~t~g=L zdKFLhX|Km*9;OMDkLWSAT{_%(okM2v1KbWQngm$p6oO@ssT*Pu`7i9`tB`F1xi;&mrk(_d79v(o zbUFb;DYGv3IF99grVd5>lo?}Y9D!LGnV@sE3K*fN|wb&8HXR%FCChiAJqCC~w1gX}W1f~!l&xO4< zRd@%*Xwr&he>x;V%B%zS)o!qC;ZXM_0yQ*rK{CZ9(`y1H%zuwc#-z3{I%o)agvCFP z4cbZd1@>UPOpB6AIBG{Uu(q%kR^M_lk}S%Nz+y~kIt>v`CEI5Je^5`OdeE#OxM{g5ug5>jY_%2aabPXrpOG?j)zh-FxbA&R<)ILB4Pnv16*%$>X2K6S~I+c0d zsE{t9+E1Zsf;XUse9@iM`@e3RUw7c{i0aNraPM!(Hb;?JhV1@WGR)-x)|r8a@Ce5o IO7a-sHwUJ|Bme*a literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/employee/EmployeeService.class b/backend/target/classes/com/zioinfo/hrm/employee/EmployeeService.class new file mode 100644 index 0000000000000000000000000000000000000000..43d60a6a2579633e05bef58da59ca2f8fe575e04 GIT binary patch literal 6685 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 AZvX%Q literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/integration/IntegrationController.class b/backend/target/classes/com/zioinfo/hrm/integration/IntegrationController.class new file mode 100644 index 0000000000000000000000000000000000000000..8556da9a1ccc78703d0890711cdc67f03dbec8a9 GIT binary patch literal 2250 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 U1ljtk)VVQ+xr6!RFhAUX0nWQmxBvhE literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/organization/OrgMapper.class b/backend/target/classes/com/zioinfo/hrm/organization/OrgMapper.class new file mode 100644 index 0000000000000000000000000000000000000000..02c3fe986b6cf29aa39ed933d05b740a44f0ff39 GIT binary patch literal 863 zcma)4+e!m55S`T4YSmircLhPw7xOBt3I$Pwieka{v>Uf0+hoaReelzK@B{oPagwdI zt%|bj<;>n?vrjp{-3j_Go3BBAhESk#K&nCMz0i8N&> zGN?U@C&6Q(B7W0)lx@pk`T9U}^&flTD12u$hqzuD%gY z1`^1t2fazt8dOn`69#O9GrAa5P^HpXXYHJab%Mhy2FD}8e~P=*V(4XS*V1-$cq@$P zN-GWZSjm!5See^aLK#y$2MawrUur{B=}u|)E>jk}@*Y!c#j-C|S4vwyW)j_N(A>F5 zM7tw7wkVu(@kQW1i+V0Si8!M<7BHmq=nel?r|6E0bgKDGJ|hgq5>4Tm9`n+OP!gWX zX&#bj5L+T$bd?(E%rzk9!XznTBO zeeDeZ_u=13Bm{0P1eNSXH*oz@kX;BX*&r<2zI)M*+`!LHhvgY3TyhIe672%%^Y)UR z^=!YKot{7M6e58x+E3ZlsuKzf=WpapTjm71%T9E{sYbJ*;|OGiNAk*bJ#xKl-mOJ6 z>6&rNz8%#=8ca4B95p_m*Z5IEn~6f#FXyy@0X8?zKY{j%py+f#pvOW7wsfNx+mc8N z+_E|J7Bc7*uu869Jmz^ai&{5!pf8D?0$bPTVPO|;64+9ULdUKM>{*wHUd<9PU*t(sqPmPFDK)hTnhadNMbKNuLX|XP6VDuj)JgeVG#QSQspqH zS6Q3m^qdVjw1C%>!o^LJJ1xuQ%`d&O`Oeyc5pa^PU|4 zd{D5xIXiUabG+CdEx0v-ksE4CEKu~^W_un*%IND@N;>D4&)A{k6L_!jR(6X(-}>mw z)dqr7hN*P)ze~<}!_x+_ipydc{6C?jzNHFOlgN>}Brs$)jLcUDQnax%q)v{^37p%ZO4D$|^az~S}ZZL%h8z$qtM2#V}D=Et#R+NpZAxXMX0ATrPxD76D_=#iKx z17qAZ<0Mb!y=bAV@-8|aBWmimnwqy%*;=BB!u;6+{id-iy$l3UTg&*QMLSkuowlT?$I6mg;zDJ>n{b1PByRVPeZ$;2AtjZ zTl|iV!g*{g3A|<2B>qx6f6(@eo>Lpj2f<>!m1Y_wuKRG7piPP0{7W73-M=*ni#<

~A8XRXLM*lP8aNH|6x|+GCJ`+juZ$ z!^zV+?=0RY(a9rK7oQSHp_|vY(5~#}Mqh=sf~^Aiu@!6=xQ5-pReWFtHz)8WMyAFN zT*DZ!f@}f@x~}5RhTT1K@xBC&kHyA2R`5U@Kics!BeY?X|MxL6u$@tNB8@%_@H)st z{%yDkM;K~~*VD8Z&}i^sw*;n{Rqur{k`Wb&OaSj;B`eI}f z!#wc_=?w)GV`Q?*`klkO67RbbGz3i~8D%W2!gHJ8&CyAMc2*4P{5nu6^=^oX>{ozf z5hSg;RPs&(k_FN*;JYM*IQwL1v?&g($Gq-Zp&cFC;T zB?1!o?2VH7Tyrvaw@l`X?bW72V-JWAi+5i1xneU8_Wxlt9?M z@s=6rk=Ai`8FBhm9AU(fE1Va`p@C{YA95y~M?KH67aZZo~Dq zrWE7~Q@~I8>p-ISex9ZAGd?8n^VMPe1)qP(pA^C0^Y;h*34d01ui#a@rtYp#nP11> PX(jiTf8d|^7k0k`^fZMd literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/payroll/PayrollController.class b/backend/target/classes/com/zioinfo/hrm/payroll/PayrollController.class new file mode 100644 index 0000000000000000000000000000000000000000..09c5cb36d05e36370583475cfcd5aa085ada2d14 GIT binary patch literal 6059 zcmcgwYjfMi6}?OGVNnnz(Gk;BbZf_sLrJ!fIF{m=t|Hrs9k!IMn6A<|eL>_(f<*!_ z07|7@H))&nnY4NInLfVuW80ZT@nk0b0sT##PR}kt5EK!MlE)nl1T3(7_uO;Oy}QeQ z|M$~>5z$xZuPKT#nyi`)^|o&6#;U2V+YQyyTDDoQt4rSfqG`CI&FvJ$8I9i5HZ`@b z8Efj*m7BckGRpWPD%{@GtDI4xyo*i0XOU67t~)SswoodSJC@6)u~saVCVT9_Zo@Rx z^Ok;%JC3;_#?$l# z`eKS6V{}jkUCgTn^C~n!c}4@6ozbI(NsssLK1E^TOA39N4l_!varae##F)ZlNql)+ zp`+rj>GRlmSHEy}38tOp^u}oWZx3o3PA9wWIxUm+l20~GBwdw{W?jspp-*dl(!y8tqhD_)`OP4f}KVGP&3IMQ^vTU=;9q8s`HEph*Z@TLo zmAxv9d9hE%>JQC$cu86|^_pCKW+NSmT40t;C$z#GP<=Ym^bNX^qF3)ztiVI1X<4T2 zD)btC6Dz)7-4`_jsgBYxTZn{le?FJR)ZbR+sSF5@vEM3}*J>e;6@{vV z5|!~iMQITm8L_5x?Kb=&lgp@C8hmARYVY1aUs%0ua}yp z^^RV$N=U3yMXe8Jvas3F&*I)YsqE!u0mV%1JuV|s45CcCp}$2Av+?-DA&CqYgz40 z*=%>$u-EsY99t@}FRQmI%|=6JkI@6LuOrlhWPyhuoCQBs=x6kEZyO>u;LnIyVCNl~%u!nk9Kzksa71A*L*1Q*;2WL9}9M58XUXc799Keeg<$z4^Ur@6QI08^cxIUdfv`v?Mr(@D-5d@+qq_b0A&%oIXmEf58 zo{??~XEW060f~8d039g6&9JNgT7b12fpt-0&Bj7lWyG(@C_3O!K+OkH6H(01OXL84 zwJTa_$^8i$p_3u-`2c)A0H624k6?BYivbyd1jP^m>BnF!@Pi@ZmB5GVfe&8?WRLG< z>BETNyTdd|2UdNqb0_c7D+2XI9#7xeCXID*?g{M_dOt&B5Rwp@$9*nzO5IICZjc44{+-dxNl0_aMja^hxiqcC{lIogpYW7n>M1rJ_oR8p}L|3 zd)ONkfE^>#7s{A~7;DpN!nOy`qFVtxJAl`bu_NK7@SB5_v5@WpzzNAIQUKfJc9G}_ zcR}LGdHn)BiXVGHRD#LiGVBGu7a(W_2ySCSFR&g08CWlo?qIY>lM8=^GPCW#PM_GO z@3(mhSC32RcNwm{6bfoKz?BU&nWfk14T(DI2la=rFX+H5`1T`w^TRAs>~&lok3SBj z*qebD9UkA3JnlEeeiD&lSCC>?L+(6yiv5Cq2|~T4;Oz(>-jUff3o-;9Ln8d!^s9*I zUze~v)`onR85YhDVE#84Dm3z2+=ZIv!KhGEdg>GU-PH8QbnKsKg^S=wNK5G;W@(5< d*zvU_N_vd`D2IM1v1if#6WV`9Cr?R#`5!3MFU|k} literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/payroll/PayrollMapper.class b/backend/target/classes/com/zioinfo/hrm/payroll/PayrollMapper.class new file mode 100644 index 0000000000000000000000000000000000000000..e9179e74c86f784c5fa69f11c149ddaaae7bb501 GIT binary patch literal 1964 zcmbVMYi|-k6umq#UFDwrd+dq${1SYxI5F@c6)Y zEz3BGzsKD3gkPk2M&&bp&JBw@J>%2qnP@9U%U$Vo63c+mLCrLqsa4Z;dJWU8$5)|} zmeG`fYA{-8$)3Yi=nF>QXL5Rx0RgAv7?EiyUm{Q@a7?f93O`1>pF>AUTYQm$Jhj9L z_qi>U@LxNQtGJS`1Lpfp*Y6qJG^@+K908Qs6L}Y zM|jHYVB>bXfq*i3OWG3O^082T7aAo|5v$a8!6;R2V}bznkBpWQaRWDg8C8-PQ9*Usy2XNBOf{OB+G5KM{kC`~ExhVV(s_u2p?9b!8Kce7`7ts!4yKHa7^qQT z#ApsvmuPVw4~q&o6>-YZHCn*;C=Y{0tfhdK(`_l;uB6-5bX%r1cyHw4L{!3B!CJ*C zlb+ZWT1N;SV0SKcx-5W(D$Qk~0CAl*hFouqK~*n7O`xhkOqc=cCT)UEoc67Vc%ISi zi0B=idWvrW!JiTR1<9`Cga7~l literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/payroll/PayrollService.class b/backend/target/classes/com/zioinfo/hrm/payroll/PayrollService.class new file mode 100644 index 0000000000000000000000000000000000000000..4d1f3f5595db81e5c4e7c70351d4816269c7b474 GIT binary patch literal 4202 zcmbtX3v(0a6+J8KWqECg9E-SyG>{VffGBl#qun-L4aX|G_zMh68j?-Rql2T9<#J`qZ^g0KO6BrYu+;IbW|2E+K3KKZ zY{T!kriQC~*>f)Dwd%VT{l-=T{G|&5vuMK0KDeenQ(*DmwBQ)u69h zb|b8r$FlAUvBLCA5^PLI0S6eq?sRN_%f;9ZE~b_;7}xNaDwwLy6~kL+g>rZT6FLs! zkcRX*%eMRr8U`k(YFT_&L(1~amN$gMcshe447IhNdv$ydM`_*ekdHHy{Z;B;3Hgah zp_Iij2J#)>Xy!15nGB{ijBICC$8ns{kl8Sr9rOCChSABTxGTlJhJ6*wHec?vR!sM% zvC<^t`HE9Fnl;0<B)!rP@|#xwZ~X=J%Q~*(Dtk1B zx(9Hi?K*YS^THnXWYG9Sw!a-UN=n z-riR0P8{-NM4#dUXZ2pH%`>Bqf;X$a`0EB8O=`+34 zmMZt49q9|Zed3fvyjONH+9Ncncv;H)p^kTj@w68fXg`Z}hqJu7S?#o1s%HJbdK!-O zuoJLv8toC9p!$W5kMJ?I6ERnoV^)M1rz;#g<-WylGi83Iwo3G2l6TpI5n?#q-1~ zcPDw5Pfo+XC!jF-cU+*6>p z!0IJh1@Gqg?jqlfrMLiw1l@pPfHVBna0&BK;TBZEZV`Y$7?$eH96Z#;BQgvvUt z&>vFSO$UZicEcz$6UlB^t2_r3is1noAVQ*H30I@&6%~Dn&qA*rMDOC-Kwt2~ zD)>`@T@+l8g6m;$NqjI7h16K{A9B+TE=v|wUPU4Q;#!K=T2BDU@nrty#3S4aIZx(a zIT(L>HUE0_=FJn)t?>x;;6bpDC%do`xcz5(4)k(_jDgGzVv;JE!ck096*FY=I5D2U zE!I~wb22~^C00}ENL6c*W@>1`rr$v-&7lI4N&z~v3RdR0BI&c@J>FY<)X>I{cPPPS zt_fKqs_J>S3!mB-=mF_D^>&tIKCd8()My<+9KlZ%@e(qn@j*gu;(|l-5*zD2){M)6MVbD1qNcr2PT-4?Ka#qyPW_ literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/performance/PerformanceController.class b/backend/target/classes/com/zioinfo/hrm/performance/PerformanceController.class new file mode 100644 index 0000000000000000000000000000000000000000..237f2dd948b1ef751d2c826ea3d1410bae313259 GIT binary patch literal 6122 zcmcgw`*#yX6u#33p<$EKKv4nZA!sR&EyzoYRY1Vl0Hwv^TQ|uxT}XDrX1BE5=N$OLr=d-ruI(D@xofP#6+IZbqF!ZWnRrJfn>$2nu z%0*_*NN2$;NkLOoU^CqNj%?E7nc0)K)Y zoAxZ%tjSrkVHT_Mv}M_@;hKOe=xD)qDte>NTby%@nq0J<8~UOw>P6Ek>qa^ z0&sl|^A>(KUX<>Ybp*L!4nz;5j%QS;QzipQ90C-}tPZ{of zlOqUc8=chX2t6xkSIidLWd_lhpg=1*(r~4p(gan4d7&C(p)_R4b4+(hFWHXNPkZjX zM4~9E+%TTd$v>{JlrS$n96V9uv$k1Q>F-#qt+*67Wj7)@F8RD5SDmrTOJMIbolVi1 z2ZTNRJmb~tw&QB_5}iXF870?7Or0Nzh1!LtR^`3pqN4CUcDTV`MHkpXkRy}i0^b+ zCn3HhAroCHlxXrQJmrN7Wty*QbeUd-*1Bw9T#?-HiZv6bsFyoNuNnE~ww$N?78uzZ z8ofzxfib_UnV2?pnuh#RI9NFL0&2m0$NSamF3Dz4$Zq{lreYZ%ZueWTe@-4njY@=! zo(+yS7Yz%=64~L{TC&3|b1|n;g}KNCW}0NIN0q%`8&wqC6CKV-dffeZZ8HO}s6UZH#D*dIpt^l>a*#-D3XN8|Cs0A8&Y}k2U&)vF6YjF0`axqX;*67)s$O z(=y!&9FIqmo{yXNN=~xdf@@ITT8O!BR`cC$vV)%J?3Tw^xDL=}!p33{jq5!J=4% zCaHkY7xBzgb5EWw;faC5avndE_z0X{rb4h+La@_#Z$#YXo3qJD98*a2#Sk z0p3nV{5BK<8Vdc^WI5s`pVN6VLwMIic&(Vfq2T3OoQ9Fl#7+jp`VQN^Ox2ihn22DD zI*Y@X^_Ec~9Wuya%Hb+dtLh=Pn<2IqgFd#wUJPyN&39lOeo<+_SKL7rBTmzU(HMCN z_RlGV&BW14HBE#$@|5a0)o`@*Mxuq^HT9S2RY}HEp5YxVbcvWn<=rF^^L`xmR}e9i z5%0!FjE}t)-`NOa=#C?X5#x~EMGLoAr-ctS z)xzBeXyNWkE!+*YaF0HPa6Utu>)Yq}SCr}u1F+688&c1sDUbnivcu!A?aVIhF`!2^w zx7!YbfzNHT$34G8AlK((LH)pG1jO}F-Xu^_bTrj!Bj$vbP=Dtg{s`311L1RredQh> z*zAnD)L}kz-wPpq>T@Zuwnk67W0Ov)HDM+nVAwNhyus922xiQv9bgqZb4Fd9GH2Qy z63BRn6d+DUjz%6s>kOMa9Q(>C^Cxn+V8uN}OQNEMt<7x=X@4eAj=#o-xnezRC2H2+ zBk4W5gn~~t?b*^v0x6D^MJ_z%M&b$VMdM|K#*ysB)a4q(U+Zfzmy5FzVwIy(H^(I`o0L={-Abcn~ z83F{-_|O0p@XatFgR?BoQg8!u*dH6XA&;%#K`|bd;$b-+?#9Ck+{Emvfk^-*Y-Mb_ z*eakNr#-lZ6pEmpj+53AD5z38355{1p}Nqzuc3;nm`H0)^BV+en(q!AAYWM6-OHtW zdg=c9r3dgZQdvtSp%*M=4V6nN7F8k2dIXObvJSPZWNyN@;$^OK2VKJ4`YI_$dg;mf brN)0SHdnzuML{WO>Hju7!#;!6KL_{=IE9J9 literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/performance/PerformanceService.class b/backend/target/classes/com/zioinfo/hrm/performance/PerformanceService.class new file mode 100644 index 0000000000000000000000000000000000000000..1b7c40a98775bb8d6599de27e5df45168061a4ce GIT binary patch literal 4736 zcmbVQ`%@d&89gfrB*X%a9OMuj2mA^zTXq`b#BvA*42Fo<27>D}t-BHntS#-Ts}(Np z`w{p3bf4)T&|lk`wh8S_|A78YolehpcUi3v6U+9@!rs?+&v(xG?p^$!|NZmd0N%!* zl89+IUvetBN0wvRn@(=qt>mhvyXm+U!!DV*TaEWs)7`O3W)cYvNA4RtMy_nwTe+LX z`)0}0@IqwSvQcGAL$1(fY=1#sLwDJ#c`N3QWj?HFm>gLwG!%U+GeM$6cII8e^R>jN} zoRU$VGdxqr5xk%wZaWV(92*JXHqXgRh@(1QM8AgQmg(I!4Kf%X8C~oGSLx_LXEz2g zn8eE(&OH;cI*#KNlDlcy8zH%upPiQi#mNhZS9J_wSc6`2>UKk~v+aZ&T}t8<{i29c zn%yvJ+te+MlQ^T}EMC*ldDXHlZ%Prcp2Dbx4$CtuwQh{#ToMx+dK&AwR>yhdIJ;V> z72h0bm8!KTkRf8?d_|uIgQWZQW9@y=xuhOj!9gmL_0>gZr9-|Z@cXp2N_R;r5|tjb7| z0gTR?$1>m!naEgBIE+y>wumfIvmWtRr?a_PGs&N}%RbkU3JgLjnUwk*UE)J)_W2Pb zRhMnfaJRaj#Iqcm#9b*k?pCf=mD6}r_Q?4U}u9pD6((!8v z-tXfGL--3=A2nI1;q{SruN@wo{Gd|>G8mi$i}ubTXF;Y=b}B{ZL2kjcO_#;D!4I1_ z&XAN zvA2t@7}DtG@Fd4{wu}9V#72L|_GIVa$Iw58{tsmSjKtt%a_n!&_9rLK^>_An?BS(O zTuJSB?cqcMm%ILjlXpMC=_}m>-Is-xWo4g*{)CV`hN2-+$DiG`ST=E07r@A zMe5Oy31pGSG0fp5++xo?yo|Rvqbdkd#1)Rpzap;kyIfOmQ+S6tNZ&NBsZ5KQ;b=nj z&GM~-@$WL`+stqPi@44=ejD)vWgZJekjHDdh#Q-FM{TEqnTEk7Y`a1{+nBp_dXNJ!#AB{=e4ryLdNeEbh+bz_M?_o6t;UlK_=u|nK zP&v!)oEGNXjdf<~@7JNRA@T3v^Gwv2@h(1KpM>~=S_qyGaYe}Rc;>^*7k&E2GxsL^ zw*Y_ptS?E*@?`35+l|Th(!CK31Y8;GDh2uE6a#Me0bRqUN#_0L=>P= zc+#`Xo&cT@DG)VOAP^3cIL|&&qF3>iarW>PA``}Z&C+W00{<+z7}2dagwlJMZuNGy z+BtlyOWbO*{jI)=U1s20bQzOsj}#g!#}L-^^vg+3-EOpp0X z!}JE}da)(LsiKT=rmjjXt_r0Cv7YJB3sNCcDH^_sZ-rj?b_?l`1BgQtnePN%n8@5a z*W`-tHo3w#?mKkFG!K(&c$HPyz&jM;9SZRd;Unb?&^=<|c*Mdow(pUixGL62t7|ws$UFGI9x?oZfnxY$SO^~j F{|C~d5Jvz2 literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/recruitment/RecruitmentController.class b/backend/target/classes/com/zioinfo/hrm/recruitment/RecruitmentController.class new file mode 100644 index 0000000000000000000000000000000000000000..3a73d5d2aaa45217c94a0d98d1075d4fa252b52e GIT binary patch literal 7291 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 literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/recruitment/RecruitmentService.class b/backend/target/classes/com/zioinfo/hrm/recruitment/RecruitmentService.class new file mode 100644 index 0000000000000000000000000000000000000000..585f227ebec31fc43694635b228afe9ba5916f21 GIT binary patch literal 5146 zcmbtY3sW1{75-KT1Y*HP4sxjTu${yPgss$%kXkM=HntHJF+mP~G-;aJuAFvkxuDve%sKhFi_LX31?>Ue&a{>~d>dG~F$$WF`>TaPXe7Wn?Rc zy`H_Xde1C*8b(6L7L8iXbTwoPUD{R$avJoCRri*hx@Xzzbq&+wg_ftH=W;d|oyq4X z)MUf6Dp}g1p@E{cZW~_1H8s5bWL7RlJ%wge+_@S0ieGWc<^VJd>F7n@AP!(8frA=O zJy!TSQaD5k)-1c+qTtmh0AC=9IRRr-M;eC-tmHK85U(e@GCHx4z!)9uA1&mW3@uPqzX>SsH_;SC7%7sxVK?ZkuP^EX}?_Pps(&xh&-DLh31Qy z@HWlu2aa2&jB3U@y=!?6>()aK`<%74x=EhmuJeEf4k`mO6*9-%y}%Dk?LcTrRY1Bu zitSo6lIx+Z!iul+X7O|>dm;SuAxm`H zQ?$UZ@3*H*G@?wd>9~$7Br#Nam$&oGNzCFepLVG~Uo=A{*EBrSFH;RK`e>RYcIZ+Q zD;gGdZR--FqFMU)bt8<8EwiSrs<~p-E$VdEwpk9I<=CY2Lcwv@v-O(jm}`P%N^CRh zxrSXgN@7pe2)1W$wx*2=OY2NO-e7gjEze%N*^eTdsfxCId0Vjcrj8ZdVqIHy-E>=7 zOYI6SH$f#QYO0yj9vwSW>QJLr4pVnLlE`*dk{rEMaq3MG_2V8Z z{z~CS@#-8|Vm-{(Y89(w*d7ylt{amf;{5io`2@#&E}K&BlG+*h!f|xeP$93PlEU~0 zd>x3Q(!;R{Ha#5;QGX+Cr&I;%M!98ZZyuUx_b9x(d+^Xa)@6_#(nxjO@5lG3^x37Q zLSD(^9UVUq@;abOBp`w^;?+ncRl}$fPq5mQPus0WHk_#6=*+~5pzKVzv+5`T=503TEz1<|o-=ze*y@Vm9_`D|dhA30kNAGB;}>|}7hf3rU_iQ%-$=c* zVU`;eGuR@C`=yRwg;Ex*F=bYh1}0RcELasMf1~45agq!@)_qSlwj+KM&)Zua+IXr^ zajL7%X7;*in=ZR%IhRBaj_`e1bQ*5Syk?0D%i-uX{j|6XX&CSL9;h>|rsMHSkZoR5 zcw6J8jDHsJnj$W-91U}vKtE>(_!L7DgZ%zCSCjl+d^TPljc>ftJNXyrnW@p<(fAIA zdvT#pP3+*Q9!w|xfv4|$iLYE37#m1EH`cdw>ehr?LlwKL1rwXX*Z$5tU;(%{3 z1eJ#Z!vtYzcwHU@-l1=iyJMNujntANHIrJNQX?P0%@}Td!A*+i6wUDf{T?U7FW@Mz z<$~Lz%20sj7;cjlP42xTxfT5A{>*J$;z%GJh;buuhaZiv)^7%A--@8MeYEMR)Z3XB zM56sx8^mD=Wt?i8AjHWKymSCB?UN-^CV<9d`V>4dbBQxPJVB&@sNp*>XnHq^*ElCK zF)aB?t?r;qsS3ta&XRKVG=>O7Fs1S~96%ZFl(&s;Tv?r6ou#XD-MiXEg%K!;RHXzL z_$*|hH!ITaiD+;-M5`XeqT{~QpvbhSt$@V)G$+)r!QEH!^{C==p>Xz5{1zSr8v1S| zS-)jqLcp1+)V8moDQf?rlG9Xb`Ba;*zTYOSGg@KIp8E52N}h?5mHD2(V2bA4zu~|IdyU z^ngg4`lKyYst3HmR4p@A#SoZq4|wFasI*K>dk=WT!~`ws5uvFT_30C~s9$%0cZ=|D z6W*OY;HiEtI)(rmg%_|V5+)#O{MU+5`0qFtef~3ki+Aq~4~rI+oct1>PiFoK{R=*Y hUn#nTpjuNh5OGqz(Suh?FAnn1@8!`y1j76y@L!GEg)IO8 literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/training/TrainingController.class b/backend/target/classes/com/zioinfo/hrm/training/TrainingController.class new file mode 100644 index 0000000000000000000000000000000000000000..28909ce4f0782135bf49db6e6c765cdf562233ae GIT binary patch literal 5895 zcmc&&>sAy;5Uz%W4K<=hB#MMZ2^eBDF%G*84D8OVv$G%)ax+(x zA9;g3LXvY5;>nLZKprY5Uw6;W&ax92h@KqIFx@@fRbN$oRo%UR{r$(EM0Ak-S87AC-rRmh%~`YH4%$Vhzzd90ovE^h3D*IX{_2t8;p0^*pfYD$`as?s z=Y_m&6*z=9PM9;6;T9dBdinxIUs9Ngu9?+Gjl9>DZm9;X9MyjXe~fmG7cJM!@kz5_ zPG$LkW!bLbn!wBGY|3_K^g>=(J?$7dzGyr1`XZmwr%WrO8~$8ITs5=)~-8g?#DIyOf8FPR}4nI#6^Lg8GHM&NFj1pcM z8#OGXi^Hg|GZgds@tPE>uWNLJhER%)v};4sjjc!u(97FI1WkuEdY#^4bYxA$L$<}^ zlZ@)s9)NARa*IOkh#7DJqtlx=lF^RJ8ip$4)v>-xH9;z@vlAk7iup_+a~7+Tuu^XS z=0QiSU_Wi3Iif``2VYRRpIOn531dY+u>_SSHF}%gMT%@-a=#!_0^7rNOC0@jC{Nfg zhW=%hJoU(W81+>4!;6vRIY$=T6b_NW{$>tk;#j8XJZLLBDg5<7qr3DWqcc(PmbVOn z1tRNsEgL+MSAqne5r$koi$gG@n^m?)sTHBfcUL0EnFPDK(j!1y8qJZ- zXnVCrKx)qN86!J^^M4WfRdLAOR6bc%(j#T!R`Uh5L6^u&krI{`HCmuWRW?#Vv#N^? zz?G8)i7L`h%2UTExZ+r6lz?EJ9cg0F(EnN}Jkxs>Hw(h_6^%ZoPq5>nVoK9}JCcA` zHr1;O^_OtNcCX;<-?{POzjRL)4Bq2i>V~a_kjrB-m9=wI_B`%}7I(0cEZkXxNGAQQTmAh5!uXAd8sVlW4Gx> z-mLf)f)3&GC@z_}?uZ*CT1{xjsR6x4^lH#=#yf#g4NuE`Vg>YCG1eQyKpUQI_prLE zxxGNIc%x{x(+(_|g4jB|+q!pAWJ8Dlkc1pn)?glHEX#s9NvE*+!S z(8DU~q~r2?8+D;oBgeX_N1D(^y=cYg1V*>TFqJTgs2fi&9)`=vN#CkdAy%E1R>g6x zqcd0_@Lq)up+%E;SLtjB*8LLe4mHoms?mA607(K(9cm9=4Cl;?K7xK9!6keN`n9nJ z1DCNtVaUNc5u)~<=X6E+?K!=76j#-I&)yEr^r|e3x3~<4$_cgd}N!2uV^}P#ds7d>eyJ!h)7s>GHA_cj-OfihI5l zWlG51WLXHSFN?9Eeq=vqhOTUrWljwho{xeKcHQ~E|0E^xw AFaQ7m literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/training/TrainingMapper.class b/backend/target/classes/com/zioinfo/hrm/training/TrainingMapper.class new file mode 100644 index 0000000000000000000000000000000000000000..97f2c293dd4bca1f70374c24ba307bd733bb14a4 GIT binary patch literal 1947 zcmbVM+j0^?5bYsc5<)^QN{qK8XhKkv_3~ngS{3hDq_o8HeF6iSxa?57LoEC>AN&A6 z%JS^&!axxXrnaWGcV@cJ>C?Ua>-UeJMD&avW+=g^)KDGgyHwI^Dd*VlINImZlV02T z92l8ly4_$hZ>j?)iEC!(Pl&9|i2e5rbVAOc2@cWd@aUHgbrncS&X%h6^} zOV_E%Kvx*e)n(h`y5|c<>VjdOj)Vp6_|x#6Bok#&d5(us@l(&!vLn98Kpwf`Blmen zXyG4tp3+=P4s&Rjgkt@WM6O}T@k5h6JnGV!|WIpqU`N? z9WB~J)=&P$f9wwkd)=w226taUM^l${5J#EENa(ug{^kK%w9pp1CfeK`%!2_~aRO{i z(x|`QoqK7@@H9o9Vmd*FcJ5|Nylh? zd~%EpjGc1^Mii)$X2fU)LzyU>#BiWAzB2eq&@9d2y_dv=EY^HLIcqLhbJ3c4T7t`R z5;ut!uokiM6wdEuT0zV(;#$Hwj71E6=Mn>7_^i^UA)Cv_Hy6^4*%S;|3T9+_dJFiM}vH8z8)iwPjF>XQA9ON4IG^PJT3c z!G_$iW@!)|Y0(Z=$%NRY@{s7A2^~j>M(m(t$Sz`bm+m2cB+VX(?BgUs4&BE)jgtpN Fe*hGL@rnQd literal 0 HcmV?d00001 diff --git a/backend/target/classes/com/zioinfo/hrm/training/TrainingService.class b/backend/target/classes/com/zioinfo/hrm/training/TrainingService.class new file mode 100644 index 0000000000000000000000000000000000000000..2faabdb753ba3c7462fd5c8e83edd09848d20ff1 GIT binary patch literal 4475 zcmbVP`*Ryt75-LQJuGh=TZx?-$8MX3#IIC9oJXzHO`_Uu%PZ@T ztg^3Rpmku`tk*41!&K=|lWJ>TLx0t7_yxD=HLQk)OOvIx{Z-#1ihTD>u{fnBo4#Gm z(G>0Ut=gN8={G$~!`Gh8%9U4#uODl5Nu%B?4bT>8{7WvUV_c5Igs?#zBGPT^j#1CugXk#yu#VC80>07EaHJlExv=}^~B~7hqSlYL(M}&%F z={``5FgALRb!^wHHM?O`oYx$OHQ?K>LoVk_uD6+M)J1#T5EN5;+oEkd4YMrvr_x)gm(LoO+E4^y3lPAz#9f0hyVsCu|>yotJRw2_~hyQQyjHZ-Eolw75C`> zKNTQu>7&O07V;UGD5tQ_mL=PUNO(?`^$n70paN1IcihJsPD}PU~A)N+7K=Du(*5Jge=pm9P3{d7C94w3MVJd1Xg9 zl$?X)oJK7TCk@7tY*5C{MjG^zUc7T}rBqtHA)tQSz;{Fz2bB!lU^Qfvcac>IxK^*S ztB5`yai;nRHSv84ys)x-x3qX)S^lAcAK}LuMwC|DmKh|Wu%5c?MC^9Fh3q3=l!Va+ zCrA_`ELodowPn1!BsxT=dUXX+s#{xkw{y2F z$MRT471l@`W0cVmB+ z^$$Gv@DqGyzHhWIJ29Hv!wYdd;P}i)>QhX%9{)sxalFj`6Lbk2qub*c#W2qBJurt8 z$n({0i8DE_z99iE<8$~tp9lqCz!463g#K) z8Jy)`o<4K*e1*Ru`mn&$BebD*y~bT??JC!f`_OKX(h%zRlOiHxlG8*BF+rnu2aT`Uh3ttJ|F8tzMj4v_-m4_0;1Yv2oBM$-& zeT&?U%w$()6{msMFU9cS&s-ovNEo6UY@rVWoszj^??Ny3{Vyy>UFQ|t(M0%%O;D+({x zf+v_05cvZNZ!${7-q&Sz-Xh!t*Cxh4hVd!B@i%Pz5&HN=Jy0{X{79DxQ_N*#lFng} z=@Sy1Q>R1LCQy?}69RTP5-FX4Dp~}M1m}YK^DRw3t^B2DH1{MJig0(=wUw8%vH_^xkO@BlKCFqIv~~}w+rruWFE3_ zGc|kofv!ZXYDcBwRi6{pyc;Uur&0kPpLMQ{os25r{t81hgJ=&r d65+6AO|*V-1%y2)L9%1`NFM!tsLwwD{{s+a8AAX7 literal 0 HcmV?d00001 diff --git a/backend/target/classes/db/schema.sql b/backend/target/classes/db/schema.sql new file mode 100644 index 0000000..01a1af8 --- /dev/null +++ b/backend/target/classes/db/schema.sql @@ -0,0 +1,515 @@ +-- GUARDiA HRM v1.0 — PostgreSQL 스키마 +-- DROP TABLE 없이 CREATE TABLE IF NOT EXISTS 사용 + +-- 사용자/권한 +CREATE TABLE IF NOT EXISTS hrm_users ( + id BIGSERIAL PRIMARY KEY, + username VARCHAR(50) NOT NULL UNIQUE, + password_hash VARCHAR(255) NOT NULL, + display_name VARCHAR(100), + email VARCHAR(200), + role VARCHAR(30) NOT NULL DEFAULT 'HR_STAFF', + active BOOLEAN NOT NULL DEFAULT true, + created_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +-- 역할: SUPERADMIN / MANAGER / HR_STAFF / VIEWER + +-- 부서 +CREATE TABLE IF NOT EXISTS hrm_departments ( + id BIGSERIAL PRIMARY KEY, + dept_code VARCHAR(30) NOT NULL UNIQUE, + dept_name VARCHAR(100) NOT NULL, + parent_id BIGINT REFERENCES hrm_departments(id), + manager_emp_id BIGINT, + sort_order INT NOT NULL DEFAULT 0, + active BOOLEAN NOT NULL DEFAULT true, + created_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +-- 직책 +CREATE TABLE IF NOT EXISTS hrm_positions ( + id BIGSERIAL PRIMARY KEY, + position_code VARCHAR(30) NOT NULL UNIQUE, + position_name VARCHAR(100) NOT NULL, + sort_order INT NOT NULL DEFAULT 0, + active BOOLEAN NOT NULL DEFAULT true +); + +-- 직급 +CREATE TABLE IF NOT EXISTS hrm_grades ( + id BIGSERIAL PRIMARY KEY, + grade_code VARCHAR(30) NOT NULL UNIQUE, + grade_name VARCHAR(100) NOT NULL, + grade_level INT NOT NULL DEFAULT 1, + sort_order INT NOT NULL DEFAULT 0, + active BOOLEAN NOT NULL DEFAULT true +); + +-- 사원 +CREATE TABLE IF NOT EXISTS hrm_employees ( + id BIGSERIAL PRIMARY KEY, + emp_no VARCHAR(30) NOT NULL UNIQUE, + name VARCHAR(100) NOT NULL, + name_en VARCHAR(100), + department_id BIGINT REFERENCES hrm_departments(id), + position_id BIGINT REFERENCES hrm_positions(id), + grade_id BIGINT REFERENCES hrm_grades(id), + employment_type VARCHAR(20) NOT NULL DEFAULT 'REGULAR', -- REGULAR/CONTRACT/PARTTIME + status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE', -- ACTIVE/LEAVE/RETIRED + hire_date DATE NOT NULL, + retire_date DATE, + email VARCHAR(200), + phone_enc TEXT, -- AES-256-GCM 암호화 + photo_url VARCHAR(500), + gender VARCHAR(10), + birth_date DATE, + address TEXT, + bank_account TEXT, -- 암호화 저장 + created_by VARCHAR(50), + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +-- 사원 경력 +CREATE TABLE IF NOT EXISTS hrm_emp_careers ( + id BIGSERIAL PRIMARY KEY, + emp_id BIGINT NOT NULL REFERENCES hrm_employees(id), + company_name VARCHAR(200) NOT NULL, + position VARCHAR(100), + start_date DATE NOT NULL, + end_date DATE, + description TEXT +); + +-- 사원 자격증 +CREATE TABLE IF NOT EXISTS hrm_emp_certs ( + id BIGSERIAL PRIMARY KEY, + emp_id BIGINT NOT NULL REFERENCES hrm_employees(id), + cert_name VARCHAR(200) NOT NULL, + cert_no VARCHAR(100), + issue_date DATE NOT NULL, + expire_date DATE, + issuer VARCHAR(200) +); + +-- 급여 지급 배치 +CREATE TABLE IF NOT EXISTS hrm_payroll ( + id BIGSERIAL PRIMARY KEY, + year INT NOT NULL, + month INT NOT NULL, + dept_id BIGINT REFERENCES hrm_departments(id), + total_amount NUMERIC(18,2) DEFAULT 0, + status VARCHAR(20) NOT NULL DEFAULT 'DRAFT', -- DRAFT/PROCESSED/APPROVED + processed_by VARCHAR(50), + processed_at TIMESTAMP, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + UNIQUE (year, month) +); + +-- 급여항목 마스터 +CREATE TABLE IF NOT EXISTS hrm_salary_items ( + id BIGSERIAL PRIMARY KEY, + item_code VARCHAR(30) NOT NULL UNIQUE, + item_name VARCHAR(100) NOT NULL, + item_type VARCHAR(20) NOT NULL DEFAULT 'ALLOWANCE', -- ALLOWANCE/DEDUCTION + is_taxable BOOLEAN NOT NULL DEFAULT true, + sort_order INT NOT NULL DEFAULT 0, + active BOOLEAN NOT NULL DEFAULT true +); + +-- 사원 급여 기준 +CREATE TABLE IF NOT EXISTS hrm_emp_salaries ( + id BIGSERIAL PRIMARY KEY, + emp_id BIGINT NOT NULL REFERENCES hrm_employees(id), + base_salary NUMERIC(15,2) NOT NULL DEFAULT 0, + allowances NUMERIC(15,2) NOT NULL DEFAULT 0, + bonus NUMERIC(15,2) NOT NULL DEFAULT 0, + effective_date DATE NOT NULL DEFAULT CURRENT_DATE, + UNIQUE (emp_id) +); + +-- 급여 명세서 +CREATE TABLE IF NOT EXISTS hrm_payslips ( + id BIGSERIAL PRIMARY KEY, + payroll_id BIGINT REFERENCES hrm_payroll(id), + emp_id BIGINT NOT NULL REFERENCES hrm_employees(id), + year INT NOT NULL, + month INT NOT NULL, + base_salary NUMERIC(15,2) NOT NULL DEFAULT 0, + total_allowance NUMERIC(15,2) NOT NULL DEFAULT 0, + total_deduction NUMERIC(15,2) NOT NULL DEFAULT 0, + net_salary NUMERIC(15,2) NOT NULL DEFAULT 0, + income_tax NUMERIC(12,2) NOT NULL DEFAULT 0, + health_ins NUMERIC(12,2) NOT NULL DEFAULT 0, + pension NUMERIC(12,2) NOT NULL DEFAULT 0, + work_days INT NOT NULL DEFAULT 0, + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + UNIQUE (emp_id, year, month) +); + +-- 출퇴근 +CREATE TABLE IF NOT EXISTS hrm_attendance ( + id BIGSERIAL PRIMARY KEY, + emp_id BIGINT NOT NULL REFERENCES hrm_employees(id), + work_date DATE NOT NULL, + check_in_time TIMESTAMP, + check_out_time TIMESTAMP, + work_minutes INT NOT NULL DEFAULT 0, + overtime_minutes INT NOT NULL DEFAULT 0, + status VARCHAR(20) NOT NULL DEFAULT 'PRESENT', -- PRESENT/ABSENT/LEAVE/HOLIDAY + created_by VARCHAR(50), + UNIQUE (emp_id, work_date) +); + +-- 휴가 유형 +CREATE TABLE IF NOT EXISTS hrm_leave_types ( + id BIGSERIAL PRIMARY KEY, + type_code VARCHAR(30) NOT NULL UNIQUE, + type_name VARCHAR(100) NOT NULL, + is_paid BOOLEAN NOT NULL DEFAULT true, + max_days INT NOT NULL DEFAULT 0, + sort_order INT NOT NULL DEFAULT 0, + active BOOLEAN NOT NULL DEFAULT true +); + +-- 휴가 신청 +CREATE TABLE IF NOT EXISTS hrm_leaves ( + id BIGSERIAL PRIMARY KEY, + emp_id BIGINT NOT NULL REFERENCES hrm_employees(id), + leave_type_id BIGINT NOT NULL REFERENCES hrm_leave_types(id), + start_date DATE NOT NULL, + end_date DATE NOT NULL, + days NUMERIC(5,1) NOT NULL DEFAULT 1, + reason TEXT, + status VARCHAR(20) NOT NULL DEFAULT 'PENDING', -- PENDING/APPROVED/REJECTED/CANCELLED + approved_by VARCHAR(50), + created_by VARCHAR(50), + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +-- 연차 부여 +CREATE TABLE IF NOT EXISTS hrm_annual_leaves ( + id BIGSERIAL PRIMARY KEY, + emp_id BIGINT NOT NULL REFERENCES hrm_employees(id), + year INT NOT NULL, + total_days NUMERIC(5,1) NOT NULL DEFAULT 15, + UNIQUE (emp_id, year) +); + +-- 초과근무 +CREATE TABLE IF NOT EXISTS hrm_overtime ( + id BIGSERIAL PRIMARY KEY, + emp_id BIGINT NOT NULL REFERENCES hrm_employees(id), + ot_date DATE NOT NULL, + start_time TIME NOT NULL, + end_time TIME NOT NULL, + ot_minutes INT NOT NULL DEFAULT 0, + reason TEXT, + status VARCHAR(20) NOT NULL DEFAULT 'APPROVED', + created_by VARCHAR(50), + created_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +-- 성과 평가 +CREATE TABLE IF NOT EXISTS hrm_performance_reviews ( + id BIGSERIAL PRIMARY KEY, + emp_id BIGINT NOT NULL REFERENCES hrm_employees(id), + year INT NOT NULL, + period VARCHAR(20) NOT NULL DEFAULT 'ANNUAL', -- ANNUAL/H1/H2/Q1/Q2/Q3/Q4 + final_grade VARCHAR(5), -- S/A/B/C/D + score NUMERIC(5,2), + comments TEXT, + status VARCHAR(20) NOT NULL DEFAULT 'DRAFT', -- DRAFT/SUBMITTED/COMPLETED + created_by VARCHAR(50), + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +-- MBO 목표 +CREATE TABLE IF NOT EXISTS hrm_goals ( + id BIGSERIAL PRIMARY KEY, + emp_id BIGINT NOT NULL REFERENCES hrm_employees(id), + year INT NOT NULL, + goal_title VARCHAR(200) NOT NULL, + goal_desc TEXT, + weight NUMERIC(5,2) NOT NULL DEFAULT 100, + target_value TEXT, + actual_value TEXT, + achievement_rate NUMERIC(5,2), + status VARCHAR(20) NOT NULL DEFAULT 'IN_PROGRESS', + created_by VARCHAR(50), + created_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +-- 역량 마스터 +CREATE TABLE IF NOT EXISTS hrm_competencies ( + id BIGSERIAL PRIMARY KEY, + competency_code VARCHAR(30) NOT NULL UNIQUE, + competency_name VARCHAR(100) NOT NULL, + category VARCHAR(50), + description TEXT, + sort_order INT NOT NULL DEFAULT 0 +); + +-- 역량 점수 +CREATE TABLE IF NOT EXISTS hrm_competency_scores ( + id BIGSERIAL PRIMARY KEY, + emp_id BIGINT NOT NULL REFERENCES hrm_employees(id), + competency_id BIGINT NOT NULL REFERENCES hrm_competencies(id), + year INT NOT NULL, + self_score NUMERIC(3,1), + manager_score NUMERIC(3,1), + peer_score NUMERIC(3,1), + final_score NUMERIC(3,1), + evaluator VARCHAR(50), + updated_at TIMESTAMP NOT NULL DEFAULT NOW(), + UNIQUE (emp_id, competency_id, year) +); + +-- 채용 공고 +CREATE TABLE IF NOT EXISTS hrm_job_postings ( + id BIGSERIAL PRIMARY KEY, + title VARCHAR(200) NOT NULL, + department_id BIGINT REFERENCES hrm_departments(id), + employment_type VARCHAR(20) NOT NULL DEFAULT 'REGULAR', + headcount INT NOT NULL DEFAULT 1, + description TEXT, + requirements TEXT, + status VARCHAR(20) NOT NULL DEFAULT 'DRAFT', -- DRAFT/PUBLISHED/CLOSED + start_date DATE, + end_date DATE, + created_by VARCHAR(50), + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +-- 지원자 +CREATE TABLE IF NOT EXISTS hrm_applicants ( + id BIGSERIAL PRIMARY KEY, + posting_id BIGINT NOT NULL REFERENCES hrm_job_postings(id), + applicant_name VARCHAR(100) NOT NULL, + email VARCHAR(200), + phone VARCHAR(30), + resume_url VARCHAR(500), + cover_letter TEXT, + status VARCHAR(30) NOT NULL DEFAULT 'APPLIED', -- APPLIED/REVIEWED/INTERVIEW/OFFER/HIRED/REJECTED + apply_date TIMESTAMP NOT NULL DEFAULT NOW(), + memo TEXT, + created_by VARCHAR(50), + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +-- 면접 +CREATE TABLE IF NOT EXISTS hrm_interviews ( + id BIGSERIAL PRIMARY KEY, + posting_id BIGINT NOT NULL REFERENCES hrm_job_postings(id), + applicant_id BIGINT NOT NULL REFERENCES hrm_applicants(id), + interview_type VARCHAR(50) NOT NULL DEFAULT 'TECHNICAL', -- DOCUMENT/TECHNICAL/HR/FINAL + scheduled_at TIMESTAMP NOT NULL, + location VARCHAR(200), + interviewers TEXT, + result VARCHAR(20), -- PASS/FAIL/PENDING + notes TEXT, + created_by VARCHAR(50), + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +-- 교육 과정 +CREATE TABLE IF NOT EXISTS hrm_training_courses ( + id BIGSERIAL PRIMARY KEY, + course_code VARCHAR(50) NOT NULL UNIQUE, + course_name VARCHAR(200) NOT NULL, + course_type VARCHAR(50) NOT NULL DEFAULT 'INTERNAL', -- INTERNAL/EXTERNAL/ONLINE + instructor VARCHAR(100), + description TEXT, + start_date DATE, + end_date DATE, + duration_hours NUMERIC(5,1) NOT NULL DEFAULT 0, + is_legal BOOLEAN NOT NULL DEFAULT false, + max_attendees INT NOT NULL DEFAULT 50, + status VARCHAR(20) NOT NULL DEFAULT 'SCHEDULED', + created_by VARCHAR(50), + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + updated_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +-- 교육 수강 +CREATE TABLE IF NOT EXISTS hrm_training_enrollments ( + id BIGSERIAL PRIMARY KEY, + emp_id BIGINT NOT NULL REFERENCES hrm_employees(id), + course_id BIGINT NOT NULL REFERENCES hrm_training_courses(id), + status VARCHAR(20) NOT NULL DEFAULT 'ENROLLED', -- ENROLLED/COMPLETED/CANCELLED + enrolled_at TIMESTAMP NOT NULL DEFAULT NOW(), + completed_at DATE, + created_by VARCHAR(50), + UNIQUE (emp_id, course_id) +); + +-- 감사 로그 +CREATE TABLE IF NOT EXISTS hrm_audit_log ( + id BIGSERIAL PRIMARY KEY, + actor VARCHAR(100) NOT NULL, + action VARCHAR(100) NOT NULL, + target_type VARCHAR(50), + target_id VARCHAR(100), + detail TEXT, + ip_addr VARCHAR(50), + created_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +-- 시스템 설정 +CREATE TABLE IF NOT EXISTS hrm_settings ( + key VARCHAR(100) PRIMARY KEY, + value TEXT, + description VARCHAR(500), + updated_by VARCHAR(50), + updated_at TIMESTAMP NOT NULL DEFAULT NOW() +); + +-- 인덱스 +CREATE INDEX IF NOT EXISTS idx_hrm_emp_dept ON hrm_employees(department_id); +CREATE INDEX IF NOT EXISTS idx_hrm_emp_status ON hrm_employees(status); +CREATE INDEX IF NOT EXISTS idx_hrm_attendance_date ON hrm_attendance(work_date); +CREATE INDEX IF NOT EXISTS idx_hrm_leaves_emp ON hrm_leaves(emp_id); +CREATE INDEX IF NOT EXISTS idx_hrm_leaves_status ON hrm_leaves(status); +CREATE INDEX IF NOT EXISTS idx_hrm_payslips_emp ON hrm_payslips(emp_id, year, month); +CREATE INDEX IF NOT EXISTS idx_hrm_perf_emp ON hrm_performance_reviews(emp_id, year); +CREATE INDEX IF NOT EXISTS idx_hrm_goals_emp ON hrm_goals(emp_id, year); +CREATE INDEX IF NOT EXISTS idx_hrm_audit_actor ON hrm_audit_log(actor); +CREATE INDEX IF NOT EXISTS idx_hrm_audit_created ON hrm_audit_log(created_at DESC); + +-- ============================================================ +-- 시드 데이터 +-- ============================================================ + +-- admin 사용자 (password: admin123) +INSERT INTO hrm_users (username, password_hash, display_name, email, role, active) +VALUES ('admin', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', 'HRM 관리자', 'admin@zioinfo.co.kr', 'SUPERADMIN', true) +ON CONFLICT (username) DO NOTHING; + +INSERT INTO hrm_users (username, password_hash, display_name, email, role, active) +VALUES ('hr_manager', '$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy', '인사팀장', 'hrmanager@zioinfo.co.kr', 'MANAGER', true) +ON CONFLICT (username) DO NOTHING; + +-- 기본 부서 +INSERT INTO hrm_departments (dept_code, dept_name, sort_order) VALUES +('MGMT', '경영지원부', 1), +('DEV', '개발부', 2), +('OPS', '운영부', 3), +('SALES', '영업부', 4), +('ADMIN', '관리부', 5) +ON CONFLICT (dept_code) DO NOTHING; + +-- 직책 +INSERT INTO hrm_positions (position_code, position_name, sort_order) VALUES +('CEO', '대표이사', 1), +('DIR', '이사', 2), +('MGR', '부장', 3), +('TEAM_LEAD', '팀장', 4), +('SENIOR', '선임', 5), +('STAFF', '사원', 6) +ON CONFLICT (position_code) DO NOTHING; + +-- 직급 +INSERT INTO hrm_grades (grade_code, grade_name, grade_level, sort_order) VALUES +('G1', '1급', 1, 1), +('G2', '2급', 2, 2), +('G3', '3급', 3, 3), +('G4', '4급', 4, 4), +('G5', '5급', 5, 5), +('G6', '6급', 6, 6) +ON CONFLICT (grade_code) DO NOTHING; + +-- 휴가 유형 +INSERT INTO hrm_leave_types (type_code, type_name, is_paid, max_days, sort_order) VALUES +('ANNUAL', '연차', true, 15, 1), +('HALF', '반차', true, 30, 2), +('SICK', '병가', false, 60, 3), +('MATERNITY', '출산휴가', true, 90, 4), +('PARENTAL', '육아휴직', false, 365, 5), +('SPECIAL', '특별휴가', true, 5, 6) +ON CONFLICT (type_code) DO NOTHING; + +-- 급여 항목 +INSERT INTO hrm_salary_items (item_code, item_name, item_type, is_taxable, sort_order) VALUES +('BASE', '기본급', 'ALLOWANCE', true, 1), +('MEAL', '식대', 'ALLOWANCE', false, 2), +('TRANSPORT', '교통비', 'ALLOWANCE', false, 3), +('OVERTIME', '초과근무수당', 'ALLOWANCE', true, 4), +('INCOME_TAX', '소득세', 'DEDUCTION', false, 10), +('RESIDENT_TAX', '지방소득세', 'DEDUCTION', false, 11), +('HEALTH_INS', '건강보험', 'DEDUCTION', false, 12), +('PENSION', '국민연금', 'DEDUCTION', false, 13), +('EMPLOY_INS', '고용보험', 'DEDUCTION', false, 14) +ON CONFLICT (item_code) DO NOTHING; + +-- 역량 마스터 +INSERT INTO hrm_competencies (competency_code, competency_name, category, sort_order) VALUES +('LEADERSHIP', '리더십', '공통역량', 1), +('COMM', '커뮤니케이션', '공통역량', 2), +('PROBLEM', '문제해결력', '공통역량', 3), +('CUSTOMER', '고객지향성', '공통역량', 4), +('TECH', '전문기술', '직무역량', 5), +('INNOVATION', '혁신능력', '직무역량', 6) +ON CONFLICT (competency_code) DO NOTHING; + +-- 법정 교육 +INSERT INTO hrm_training_courses (course_code, course_name, course_type, duration_hours, is_legal, max_attendees) VALUES +('LEGAL_SEXUAL', '직장 내 성희롱 예방교육', 'ONLINE', 1.0, true, 999), +('LEGAL_DISABLED', '장애인 인식개선 교육', 'ONLINE', 1.0, true, 999), +('LEGAL_SAFETY', '산업안전보건교육', 'INTERNAL', 2.0, true, 999), +('LEGAL_PRIVACY', '개인정보보호 교육', 'ONLINE', 1.0, true, 999) +ON CONFLICT (course_code) DO NOTHING; + +-- 샘플 사원 5명 +INSERT INTO hrm_employees (emp_no, name, department_id, position_id, grade_id, employment_type, status, hire_date, email, created_by) +SELECT 'EMP-2024-0001', '홍길동', + (SELECT id FROM hrm_departments WHERE dept_code='DEV'), + (SELECT id FROM hrm_positions WHERE position_code='MGR'), + (SELECT id FROM hrm_grades WHERE grade_code='G3'), + 'REGULAR', 'ACTIVE', '2020-03-02', 'hong@zioinfo.co.kr', 'admin' +WHERE NOT EXISTS (SELECT 1 FROM hrm_employees WHERE emp_no='EMP-2024-0001'); + +INSERT INTO hrm_employees (emp_no, name, department_id, position_id, grade_id, employment_type, status, hire_date, email, created_by) +SELECT 'EMP-2024-0002', '김철수', + (SELECT id FROM hrm_departments WHERE dept_code='OPS'), + (SELECT id FROM hrm_positions WHERE position_code='SENIOR'), + (SELECT id FROM hrm_grades WHERE grade_code='G4'), + 'REGULAR', 'ACTIVE', '2021-07-01', 'kim@zioinfo.co.kr', 'admin' +WHERE NOT EXISTS (SELECT 1 FROM hrm_employees WHERE emp_no='EMP-2024-0002'); + +INSERT INTO hrm_employees (emp_no, name, department_id, position_id, grade_id, employment_type, status, hire_date, email, created_by) +SELECT 'EMP-2024-0003', '이영희', + (SELECT id FROM hrm_departments WHERE dept_code='SALES'), + (SELECT id FROM hrm_positions WHERE position_code='TEAM_LEAD'), + (SELECT id FROM hrm_grades WHERE grade_code='G3'), + 'REGULAR', 'ACTIVE', '2019-01-15', 'lee@zioinfo.co.kr', 'admin' +WHERE NOT EXISTS (SELECT 1 FROM hrm_employees WHERE emp_no='EMP-2024-0003'); + +INSERT INTO hrm_employees (emp_no, name, department_id, position_id, grade_id, employment_type, status, hire_date, email, created_by) +SELECT 'EMP-2024-0004', '박민수', + (SELECT id FROM hrm_departments WHERE dept_code='DEV'), + (SELECT id FROM hrm_positions WHERE position_code='STAFF'), + (SELECT id FROM hrm_grades WHERE grade_code='G5'), + 'REGULAR', 'ACTIVE', '2023-03-02', 'park@zioinfo.co.kr', 'admin' +WHERE NOT EXISTS (SELECT 1 FROM hrm_employees WHERE emp_no='EMP-2024-0004'); + +INSERT INTO hrm_employees (emp_no, name, department_id, position_id, grade_id, employment_type, status, hire_date, email, created_by) +SELECT 'EMP-2024-0005', '최지현', + (SELECT id FROM hrm_departments WHERE dept_code='ADMIN'), + (SELECT id FROM hrm_positions WHERE position_code='SENIOR'), + (SELECT id FROM hrm_grades WHERE grade_code='G4'), + 'REGULAR', 'ACTIVE', '2022-05-09', 'choi@zioinfo.co.kr', 'admin' +WHERE NOT EXISTS (SELECT 1 FROM hrm_employees WHERE emp_no='EMP-2024-0005'); + +-- 기본 설정 +INSERT INTO hrm_settings (key, value, description) VALUES +('company_name', '지오정보기술(주)', '회사명'), +('fiscal_year_start', '01', '회계연도 시작월'), +('annual_leave_base', '15', '기본 연차 일수'), +('payroll_day', '25', '급여 지급일') +ON CONFLICT (key) DO NOTHING; diff --git a/backend/target/classes/mapper/AdminMapper.xml b/backend/target/classes/mapper/AdminMapper.xml new file mode 100644 index 0000000..1459b2c --- /dev/null +++ b/backend/target/classes/mapper/AdminMapper.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + INSERT INTO hrm_users (username, password_hash, display_name, email, role, active) + VALUES (#{username}, #{passwordHash}, #{displayName}, #{email}, #{role}, #{active}) + + + + UPDATE hrm_users SET + display_name=#{displayName}, email=#{email}, role=#{role} + , password_hash=#{passwordHash} + WHERE id=#{id} + + + + UPDATE hrm_users SET active=#{active} WHERE id=#{id} + + + + + + + + INSERT INTO hrm_audit_log (actor, action, target_type, target_id, detail, ip_addr) + VALUES (#{actor}, #{action}, #{targetType}, #{targetId}, #{detail}, #{ipAddr}) + + + + + + + + INSERT INTO hrm_settings (key, value, updated_by) + VALUES (#{key}, #{value}, #{updatedBy}) + ON CONFLICT (key) DO UPDATE SET value=#{value}, updated_by=#{updatedBy}, updated_at=NOW() + + + diff --git a/backend/target/classes/mapper/AttendanceMapper.xml b/backend/target/classes/mapper/AttendanceMapper.xml new file mode 100644 index 0000000..e3498d7 --- /dev/null +++ b/backend/target/classes/mapper/AttendanceMapper.xml @@ -0,0 +1,99 @@ + + + + + + + + + + INSERT INTO hrm_attendance (emp_id, work_date, check_in_time, status) + VALUES (#{empId}, #{workDate}, #{checkInTime}, 'PRESENT') + ON CONFLICT (emp_id, work_date) DO UPDATE SET check_in_time=#{checkInTime} + + + + UPDATE hrm_attendance SET + check_out_time=#{checkOutTime}, + work_minutes=EXTRACT(EPOCH FROM (#{checkOutTime}::timestamp - check_in_time))/60 + WHERE emp_id=#{empId} AND work_date=#{workDate} + + + + + + INSERT INTO hrm_leaves (emp_id, leave_type_id, start_date, end_date, days, reason, status, created_by) + VALUES (#{empId}, #{leaveTypeId}, #{startDate}, #{endDate}, #{days}, #{reason}, #{status}, #{createdBy}) + + + + UPDATE hrm_leaves SET status=#{status}, approved_by=#{approvedBy}, updated_at=NOW() + WHERE id=#{id} + + + + + + + + INSERT INTO hrm_leave_types (type_code, type_name, is_paid, max_days) + VALUES (#{typeCode}, #{typeName}, #{isPaid}, #{maxDays}) + + + + + + INSERT INTO hrm_overtime (emp_id, ot_date, start_time, end_time, ot_minutes, reason, created_by) + VALUES (#{empId}, #{otDate}, #{startTime}, #{endTime}, #{otMinutes}, #{reason}, #{createdBy}) + + + diff --git a/backend/target/classes/mapper/DashboardMapper.xml b/backend/target/classes/mapper/DashboardMapper.xml new file mode 100644 index 0000000..b478d1e --- /dev/null +++ b/backend/target/classes/mapper/DashboardMapper.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + diff --git a/backend/target/classes/mapper/EmployeeMapper.xml b/backend/target/classes/mapper/EmployeeMapper.xml new file mode 100644 index 0000000..4d8e95f --- /dev/null +++ b/backend/target/classes/mapper/EmployeeMapper.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + INSERT INTO hrm_employees + (emp_no, name, name_en, department_id, position_id, grade_id, + employment_type, status, hire_date, email, phone_enc, photo_url, gender, birth_date, address, bank_account, created_by) + VALUES + (#{empNo}, #{name}, #{nameEn}, #{departmentId}, #{positionId}, #{gradeId}, + #{employmentType}, #{status}, #{hireDate}, #{email}, #{phoneEnc}, #{photoUrl}, #{gender}, #{birthDate}, #{address}, #{bankAccount}, #{createdBy}) + + + + UPDATE hrm_employees SET + name=#{name}, name_en=#{nameEn}, department_id=#{departmentId}, + position_id=#{positionId}, grade_id=#{gradeId}, + employment_type=#{employmentType}, email=#{email}, + phone_enc=#{phoneEnc}, photo_url=#{photoUrl}, gender=#{gender}, + birth_date=#{birthDate}, address=#{address}, updated_at=NOW() + WHERE id=#{id} + + + + UPDATE hrm_employees SET status=#{status}, retire_date=#{retireDate}, updated_at=NOW() + WHERE id=#{id} + + + + + + INSERT INTO hrm_emp_careers (emp_id, company_name, position, start_date, end_date, description) + VALUES (#{empId}, #{companyName}, #{position}, #{startDate}, #{endDate}, #{description}) + + + DELETE FROM hrm_emp_careers WHERE id=#{id} + + + + + INSERT INTO hrm_emp_certs (emp_id, cert_name, cert_no, issue_date, expire_date, issuer) + VALUES (#{empId}, #{certName}, #{certNo}, #{issueDate}, #{expireDate}, #{issuer}) + + + DELETE FROM hrm_emp_certs WHERE id=#{id} + + diff --git a/backend/target/classes/mapper/OrgMapper.xml b/backend/target/classes/mapper/OrgMapper.xml new file mode 100644 index 0000000..4a813bf --- /dev/null +++ b/backend/target/classes/mapper/OrgMapper.xml @@ -0,0 +1,54 @@ + + + + + + + + INSERT INTO hrm_departments (dept_code, dept_name, parent_id, manager_emp_id, sort_order) + VALUES (#{deptCode}, #{deptName}, #{parentId}, #{managerEmpId}, #{sortOrder}) + + + + UPDATE hrm_departments SET + dept_name=#{deptName}, parent_id=#{parentId}, + manager_emp_id=#{managerEmpId}, sort_order=#{sortOrder} + WHERE id=#{id} + + + + UPDATE hrm_departments SET active=false WHERE id=#{id} + + + + + + INSERT INTO hrm_positions (position_code, position_name, sort_order) + VALUES (#{positionCode}, #{positionName}, #{sortOrder}) + + + + + + INSERT INTO hrm_grades (grade_code, grade_name, grade_level, sort_order) + VALUES (#{gradeCode}, #{gradeName}, #{gradeLevel}, #{sortOrder}) + + + diff --git a/backend/target/classes/mapper/PayrollMapper.xml b/backend/target/classes/mapper/PayrollMapper.xml new file mode 100644 index 0000000..307560e --- /dev/null +++ b/backend/target/classes/mapper/PayrollMapper.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + INSERT INTO hrm_payroll (year, month, status, processed_by, processed_at) + VALUES (#{year}, #{month}, #{status}, #{processedBy}, #{processedAt}) + ON CONFLICT (year, month) DO UPDATE SET status=#{status}, processed_at=NOW() + + + + UPDATE hrm_payroll SET status=#{status} WHERE id=#{id} + + + + + + + + INSERT INTO hrm_emp_salaries (emp_id, base_salary, allowances, bonus, effective_date) + VALUES (#{empId}, #{baseSalary}, #{allowances}, #{bonus}, #{effectiveDate}) + ON CONFLICT (emp_id) DO UPDATE SET + base_salary=#{baseSalary}, allowances=#{allowances}, + bonus=#{bonus}, effective_date=#{effectiveDate} + + + + + diff --git a/backend/target/classes/mapper/PerformanceMapper.xml b/backend/target/classes/mapper/PerformanceMapper.xml new file mode 100644 index 0000000..e16f764 --- /dev/null +++ b/backend/target/classes/mapper/PerformanceMapper.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + INSERT INTO hrm_performance_reviews (emp_id, year, period, status, created_by) + VALUES (#{empId}, #{year}, #{period}, #{status}, #{createdBy}) + + + + UPDATE hrm_performance_reviews SET + final_grade=#{finalGrade}, score=#{score}, comments=#{comments}, updated_at=NOW() + WHERE id=#{id} + + + + UPDATE hrm_performance_reviews SET status=#{status}, updated_at=NOW() WHERE id=#{id} + + + + + + INSERT INTO hrm_goals (emp_id, year, goal_title, goal_desc, weight, target_value, created_by) + VALUES (#{empId}, #{year}, #{goalTitle}, #{goalDesc}, #{weight}, #{targetValue}, #{createdBy}) + + + + UPDATE hrm_goals SET + goal_title=#{goalTitle}, weight=#{weight}, target_value=#{targetValue}, + actual_value=#{actualValue}, achievement_rate=#{achievementRate}, status=#{status} + WHERE id=#{id} + + + + + + INSERT INTO hrm_competency_scores (emp_id, competency_id, year, self_score, manager_score, evaluator) + VALUES (#{empId}, #{competencyId}, #{year}, #{selfScore}, #{managerScore}, #{evaluator}) + ON CONFLICT (emp_id, competency_id, year) DO UPDATE SET + manager_score=#{managerScore}, evaluator=#{evaluator}, updated_at=NOW() + + + diff --git a/backend/target/classes/mapper/RecruitmentMapper.xml b/backend/target/classes/mapper/RecruitmentMapper.xml new file mode 100644 index 0000000..5051652 --- /dev/null +++ b/backend/target/classes/mapper/RecruitmentMapper.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + INSERT INTO hrm_job_postings (title, department_id, employment_type, headcount, description, requirements, status, start_date, end_date, created_by) + VALUES (#{title}, #{departmentId}, #{employmentType}, #{headcount}, #{description}, #{requirements}, #{status}, #{startDate}, #{endDate}, #{createdBy}) + + + + UPDATE hrm_job_postings SET + title=#{title}, department_id=#{departmentId}, employment_type=#{employmentType}, + headcount=#{headcount}, description=#{description}, requirements=#{requirements}, + start_date=#{startDate}, end_date=#{endDate}, updated_at=NOW() + WHERE id=#{id} + + + + UPDATE hrm_job_postings SET status=#{status}, updated_at=NOW() WHERE id=#{id} + + + + + + + + INSERT INTO hrm_applicants (posting_id, applicant_name, email, phone, resume_url, cover_letter, status, apply_date, created_by) + VALUES (#{postingId}, #{applicantName}, #{email}, #{phone}, #{resumeUrl}, #{coverLetter}, #{status}, NOW(), #{createdBy}) + + + + UPDATE hrm_applicants SET status=#{status}, memo=#{memo}, updated_at=NOW() WHERE id=#{id} + + + + + + INSERT INTO hrm_interviews (posting_id, applicant_id, interview_type, scheduled_at, location, interviewers, created_by) + VALUES (#{postingId}, #{applicantId}, #{interviewType}, #{scheduledAt}, #{location}, #{interviewers}, #{createdBy}) + + + + UPDATE hrm_interviews SET + interview_type=#{interviewType}, scheduled_at=#{scheduledAt}, + location=#{location}, result=#{result}, notes=#{notes}, updated_at=NOW() + WHERE id=#{id} + + + diff --git a/backend/target/classes/mapper/TrainingMapper.xml b/backend/target/classes/mapper/TrainingMapper.xml new file mode 100644 index 0000000..693aa13 --- /dev/null +++ b/backend/target/classes/mapper/TrainingMapper.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + INSERT INTO hrm_training_courses (course_code, course_name, course_type, instructor, description, + start_date, end_date, duration_hours, is_legal, max_attendees, status, created_by) + VALUES (#{courseCode}, #{courseName}, #{courseType}, #{instructor}, #{description}, + #{startDate}, #{endDate}, #{durationHours}, #{isLegal}, #{maxAttendees}, 'SCHEDULED', #{createdBy}) + + + + UPDATE hrm_training_courses SET + course_name=#{courseName}, instructor=#{instructor}, + start_date=#{startDate}, end_date=#{endDate}, + duration_hours=#{durationHours}, max_attendees=#{maxAttendees}, updated_at=NOW() + WHERE id=#{id} + + + + + + INSERT INTO hrm_training_enrollments (emp_id, course_id, status, enrolled_at, created_by) + VALUES (#{empId}, #{courseId}, #{status}, NOW(), #{createdBy}) + ON CONFLICT (emp_id, course_id) DO NOTHING + + + + UPDATE hrm_training_enrollments SET status=#{status}, completed_at=#{completedAt} + WHERE id=#{id} + + + + + + + diff --git a/backend/target/classes/mapper/UserMapper.xml b/backend/target/classes/mapper/UserMapper.xml new file mode 100644 index 0000000..88b9afa --- /dev/null +++ b/backend/target/classes/mapper/UserMapper.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + INSERT INTO hrm_users (username, password_hash, display_name, email, role, active) + VALUES (#{username}, #{passwordHash}, #{displayName}, #{email}, #{role}, #{active}) + + + diff --git a/frontend/index.html b/frontend/index.html index 76aa26c..aa320b4 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,7 +2,7 @@ - + GUARDiA HRM — AI 인사관리 플랫폼 diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 9e98f89..ca15a15 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -10,21 +10,7 @@ import PerformancePage from './pages/PerformancePage' import RecruitmentPage from './pages/RecruitmentPage' import TrainingPage from './pages/TrainingPage' import AiPage from './pages/AiPage' -import WiseAiPage from './pages/WiseAiPage' -import AiPlatformSettings from './pages/AiPlatformSettings' import AdminPage from './pages/AdminPage' -import MyPage from './pages/MyPage' -import MobileApp from './pages/MobileApp' -// UIWS(UIMS) 이식 — 공통 업무협업 화면(업무일지·일정·쪽지·통계) -import WorklogList from './pages/uiws/WorklogList' -import ScheduleCalendar from './pages/uiws/ScheduleCalendar' -import MessageBox from './pages/uiws/MessageBox' -import StatsPivot from './pages/uiws/StatsPivot' -// UIWS system(권한관리) 이식 — 시스템관리(권한) 화면 셸(탭: 권한/역할-메뉴/공통코드/메뉴/부서/거래처) -import SystemPage from './pages/uiws/system/SystemPage' - -// 시스템관리(권한)는 관리자 전용. 백엔드 /api/system/** 조회=SUPERADMIN/MANAGER, 쓰기=SUPERADMIN. -const ADMIN_ROLES = ['SUPERADMIN', 'MANAGER', 'ADMIN'] const MENU = [ { path: '/dashboard', label: '대시보드', icon: '📊' }, @@ -35,25 +21,13 @@ const MENU = [ { path: '/performance', label: '성과평가', icon: '🎯' }, { path: '/recruitment', label: '채용관리', icon: '🔍' }, { path: '/training', label: '교육관리', icon: '📚' }, - // UIWS 이식 — 업무 협업 - { path: '/worklogs', label: '업무일지', icon: '📝' }, - { path: '/schedules', label: '일정관리', icon: '📅' }, - { path: '/messages', label: '쪽지함', icon: '✉️' }, - { path: '/work-stats', label: '업무통계', icon: '📈' }, { path: '/ai', label: 'AI 인사분석', icon: '🤖' }, - { path: '/wise-ai', label: 'WISE AI', icon: '✨' }, - { path: '/ai-platform', label: 'AI 플랫폼 설정', icon: '🧠', adminOnly: true }, { path: '/admin', label: '시스템관리', icon: '⚙️' }, - // UIWS system 이식 — 권한·코드·메뉴·부서·거래처 (관리자 전용) - { path: '/system', label: '시스템관리(권한)', icon: '🔐', adminOnly: true }, - { path: '/mobile-app', label: '모바일 앱', icon: '📱' }, ] function Layout({ children }: { children: React.ReactNode }) { const [collapsed, setCollapsed] = useState(false) const user = JSON.parse(localStorage.getItem('hrm_user') || '{"displayName":"관리자","role":"SUPERADMIN"}') - const isAdmin = ADMIN_ROLES.includes(user.role) - const visibleMenu = MENU.filter(m => !m.adminOnly || isAdmin) return (

@@ -71,7 +45,7 @@ function Layout({ children }: { children: React.ReactNode }) {