uEngine6 BPM 튜토리얼
산업 예제33

ERP: 구매 발주 결재와 대금 지급

이벤트 소싱 ERP(uEngine ERP)의 이벤트로 BPM 프로세스를 시작하고, 결재는 BPM 폼에서, 입고·청구서·지급은 BPM 작업 안에 뜬 ERP 화면에서 처리합니다. 두 시스템은 Kafka 이벤트로만 만납니다.

무엇을 · 왜#

ERP는 거래를 기록하는 시스템입니다. 누가 결재하는지, 금액에 따라 결재선이 어떻게 달라지는지, 다음 일을 누가 맡을지는 회사마다 다르고 자주 바뀝니다. 그 흐름을 BPM에 두면 ERP를 고치지 않고 결재선을 바꿀 수 있습니다. 업무함·감사 기록·되돌리기도 한곳에서 씁니다.

무엇 누가 맡나
발주서·입고·청구서·지급의 기록과 규칙(수량·금액·상태·권한) ERP. BPM은 ERP의 표를 읽지도 쓰지도 않습니다
결재선(팀장 → 1,000만 원 이상이면 대표), 담당자 업무함, 진행 기록 BPM
둘 사이의 메시지 ERP 플러그인(아웃박스) + 브리지 + Kafka

가상 회사 「가온가전」(uEngine ERP 튜토리얼과 같은 회사)의 다섯 사람이 등장합니다.

사람 BPM 그룹 · 역할 하는 일
최구매 구매팀 · purchase ERP에서 발주서를 쓰고 확정
윤팀장 구매결재 · purchase-lead 팀장 결재(BPM 폼)
김대표 경영진 · ceo 1,000만 원 이상 결재(BPM 폼)
이재고 물류팀 · warehouse 입고 처리(BPM 작업 안의 ERP 입고 전표)
정회계 회계팀 · accounting 공급사 청구서·대금 지급(BPM 작업 안의 ERP 화면)

먼저 알아 둘 것

  • 화면과 수치는 로컬 스택(uEngine6 bmt/sds-process-test 브랜치, uEngine ERP feat/bpm-integration)을 가상 회사 데이터로 실행해 얻었습니다.
  • 이 브랜치에서 예제를 만들면서 확인한 엔진·프런트 결함 8건과 우회 방법은 예제 저장소의 docs/INTEGRATION.md 9절에 있습니다. 예제 저장소 bpm-sample-app-erp는 공개 준비 중이며, 공개되면 이 자리에 링크를 둡니다. 업무 목록의 「선점」은 시연 스택에서 프런트 패치와 설정 하나(esb.inbound-unwrap.enabled: true)로 고친 뒤 촬영했습니다.
  • 사용자 비밀번호(gaon-1234)는 로컬 시연용입니다.

구성#

ERP(Supabase) ─ 구독 handler ─▶ erp_bpm.outbox ─▶ 브리지 ─▶ Kafka bpm-topic ─▶ uEngine6 process-service
     ▲                                              │  (헤더 type · corrKey · emnb)
     └──────── ERP 명령(승인·취소, 멱등 키) ◀──────── 브리지가 bpm-bridge 의 할 일 목록을 읽음(외부 작업)
브라우저 ─▶ keycloak-gateway(:8288) ─┬─ /**      → uEngine6 화면
                                    └─ /erp/**  → ERP 웹(같은 출처라 BPM 작업 안 iframe 으로 열림)
  • ERP → BPM: ERP 플러그인 bpm_link가 이벤트를 구독해 BPM 메시지를 같은 DB 트랜잭션 안에서 아웃박스에 쌓습니다. 브리지가 순서대로 Kafka로 보냅니다.
  • BPM → ERP: 「ERP 승인 반영」「ERP 반려 반영」은 연동 계정 bpm-bridge 앞으로 생기는 작업입니다. 브리지가 그 할 일 목록을 읽어 ERP 명령을 실행하고, ERP가 낸 결과 이벤트가 그 작업을 끝냅니다(Camunda의 external task와 같은 개념).
  • 화면: ERP 웹을 BPM과 같은 게이트웨이 뒤(/erp/**)에 붙입니다. ERP는 같은 출처의 iframe 안에서 메뉴 없는 임베드 모드가 됩니다.

따라 하기#

  1. 프로세스 보기. BPM에서 구매발주/구매발주결재를 엽니다. 레인 6개(구매 담당·구매 팀장·대표·ERP 연동·재고·회계), 결재 2단계, 시스템 작업 2개, ERP 화면 작업 3개입니다.
    구매발주결재 프로세스
    구매발주결재 프로세스
  2. 발주 확정(ERP). 최구매로 ERP(/erp/)에 로그인해 견적 요청을 씁니다. 공급사 바람부품, 에어서큘레이터 60대(3,000,000원)입니다. 확정을 누르면 최구매는 관리자가 아니어서 발주서가 승인 대기가 됩니다.
    승인 대기
    승인 대기
  3. 이벤트가 BPM으로. ERP가 PurchaseOrderApprovalRequested를 남기고, 브리지 화면(/erp-bridge/)에 ERP → BPM 줄이 나타납니다. BPM은 이 이벤트로 인스턴스 발주결재 P00001 바람부품을 시작합니다.
  4. 팀장 결재(BPM). 윤팀장의 업무 목록에 팀장 결재가 옵니다. 구매결재 그룹에서 팀장 역할을 가진 사람에게만 보입니다. 카드의 체크 상자를 고르고 선택 1건 선점을 누른 뒤 카드를 엽니다. 폼에는 발주 번호·공급사·금액·요청자가 미리 채워져 있습니다. 승인을 고르고 의견을 적어 제출 완료를 누릅니다.
    팀장 결재 폼
    팀장 결재 폼
  5. ERP에 승인 반영(시스템 작업). 1,000만 원 미만이라 대표 결재 없이 ERP 승인 반영으로 갑니다. 브리지가 ERP에 approve를 보내고, ERP의 PurchaseOrderConfirmed가 돌아와 이 작업을 끝냅니다. ERP의 발주서는 확정되고 입고 전표가 생깁니다.
  6. 입고(BPM 작업 안의 ERP 화면). 이재고가 입고 처리를 선점해 엽니다. 오른쪽에 ERP 입고 전표 WH/IN/00001이 그대로 뜹니다. 처리 완료를 누르면 ERP의 PickingValidated가 BPM 작업을 끝냅니다.
    BPM 작업 안의 ERP 입고 전표
    BPM 작업 안의 ERP 입고 전표
  7. 공급사 청구서. 정회계가 공급사 청구서 등록을 엽니다. ERP 발주서에서 청구서 만들기를 누르고 청구일을 넣어 게시합니다. InvoicePosted가 이 작업을 끝내고 대금 지급이 생깁니다.
  8. 대금 지급. 대금 지급을 열면 그 청구서가 뜹니다. 지급 등록을 확인하면 InvoicePaymentApplied가 마지막 작업을 끝내고 인스턴스가 지급 완료로 끝납니다.
    연동 이벤트
    연동 이벤트
  9. 대표 결재와 반려. 한결전자부품에 제습기 70대(1,260만 원)로 같은 절차를 진행하면, 윤팀장 승인 뒤 대표 결재가 김대표에게 갑니다. 김대표가 반려하면 ERP 반려 반영에서 브리지가 ERP 발주서를 취소하고, 프로세스는 반려 종료로 끝납니다. ERP 이력에는 BPM 연동이 취소한 것으로 남습니다.

영상으로 더 보기#

9단계 「대표 결재와 반려」를 따로 촬영한 영상입니다. 반려가 ERP 발주 취소로 이어지고, ERP 이력에 연동 계정이 남는 것까지 확인합니다.

어떻게 설정했나#

메시지 계약#

ERP 이벤트 (Kafka 헤더 type) BPM에서 payload(모두 프로세스 변수가 됨)
PurchaseOrderApprovalRequested 메시지 시작 이벤트 poId, poNumber, vendorName, amountTotal(정수), amountText, requestedBy
PurchaseOrderConfirmed · PurchaseOrderCancelled 시스템 작업 완료 poNumber, confirmedBy · cancelledBy
PickingValidated · InvoicePosted · InvoicePaymentApplied ERP 화면 작업 완료 receiptNumber · billId, billNumber · paymentState, amountPaid

헤더 corrKey와 payload 첫 필드 poId는 발주서 id이고, 인스턴스의 상관 키입니다. 헤더 emnb는 그 일을 한 사람입니다. 사람 작업을 이벤트로 끝낼 때 엔진은 그 작업을 선점한 사람과 emnb가 같은지 봅니다. 그래서 이재고·정회계는 ERP에서 처리하기 전에 BPM 작업을 선점합니다.

레인 — 그룹 + 역할 + 경합#

{"roleResolutionContext": {"_type": "org.uengine.five.overriding.IAMRoleResolutionContext",
                           "groupName": "구매결재", "scope": "purchase-lead"},
 "dispatchingOption": 1}

작업은 담당자 없이 생기고, 그 그룹·역할을 가진 사람의 업무 목록에 보이며, 한 사람이 선점하면 그 사람의 작업이 됩니다.

결재 폼 — 미리 채우기(in)와 결과 받기(out)#

{"_type": "org.uengine.kernel.FormActivity", "tool": "formHandler:발주결재_팀장",
 "variableForHtmlFormContext": {"name": "발주결재_팀장"},
 "eventSynchronization": {"eventType": "", "attributes": [], "mappingContext": {"mappingElements": [
   {"argument": {"text": "발주결재_팀장.poNumber"}, "direction": "in", "variable": {"name": "poNumber"}},
   {"argument": {"text": "leadDecision"}, "direction": "out", "variable": {"name": "[Arguments].decision"}}]}}}

ERP 화면 작업과 시스템 작업#

{"_type": "org.uengine.kernel.URLActivity",
 "url": "http://localhost:8288/erp/#/by-origin/picking/<%instance.corrkey%>",
 "eventSynchronizations": [{"eventType": "PickingValidated",
   "attributes": [{"name": "poId", "className": "String", "isKey": true, "isCorrKey": true}]}]}

시스템 작업은 같은 eventSynchronizations를 설정한 사람 작업(HumanActivity)이고, 레인이 DirectRoleResolutionContext로 bpm-bridge를 가리킵니다.

게이트웨이 라우트#

- id: erp-web
  uri: http://localhost:5302        # ERP web 정적 빌드
  predicates:
    - Path=/erp, /erp/**             # 프런트 catch-all(/**) 보다 앞에

확인 문제#

이재고가 BPM 작업을 선점하지 않고 ERP에서 바로 입고를 처리하면 어떻게 되나요?

ERP는 입고를 처리하고 이벤트를 냅니다. 하지만 BPM은 「입고 처리」 작업을 선점한 사람이 없어 그 이벤트로 작업을 끝내지 않습니다(Work item must be claimed before completion). 작업을 선점한 뒤 ERP에서 다시 확인하면 됩니다.

브리지가 같은 시스템 작업을 두 번 처리하면 ERP 발주가 두 번 승인되나요?

아닙니다. 브리지는 bpm:<인스턴스>:<작업>을 ERP 명령의 멱등 키로 보내므로 두 번째 명령은 반영되지 않고, 결과 이벤트도 처음 한 번만 나갑니다.

결재선을 「500만 원 이상이면 대표」로 바꾸려면 ERP를 고쳐야 하나요?

아닙니다. BPM 게이트웨이의 조건(amountTotal >= 10000000)만 바꿔 배포합니다. ERP는 승인 요청과 승인 명령만 압니다.

© uEngine Solutions · 문의하기 · 이 튜토리얼의 화면과 결과는 uEngine6 (bmt/sds-process-test 브랜치)을 로컬에서 실제로 실행해 얻었습니다. 회사와 사람은 가상입니다.