7. Asset 서비스
client.asset() 으로 접근합니다. PlantPulse 자산 모델의 핵심 — 기업 > 사이트 > 구역 > 라인 > 설비 의 다섯 층 트리를 관리합니다.
ID 규칙: 구역
AREA_· 라인LINE_· 설비EQUIPMENT_(이미 있는ASSET_도 유효).asset_id를 비워 두면 플랫폼이 발급합니다. 트리의 층을 결정하는 것은 ID 가 아니라asset_type입니다. 자세한 내용은 도메인 ID 규칙 참고.
7.1 메서드 일람
| 메서드 | 반환 타입 | HTTP | 엔드포인트 |
|---|---|---|---|
create(AssetRequestV5) | AssetResponseV5 또는 null | POST | /api/v5/asset |
update(asset_id, AssetRequestV5) | AssetResponseV5 또는 null | PUT | /api/v5/asset/{id} |
delete(asset_id) | boolean | DELETE | /api/v5/asset/{id} |
get(asset_id) | AssetResponseV5 또는 null | GET | /api/v5/asset/{id} |
list() | List<AssetResponseV5> | GET | /api/v5/asset |
exists(asset_id) | boolean | GET | /api/v5/asset/{id}/exists |
getByExternal(system, external_id) | AssetResponseV5 또는 null | GET | /api/v5/asset/by-external/{system}/{external_id} |
patchProperties(asset_id, 요청) | AssetResponseV5 또는 null | PATCH | /api/v5/asset/{id}/properties |
Asset 서비스는 단순화된 CRUD에 집중되어 있습니다. 트리 계층 조회는 Path 서비스 또는 클라이언트 측 필터링(
list()후asset_type필터)을 사용하시면 됩니다.
7.2 Asset Type 값
| asset_type | 의미 |
|---|---|
E | 기업 (Enterprise) — 설치에 하나, 트리의 뿌리 |
S | 사이트 (Site) |
A | 구역 (Area) |
L | 라인 (Line) |
M | 설비 (Equipment, Machine) |
POST /api/v5/asset 으로 만드는 것은 A · L · M 입니다. 사이트는 Site 서비스 로 만듭니다.
GET /api/v5/asset 은 asset_type 을 주지 않으면 구역·라인·설비를 돌려주고, asset_type=E 나 S 를 주면 그 층을 돌려줍니다.
E · S 를 설비로 세지 마십시오. 층은 ID 접두가 아니라 이 값으로 판단합니다.
7.3 AssetRequestV5 / AssetResponseV5
| 필드 | 타입 | 설명 |
|---|---|---|
asset_id | String | 자산 ID (PK). 비워 두면 발급 |
asset_name | String | 표시명 |
parent_asset_id | String | 부모 자산 ID — 바로 위 층 |
asset_order | String | 형제 간 정렬 순서 |
asset_type | String | A/L/M (응답에는 E·S 도). 만든 뒤에는 바꿀 수 없음 |
asset_class_code | String | 설비 분류 코드 (CNC, INJECTION 등). 그 층의 분류여야 하며, 아니면 422 |
site_id | String | 소속 사이트 |
description | String | 설명 (필수) |
operational_status_code | String | (응답) 운전 상태 |
external_system / external_id | String | (응답) 다른 시스템이 이 노드를 부르는 번호 |
insert_date / update_date | String | (응답 전용) 시각 |
단건 조회는 그 층의 고유 속성까지 싣고, 목록은 공통 칸(operational_status_code · external_system · external_id)만 싣습니다.
고유 속성 — /api/v5/asset/{id}/properties
층마다 자기만 가진 속성이 있습니다. GET 으로 읽고 PATCH(또는 PUT)로 고칩니다. 보낸 키만 쓰고, null 은 지움이며,
보내지 않은 키는 그대로입니다.
| 층 | 키 |
|---|---|
| 기업 제외 공통 | operational_status_code, external_system + external_id (둘 다이거나 둘 다 없음) |
사이트 S | timezone, country, site_code, address, lat, lng |
구역 A | area_function_code(PRODUCTION · STORAGE · UTILITY), responsible_employee_id |
라인 L | line_kind_code(PRODUCTION_LINE · PROCESS_CELL · PRODUCTION_UNIT), design_rate(시간당, 0 초과) + design_rate_unit_of_measure(UN/ECE Rec 20, 둘 다이거나 둘 다 없음), responsible_employee_id |
설비 M | manufacturer, model, serial_number, installed_date, commissioned_date(설치일 이후), warranty_end_date(yyyy-MM-dd), criticality_code(A · B · C), functional_location(사이트 안에서 유일) |
| 응답 | 뜻 |
|---|---|
422 asset.invalid.<키> | 그 층에 없는 키, 모르는 코드, 반쪽 쌍, 날짜 순서 위반 |
422 asset.invalid.asset_type | 층을 바꾸려 함 — 층은 바뀌지 않습니다 |
409 asset.duplicate.external_id | 다른 노드가 같은 외부 번호를 씀 |
409 asset.duplicate.functional_location | 같은 사이트의 다른 설비가 같은 기능 위치를 씀 |
응답의 effective_responsible_employee_id 는 실제 담당자입니다 — 자기 칸이 비었으면 가장 가까운 상위(라인 → 구역)의 담당을 물려받고,
effective_responsible_from_asset_id 가 어디서 물려받았는지 말합니다.
GET /api/v5/asset/{id} 응답의 qr_payload 는 QR 라벨에 인쇄하는 주소입니다. 이 호출은 토큰을 만들지 않습니다 — 콘솔에서 라벨을 한 번 열어 토큰이 생긴 설비만 채워지고, 아직 없거나 콘솔 주소가 비어 있으면 "" 입니다.
외부 번호로 찾기
GET /api/v5/asset/by-external/MAXIMO/BEDFORD%2F11430
GET /api/v5/asset/by-external?system=MAXIMO&external_id=BEDFORD/11430
모든 층에서 찾습니다. 없으면 404 asset.not_found, 시스템과 번호 중 하나만 주면 422 입니다.
7.4 사용 예시
Area → Line → Equipment 트리 생성
import plantpulse.api.v5.dto.request.AssetRequestV5;
// 1) Area
AssetRequestV5 area = new AssetRequestV5();
area.setSite_id("SITE_DJ");
area.setParent_asset_id("SITE_DJ");
area.setAsset_name("조립구역");
area.setAsset_type("A");
client.asset().create(area);
// asset_id 를 비워 두면 플랫폼이 발급합니다 — 응답에서 AREA_000001 을 받습니다
// 2) Line
AssetRequestV5 line = new AssetRequestV5();
line.setSite_id("SITE_DJ");
line.setParent_asset_id("AREA_000001");
line.setAsset_name("1번 조립 라인");
line.setAsset_type("L");
client.asset().create(line);
// 3) Equipment (CNC 머신)
AssetRequestV5 cnc = new AssetRequestV5();
cnc.setSite_id("SITE_DJ");
cnc.setParent_asset_id("LINE_000001");
cnc.setAsset_name("CNC #1");
cnc.setAsset_type("M");
cnc.setAsset_class_code("CNC");
cnc.setDescription("DMG MORI NLX-2500");
client.asset().create(cnc);
단건 조회
import plantpulse.api.v5.dto.response.AssetResponseV5;
AssetResponseV5 cnc = client.asset().get("EQUIPMENT_000001");
if (cnc != null) {
System.out.println(cnc.getAsset_name() + " (" + cnc.getAsset_type() + ")");
System.out.println("부모: " + cnc.getParent_asset_id());
}
전체 목록 + 타입별 필터링
서버 쪽 필터는 GET /api/v5/asset?asset_type=M · operational_status_code= · external_system= 입니다. 자바 클라이언트의 list() 는 필터를 받지 않으므로 받은 뒤 거릅니다.
import java.util.List;
import java.util.stream.Collectors;
List<AssetResponseV5> all = client.asset().list();
// Equipment만
List<AssetResponseV5> equipments = all.stream()
.filter(a -> "M".equals(a.getAsset_type()))
.collect(Collectors.toList());
// 대전공장의 Line만
List<AssetResponseV5> djLines = all.stream()
.filter(a -> "SITE_DJ".equals(a.getSite_id()))
.filter(a -> "L".equals(a.getAsset_type()))
.collect(Collectors.toList());
트리 재구성 (클라이언트 측)
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
// asset_id 인덱스
Map<String, AssetResponseV5> byId = new HashMap<>();
for (AssetResponseV5 a : client.asset().list()) {
byId.put(a.getAsset_id(), a);
}
// 부모-자식 매핑
Map<String, List<AssetResponseV5>> children = new HashMap<>();
for (AssetResponseV5 a : byId.values()) {
children
.computeIfAbsent(a.getParent_asset_id(), k -> new ArrayList<>())
.add(a);
}
// 사이트(SITE_DJ) 산하 트리 출력
printTree(children, "SITE_DJ", 0);
static void printTree(Map<String, List<AssetResponseV5>> children, String id, int depth) {
List<AssetResponseV5> kids = children.getOrDefault(id, List.of());
for (AssetResponseV5 c : kids) {
System.out.println(" ".repeat(depth) + c.getAsset_id() + " — " + c.getAsset_name());
printTree(children, c.getAsset_id(), depth + 1);
}
}
자산 수정
AssetRequestV5 update = new AssetRequestV5();
update.setSite_id("SITE_DJ");
update.setParent_asset_id("LINE_000001");
update.setAsset_id("EQUIPMENT_000001");
update.setAsset_name("CNC #1 (보수 후)");
update.setAsset_type("M");
update.setAsset_class_code("CNC");
update.setDescription("2026-05-10 베어링 교체 완료");
client.asset().update("EQUIPMENT_000001", update);
자산 삭제
⚠️ 자산 삭제 전에 그 자산에 연결된 모든 Tag·하위 Asset을 먼저 삭제해야 합니다.
boolean ok = client.asset().delete("EQUIPMENT_OLD");
7.5 활용 시나리오
시나리오 — 사이트의 Equipment 목록과 각 Tag 수
String siteId = "SITE_DJ";
List<AssetResponseV5> equipments = client.asset().list().stream()
.filter(a -> siteId.equals(a.getSite_id()))
.filter(a -> "M".equals(a.getAsset_type()))
.collect(Collectors.toList());
for (AssetResponseV5 eq : equipments) {
long tagCount = client.tag().countByAsset(eq.getAsset_id());
System.out.printf("%s — Tag %d개%n", eq.getAsset_name(), tagCount);
}
시나리오 — Line 단위 OEE 대시보드 데이터 수집
// 1) Line 목록
List<AssetResponseV5> lines = client.asset().list().stream()
.filter(a -> "L".equals(a.getAsset_type()))
.filter(a -> "SITE_DJ".equals(a.getSite_id()))
.collect(Collectors.toList());
// 2) 각 Line 산하 Equipment 조회 → 자산 경로 → 작업지시 → KPI 집계
for (AssetResponseV5 line : lines) {
List<AssetResponseV5> machines = client.asset().list().stream()
.filter(a -> line.getAsset_id().equals(a.getParent_asset_id()))
.collect(Collectors.toList());
for (AssetResponseV5 m : machines) {
long now = System.currentTimeMillis();
// 현재 작업지시는 Order 서비스로 조회
// ...
}
}