diff --git a/ERP_SYNC_README.md b/ERP_SYNC_README.md
new file mode 100644
index 0000000..c187498
--- /dev/null
+++ b/ERP_SYNC_README.md
@@ -0,0 +1,226 @@
+# ERP API 데이터 동기화 배치 시스템
+
+## 개요
+이 시스템은 외부 ERP API로부터 거래처, 부서, 사원 정보를 자동으로 가져와 PLM 시스템의 데이터베이스에 동기화하는 배치 프로그램입니다.
+
+## 주요 기능
+
+### 1. 거래처 정보 동기화 (CustomerApiClient)
+- **API 엔드포인트**: `/apiproxy/api16S11`
+- **대상 테이블**: `client_mng`
+- **동기화 데이터**:
+ - 거래처코드, 거래처명, 거래처약칭
+ - 사업자등록번호, 대표자명
+ - 업태, 종목
+ - 주소, 연락처 정보
+ - 사용여부
+
+### 2. 부서 정보 동기화 (DepartmentApiClient)
+- **API 엔드포인트**: `/apiproxy/api16S10`
+- **대상 테이블**: `dept_info`
+- **동기화 데이터**:
+ - 부서코드, 부서명
+ - 회사코드, 부서레벨
+ - 상위부서 정보
+ - 정렬순서, 등록일/종료일
+
+### 3. 사원 정보 동기화 (EmployeeApiClient)
+- **API 엔드포인트**: `/apiproxy/api16S05`
+- **대상 테이블**: `user_info`
+- **동기화 데이터**:
+ - 사원번호, 사원코드, 사원명
+ - 부서 정보
+ - 직급, 직책
+ - 연락처 (이메일, 휴대폰)
+ - 재직구분, 입사일, 생년월일
+
+## 배치 실행 스케줄
+
+### 자동 실행
+- **실행 시간**: 매일 새벽 00시 00분
+- **Cron 표현식**: `0 0 0 * * ?`
+- **실행 메서드**: `BatchService.syncErpData()`
+
+### 수동 실행
+필요시 `BatchService.syncErpData()` 메서드를 직접 호출하여 수동 실행 가능
+
+## 설치 및 설정
+
+### 1. 데이터베이스 설정
+```bash
+# PostgreSQL에 접속하여 테이블 생성 스크립트 실행
+psql -U [사용자명] -d [데이터베이스명] -f database/create_erp_sync_tables.sql
+```
+
+### 2. 소스 파일 구조
+```
+wace_plm/
+├── src/com/pms/
+│ ├── api/
+│ │ ├── CustomerApiClient.java # 거래처 API 클라이언트
+│ │ ├── DepartmentApiClient.java # 부서 API 클라이언트
+│ │ └── EmployeeApiClient.java # 사원 API 클라이언트
+│ ├── service/
+│ │ └── BatchService.java # 배치 서비스 (동기화 로직)
+│ └── mapper/
+│ └── batch.xml # MyBatis 매퍼 (SQL 쿼리)
+└── database/
+ └── create_erp_sync_tables.sql # 테이블 생성 스크립트
+```
+
+### 3. MyBatis 설정
+`mybatisConf.xml`에 batch 매퍼가 등록되어 있는지 확인:
+```xml
+
+```
+
+### 4. API 인증 정보
+API 클라이언트에 다음 정보가 설정되어 있습니다:
+- **Base URL**: `https://erp.rps-korea.com`
+- **회사코드**: `1000`
+- **Caller Name**: `API_gcmsAmaranth40578`
+- **Access Token**: (소스 코드 참조)
+- **Hash Key**: (소스 코드 참조)
+
+## 동작 방식
+
+### 1. API 호출
+각 API 클라이언트가 ERP 시스템에 HTTPS POST 요청을 전송합니다.
+- TLS 1.2 프로토콜 사용
+- HMacSHA256 기반 인증
+- JSON 형식 데이터 송수신
+
+### 2. 데이터 파싱
+API 응답 JSON을 파싱하여 Map 객체로 변환합니다.
+- JDK 1.7 호환 방식의 수동 JSON 파싱
+- 필요한 필드만 추출
+
+### 3. 데이터베이스 저장
+- 기존 데이터 존재 여부 확인 (코드 기준)
+- 신규 데이터: INSERT
+- 기존 데이터: UPDATE
+- 트랜잭션 처리로 데이터 무결성 보장
+
+### 4. 로깅
+- 각 단계별 진행 상황 콘솔 출력
+- 성공/실패 건수 집계
+- 오류 발생 시 상세 로그 출력
+
+## 에러 처리
+
+### API 호출 실패
+- 연결 타임아웃: 30초
+- 리다이렉트 자동 처리
+- HTTP 에러 코드 확인 및 로깅
+
+### 데이터 파싱 오류
+- JSON 파싱 실패 시 해당 레코드 스킵
+- 오류 로그 출력 후 다음 레코드 처리 계속
+
+### 데이터베이스 오류
+- 트랜잭션 롤백
+- 전체 배치 실패 처리
+- 상세 오류 메시지 출력
+
+## 모니터링
+
+### 로그 확인
+배치 실행 시 다음 정보가 출력됩니다:
+```
+====================================
+ERP 데이터 동기화 배치 시작
+====================================
+거래처 정보 동기화 시작...
+거래처 정보 동기화 완료 - 신규: X건, 수정: Y건
+부서 정보 동기화 시작...
+부서 정보 동기화 완료 - 신규: X건, 수정: Y건
+사원 정보 동기화 시작...
+사원 정보 동기화 완료 - 신규: X건, 수정: Y건
+ERP 데이터 동기화 배치 완료
+```
+
+### 데이터 확인
+```sql
+-- 거래처 정보 확인
+SELECT COUNT(*) FROM client_mng WHERE reg_user = 'batch_system';
+
+-- 부서 정보 확인
+SELECT COUNT(*) FROM dept_info WHERE reg_user = 'batch_system';
+
+-- 사원 정보 확인
+SELECT COUNT(*) FROM user_info WHERE reg_user = 'batch_system';
+
+-- 최근 동기화 데이터 확인
+SELECT * FROM client_mng WHERE reg_user = 'batch_system'
+ORDER BY reg_date DESC LIMIT 10;
+```
+
+## 주의사항
+
+1. **데이터 중복 방지**
+ - 거래처: `tr_cd` (거래처코드) 기준
+ - 부서: `dept_cd` (부서코드) 기준
+ - 사원: `emp_cd` (사원코드) 기준
+
+2. **네트워크 환경**
+ - ERP API 서버 접근 가능 여부 확인
+ - 방화벽 설정 확인
+ - SSL 인증서 검증 우회 (개발 환경)
+
+3. **성능 고려사항**
+ - 대량 데이터 처리 시 시간 소요 가능
+ - 배치 실행 시간대 조정 가능 (현재: 새벽 00시)
+ - 필요시 페이징 처리 추가 고려
+
+4. **데이터 정합성**
+ - 배치 실행 전 데이터 백업 권장
+ - 테스트 환경에서 충분한 검증 후 운영 적용
+
+## 트러블슈팅
+
+### API 호출 실패
+```
+원인: 네트워크 연결 문제, 인증 실패
+해결:
+- 네트워크 연결 확인
+- API 인증 정보 확인
+- ERP 서버 상태 확인
+```
+
+### JSON 파싱 오류
+```
+원인: API 응답 형식 변경
+해결:
+- API 응답 로그 확인
+- 파싱 로직 수정
+```
+
+### 데이터베이스 오류
+```
+원인: 테이블 구조 불일치, 권한 문제
+해결:
+- 테이블 생성 스크립트 재실행
+- 데이터베이스 권한 확인
+```
+
+## 향후 개선 사항
+
+1. **성능 최적화**
+ - 배치 INSERT/UPDATE 처리
+ - 페이징 처리 추가
+
+2. **모니터링 강화**
+ - 배치 실행 이력 테이블 추가
+ - 알림 기능 추가 (이메일, 슬랙 등)
+
+3. **에러 복구**
+ - 실패 레코드 재처리 로직
+ - 부분 실패 시 이어서 처리
+
+4. **설정 외부화**
+ - API URL, 인증 정보를 설정 파일로 분리
+ - 환경별 설정 관리
+
+## 문의
+
+기술 지원이 필요한 경우 개발팀에 문의하시기 바랍니다.
diff --git a/src/com/pms/api/CustomerApiClient.java b/src/com/pms/api/CustomerApiClient.java
new file mode 100644
index 0000000..8624990
--- /dev/null
+++ b/src/com/pms/api/CustomerApiClient.java
@@ -0,0 +1,255 @@
+package com.pms.api;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.security.cert.X509Certificate;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import org.apache.commons.codec.binary.Base64;
+import java.util.Random;
+
+/**
+ * 거래처 정보 조회 API 클라이언트
+ * 복수의 일반, 금융 거래처 정보를 조회하는 API를 호출합니다.
+ */
+public class CustomerApiClient {
+
+ private static final String API_URL = "~/apiproxy/api16S11";
+ private static final String CALLER_NAME = "API_gcmsAmaranth40578";
+ private static final String ACCESS_TOKEN = "MN5KzKBWRAa92BPxDlRLl3GcsxeZXc";
+ private static final String HASH_KEY = "22519103205540290721741689643674301018832465";
+ private static final String GROUP_SEQ = "gcmsAmaranth40578";
+
+ /**
+ * 거래처 정보를 조회합니다.
+ *
+ * @param baseUrl API 서버의 기본 URL (예: https://erp.rps-korea.com)
+ * @param coCd 회사코드 (4자리, 필수)
+ * @return API 응답 결과 (JSON 문자열)
+ * @throws Exception API 호출 중 발생하는 예외
+ */
+ public String getCustomerList(String baseUrl, String coCd) throws Exception {
+ if (coCd == null || coCd.trim().isEmpty()) {
+ throw new IllegalArgumentException("회사코드(coCd)는 필수입니다.");
+ }
+
+ // JDK 1.7에서 TLS 1.2 활성화
+ System.setProperty("https.protocols", "TLSv1.2");
+
+ // SSL 인증서 검증 우회 (개발 환경용)
+ TrustManager[] trustAllCerts = new TrustManager[] {
+ new X509TrustManager() {
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+ public void checkClientTrusted(X509Certificate[] certs, String authType) {
+ }
+ public void checkServerTrusted(X509Certificate[] certs, String authType) {
+ }
+ }
+ };
+
+ SSLContext sc = SSLContext.getInstance("TLS");
+ sc.init(null, trustAllCerts, new java.security.SecureRandom());
+ HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+ HttpsURLConnection.setDefaultHostnameVerifier(new javax.net.ssl.HostnameVerifier() {
+ public boolean verify(String hostname, javax.net.ssl.SSLSession session) {
+ return true;
+ }
+ });
+
+ // API URL 구성
+ String urlPath = API_URL.replace("~", "");
+ String cleanBaseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
+ String fullUrl = cleanBaseUrl + urlPath;
+ URL url = new URL(fullUrl);
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+
+ // 연결 타임아웃 설정 (30초)
+ connection.setConnectTimeout(30000);
+ connection.setReadTimeout(30000);
+ connection.setInstanceFollowRedirects(false);
+
+ try {
+ // HTTP 메서드 설정
+ connection.setRequestMethod("POST");
+ connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
+ connection.setRequestProperty("Accept", "application/json");
+
+ // 인증 헤더 설정
+ connection.setRequestProperty("callerName", CALLER_NAME);
+ connection.setRequestProperty("Authorization", "Bearer " + ACCESS_TOKEN);
+
+ String transactionId = generateTransactionId();
+ connection.setRequestProperty("transaction-id", transactionId);
+
+ String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
+ connection.setRequestProperty("timestamp", timestamp);
+
+ connection.setRequestProperty("groupSeq", GROUP_SEQ);
+
+ String wehagoSign = generateWehagoSign(ACCESS_TOKEN, transactionId, timestamp, urlPath);
+ connection.setRequestProperty("wehago-sign", wehagoSign);
+
+ // 요청 본문 작성
+ String requestBody = buildRequestBody(coCd);
+
+ // 요청 전송 설정
+ connection.setDoOutput(true);
+ connection.setDoInput(true);
+
+ // 요청 본문 전송
+ OutputStreamWriter writer = new OutputStreamWriter(
+ connection.getOutputStream(), StandardCharsets.UTF_8);
+ writer.write(requestBody);
+ writer.flush();
+ writer.close();
+
+ // 응답 코드 확인
+ int responseCode = connection.getResponseCode();
+
+ // 리다이렉트 처리
+ if (responseCode == 301 || responseCode == 302 || responseCode == 303 ||
+ responseCode == 307 || responseCode == 308) {
+ String location = connection.getHeaderField("Location");
+ connection.disconnect();
+
+ if (location != null) {
+ URL redirectUrl = new URL(location);
+ connection = (HttpURLConnection) redirectUrl.openConnection();
+ connection.setConnectTimeout(30000);
+ connection.setReadTimeout(30000);
+ connection.setInstanceFollowRedirects(false);
+ connection.setRequestMethod("POST");
+ connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
+ connection.setRequestProperty("Accept", "application/json");
+
+ connection.setRequestProperty("callerName", CALLER_NAME);
+ connection.setRequestProperty("Authorization", "Bearer " + ACCESS_TOKEN);
+ connection.setRequestProperty("transaction-id", transactionId);
+ connection.setRequestProperty("timestamp", timestamp);
+ connection.setRequestProperty("groupSeq", GROUP_SEQ);
+ connection.setRequestProperty("wehago-sign", wehagoSign);
+
+ connection.setDoOutput(true);
+ connection.setDoInput(true);
+
+ OutputStreamWriter redirectWriter = new OutputStreamWriter(
+ connection.getOutputStream(), StandardCharsets.UTF_8);
+ redirectWriter.write(requestBody);
+ redirectWriter.flush();
+ redirectWriter.close();
+
+ responseCode = connection.getResponseCode();
+ }
+ }
+
+ // 응답 읽기
+ BufferedReader reader = null;
+ StringBuilder response = new StringBuilder();
+
+ try {
+ if (responseCode >= 200 && responseCode < 300) {
+ reader = new BufferedReader(new InputStreamReader(
+ connection.getInputStream(), StandardCharsets.UTF_8));
+ } else {
+ java.io.InputStream errorStream = connection.getErrorStream();
+ if (errorStream != null) {
+ reader = new BufferedReader(new InputStreamReader(
+ errorStream, StandardCharsets.UTF_8));
+ } else {
+ throw new Exception("API 호출 실패: HTTP " + responseCode + " (에러 응답 본문 없음)");
+ }
+ }
+
+ String line;
+ while ((line = reader.readLine()) != null) {
+ response.append(line);
+ }
+ } finally {
+ if (reader != null) {
+ reader.close();
+ }
+ }
+
+ if (responseCode >= 200 && responseCode < 300) {
+ return response.toString();
+ } else {
+ throw new Exception("API 호출 실패: HTTP " + responseCode + " - " + response.toString());
+ }
+
+ } finally {
+ connection.disconnect();
+ }
+ }
+
+ /**
+ * 요청 본문 JSON을 생성합니다.
+ */
+ private String buildRequestBody(String coCd) {
+ StringBuilder json = new StringBuilder();
+ json.append("{");
+ json.append("\"coCd\":\"").append(escapeJson(coCd)).append("\"");
+ json.append("}");
+ return json.toString();
+ }
+
+ /**
+ * JSON 문자열 이스케이프 처리
+ */
+ private String escapeJson(String value) {
+ if (value == null) {
+ return "";
+ }
+ return value.replace("\\", "\\\\")
+ .replace("\"", "\\\"")
+ .replace("\n", "\\n")
+ .replace("\r", "\\r")
+ .replace("\t", "\\t");
+ }
+
+ /**
+ * 32자리 랜덤 transaction-id 생성
+ */
+ private String generateTransactionId() {
+ String chars = "0123456789abcdef";
+ Random random = new Random();
+ StringBuilder sb = new StringBuilder(32);
+ for (int i = 0; i < 32; i++) {
+ sb.append(chars.charAt(random.nextInt(chars.length())));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Wehago-sign 생성
+ */
+ private String generateWehagoSign(String accessToken, String transactionId,
+ String timestamp, String urlPath) throws Exception {
+ try {
+ String value = accessToken + transactionId + timestamp + urlPath;
+
+ SecretKeySpec keySpec = new SecretKeySpec(
+ HASH_KEY.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
+ Mac mac = Mac.getInstance("HmacSHA256");
+ mac.init(keySpec);
+ byte[] encrypted = mac.doFinal(value.getBytes(StandardCharsets.UTF_8));
+
+ String base64Binary = Base64.encodeBase64String(encrypted);
+ return base64Binary;
+
+ } catch (Exception e) {
+ System.err.println("Wehago-sign 생성 오류: " + e.getMessage());
+ e.printStackTrace();
+ throw e;
+ }
+ }
+}
diff --git a/src/com/pms/api/DepartmentApiClient.java b/src/com/pms/api/DepartmentApiClient.java
new file mode 100644
index 0000000..d5e96e5
--- /dev/null
+++ b/src/com/pms/api/DepartmentApiClient.java
@@ -0,0 +1,261 @@
+package com.pms.api;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.security.cert.X509Certificate;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import org.apache.commons.codec.binary.Base64;
+import java.util.Random;
+
+/**
+ * 부서 정보 조회 API 클라이언트
+ * 회사의 부서정보를 조회하는 API를 호출합니다.
+ */
+public class DepartmentApiClient {
+
+ private static final String API_URL = "~/apiproxy/api16S10";
+ private static final String CALLER_NAME = "API_gcmsAmaranth40578";
+ private static final String ACCESS_TOKEN = "MN5KzKBWRAa92BPxDlRLl3GcsxeZXc";
+ private static final String HASH_KEY = "22519103205540290721741689643674301018832465";
+ private static final String GROUP_SEQ = "gcmsAmaranth40578";
+
+ /**
+ * 부서 정보를 조회합니다.
+ *
+ * @param baseUrl API 서버의 기본 URL (예: https://erp.rps-korea.com)
+ * @param coCd 회사코드 (4자리, 필수)
+ * @param searchText 검색명 (부서코드 또는 부서명, 선택사항, 미입력 시 전체 조회)
+ * @return API 응답 결과 (JSON 문자열)
+ * @throws Exception API 호출 중 발생하는 예외
+ */
+ public String getDepartmentList(String baseUrl, String coCd, String searchText) throws Exception {
+ if (coCd == null || coCd.trim().isEmpty()) {
+ throw new IllegalArgumentException("회사코드(coCd)는 필수입니다.");
+ }
+
+ // JDK 1.7에서 TLS 1.2 활성화
+ System.setProperty("https.protocols", "TLSv1.2");
+
+ // SSL 인증서 검증 우회 (개발 환경용)
+ TrustManager[] trustAllCerts = new TrustManager[] {
+ new X509TrustManager() {
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+ public void checkClientTrusted(X509Certificate[] certs, String authType) {
+ }
+ public void checkServerTrusted(X509Certificate[] certs, String authType) {
+ }
+ }
+ };
+
+ SSLContext sc = SSLContext.getInstance("TLS");
+ sc.init(null, trustAllCerts, new java.security.SecureRandom());
+ HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+ HttpsURLConnection.setDefaultHostnameVerifier(new javax.net.ssl.HostnameVerifier() {
+ public boolean verify(String hostname, javax.net.ssl.SSLSession session) {
+ return true;
+ }
+ });
+
+ // API URL 구성
+ String urlPath = API_URL.replace("~", "");
+ String cleanBaseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
+ String fullUrl = cleanBaseUrl + urlPath;
+ URL url = new URL(fullUrl);
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+
+ // 연결 타임아웃 설정 (30초)
+ connection.setConnectTimeout(30000);
+ connection.setReadTimeout(30000);
+ connection.setInstanceFollowRedirects(false);
+
+ try {
+ // HTTP 메서드 설정
+ connection.setRequestMethod("POST");
+ connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
+ connection.setRequestProperty("Accept", "application/json");
+
+ // 인증 헤더 설정
+ connection.setRequestProperty("callerName", CALLER_NAME);
+ connection.setRequestProperty("Authorization", "Bearer " + ACCESS_TOKEN);
+
+ String transactionId = generateTransactionId();
+ connection.setRequestProperty("transaction-id", transactionId);
+
+ String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
+ connection.setRequestProperty("timestamp", timestamp);
+
+ connection.setRequestProperty("groupSeq", GROUP_SEQ);
+
+ String wehagoSign = generateWehagoSign(ACCESS_TOKEN, transactionId, timestamp, urlPath);
+ connection.setRequestProperty("wehago-sign", wehagoSign);
+
+ // 요청 본문 작성
+ String requestBody = buildRequestBody(coCd, searchText);
+
+ // 요청 전송 설정
+ connection.setDoOutput(true);
+ connection.setDoInput(true);
+
+ // 요청 본문 전송
+ OutputStreamWriter writer = new OutputStreamWriter(
+ connection.getOutputStream(), StandardCharsets.UTF_8);
+ writer.write(requestBody);
+ writer.flush();
+ writer.close();
+
+ // 응답 코드 확인
+ int responseCode = connection.getResponseCode();
+
+ // 리다이렉트 처리
+ if (responseCode == 301 || responseCode == 302 || responseCode == 303 ||
+ responseCode == 307 || responseCode == 308) {
+ String location = connection.getHeaderField("Location");
+ connection.disconnect();
+
+ if (location != null) {
+ URL redirectUrl = new URL(location);
+ connection = (HttpURLConnection) redirectUrl.openConnection();
+ connection.setConnectTimeout(30000);
+ connection.setReadTimeout(30000);
+ connection.setInstanceFollowRedirects(false);
+ connection.setRequestMethod("POST");
+ connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
+ connection.setRequestProperty("Accept", "application/json");
+
+ connection.setRequestProperty("callerName", CALLER_NAME);
+ connection.setRequestProperty("Authorization", "Bearer " + ACCESS_TOKEN);
+ connection.setRequestProperty("transaction-id", transactionId);
+ connection.setRequestProperty("timestamp", timestamp);
+ connection.setRequestProperty("groupSeq", GROUP_SEQ);
+ connection.setRequestProperty("wehago-sign", wehagoSign);
+
+ connection.setDoOutput(true);
+ connection.setDoInput(true);
+
+ OutputStreamWriter redirectWriter = new OutputStreamWriter(
+ connection.getOutputStream(), StandardCharsets.UTF_8);
+ redirectWriter.write(requestBody);
+ redirectWriter.flush();
+ redirectWriter.close();
+
+ responseCode = connection.getResponseCode();
+ }
+ }
+
+ // 응답 읽기
+ BufferedReader reader = null;
+ StringBuilder response = new StringBuilder();
+
+ try {
+ if (responseCode >= 200 && responseCode < 300) {
+ reader = new BufferedReader(new InputStreamReader(
+ connection.getInputStream(), StandardCharsets.UTF_8));
+ } else {
+ java.io.InputStream errorStream = connection.getErrorStream();
+ if (errorStream != null) {
+ reader = new BufferedReader(new InputStreamReader(
+ errorStream, StandardCharsets.UTF_8));
+ } else {
+ throw new Exception("API 호출 실패: HTTP " + responseCode + " (에러 응답 본문 없음)");
+ }
+ }
+
+ String line;
+ while ((line = reader.readLine()) != null) {
+ response.append(line);
+ }
+ } finally {
+ if (reader != null) {
+ reader.close();
+ }
+ }
+
+ if (responseCode >= 200 && responseCode < 300) {
+ return response.toString();
+ } else {
+ throw new Exception("API 호출 실패: HTTP " + responseCode + " - " + response.toString());
+ }
+
+ } finally {
+ connection.disconnect();
+ }
+ }
+
+ /**
+ * 요청 본문 JSON을 생성합니다.
+ */
+ private String buildRequestBody(String coCd, String searchText) {
+ StringBuilder json = new StringBuilder();
+ json.append("{");
+ json.append("\"coCd\":\"").append(escapeJson(coCd)).append("\"");
+
+ if (searchText != null && !searchText.trim().isEmpty()) {
+ json.append(",\"searchText\":\"").append(escapeJson(searchText)).append("\"");
+ }
+
+ json.append("}");
+ return json.toString();
+ }
+
+ /**
+ * JSON 문자열 이스케이프 처리
+ */
+ private String escapeJson(String value) {
+ if (value == null) {
+ return "";
+ }
+ return value.replace("\\", "\\\\")
+ .replace("\"", "\\\"")
+ .replace("\n", "\\n")
+ .replace("\r", "\\r")
+ .replace("\t", "\\t");
+ }
+
+ /**
+ * 32자리 랜덤 transaction-id 생성
+ */
+ private String generateTransactionId() {
+ String chars = "0123456789abcdef";
+ Random random = new Random();
+ StringBuilder sb = new StringBuilder(32);
+ for (int i = 0; i < 32; i++) {
+ sb.append(chars.charAt(random.nextInt(chars.length())));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Wehago-sign 생성
+ */
+ private String generateWehagoSign(String accessToken, String transactionId,
+ String timestamp, String urlPath) throws Exception {
+ try {
+ String value = accessToken + transactionId + timestamp + urlPath;
+
+ SecretKeySpec keySpec = new SecretKeySpec(
+ HASH_KEY.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
+ Mac mac = Mac.getInstance("HmacSHA256");
+ mac.init(keySpec);
+ byte[] encrypted = mac.doFinal(value.getBytes(StandardCharsets.UTF_8));
+
+ String base64Binary = Base64.encodeBase64String(encrypted);
+ return base64Binary;
+
+ } catch (Exception e) {
+ System.err.println("Wehago-sign 생성 오류: " + e.getMessage());
+ e.printStackTrace();
+ throw e;
+ }
+ }
+}
diff --git a/src/com/pms/api/EmployeeApiClient.java b/src/com/pms/api/EmployeeApiClient.java
new file mode 100644
index 0000000..cceaaaf
--- /dev/null
+++ b/src/com/pms/api/EmployeeApiClient.java
@@ -0,0 +1,268 @@
+package com.pms.api;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.security.cert.X509Certificate;
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+import org.apache.commons.codec.binary.Base64;
+import java.util.Random;
+
+/**
+ * 사원 정보 조회 API 클라이언트
+ * 사원의 목록을 조회하는 API를 호출합니다.
+ */
+public class EmployeeApiClient {
+
+ private static final String API_URL = "~/apiproxy/api16S05";
+ private static final String CALLER_NAME = "API_gcmsAmaranth40578";
+ private static final String ACCESS_TOKEN = "MN5KzKBWRAa92BPxDlRLl3GcsxeZXc";
+ private static final String HASH_KEY = "22519103205540290721741689643674301018832465";
+ private static final String GROUP_SEQ = "gcmsAmaranth40578";
+
+ /**
+ * 사원 정보를 조회합니다.
+ *
+ * @param baseUrl API 서버의 기본 URL (예: https://erp.rps-korea.com)
+ * @param coCd 회사코드 (4자리, 필수)
+ * @return API 응답 결과 (JSON 문자열)
+ * @throws Exception API 호출 중 발생하는 예외
+ */
+ public String getEmployeeList(String baseUrl, String coCd) throws Exception {
+ if (coCd == null || coCd.trim().isEmpty()) {
+ throw new IllegalArgumentException("회사코드(coCd)는 필수입니다.");
+ }
+
+ // JDK 1.7에서 TLS 1.2 활성화
+ System.setProperty("https.protocols", "TLSv1.2");
+
+ // SSL 인증서 검증 우회 (개발 환경용)
+ TrustManager[] trustAllCerts = new TrustManager[] {
+ new X509TrustManager() {
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+ public void checkClientTrusted(X509Certificate[] certs, String authType) {
+ }
+ public void checkServerTrusted(X509Certificate[] certs, String authType) {
+ }
+ }
+ };
+
+ SSLContext sc = SSLContext.getInstance("TLS");
+ sc.init(null, trustAllCerts, new java.security.SecureRandom());
+ HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+ HttpsURLConnection.setDefaultHostnameVerifier(new javax.net.ssl.HostnameVerifier() {
+ public boolean verify(String hostname, javax.net.ssl.SSLSession session) {
+ return true;
+ }
+ });
+
+ // API URL 구성
+ String urlPath = API_URL.replace("~", "");
+ String cleanBaseUrl = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
+ String fullUrl = cleanBaseUrl + urlPath;
+ URL url = new URL(fullUrl);
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+
+ // 연결 타임아웃 설정 (30초)
+ connection.setConnectTimeout(30000);
+ connection.setReadTimeout(30000);
+ connection.setInstanceFollowRedirects(false);
+
+ try {
+ // HTTP 메서드 설정
+ connection.setRequestMethod("POST");
+ connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
+ connection.setRequestProperty("Accept", "application/json");
+
+ // 인증 헤더 설정
+ connection.setRequestProperty("callerName", CALLER_NAME);
+ connection.setRequestProperty("Authorization", "Bearer " + ACCESS_TOKEN);
+
+ String transactionId = generateTransactionId();
+ connection.setRequestProperty("transaction-id", transactionId);
+
+ String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
+ connection.setRequestProperty("timestamp", timestamp);
+
+ connection.setRequestProperty("groupSeq", GROUP_SEQ);
+
+ String wehagoSign = generateWehagoSign(ACCESS_TOKEN, transactionId, timestamp, urlPath);
+ connection.setRequestProperty("wehago-sign", wehagoSign);
+
+ // 요청 본문 작성
+ String requestBody = buildRequestBody(coCd);
+
+ // 요청 전송 설정
+ connection.setDoOutput(true);
+ connection.setDoInput(true);
+
+ // 요청 본문 전송
+ OutputStreamWriter writer = new OutputStreamWriter(
+ connection.getOutputStream(), StandardCharsets.UTF_8);
+ writer.write(requestBody);
+ writer.flush();
+ writer.close();
+
+ // 응답 코드 확인
+ int responseCode = connection.getResponseCode();
+
+ // 리다이렉트 처리
+ if (responseCode == 301 || responseCode == 302 || responseCode == 303 ||
+ responseCode == 307 || responseCode == 308) {
+ String location = connection.getHeaderField("Location");
+ connection.disconnect();
+
+ if (location != null) {
+ URL redirectUrl = new URL(location);
+ connection = (HttpURLConnection) redirectUrl.openConnection();
+ connection.setConnectTimeout(30000);
+ connection.setReadTimeout(30000);
+ connection.setInstanceFollowRedirects(false);
+ connection.setRequestMethod("POST");
+ connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
+ connection.setRequestProperty("Accept", "application/json");
+
+ connection.setRequestProperty("callerName", CALLER_NAME);
+ connection.setRequestProperty("Authorization", "Bearer " + ACCESS_TOKEN);
+ connection.setRequestProperty("transaction-id", transactionId);
+ connection.setRequestProperty("timestamp", timestamp);
+ connection.setRequestProperty("groupSeq", GROUP_SEQ);
+ connection.setRequestProperty("wehago-sign", wehagoSign);
+
+ connection.setDoOutput(true);
+ connection.setDoInput(true);
+
+ OutputStreamWriter redirectWriter = new OutputStreamWriter(
+ connection.getOutputStream(), StandardCharsets.UTF_8);
+ redirectWriter.write(requestBody);
+ redirectWriter.flush();
+ redirectWriter.close();
+
+ responseCode = connection.getResponseCode();
+ }
+ }
+
+ // 응답 읽기
+ BufferedReader reader = null;
+ StringBuilder response = new StringBuilder();
+
+ try {
+ if (responseCode >= 200 && responseCode < 300) {
+ reader = new BufferedReader(new InputStreamReader(
+ connection.getInputStream(), StandardCharsets.UTF_8));
+ } else {
+ java.io.InputStream errorStream = connection.getErrorStream();
+ if (errorStream != null) {
+ reader = new BufferedReader(new InputStreamReader(
+ errorStream, StandardCharsets.UTF_8));
+ } else {
+ throw new Exception("API 호출 실패: HTTP " + responseCode + " (에러 응답 본문 없음)");
+ }
+ }
+
+ String line;
+ while ((line = reader.readLine()) != null) {
+ response.append(line);
+ }
+ } finally {
+ if (reader != null) {
+ reader.close();
+ }
+ }
+
+ if (responseCode >= 200 && responseCode < 300) {
+ return response.toString();
+ } else {
+ throw new Exception("API 호출 실패: HTTP " + responseCode + " - " + response.toString());
+ }
+
+ } finally {
+ connection.disconnect();
+ }
+ }
+
+ /**
+ * 요청 본문 JSON을 생성합니다.
+ */
+ private String buildRequestBody(String coCd) {
+ StringBuilder json = new StringBuilder();
+ json.append("{");
+
+ // header 섹션
+ json.append("\"header\":{");
+ json.append("\"groupSeq\":\"").append(escapeJson(GROUP_SEQ)).append("\"");
+ json.append(",\"empSeq\":\"\"");
+ json.append(",\"tId\":\"\"");
+ json.append(",\"pId\":\"\"");
+ json.append("}");
+
+ // body 섹션
+ json.append(",\"body\":{");
+ json.append("\"coCd\":\"").append(escapeJson(coCd)).append("\"");
+ json.append("}");
+
+ json.append("}");
+ return json.toString();
+ }
+
+ /**
+ * JSON 문자열 이스케이프 처리
+ */
+ private String escapeJson(String value) {
+ if (value == null) {
+ return "";
+ }
+ return value.replace("\\", "\\\\")
+ .replace("\"", "\\\"")
+ .replace("\n", "\\n")
+ .replace("\r", "\\r")
+ .replace("\t", "\\t");
+ }
+
+ /**
+ * 32자리 랜덤 transaction-id 생성
+ */
+ private String generateTransactionId() {
+ String chars = "0123456789abcdef";
+ Random random = new Random();
+ StringBuilder sb = new StringBuilder(32);
+ for (int i = 0; i < 32; i++) {
+ sb.append(chars.charAt(random.nextInt(chars.length())));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Wehago-sign 생성
+ */
+ private String generateWehagoSign(String accessToken, String transactionId,
+ String timestamp, String urlPath) throws Exception {
+ try {
+ String value = accessToken + transactionId + timestamp + urlPath;
+
+ SecretKeySpec keySpec = new SecretKeySpec(
+ HASH_KEY.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
+ Mac mac = Mac.getInstance("HmacSHA256");
+ mac.init(keySpec);
+ byte[] encrypted = mac.doFinal(value.getBytes(StandardCharsets.UTF_8));
+
+ String base64Binary = Base64.encodeBase64String(encrypted);
+ return base64Binary;
+
+ } catch (Exception e) {
+ System.err.println("Wehago-sign 생성 오류: " + e.getMessage());
+ e.printStackTrace();
+ throw e;
+ }
+ }
+}
diff --git a/src/com/pms/controller/AdminController.java b/src/com/pms/controller/AdminController.java
index 7356066..b0f933c 100644
--- a/src/com/pms/controller/AdminController.java
+++ b/src/com/pms/controller/AdminController.java
@@ -27,6 +27,7 @@ import com.pms.common.utils.Constants;
import com.pms.common.SqlMapConfig;
import com.pms.service.AdminService;
import com.pms.service.CommonService;
+import com.pms.service.BatchService;
import org.apache.ibatis.session.SqlSession;
@@ -39,6 +40,9 @@ public class AdminController extends BaseService {
@Autowired
CommonService commonService;
+ @Autowired
+ BatchService batchService;
+
@RequestMapping("/admin/adminMainFS.do")
public String adminMainFS(HttpServletRequest request, @RequestParam Map paramMap){
//관리자 메뉴 호출 전 관리자 권한 여부 확인
@@ -5196,4 +5200,85 @@ public String clientImportFileProc(HttpServletRequest request, HttpSession sessi
return resultMap;
}
+
+ /**
+ * ERP 사원 정보 동기화 수동 실행
+ * @param request
+ * @param paramMap
+ * @return
+ */
+ @RequestMapping("/admin/syncEmployeeDataManual.do")
+ @ResponseBody
+ public Map syncEmployeeDataManual(HttpServletRequest request, @RequestParam Map paramMap) {
+ Map resultMap = new HashMap();
+
+ try {
+ System.out.println("====================================");
+ System.out.println("관리자 수동 ERP 사원 동기화 실행 요청");
+ System.out.println("====================================");
+
+ resultMap = batchService.syncEmployeeDataManual();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ resultMap.put("success", false);
+ resultMap.put("message", "사원 동기화 실행 중 오류가 발생했습니다: " + e.getMessage());
+ }
+
+ return resultMap;
+ }
+
+ /**
+ * ERP 부서 정보 동기화 수동 실행
+ * @param request
+ * @param paramMap
+ * @return
+ */
+ @RequestMapping("/admin/syncDepartmentDataManual.do")
+ @ResponseBody
+ public Map syncDepartmentDataManual(HttpServletRequest request, @RequestParam Map paramMap) {
+ Map resultMap = new HashMap();
+
+ try {
+ System.out.println("====================================");
+ System.out.println("관리자 수동 ERP 부서 동기화 실행 요청");
+ System.out.println("====================================");
+
+ resultMap = batchService.syncDepartmentDataManual();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ resultMap.put("success", false);
+ resultMap.put("message", "부서 동기화 실행 중 오류가 발생했습니다: " + e.getMessage());
+ }
+
+ return resultMap;
+ }
+
+ /**
+ * ERP 거래처 정보 동기화 수동 실행
+ * @param request
+ * @param paramMap
+ * @return
+ */
+ @RequestMapping("/admin/syncCustomerDataManual.do")
+ @ResponseBody
+ public Map syncCustomerDataManual(HttpServletRequest request, @RequestParam Map paramMap) {
+ Map resultMap = new HashMap();
+
+ try {
+ System.out.println("====================================");
+ System.out.println("관리자 수동 ERP 거래처 동기화 실행 요청");
+ System.out.println("====================================");
+
+ resultMap = batchService.syncCustomerDataManual();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ resultMap.put("success", false);
+ resultMap.put("message", "거래처 동기화 실행 중 오류가 발생했습니다: " + e.getMessage());
+ }
+
+ return resultMap;
+ }
}
diff --git a/src/com/pms/mapper/batch.xml b/src/com/pms/mapper/batch.xml
new file mode 100644
index 0000000..d99f6ee
--- /dev/null
+++ b/src/com/pms/mapper/batch.xml
@@ -0,0 +1,178 @@
+
+
+
+
+
+
+
+ INSERT INTO client_mng (
+ objid, client_cd, client_nm, tr_nmk, client_nmk, attr_nmk, client_type,
+ bus_reg_no, resident_no, ceo_nm, ceo_nmk, bus_type, bus_item,
+ post_no, addr1, addr2, tel_no, fax_no, homepage, email,
+ liq_rs, tr_fg, country_nm, class_cd, class_nm, grade_cd, grade_nm,
+ collect_client_cd, collect_client_nm, region_cd, region_nm,
+ trade_start_dt, trade_end_dt, use_yn, contract_start_dt, contract_end_dt,
+ trade_type, discount_rate, contract_amt, monthly_fee, payment_term, rcp_tp,
+ credit_limit, limit_return_day,
+ pur_bank_cd, pur_bank_nm, pur_branch_nm, pur_account_no, pur_account_holder,
+ pur_pay_plan, pur_slip_type, pur_tax_type,
+ sale_bank_cd, sale_bank_nm, sale_branch_nm, sale_account_no,
+ sale_collect_plan, sale_slip_type, sale_tax_type,
+ vendor_dept_nm, vendor_position, vendor_duty, vendor_manager_nm,
+ vendor_tel, vendor_ext, vendor_mobile, vendor_email,
+ mgr_dept_cd, mgr_dept_nm, mgr_position, mgr_duty, mgr_emp_cd, mgr_emp_nm,
+ mgr_tel, mgr_ext, mgr_mobile, mgr_email, mgr_remark,
+ rec_remark, rec_post_no, rec_addr1, rec_addr2, rec_tel, rec_fax,
+ project_cd, project_nm, pjt_nmk, ext_data_cd, e_tax_yn,
+ unit_report_client, sub_bus_no, procurement_yn, use_fg, for_yn,
+ plan_day_type, plan_day, purpose_type,
+ insert_id, insert_dt, modify_id, modify_dt
+ ) VALUES (
+ #{objid}::numeric, #{client_cd}, #{client_nm}, #{tr_nmk}, #{client_nmk}, #{attr_nmk}, #{client_type},
+ #{bus_reg_no}, #{resident_no}, #{ceo_nm}, #{ceo_nmk}, #{bus_type}, #{bus_item},
+ #{post_no}, #{addr1}, #{addr2}, #{tel_no}, #{fax_no}, #{homepage}, #{email},
+ #{liq_rs}, #{tr_fg}, #{country_nm}, #{class_cd}, #{class_nm}, #{grade_cd}, #{grade_nm},
+ #{collect_client_cd}, #{collect_client_nm}, #{region_cd}, #{region_nm},
+ #{trade_start_dt}, #{trade_end_dt}, #{use_yn}, #{contract_start_dt}, #{contract_end_dt},
+ #{trade_type}, #{discount_rate}, #{contract_amt}, #{monthly_fee}, #{payment_term}, #{rcp_tp},
+ #{credit_limit}, #{limit_return_day},
+ #{pur_bank_cd}, #{pur_bank_nm}, #{pur_branch_nm}, #{pur_account_no}, #{pur_account_holder},
+ #{pur_pay_plan}, #{pur_slip_type}, #{pur_tax_type},
+ #{sale_bank_cd}, #{sale_bank_nm}, #{sale_branch_nm}, #{sale_account_no},
+ #{sale_collect_plan}, #{sale_slip_type}, #{sale_tax_type},
+ #{vendor_dept_nm}, #{vendor_position}, #{vendor_duty}, #{vendor_manager_nm},
+ #{vendor_tel}, #{vendor_ext}, #{vendor_mobile}, #{vendor_email},
+ #{mgr_dept_cd}, #{mgr_dept_nm}, #{mgr_position}, #{mgr_duty}, #{mgr_emp_cd}, #{mgr_emp_nm},
+ #{mgr_tel}, #{mgr_ext}, #{mgr_mobile}, #{mgr_email}, #{mgr_remark},
+ #{rec_remark}, #{rec_post_no}, #{rec_addr1}, #{rec_addr2}, #{rec_tel}, #{rec_fax},
+ #{project_cd}, #{project_nm}, #{pjt_nmk}, #{ext_data_cd}, #{e_tax_yn},
+ #{unit_report_client}, #{sub_bus_no}, #{procurement_yn}, #{use_fg}, #{for_yn},
+ #{plan_day_type}, #{plan_day}, #{purpose_type},
+ #{insert_id}, now()
+ ) ON CONFLICT (client_cd) DO
+ UPDATE SET
+ client_nm = #{client_nm}, tr_nmk = #{tr_nmk}, client_nmk = #{client_nmk}, attr_nmk = #{attr_nmk},
+ client_type = #{client_type}, bus_reg_no = #{bus_reg_no}, resident_no = #{resident_no},
+ ceo_nm = #{ceo_nm}, ceo_nmk = #{ceo_nmk}, bus_type = #{bus_type}, bus_item = #{bus_item},
+ post_no = #{post_no}, addr1 = #{addr1}, addr2 = #{addr2}, tel_no = #{tel_no}, fax_no = #{fax_no},
+ homepage = #{homepage}, email = #{email}, liq_rs = #{liq_rs}, tr_fg = #{tr_fg},
+ country_nm = #{country_nm}, class_cd = #{class_cd}, class_nm = #{class_nm},
+ grade_cd = #{grade_cd}, grade_nm = #{grade_nm}, collect_client_cd = #{collect_client_cd},
+ collect_client_nm = #{collect_client_nm}, region_cd = #{region_cd}, region_nm = #{region_nm},
+ trade_start_dt = #{trade_start_dt}, trade_end_dt = #{trade_end_dt}, use_yn = #{use_yn},
+ contract_start_dt = #{contract_start_dt}, contract_end_dt = #{contract_end_dt},
+ trade_type = #{trade_type}, discount_rate = #{discount_rate}, contract_amt = #{contract_amt},
+ monthly_fee = #{monthly_fee}, payment_term = #{payment_term}, rcp_tp = #{rcp_tp},
+ credit_limit = #{credit_limit}, limit_return_day = #{limit_return_day},
+ pur_bank_cd = #{pur_bank_cd}, pur_bank_nm = #{pur_bank_nm}, pur_branch_nm = #{pur_branch_nm},
+ pur_account_no = #{pur_account_no}, pur_account_holder = #{pur_account_holder},
+ pur_pay_plan = #{pur_pay_plan}, pur_slip_type = #{pur_slip_type}, pur_tax_type = #{pur_tax_type},
+ sale_bank_cd = #{sale_bank_cd}, sale_bank_nm = #{sale_bank_nm}, sale_branch_nm = #{sale_branch_nm},
+ sale_account_no = #{sale_account_no}, sale_collect_plan = #{sale_collect_plan},
+ sale_slip_type = #{sale_slip_type}, sale_tax_type = #{sale_tax_type},
+ vendor_dept_nm = #{vendor_dept_nm}, vendor_position = #{vendor_position}, vendor_duty = #{vendor_duty},
+ vendor_manager_nm = #{vendor_manager_nm}, vendor_tel = #{vendor_tel}, vendor_ext = #{vendor_ext},
+ vendor_mobile = #{vendor_mobile}, vendor_email = #{vendor_email},
+ mgr_dept_cd = #{mgr_dept_cd}, mgr_dept_nm = #{mgr_dept_nm}, mgr_position = #{mgr_position},
+ mgr_duty = #{mgr_duty}, mgr_emp_cd = #{mgr_emp_cd}, mgr_emp_nm = #{mgr_emp_nm},
+ mgr_tel = #{mgr_tel}, mgr_ext = #{mgr_ext}, mgr_mobile = #{mgr_mobile},
+ mgr_email = #{mgr_email}, mgr_remark = #{mgr_remark},
+ rec_remark = #{rec_remark}, rec_post_no = #{rec_post_no}, rec_addr1 = #{rec_addr1},
+ rec_addr2 = #{rec_addr2}, rec_tel = #{rec_tel}, rec_fax = #{rec_fax},
+ project_cd = #{project_cd}, project_nm = #{project_nm}, pjt_nmk = #{pjt_nmk},
+ ext_data_cd = #{ext_data_cd}, e_tax_yn = #{e_tax_yn}, unit_report_client = #{unit_report_client},
+ sub_bus_no = #{sub_bus_no}, procurement_yn = #{procurement_yn}, use_fg = #{use_fg},
+ for_yn = #{for_yn}, plan_day_type = #{plan_day_type}, plan_day = #{plan_day},
+ purpose_type = #{purpose_type}, modify_id = #{modify_id}, modify_dt = now()
+
+
+
+
+ INSERT INTO dept_info (
+ dept_code,
+ parent_dept_code,
+ dept_name,
+ status,
+ data_type,
+ regdate
+ ) VALUES (
+ #{dept_code},
+ #{parent_dept_code},
+ #{dept_name},
+ #{status},
+ #{data_type},
+ NOW()
+ ) ON CONFLICT (dept_code) DO
+ UPDATE SET
+ parent_dept_code = #{parent_dept_code},
+ dept_name = #{dept_name},
+ status = #{status},
+ data_type = #{data_type}
+
+
+
+
+ INSERT INTO user_info (
+ sabun,
+ user_id,
+ user_password,
+ user_name,
+ user_name_eng,
+ dept_code,
+ dept_name,
+ position_code,
+ position_name,
+ rank,
+ email,
+ cell_phone,
+ user_type,
+ user_type_name,
+ status,
+ end_date,
+ data_type,
+ regdate
+ ) VALUES (
+ #{sabun},
+ #{user_id},
+ #{user_password},
+ #{user_name},
+ #{user_name_eng},
+ #{dept_code},
+ #{dept_name},
+ #{position_code},
+ #{position_name},
+ #{rank},
+ #{email},
+ #{cell_phone},
+ #{user_type},
+ #{user_type_name},
+ #{status},
+ CASE
+ WHEN #{end_date} IS NULL OR #{end_date} = '' THEN NULL
+ ELSE TO_TIMESTAMP(#{end_date}, 'YYYYMMDD')
+ END,
+ #{data_type},
+ NOW()
+ ) ON CONFLICT (user_id) DO
+ UPDATE SET
+ sabun = #{sabun},
+ user_name = #{user_name},
+ user_name_eng = #{user_name_eng},
+ dept_code = #{dept_code},
+ dept_name = #{dept_name},
+ position_code = #{position_code},
+ position_name = #{position_name},
+ rank = #{rank},
+ email = #{email},
+ cell_phone = #{cell_phone},
+ user_type = #{user_type},
+ user_type_name = #{user_type_name},
+ status = #{status},
+ end_date = CASE
+ WHEN #{end_date} IS NULL OR #{end_date} = '' THEN NULL
+ ELSE TO_TIMESTAMP(#{end_date}, 'YYYYMMDD')
+ END,
+ data_type = #{data_type}
+
+
+
diff --git a/src/com/pms/mapper/mybatisConf.xml b/src/com/pms/mapper/mybatisConf.xml
index 66dfcab..d717bfb 100644
--- a/src/com/pms/mapper/mybatisConf.xml
+++ b/src/com/pms/mapper/mybatisConf.xml
@@ -89,10 +89,11 @@
-
-
-
-
-
-
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/pms/service/BatchService.java b/src/com/pms/service/BatchService.java
index 01f8a92..df71139 100644
--- a/src/com/pms/service/BatchService.java
+++ b/src/com/pms/service/BatchService.java
@@ -17,6 +17,9 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestParam;
+import com.pms.api.CustomerApiClient;
+import com.pms.api.DepartmentApiClient;
+import com.pms.api.EmployeeApiClient;
import com.pms.common.Message;
import com.pms.common.SqlMapConfig;
import com.pms.common.bean.PersonBean;
@@ -35,6 +38,8 @@ public class BatchService extends BaseService {
* @throws Exception
*/
+ // 파트 도면 파일 자동 연결 배치 - 주석 처리
+ /*
static List targetFileList = new ArrayList();
@Scheduled(cron="0 59 23 * * ?")
@@ -160,9 +165,657 @@ public class BatchService extends BaseService {
}
return targetFileList;
}
+ */
+ /**
+ * ERP API 데이터 동기화 배치 (매일 새벽 00시 실행)
+ * 거래처, 부서, 사원 정보를 ERP API로부터 가져와 DB에 저장
+ */
+ @Scheduled(cron="0 0 0 * * ?")
+ public void syncErpData() {
+ System.out.println("====================================");
+ System.out.println("ERP 데이터 동기화 배치 시작 (자동)");
+ System.out.println("====================================");
+
+ executeSyncErpData();
+ }
+ /**
+ * 사원 정보만 동기화 (수동 실행)
+ * @return 성공 여부 Map
+ */
+ public Map syncEmployeeDataManual() {
+ Map result = new HashMap();
+ SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
+
+ System.out.println("====================================");
+ System.out.println("ERP 사원 정보 동기화 시작 (수동)");
+ System.out.println("====================================");
+
+ try {
+ String baseUrl = "https://erp.rps-korea.com";
+ String coCd = "1000";
+
+ syncEmployeeData(sqlSession, baseUrl, coCd);
+
+ sqlSession.commit();
+ result.put("success", true);
+ result.put("message", "사원 정보 동기화가 완료되었습니다.");
+ System.out.println("ERP 사원 정보 동기화 완료");
+ } catch (Exception e) {
+ sqlSession.rollback();
+ result.put("success", false);
+ result.put("message", "사원 정보 동기화 중 오류가 발생했습니다: " + e.getMessage());
+ System.err.println("사원 정보 동기화 오류: " + e.getMessage());
+ e.printStackTrace();
+ } finally {
+ sqlSession.close();
+ }
+
+ return result;
+ }
+ /**
+ * 부서 정보만 동기화 (수동 실행)
+ * @return 성공 여부 Map
+ */
+ public Map syncDepartmentDataManual() {
+ Map result = new HashMap();
+ SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
+
+ System.out.println("====================================");
+ System.out.println("ERP 부서 정보 동기화 시작 (수동)");
+ System.out.println("====================================");
+
+ try {
+ String baseUrl = "https://erp.rps-korea.com";
+ String coCd = "1000";
+
+ syncDepartmentData(sqlSession, baseUrl, coCd);
+
+ sqlSession.commit();
+ result.put("success", true);
+ result.put("message", "부서 정보 동기화가 완료되었습니다.");
+ System.out.println("ERP 부서 정보 동기화 완료");
+ } catch (Exception e) {
+ sqlSession.rollback();
+ result.put("success", false);
+ result.put("message", "부서 정보 동기화 중 오류가 발생했습니다: " + e.getMessage());
+ System.err.println("부서 정보 동기화 오류: " + e.getMessage());
+ e.printStackTrace();
+ } finally {
+ sqlSession.close();
+ }
+
+ return result;
+ }
+
+ /**
+ * 거래처 정보만 동기화 (수동 실행)
+ * @return 성공 여부 Map
+ */
+ public Map syncCustomerDataManual() {
+ Map result = new HashMap();
+ SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
+
+ System.out.println("====================================");
+ System.out.println("ERP 거래처 정보 동기화 시작 (수동)");
+ System.out.println("====================================");
+
+ try {
+ String baseUrl = "https://erp.rps-korea.com";
+ String coCd = "1000";
+
+ syncCustomerData(sqlSession, baseUrl, coCd);
+
+ sqlSession.commit();
+ result.put("success", true);
+ result.put("message", "거래처 정보 동기화가 완료되었습니다.");
+ System.out.println("ERP 거래처 정보 동기화 완료");
+ } catch (Exception e) {
+ sqlSession.rollback();
+ result.put("success", false);
+ result.put("message", "거래처 정보 동기화 중 오류가 발생했습니다: " + e.getMessage());
+ System.err.println("거래처 정보 동기화 오류: " + e.getMessage());
+ e.printStackTrace();
+ } finally {
+ sqlSession.close();
+ }
+
+ return result;
+ }
+
+ /**
+ * ERP 데이터 동기화 실제 실행 로직
+ */
+ private void executeSyncErpData() {
+ SqlSession sqlSession = SqlMapConfig.getInstance().getSqlSession(false);
+
+ try {
+ // API 기본 URL 및 회사코드 설정
+ String baseUrl = "https://erp.rps-korea.com";
+ String coCd = "1000";
+
+ // 1. 거래처 정보 동기화
+ syncCustomerData(sqlSession, baseUrl, coCd);
+
+ // 2. 부서 정보 동기화
+ syncDepartmentData(sqlSession, baseUrl, coCd);
+
+ // 3. 사원 정보 동기화
+ syncEmployeeData(sqlSession, baseUrl, coCd);
+
+ sqlSession.commit();
+ System.out.println("ERP 데이터 동기화 배치 완료");
+
+ } catch (Exception e) {
+ sqlSession.rollback();
+ System.err.println("ERP 데이터 동기화 중 오류 발생: " + e.getMessage());
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ } finally {
+ sqlSession.close();
+ }
+ }
+
+ /**
+ * 거래처 정보 동기화
+ */
+ private void syncCustomerData(SqlSession sqlSession, String baseUrl, String coCd) {
+ try {
+ System.out.println("거래처 정보 동기화 시작...");
+
+ CustomerApiClient customerClient = new CustomerApiClient();
+ String jsonResponse = customerClient.getCustomerList(baseUrl, coCd);
+
+ // JSON 파싱 및 DB 저장
+ List