crm-rest-api/docs/mis-integration-api.md

378 lines
14 KiB
Markdown

# 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
```