install 과 container 통합

This commit is contained in:
Hyojin Ahn 2026-07-10 14:49:01 -04:00
parent 7b33e83c55
commit 5ae1c91ad9
17 changed files with 922 additions and 34 deletions

377
docs/mis-integration-api.md Normal file
View File

@ -0,0 +1,377 @@
# 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
```

View File

@ -2,6 +2,7 @@ package com.goi.erp.controller;
import com.goi.erp.common.permission.PermissionChecker;
import com.goi.erp.common.permission.PermissionSet;
import com.goi.erp.dto.CustomerContainerIntegrateRequestDto;
import com.goi.erp.dto.CustomerContainerRequestDto;
import com.goi.erp.dto.CustomerContainerResponseDto;
import com.goi.erp.service.CustomerContainerService;
@ -54,6 +55,19 @@ public class CustomerContainerController {
);
}
// MIS 통합: accountNo(=cus_no) 고객의 컨테이너를 통째로 replace-all
@PostMapping("/integrate")
public ResponseEntity<List<CustomerContainerResponseDto>> integrate(
@RequestBody CustomerContainerIntegrateRequestDto dto
) {
if (!PermissionChecker.canCreateCRM(getPermission())) {
throw new AccessDeniedException("No permission");
}
return ResponseEntity.ok(service.integrateByAccountNo(dto));
}
@GetMapping("/customer/{customerId}")
public ResponseEntity<List<CustomerContainerResponseDto>> getByCustomerId(
@PathVariable Long customerId

View File

@ -2,8 +2,11 @@ package com.goi.erp.controller;
import com.goi.erp.common.permission.PermissionChecker;
import com.goi.erp.common.permission.PermissionSet;
import com.goi.erp.dto.CustomerDailyInstallIntegrateRequestDto;
import com.goi.erp.dto.CustomerDailyInstallIntegrateResponseDto;
import com.goi.erp.dto.CustomerDailyInstallRequestDto;
import com.goi.erp.dto.CustomerDailyInstallResponseDto;
import com.goi.erp.service.CustomerDailyInstallIntegrationService;
import com.goi.erp.service.CustomerDailyInstallService;
import com.goi.erp.token.PermissionAuthenticationToken;
@ -27,6 +30,7 @@ import java.util.List;
public class CustomerDailyInstallController {
private final CustomerDailyInstallService service;
private final CustomerDailyInstallIntegrationService integrationService;
private PermissionSet getPermission() {
@ -56,6 +60,22 @@ public class CustomerDailyInstallController {
);
}
// MIS 통합: install 1건 + container N건을 번에 upsert / replace-all
@PostMapping("/integrate")
public ResponseEntity<CustomerDailyInstallIntegrateResponseDto> integrate(
@RequestBody CustomerDailyInstallIntegrateRequestDto dto
) {
if (!PermissionChecker.canCreateCRM(getPermission())) {
throw new AccessDeniedException("No permission");
}
return new ResponseEntity<>(
integrationService.integrate(dto),
HttpStatus.OK
);
}
@GetMapping("/date/{installDate}")
public ResponseEntity<List<CustomerDailyInstallResponseDto>> getByInstallDate(
@PathVariable
@ -96,4 +116,21 @@ public class CustomerDailyInstallController {
return ResponseEntity.noContent().build();
}
// MIS 통합: 자연키(accountNo, installDate, workType) install + 딸린 container 삭제 (멱등)
@DeleteMapping("/customer/{accountNo}/date/{installDate}/jobtype/{workType}")
public ResponseEntity<Void> deleteByBusinessKey(
@PathVariable String accountNo,
@PathVariable @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate installDate,
@PathVariable String workType
) {
if (!PermissionChecker.canDeleteCRM(getPermission())) {
throw new AccessDeniedException("No permission");
}
integrationService.deleteByBusinessKey(accountNo, workType, installDate);
return ResponseEntity.noContent().build();
}
}

View File

@ -171,6 +171,11 @@ public class CustomerDailyOrderController {
@PathVariable @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate orderDate,
@PathVariable String jobType) {
PermissionSet permissions = getPermission();
if (!PermissionChecker.canDeleteCRM(permissions)) {
throw new AccessDeniedException("You do not have permission to delete daily order");
}
dailyOrderService.removeDailyOrderByBusinessKey(customerNo, orderDate, jobType);
return ResponseEntity.noContent().build(); // 204
}

View File

@ -0,0 +1,24 @@
package com.goi.erp.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* MIS CRM 고객 컨테이너 통합 요청.
* accountNo(=cus_no) 고객을 찾고, 고객의 컨테이너를 통째로 replace-all 한다.
* (CRM 에서는 고객 컨테이너를 수정하지 않으므로 MIS 단일 소스로 보고 전량 동기화한다.)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CustomerContainerIntegrateRequestDto {
private String accountNo;
private List<CustomerContainerRequestDto> containers;
}

View File

@ -0,0 +1,25 @@
package com.goi.erp.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* MIS CRM 통합 요청.
* tbl_daily_install 1건과 그에 딸린 tbl_daily_install_container N건을 번에 받는다.
* install 자연키(accountNo, workType, installDate) upsert 하고,
* containers 저장된 install 내부 id 기준으로 replace-all 한다.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CustomerDailyInstallIntegrateRequestDto {
private CustomerDailyInstallRequestDto install;
private List<CustomerDailyInstallContainerRequestDto> containers;
}

View File

@ -0,0 +1,19 @@
package com.goi.erp.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CustomerDailyInstallIntegrateResponseDto {
private CustomerDailyInstallResponseDto install;
private List<CustomerDailyInstallContainerResponseDto> containers;
}

View File

@ -19,6 +19,9 @@ public class CustomerDailyInstallRequestDto {
private Long cdiDriverId;
// MIS 에서 di_driver_uid 호출 employee_external_map(MIS_DRIVER) 참조해 내부 empId 변환
private String cdiExternalDriverId;
private LocalDate cdiInstallDate;
private LocalTime cdiInstallTime;
@ -43,6 +46,9 @@ public class CustomerDailyInstallRequestDto {
private Long cdiCustomerDriverId;
// MIS 에서 di_customer_driver_uid 호출 MIS_DRIVER 참조해 내부 empId 변환 (선택)
private String cdiExternalCustomerDriverId;
private String cdiWorkType;
private Integer cdiOilPickup;
@ -60,4 +66,10 @@ public class CustomerDailyInstallRequestDto {
private String cdiStatus;
private String cdiCreatedBy;
// MIS 에서 member id 호출 employee_external_map(MIS_MEMBER) 참조해 empNo 변환
private String cdiExternalCreatedBy;
// ERP 내부 호출 로그인 사용자 (createdBy/updatedBy 직접 지정)
private String cdiLoginUser;
}

View File

@ -2,6 +2,7 @@ package com.goi.erp.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@ -15,12 +16,16 @@ import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.time.LocalDateTime;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@Entity
@Table(schema = "crm", name = "customer_container")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EntityListeners(AuditingEntityListener.class)
public class CustomerContainer {
@Id
@ -79,6 +84,7 @@ public class CustomerContainer {
@Column(name = "cco_pickup_at")
private LocalDateTime ccoPickupAt;
@LastModifiedDate
@Column(name = "cco_modified_at")
private LocalDateTime ccoModifiedAt;
}

View File

@ -2,6 +2,7 @@ package com.goi.erp.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@ -20,6 +21,7 @@ import java.util.UUID;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@Entity
@Table(schema = "crm", name = "customer_daily_install")
@ -27,6 +29,7 @@ import org.springframework.data.annotation.LastModifiedDate;
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EntityListeners(AuditingEntityListener.class)
public class CustomerDailyInstall {
@Id

View File

@ -12,4 +12,7 @@ public interface CustomerContainerRepository extends JpaRepository<CustomerConta
List<CustomerContainer> findByCcoCustomerId(Long ccoCustomerId);
List<CustomerContainer> findByCcoCustomerIdAndCcoStatus(Long ccoCustomerId, String ccoStatus);
// MIS 통합 replace-all: 특정 고객의 컨테이너 전부 삭제 재삽입
void deleteByCcoCustomerId(Long ccoCustomerId);
}

View File

@ -16,4 +16,7 @@ public interface CustomerDailyInstallContainerRepository
Long cdicDailyInstallId,
String cdicAction
);
// MIS 통합 replace-all: 특정 install 컨테이너 전부 삭제 재삽입
void deleteByCdicDailyInstallId(Long cdicDailyInstallId);
}

View File

@ -6,12 +6,20 @@ import org.springframework.stereotype.Repository;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
@Repository
public interface CustomerDailyInstallRepository extends JpaRepository<CustomerDailyInstall, Long> {
List<CustomerDailyInstall> findByCdiInstallDate(LocalDate cdiInstallDate);
// MIS 통합 자연키: (accountno, work_type, install_date) == tbl_daily_install UNIQUE KEY
Optional<CustomerDailyInstall> findByCdiAccountNoAndCdiWorkTypeAndCdiInstallDate(
String cdiAccountNo,
String cdiWorkType,
LocalDate cdiInstallDate
);
List<CustomerDailyInstall> findByCdiDriverId(Long cdiDriverId);
List<CustomerDailyInstall> findByCdiDriverIdAndCdiInstallDate(

View File

@ -1,9 +1,11 @@
package com.goi.erp.service;
import com.goi.erp.dto.CustomerContainerIntegrateRequestDto;
import com.goi.erp.dto.CustomerContainerRequestDto;
import com.goi.erp.dto.CustomerContainerResponseDto;
import com.goi.erp.entity.CustomerContainer;
import com.goi.erp.repository.CustomerContainerRepository;
import com.goi.erp.repository.CustomerRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
@ -18,26 +20,11 @@ import java.util.stream.Collectors;
public class CustomerContainerService {
private final CustomerContainerRepository customerContainerRepository;
private final CustomerRepository customerRepository;
public CustomerContainerResponseDto create(CustomerContainerRequestDto dto) {
CustomerContainer entity = CustomerContainer.builder()
.ccoCustomerId(dto.getCcoCustomerId())
.ccoType(dto.getCcoType())
.ccoCapacity(dto.getCcoCapacity())
.ccoOwnerType(dto.getCcoOwnerType())
.ccoIsUsed(dto.getCcoIsUsed())
.ccoHasLock(dto.getCcoHasLock())
.ccoHasWheel(dto.getCcoHasWheel())
.ccoHasWoodframe(dto.getCcoHasWoodframe())
.ccoStatus(dto.getCcoStatus())
.ccoInstallNote(dto.getCcoInstallNote())
.ccoPickupNote(dto.getCcoPickupNote())
.ccoInstallDate(dto.getCcoInstallDate())
.ccoPickupDate(dto.getCcoPickupDate())
.ccoInstallAt(dto.getCcoInstallAt())
.ccoPickupAt(dto.getCcoPickupAt())
.build();
CustomerContainer entity = buildEntity(dto, dto.getCcoCustomerId());
entity = customerContainerRepository.save(entity);
@ -52,6 +39,43 @@ public class CustomerContainerService {
.collect(Collectors.toList());
}
/**
* MIS 통합 replace-all.
* accountNo(=cus_no) 고객을 찾아, 고객의 기존 컨테이너를 전부 삭제하고 전달된 목록으로 재삽입한다.
* containers == null 이면 컨테이너 정보가 전달되지 않은 것으로 보고 아무것도 건드리지 않는다.
* containers 리스트이면 "컨테이너 없음" 으로 보고 전부 삭제한다.
*/
@Transactional
public List<CustomerContainerResponseDto> integrateByAccountNo(
CustomerContainerIntegrateRequestDto request
) {
if (request.getAccountNo() == null) {
throw new RuntimeException("accountNo is required");
}
Long customerId = customerRepository.findByCusNo(request.getAccountNo())
.map(c -> c.getCusId())
.orElseThrow(() -> new RuntimeException(
"Customer not found by accountNo: " + request.getAccountNo()));
List<CustomerContainerRequestDto> dtos = request.getContainers();
if (dtos == null) {
return getByCustomerId(customerId);
}
customerContainerRepository.deleteByCcoCustomerId(customerId);
List<CustomerContainer> entities = dtos.stream()
.map(dto -> buildEntity(dto, customerId))
.collect(Collectors.toList());
return customerContainerRepository.saveAll(entities)
.stream()
.map(this::mapToDto)
.collect(Collectors.toList());
}
@Transactional
public CustomerContainerResponseDto update(Long id, CustomerContainerRequestDto dto) {
@ -71,6 +95,29 @@ public class CustomerContainerService {
customerContainerRepository.delete(entity);
}
private CustomerContainer buildEntity(CustomerContainerRequestDto dto, Long customerId) {
return CustomerContainer.builder()
.ccoCustomerId(customerId)
.ccoType(dto.getCcoType())
.ccoCapacity(dto.getCcoCapacity())
.ccoOwnerType(dto.getCcoOwnerType())
.ccoIsUsed(dto.getCcoIsUsed())
.ccoHasLock(dto.getCcoHasLock())
.ccoHasWheel(dto.getCcoHasWheel())
.ccoHasWoodframe(dto.getCcoHasWoodframe())
.ccoStatus(dto.getCcoStatus())
.ccoCreateddate(dto.getCcoCreateddate())
.ccoUpdateddate(dto.getCcoUpdateddate())
.ccoInstallNote(dto.getCcoInstallNote())
.ccoPickupNote(dto.getCcoPickupNote())
.ccoInstallDate(dto.getCcoInstallDate())
.ccoPickupDate(dto.getCcoPickupDate())
.ccoInstallAt(dto.getCcoInstallAt())
.ccoPickupAt(dto.getCcoPickupAt())
.build(); // ccoModifiedAt @LastModifiedDate 자동 세팅
}
private void updateInternal(CustomerContainer entity, CustomerContainerRequestDto dto) {
if (dto.getCcoType() != null) entity.setCcoType(dto.getCcoType());
@ -81,6 +128,14 @@ public class CustomerContainerService {
if (dto.getCcoHasWheel() != null) entity.setCcoHasWheel(dto.getCcoHasWheel());
if (dto.getCcoHasWoodframe() != null) entity.setCcoHasWoodframe(dto.getCcoHasWoodframe());
if (dto.getCcoStatus() != null) entity.setCcoStatus(dto.getCcoStatus());
if (dto.getCcoInstallNote() != null) entity.setCcoInstallNote(dto.getCcoInstallNote());
if (dto.getCcoPickupNote() != null) entity.setCcoPickupNote(dto.getCcoPickupNote());
if (dto.getCcoInstallDate() != null) entity.setCcoInstallDate(dto.getCcoInstallDate());
if (dto.getCcoPickupDate() != null) entity.setCcoPickupDate(dto.getCcoPickupDate());
if (dto.getCcoInstallAt() != null) entity.setCcoInstallAt(dto.getCcoInstallAt());
if (dto.getCcoPickupAt() != null) entity.setCcoPickupAt(dto.getCcoPickupAt());
if (dto.getCcoUpdateddate() != null) entity.setCcoUpdateddate(dto.getCcoUpdateddate());
// ccoModifiedAt @LastModifiedDate 자동 갱신
}
private CustomerContainerResponseDto mapToDto(CustomerContainer entity) {
@ -96,6 +151,15 @@ public class CustomerContainerService {
.ccoHasWheel(entity.getCcoHasWheel())
.ccoHasWoodframe(entity.getCcoHasWoodframe())
.ccoStatus(entity.getCcoStatus())
.ccoCreateddate(entity.getCcoCreateddate())
.ccoUpdateddate(entity.getCcoUpdateddate())
.ccoInstallNote(entity.getCcoInstallNote())
.ccoPickupNote(entity.getCcoPickupNote())
.ccoInstallDate(entity.getCcoInstallDate())
.ccoPickupDate(entity.getCcoPickupDate())
.ccoInstallAt(entity.getCcoInstallAt())
.ccoPickupAt(entity.getCcoPickupAt())
.ccoModifiedAt(entity.getCcoModifiedAt())
.build();
}
}

View File

@ -51,6 +51,45 @@ public class CustomerDailyInstallContainerService {
.collect(Collectors.toList());
}
/**
* MIS 통합 replace-all.
* 해당 install 기존 컨테이너를 전부 삭제하고, 전달된 컨테이너 목록으로 재삽입한다.
* (컨테이너 행에는 자연키가 없어 개별 upsert 불가능하므로 install 단위로 동기화한다.)
* dtos == null 이면 컨테이너 정보가 전달되지 않은 것으로 보고 아무것도 건드리지 않는다.
* dtos 리스트이면 "컨테이너 없음" 으로 보고 전부 삭제한다.
*/
@Transactional
public List<CustomerDailyInstallContainerResponseDto> replaceForInstall(
Long dailyInstallId,
List<CustomerDailyInstallContainerRequestDto> dtos
) {
if (dtos == null) {
return getByDailyInstallId(dailyInstallId);
}
repository.deleteByCdicDailyInstallId(dailyInstallId);
List<CustomerDailyInstallContainer> entities = dtos.stream()
.map(dto -> CustomerDailyInstallContainer.builder()
.cdicDailyInstallId(dailyInstallId) // 부모 install 내부 id 강제
.cdicAction(dto.getCdicAction())
.cdicType(dto.getCdicType())
.cdicCapacity(dto.getCdicCapacity())
.cdicIsUsed(dto.getCdicIsUsed())
.cdicHasLock(dto.getCdicHasLock())
.cdicHasWheel(dto.getCdicHasWheel())
.cdicHasWoodframe(dto.getCdicHasWoodframe())
.cdicOwnerType(dto.getCdicOwnerType())
.build())
.collect(Collectors.toList());
return repository.saveAll(entities)
.stream()
.map(this::mapToDto)
.collect(Collectors.toList());
}
@Transactional
public CustomerDailyInstallContainerResponseDto update(
Long id,
@ -73,6 +112,14 @@ public class CustomerDailyInstallContainerService {
repository.delete(entity);
}
/**
* 특정 install 속한 컨테이너 전부 삭제 (install 삭제 고아 방지).
*/
@Transactional
public void deleteForInstall(Long dailyInstallId) {
repository.deleteByCdicDailyInstallId(dailyInstallId);
}
private void updateInternal(
CustomerDailyInstallContainer entity,
CustomerDailyInstallContainerRequestDto dto
@ -95,6 +142,10 @@ public class CustomerDailyInstallContainerService {
.cdicType(entity.getCdicType())
.cdicCapacity(entity.getCdicCapacity())
.cdicIsUsed(entity.getCdicIsUsed())
.cdicHasLock(entity.getCdicHasLock())
.cdicHasWheel(entity.getCdicHasWheel())
.cdicHasWoodframe(entity.getCdicHasWoodframe())
.cdicOwnerType(entity.getCdicOwnerType())
.build();
}
}

View File

@ -0,0 +1,72 @@
package com.goi.erp.service;
import com.goi.erp.dto.CustomerDailyInstallContainerResponseDto;
import com.goi.erp.dto.CustomerDailyInstallIntegrateRequestDto;
import com.goi.erp.dto.CustomerDailyInstallIntegrateResponseDto;
import com.goi.erp.dto.CustomerDailyInstallResponseDto;
import com.goi.erp.entity.CustomerDailyInstall;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.List;
/**
* MIS 호출하는 daily-install 통합 오케스트레이션.
* install upsert(자연키) 저장된 내부 id container replace-all 트랜잭션으로 처리한다.
*/
@Service
@RequiredArgsConstructor
public class CustomerDailyInstallIntegrationService {
private final CustomerDailyInstallService installService;
private final CustomerDailyInstallContainerService containerService;
@Transactional
public CustomerDailyInstallIntegrateResponseDto integrate(
CustomerDailyInstallIntegrateRequestDto request
) {
if (request.getInstall() == null) {
throw new RuntimeException("install payload is required");
}
// 1. install upsert (accountNo, workType, installDate)
CustomerDailyInstall saved = installService.upsert(request.getInstall());
// 2. 저장된 install 내부 id 기준으로 container replace-all
List<CustomerDailyInstallContainerResponseDto> containers =
containerService.replaceForInstall(saved.getCdiId(), request.getContainers());
CustomerDailyInstallResponseDto install = installService.mapToDto(saved);
return CustomerDailyInstallIntegrateResponseDto.builder()
.install(install)
.containers(containers)
.build();
}
/**
* 자연키 (accountNo, workType, installDate) install 삭제한다.
* 딸린 컨테이너를 먼저 삭제해 고아를 남기지 않는다.
* 대상이 없으면 멱등 처리(아무것도 하지 않음).
*
* @return 실제로 삭제했으면 true, 대상이 없어 넘어갔으면 false
*/
@Transactional
public boolean deleteByBusinessKey(String accountNo, String workType, LocalDate installDate) {
CustomerDailyInstall existing = installService.findByBusinessKey(accountNo, workType, installDate);
if (existing == null) {
return false;
}
containerService.deleteForInstall(existing.getCdiId());
installService.deleteEntity(existing);
return true;
}
}

View File

@ -4,6 +4,7 @@ import com.goi.erp.dto.CustomerDailyInstallRequestDto;
import com.goi.erp.dto.CustomerDailyInstallResponseDto;
import com.goi.erp.entity.CustomerDailyInstall;
import com.goi.erp.repository.CustomerDailyInstallRepository;
import com.goi.erp.repository.CustomerRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
@ -12,6 +13,8 @@ import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
@ -19,15 +22,52 @@ import java.util.stream.Collectors;
public class CustomerDailyInstallService {
private final CustomerDailyInstallRepository repository;
private final CustomerRepository customerRepository;
private final HcmEmployeeClient hcmEmployeeClient;
/**
* CREATE (POST /customer-daily-install)
* DailyOrder 동일하게 자연키 기준 upsert 동작한다.
*/
public CustomerDailyInstallResponseDto create(CustomerDailyInstallRequestDto dto) {
return mapToDto(upsert(dto));
}
/**
* MIS 통합용 upsert.
* 자연키 (accountNo, workType, installDate) 조회 있으면 update, 없으면 insert.
* 통합(integrate) 흐름에서 저장된 cdiId 필요하므로 엔티티를 반환한다.
*/
@Transactional
public CustomerDailyInstall upsert(CustomerDailyInstallRequestDto dto) {
Long customerId = resolveCustomerId(dto);
Long driverId = resolveDriverId(dto.getCdiDriverId(), dto.getCdiExternalDriverId());
Long customerDriverId = resolveDriverId(dto.getCdiCustomerDriverId(), dto.getCdiExternalCustomerDriverId());
String createdBy = resolveCreatedBy(dto);
CustomerDailyInstall existing = repository
.findByCdiAccountNoAndCdiWorkTypeAndCdiInstallDate(
dto.getCdiAccountNo(),
dto.getCdiWorkType(),
dto.getCdiInstallDate()
)
.orElse(null);
// UPDATE (cdiUpdatedAt @LastModifiedDate 자동 갱신)
if (existing != null) {
applyUpdate(existing, dto, customerId, driverId, customerDriverId);
return repository.save(existing);
}
// CREATE
CustomerDailyInstall entity = CustomerDailyInstall.builder()
.cdiUuid(UUID.randomUUID())
.cdiOrderSeq(dto.getCdiOrderSeq())
.cdiDriverId(dto.getCdiDriverId())
.cdiDriverId(driverId)
.cdiInstallDate(dto.getCdiInstallDate())
.cdiInstallTime(dto.getCdiInstallTime())
.cdiCustomerId(dto.getCdiCustomerId())
.cdiCustomerId(customerId)
.cdiAccountNo(dto.getCdiAccountNo())
.cdiCustomerName(dto.getCdiCustomerName())
.cdiPhone(dto.getCdiPhone())
@ -36,13 +76,19 @@ public class CustomerDailyInstallService {
.cdiPostalCode(dto.getCdiPostalCode())
.cdiGeoLat(dto.getCdiGeoLat())
.cdiGeoLon(dto.getCdiGeoLon())
.cdiCustomerDriverId(customerDriverId)
.cdiWorkType(dto.getCdiWorkType())
.cdiOilPickup(dto.getCdiOilPickup())
.cdiSludge(dto.getCdiSludge())
.cdiRequestNote(dto.getCdiRequestNote())
.cdiWorkNote(dto.getCdiWorkNote())
.cdiInstallNote(dto.getCdiInstallNote())
.cdiCsNote(dto.getCdiCsNote())
.cdiStatus(dto.getCdiStatus())
.build();
.cdiCreatedBy(createdBy)
.build(); // cdiCreatedAt / cdiUpdatedAt auditing 자동 세팅
entity = repository.save(entity);
return mapToDto(entity);
return repository.save(entity);
}
public List<CustomerDailyInstallResponseDto> getByInstallDate(LocalDate date) {
@ -59,7 +105,10 @@ public class CustomerDailyInstallService {
CustomerDailyInstall entity = repository.findById(id)
.orElseThrow(() -> new RuntimeException("CustomerDailyInstall not found"));
updateInternal(entity, dto);
Long driverId = resolveDriverId(dto.getCdiDriverId(), dto.getCdiExternalDriverId());
Long customerDriverId = resolveDriverId(dto.getCdiCustomerDriverId(), dto.getCdiExternalCustomerDriverId());
applyUpdate(entity, dto, null, driverId, customerDriverId);
return mapToDto(entity);
}
@ -72,25 +121,141 @@ public class CustomerDailyInstallService {
repository.delete(entity);
}
private void updateInternal(CustomerDailyInstall entity, CustomerDailyInstallRequestDto dto) {
if (dto.getCdiDriverId() != null) entity.setCdiDriverId(dto.getCdiDriverId());
if (dto.getCdiInstallTime() != null) entity.setCdiInstallTime(dto.getCdiInstallTime());
if (dto.getCdiStatus() != null) entity.setCdiStatus(dto.getCdiStatus());
if (dto.getCdiWorkNote() != null) entity.setCdiWorkNote(dto.getCdiWorkNote());
/**
* 자연키 (accountNo, workType, installDate) 조회. 없으면 null.
*/
public CustomerDailyInstall findByBusinessKey(String accountNo, String workType, LocalDate installDate) {
return repository
.findByCdiAccountNoAndCdiWorkTypeAndCdiInstallDate(accountNo, workType, installDate)
.orElse(null);
}
private CustomerDailyInstallResponseDto mapToDto(CustomerDailyInstall entity) {
public void deleteEntity(CustomerDailyInstall entity) {
repository.delete(entity);
}
/**
* 공통 업데이트 (null 아닌 값만 반영).
* customerId 자연키(account_no) 조회되므로 upsert 흐름에서만 재설정한다.
*/
private void applyUpdate(
CustomerDailyInstall entity,
CustomerDailyInstallRequestDto dto,
Long customerId,
Long driverId,
Long customerDriverId
) {
if (dto.getCdiOrderSeq() != null) entity.setCdiOrderSeq(dto.getCdiOrderSeq());
if (driverId != null) entity.setCdiDriverId(driverId);
if (customerId != null) entity.setCdiCustomerId(customerId);
if (dto.getCdiInstallTime() != null) entity.setCdiInstallTime(dto.getCdiInstallTime());
if (dto.getCdiCustomerName() != null) entity.setCdiCustomerName(dto.getCdiCustomerName());
if (dto.getCdiPhone() != null) entity.setCdiPhone(dto.getCdiPhone());
if (dto.getCdiAddress() != null) entity.setCdiAddress(dto.getCdiAddress());
if (dto.getCdiCity() != null) entity.setCdiCity(dto.getCdiCity());
if (dto.getCdiPostalCode() != null) entity.setCdiPostalCode(dto.getCdiPostalCode());
if (dto.getCdiGeoLat() != null) entity.setCdiGeoLat(dto.getCdiGeoLat());
if (dto.getCdiGeoLon() != null) entity.setCdiGeoLon(dto.getCdiGeoLon());
if (customerDriverId != null) entity.setCdiCustomerDriverId(customerDriverId);
if (dto.getCdiWorkType() != null) entity.setCdiWorkType(dto.getCdiWorkType());
if (dto.getCdiOilPickup() != null) entity.setCdiOilPickup(dto.getCdiOilPickup());
if (dto.getCdiSludge() != null) entity.setCdiSludge(dto.getCdiSludge());
if (dto.getCdiRequestNote() != null) entity.setCdiRequestNote(dto.getCdiRequestNote());
if (dto.getCdiWorkNote() != null) entity.setCdiWorkNote(dto.getCdiWorkNote());
if (dto.getCdiInstallNote() != null) entity.setCdiInstallNote(dto.getCdiInstallNote());
if (dto.getCdiCsNote() != null) entity.setCdiCsNote(dto.getCdiCsNote());
if (dto.getCdiStatus() != null) entity.setCdiStatus(dto.getCdiStatus());
}
public CustomerDailyInstallResponseDto mapToDto(CustomerDailyInstall entity) {
if (entity == null) return null;
return CustomerDailyInstallResponseDto.builder()
.cdiUuid(entity.getCdiUuid())
.cdiUuid(entity.getCdiUuid())
.cdiOrderSeq(entity.getCdiOrderSeq())
.cdiDriverId(entity.getCdiDriverId())
.cdiInstallDate(entity.getCdiInstallDate())
.cdiInstallTime(entity.getCdiInstallTime())
.cdiCustomerId(entity.getCdiCustomerId())
.cdiAccountNo(entity.getCdiAccountNo())
.cdiCustomerName(entity.getCdiCustomerName())
.cdiPhone(entity.getCdiPhone())
.cdiAddress(entity.getCdiAddress())
.cdiCity(entity.getCdiCity())
.cdiPostalCode(entity.getCdiPostalCode())
.cdiGeoLat(entity.getCdiGeoLat())
.cdiGeoLon(entity.getCdiGeoLon())
.cdiCustomerDriverId(entity.getCdiCustomerDriverId())
.cdiWorkType(entity.getCdiWorkType())
.cdiOilPickup(entity.getCdiOilPickup())
.cdiSludge(entity.getCdiSludge())
.cdiRequestNote(entity.getCdiRequestNote())
.cdiWorkNote(entity.getCdiWorkNote())
.cdiInstallNote(entity.getCdiInstallNote())
.cdiCsNote(entity.getCdiCsNote())
.cdiStatus(entity.getCdiStatus())
.cdiCreatedBy(entity.getCdiCreatedBy())
.cdiCreatedAt(entity.getCdiCreatedAt())
.cdiUpdatedAt(entity.getCdiUpdatedAt())
.build();
}
/**
* accountNo(=cus_no) 내부 customerId 해석. MIS di_customer_uid 모르므로 accountNo 찾는다.
*/
private Long resolveCustomerId(CustomerDailyInstallRequestDto dto) {
// 1. ERP 내부 호출: customerId 직접 전달
if (dto.getCdiCustomerId() != null) {
return dto.getCdiCustomerId();
}
// 2. MIS: accountNo == cus_no
if (dto.getCdiAccountNo() != null) {
return customerRepository.findByCusNo(dto.getCdiAccountNo())
.map(c -> c.getCusId())
.orElseThrow(() -> new RuntimeException("Customer not found by accountNo: " + dto.getCdiAccountNo()));
}
return null;
}
/**
* 내부 driverId 직접값 우선, 없으면 MIS externalId HCM 에서 empId 변환.
*/
private Long resolveDriverId(Long directDriverId, String externalDriverId) {
if (directDriverId != null) {
return directDriverId;
}
if (externalDriverId != null) {
Map<String, Object> body = hcmEmployeeClient.getEmpFromExternalId("MIS_DRIVER", externalDriverId);
if (body != null && body.get("eexEmpId") instanceof Number num) {
return num.longValue();
}
}
return null;
}
/**
* createdBy 해석: 로그인 사용자 > MIS member(empNo 변환) > 원본 문자열.
*/
private String resolveCreatedBy(CustomerDailyInstallRequestDto dto) {
if (dto.getCdiLoginUser() != null) {
return dto.getCdiLoginUser();
}
if (dto.getCdiExternalCreatedBy() != null) {
Map<String, Object> body = hcmEmployeeClient.getEmpFromExternalId("MIS_MEMBER", dto.getCdiExternalCreatedBy());
if (body != null && body.get("eexEmpNo") instanceof String empNo) {
return empNo;
}
}
return dto.getCdiCreatedBy();
}
}