# MIS → CRM 통합 API (crm-rest-api) MIS 가 CRM 으로 데이터를 밀어 넣을 때 호출하는 **통합(integrate) 엔드포인트** 명세입니다. 이번 작업으로 추가된 2개 엔드포인트만 다룹니다. | # | 대상 | Method | Path | |---|------|--------|------| | 1 | Daily Install (+ Container) 통합 | `POST` | `/customer-daily-install/integrate` | | 2 | Daily Install 삭제 (자연키) | `DELETE` | `/customer-daily-install/customer/{accountNo}/date/{installDate}/jobtype/{workType}` | | 3 | Customer Container 통합 | `POST` | `/customer-container/integrate` | --- ## 공통 사항 ### Base URL ``` http://{host}:8082/crm-rest-api ``` - 서버 포트 `8082`, context-path `/crm-rest-api` 포함. - 예: `http://192.168.2.172:8082/crm-rest-api/customer-daily-install/integrate` ### 인증 (필수) JWT 를 아래 둘 중 하나로 전달합니다. **쿠키 우선**, 없으면 헤더. - 쿠키: `AUTH_TOKEN={jwt}` - 헤더: `Authorization: Bearer {jwt}` 권한: 두 엔드포인트 모두 **CRM 생성 권한**(`Module=C, Action=C`, 또는 ADMIN) 필요. ### 요청 형식 - `Content-Type: application/json` - 문자 인코딩 UTF-8 ### 날짜/시간 포맷 (JSON 문자열) | 타입 | 포맷 | 예 | |------|------|-----| | date | `yyyy-MM-dd` | `"2026-07-08"` | | time | `HH:mm:ss` | `"14:30:00"` | | datetime | `yyyy-MM-dd'T'HH:mm:ss` | `"2026-07-08T14:30:00"` | | 문자열 타임스탬프(14자리) | `yyyyMMddHHmmss` | `"20260708143000"` | > `createddate` / `updateddate` 처럼 varchar(14) 로 저장되는 필드는 위 14자리 문자열 그대로 전달합니다. ### 공통 응답 코드 | 코드 | 의미 | 본문 예 | |------|------|---------| | `200 OK` | 성공 | (엔드포인트별 응답 참조) | | `400 Bad Request` | 업무 오류 (예: accountNo 로 고객 못 찾음) | `{ "error": "Customer not found by accountNo: 12345", "timestamp": "...", "status": 400 }` | | `401 Unauthorized` | 토큰 만료/무효 | `{ "error": "Session has expired." }` 또는 `{ "error": "Invalid login information." }` | | `403 Forbidden` | 권한 없음 | `{ "error": "Forbidden", "message": "No permission", "status": 403, "timestamp": "..." }` | | `500 Internal Server Error` | 서버 오류 | `{ "error": "Internal Server Error", "message": "...", "status": 500 }` | ### 내부 ID 자동 변환 규칙 (중요) MIS 는 CRM 내부 PK 를 모르므로, CRM 이 아래 규칙으로 변환합니다. **MIS 는 내부 id 대신 아래 값을 보냅니다.** | 논리 대상 | MIS 가 보내는 값 | CRM 변환 방식 | |-----------|------------------|----------------| | 고객 | `accountNo` (= MIS `di_accountno` / `cc_accountno`) | `customer.cus_no` 로 조회 → 내부 `cusId` | | 기사(driver) | `externalDriverId` (= MIS `di_driver_uid`) | HCM `employee_external_map(MIS_DRIVER)` 조회 → 내부 `empId` | | 생성자 | `externalCreatedBy` (= MIS member id) | HCM `employee_external_map(MIS_MEMBER)` 조회 → `empNo` | > `accountNo == cus_no` 전제. 해당 고객이 CRM 에 없으면 `400` (`Customer not found by accountNo`). --- ## 1. Daily Install 통합 ``` POST /customer-daily-install/integrate ``` `tbl_daily_install` 1건과 그에 딸린 `tbl_daily_install_container` N건을 **한 번의 호출·한 트랜잭션**으로 반영합니다. ### 동작 - **install 은 자연키로 upsert**: `(accountNo, workType, installDate)` — MIS `tbl_daily_install` 의 `UNIQUE KEY (di_accountno, di_work_type, di_install_date)` 와 동일. - 기존 있으면 update, 없으면 insert. - **containers 는 install 단위로 replace-all**: 컨테이너 행에는 자연키가 없으므로, 해당 install 의 기존 컨테이너를 **전부 삭제 후 재삽입**. - `containers` 필드 **생략/`null`** → 컨테이너 손대지 않음. - `containers: []` (빈 배열) → 해당 install 의 컨테이너 **전부 삭제**. - `containers: [ ... ]` → 통째로 교체. - 컨테이너의 부모 연결(`cdicDailyInstallId`)은 저장된 install 의 내부 id 로 **서버가 자동 세팅**하므로 요청에 넣을 필요 없음(넣어도 무시). - 타임스탬프(`cdiCreatedAt` / `cdiUpdatedAt`)는 **서버 auditing 이 자동 기록**. ### 요청 본문 ```jsonc { "install": { // ── 자연키 (필수) ── "cdiAccountNo": "A00123", // = di_accountno (= cus_no) "cdiWorkType": "INSTALL", // = di_work_type "cdiInstallDate": "2026-07-08", // = di_install_date // ── 필수(NOT NULL) ── "cdiOrderSeq": 1, // = di_order_seq "cdiExternalDriverId": "88", // = di_driver_uid → 내부 empId 로 변환 // ── 선택 ── "cdiInstallTime": "14:30:00", "cdiCustomerName": "홍길동 식당", "cdiPhone": "416-555-1234", "cdiAddress": "123 Main St", "cdiCity": "Toronto", "cdiPostalCode": "M1A1A1", "cdiGeoLat": 43.6532100, "cdiGeoLon": -79.3831800, "cdiExternalCustomerDriverId": "88", // = di_customer_driver_uid → 내부 empId (선택) "cdiOilPickup": 1, "cdiSludge": 0, "cdiRequestNote": "...", "cdiWorkNote": "...", "cdiInstallNote": "...", "cdiCsNote": "...", "cdiStatus": "A", "cdiExternalCreatedBy": "1002" // = MIS member id → empNo 로 변환 }, "containers": [ { "cdicAction": "IN", // 필수 "cdicType": "T20", // 필수 "cdicCapacity": 20, "cdicIsUsed": "N", "cdicHasLock": "N", "cdicHasWheel": "N", "cdicHasWoodframe":"N", "cdicOwnerType": "C" } ] } ``` #### `install` 필드 (MIS `tbl_daily_install` 매핑) | 필드 | 타입 | 필수 | MIS 컬럼 | 비고 | |------|------|:---:|----------|------| | `cdiAccountNo` | string | ✅ | `di_accountno` | 자연키 · 고객 해석(=`cus_no`) | | `cdiWorkType` | string(10) | ✅ | `di_work_type` | 자연키 | | `cdiInstallDate` | date | ✅ | `di_install_date` | 자연키 | | `cdiOrderSeq` | int | ✅ | `di_order_seq` | | | `cdiExternalDriverId` | string | ✅\* | `di_driver_uid` | 내부 driver empId 로 변환. `cdiDriverId`(내부 Long) 직접 전달도 가능 | | `cdiInstallTime` | time | | `di_install_time` | | | `cdiCustomerName` | string(100) | | `di_customer_name` | | | `cdiPhone` | string(50) | | `di_phone` | | | `cdiAddress` | string(200) | | `di_address` | | | `cdiCity` | string(50) | | `di_city` | | | `cdiPostalCode` | string(20) | | `di_postal` | | | `cdiGeoLat` | number | | `di_lat` | | | `cdiGeoLon` | number | | `di_lng` | | | `cdiExternalCustomerDriverId` | string | | `di_customer_driver_uid` | 내부 empId 로 변환(선택) | | `cdiWorkType`(위 자연키와 동일) | | | | | | `cdiOilPickup` | int | | `di_oil_pickup` | | | `cdiSludge` | int | | `di_sludge` | | | `cdiRequestNote` | string | | `di_request_note` | | | `cdiWorkNote` | string | | `di_work_note` | | | `cdiInstallNote` | string | | `di_install_note` | | | `cdiCsNote` | string | | `di_cs_note` | | | `cdiStatus` | string(1) | | `di_status` | 기본 'A' | | `cdiExternalCreatedBy` | string | | `di_created_by`(member id) | `empNo` 로 변환 | | `cdiLoginUser` | string | | — | ERP 내부 호출용(생성/수정자 직접 지정). MIS 는 보통 미사용 | \* 내부 `cdiDriverId`(Long) 를 직접 주면 그 값을 우선 사용. MIS 는 `cdiExternalDriverId` 로 전달. > MIS 의 `di_wait_uid`, `di_lock_date`, `di_container_applied` 는 CRM 에 대응 컬럼이 없어 **저장하지 않습니다.** #### `containers[]` 필드 (MIS `tbl_daily_install_container` 매핑) | 필드 | 타입 | 필수 | MIS 컬럼 | |------|------|:---:|----------| | `cdicAction` | string(10) | ✅ | `dic_action` | | `cdicType` | string(10) | ✅ | `dic_type` | | `cdicCapacity` | int | | `dic_capacity` | | `cdicIsUsed` | string(1) | | `dic_is_used` | | `cdicHasLock` | string(1) | | `dic_has_lock` | | `cdicHasWheel` | string(1) | | `dic_has_wheel` | | `cdicHasWoodframe` | string(1) | | `dic_has_woodframe` | | `cdicOwnerType` | string(1) | | `dic_owner_type` | > `dic_di_uid`(부모 install FK)는 서버가 자동 세팅하므로 보내지 않습니다. ### 응답 `200 OK` ```jsonc { "install": { "cdiUuid": "0f9c...-...", "cdiOrderSeq": 1, "cdiDriverId": 57, "cdiInstallDate": "2026-07-08", "cdiInstallTime": "14:30:00", "cdiCustomerId": 1024, "cdiAccountNo": "A00123", "cdiCustomerName": "홍길동 식당", "cdiWorkType": "INSTALL", "cdiStatus": "A", "cdiCreatedBy": "E1002", "cdiCreatedAt": "2026-07-08T14:31:05", "cdiUpdatedAt": "2026-07-08T14:31:05" // ... 나머지 필드 포함 }, "containers": [ { "cdicId": 3037, "cdicDailyInstallId": 1235, "cdicAction": "IN", "cdicType": "T20", "cdicCapacity": 20, "cdicIsUsed": "N", "cdicHasLock": "N", "cdicHasWheel": "N", "cdicHasWoodframe": "N", "cdicOwnerType": "C" } ] } ``` --- ## 2. Daily Install 삭제 (자연키) ``` DELETE /customer-daily-install/customer/{accountNo}/date/{installDate}/jobtype/{workType} ``` MIS 에서 install 이 삭제/이동되었을 때, 자연키로 CRM 의 install 을 삭제합니다. ### 경로 변수 | 변수 | 타입 | MIS 컬럼 | 예 | |------|------|----------|-----| | `accountNo` | string | `di_accountno` (= `cus_no`) | `A00123` | | `installDate` | date (`yyyy-MM-dd`) | `di_install_date` | `2026-07-08` | | `workType` | string | `di_work_type` | `INSTALL` | ### 동작 - 자연키 `(accountNo, workType, installDate)` 로 install 을 찾음. - **딸린 컨테이너(`tbl_daily_install_container`)를 먼저 전부 삭제**한 뒤 install 을 삭제(고아 방지). - 대상이 없어도 **멱등**: 그대로 `204` 반환(에러 아님). 재시도/중복 호출 안전. - 요청 본문 없음. ### 응답 | 코드 | 의미 | |------|------| | `204 No Content` | 삭제 완료 또는 대상 없음(멱등) | | `403 Forbidden` | 권한 없음 | > 권한: **CRM 삭제 권한**(`Module=C, Action=D`) 필요. (통합 POST 는 생성 권한 `C` 이지만, 이 삭제는 삭제 권한 `D` 를 확인함.) ### 호출 예시 ```bash curl -X DELETE \ "http://192.168.2.172:8082/crm-rest-api/customer-daily-install/customer/A00123/date/2026-07-08/jobtype/INSTALL" \ -H "Authorization: Bearer $JWT" ``` --- ## 3. Customer Container 통합 ``` POST /customer-container/integrate ``` 특정 고객의 컨테이너 목록을 **통째로 교체(replace-all)** 합니다. CRM 에서는 고객 컨테이너를 수정하지 않고 **MIS 를 단일 소스**로 보기 때문에 전량 동기화합니다. ### 동작 - `accountNo`(= `cc_accountno` = `cus_no`) 로 고객을 찾아 내부 `cusId` 로 변환. - 그 고객의 **기존 컨테이너 전부 삭제 후 재삽입**. - `containers` **생략/`null`** → 아무것도 건드리지 않음(현재 목록 반환). - `containers: []` → 해당 고객 컨테이너 **전부 삭제**. - `containers: [ ... ]` → 통째로 교체. - `ccoModifiedAt` 은 서버 auditing 이 자동 기록. ### 요청 본문 ```jsonc { "accountNo": "A00123", // = cc_accountno (= cus_no) "containers": [ { "ccoType": "T20", // 필수 "ccoCapacity": 20, "ccoOwnerType": "C", "ccoIsUsed": "Y", "ccoHasLock": "N", "ccoHasWheel": "N", "ccoHasWoodframe":"N", "ccoStatus": "I", "ccoCreateddate": "20260708143000", // varchar(14) "ccoUpdateddate": "20260708143000", // varchar(14) "ccoInstallNote": "...", "ccoPickupNote": "...", "ccoInstallDate": "2026-07-08", "ccoPickupDate": null, "ccoInstallAt": "2026-07-08T14:30:00", "ccoPickupAt": null } ] } ``` #### 최상위 필드 | 필드 | 타입 | 필수 | 비고 | |------|------|:---:|------| | `accountNo` | string | ✅ | `cc_accountno` (= `cus_no`). 고객 해석용 | | `containers` | array | | 아래 표. 교체 규칙은 위 "동작" 참조 | #### `containers[]` 필드 (MIS `tbl_customer_container` 매핑) | 필드 | 타입 | 필수 | MIS 컬럼 | |------|------|:---:|----------| | `ccoType` | string(10) | ✅ | `cc_type` | | `ccoCapacity` | int | | `cc_capacity` | | `ccoOwnerType` | string(1) | | `cc_owner_type` | | `ccoIsUsed` | string(1) | | `cc_is_used` | | `ccoHasLock` | string(1) | | `cc_has_lock` | | `ccoHasWheel` | string(1) | | `cc_has_wheel` | | `ccoHasWoodframe` | string(1) | | `cc_has_woodframe` | | `ccoStatus` | string(1) | | `cc_status` | | `ccoCreateddate` | string(14) | | `cc_createddate` | | `ccoUpdateddate` | string(14) | | `cc_updateddate` | | `ccoInstallNote` | string(500) | | `cc_install_note` | | `ccoPickupNote` | string(500) | | `cc_pickup_note` | | `ccoInstallDate` | date | | `cc_install_date` | | `ccoPickupDate` | date | | `cc_pickup_date` | | `ccoInstallAt` | datetime | | `cc_install_at` | | `ccoPickupAt` | datetime | | `cc_pickup_at` | > `cc_uid`(MIS 내부 PK)는 사용하지 않습니다. 고객 단위 전량 교체이므로 컨테이너 개별 식별자가 필요 없습니다. > `ccoCustomerId` 는 `accountNo` 로 서버가 채우므로 보내지 않습니다(보내도 무시). ### 응답 `200 OK` 교체 후 그 고객의 전체 컨테이너 목록(배열)을 반환합니다. ```jsonc [ { "ccoId": 30235, "ccoCustomerId": 1024, "ccoType": "T20", "ccoCapacity": 20, "ccoOwnerType": "C", "ccoIsUsed": "Y", "ccoHasLock": "N", "ccoHasWheel": "N", "ccoHasWoodframe": "N", "ccoStatus": "I", "ccoCreateddate": "20260708143000", "ccoUpdateddate": "20260708143000", "ccoInstallNote": "...", "ccoPickupNote": "...", "ccoInstallDate": "2026-07-08", "ccoPickupDate": null, "ccoInstallAt": "2026-07-08T14:30:00", "ccoPickupAt": null, "ccoModifiedAt": "2026-07-08T14:31:05" } ] ``` --- ## 호출 예시 (curl) ```bash # 1) Daily Install 통합 curl -X POST "http://192.168.2.172:8082/crm-rest-api/customer-daily-install/integrate" \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d @install.json # 2) Customer Container 통합 curl -X POST "http://192.168.2.172:8082/crm-rest-api/customer-container/integrate" \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d @container.json ```