본문으로 건너뛰기

ERP 화면 뒤 API를 읽는 법: REA로 Odoo 판매 흐름을 역추적해 보고, 더존에 적용할 때 지킬 선까지

· 약 18분
Datapopcorn CEO / AI automation educator
이 글과 연결된 글 보기
35편 · 159개 연결
100%
문자로 온 네이버페이 포인트 기프티콘, AI 브라우저 에이전트에게 등록시켜 봤습니다해외 출장 가이드북, 메일·캘린더·지도에 흩어진 정보로 15쪽 PDF 만들기 (프롬프트 포함)이제 직원 급여도 AI가 보냅니다: 은행 앱 급여 이체부터 급여명세서 발송까지 AI 에이전트에 맡긴 8단계 (ADB)[AI 게임 리그 EP.1] AI 혼자 식물vs좀비 엔딩까지: Claude는 50판을 깼고 ChatGPT는 24판에서 멈췄습니다AI가 내 책을 학습하는 대가, 내 몫은 16만 원이었다유료 문자 API 대신 내 안드로이드폰으로 무료 SMS 게이트웨이 만들기: Aside·무선 ADB·TextBee 실전 연동기터미널에서 AI 브라우저에게 일 시키기, Aside CLI 사용법문자로 온 네이버페이 기프티콘, Aside에 한 줄로 등록하기베를린 출장 교통 앱 가이드: 한국 번호로 BVG·FREE NOW·Uber·Bolt 가입해 본 8단계 (2026)ChatGPT 데스크톱 앱으로 실시간 통역하는 법챗GPT 새 한국어 음성 '한울·햇살' 설정하고 영어 회화 연습에 쓰는 법 (GPT-Live-1.1)Claude Code 사용량 한도에 걸려도 작업을 이어가는 법: API 크레딧으로 claude-api 명령 만들기 5단계먼저 연락하는 Claude, Claude Duet 정리: 무엇이 다르고 한국에서 대기자 신청은 어떻게 하나클로드 사용량 100%에서 0%로, 무료 초기화 직접 해봤습니다Claude Team 플랜에만 있는 기능과 첫 설정: 개인 Pro·Max 화면 비교부터 월간 API 크레딧(최대 $500) 받기, 슬랙 DM으로 일 시키기까지 14단계Daytona란 무엇인가? AI가 만든 코드를 안전하게 실행하는 샌드박스패스트파이브 성수연무장점 후기: 성수역에서 가는 길, 지하 입구, 라운지 시설까지패스트파이브 뚝섬점 후기: 뚝섬역 4번 출구에서 가는 길, 지하 메인 라운지, 태스크룸까지Gemini 4 Argon 직접 써봤습니다: Pro 요금제에서 켜는 법, Flash와 같은 질문 비교, 3D 게임까지AI로 코딩은 빨라졌는데 배포는 누가 책임지나: 인포그랩 Next Delivery Stack 2026 후기문자로 쓰는 AI 비서 instinct, 처음엔 무슨 일을 시켜볼까요?링크드인 글 하나에 여러 명 이름 올리기: 협업 게시물(Collaborative Posts) 사용법러다이트(Ludyte) 가입부터 첫 영상까지: 무료 9회로 직접 해본 기록Muse 가입부터 첫 작업까지: 처음 쓰는 사람을 위한 8단계 가이드n8n 새 기능 직접 써보기: Assistant, Agents, MCP, 마이그레이션 리포트를 셀프호스팅에서 확인하는 법n8n v3 베타 먼저 올려 봤습니다: 셀프호스팅 2.45.3에서 3.0.0으로 업그레이드하는 순서와 확인할 것사업자 있으신 분, 050 안심번호 무료로 받으세요 (네이버 스마트콜 발급 방법)네이버 스마트플레이스 똑똑하게 채우는 법, AI 에이전트로 관리하기까지OpenAI Dots는 Muse 대항마가 아니라 '클라우드 PC 비서'입니다해외 출장 예약하고 개인 카드를 한 번도 안 꺼냈습니다 — 법인 출장 플랫폼 Perk 사용기피클(Pickle) AI 에이전트 사용법: 설치부터 첫 일 맡기기까지, Grok Bot·Muse·Dots와 비교REA가 뭔가요? 공식 사례부터 설치·팩맨 실습까지ERP 화면 뒤 API를 읽는 법: REA로 Odoo 판매 흐름을 역추적해 보고, 더존에 적용할 때 지킬 선까지Softr로 교육 수강생 포털 만들기: AI 빌더로 뼈대 만들고, 버그 6개 직접 고친 기록Codex에 영상 넣고 "자막 따줘" 한마디|AI로 영상 자막 자동 생성하는 방법

드래그로 이동 · 휠/두 손가락으로 확대 · 방향키로 글 선택 · 0으로 맞춤. 실선: 본문 링크, 점선: 시리즈·주제의 대표 연결.

표시된 글 35편 목록
전체 글 연결 탐색

회사 ERP를 AI 에이전트나 자동화 도구에 연결하려는 실무자와, 더존 ERP를 쓰는 고객사의 연동 범위를 진단해야 하는 컨설턴트를 위한 글입니다. ERP를 AI에 붙이려면 먼저 "직원이 화면에서 버튼을 누를 때 시스템 안에서 무슨 일이 일어나는지"를 알아야 합니다. 그래서 리버스 엔지니어링 도구 REA로 오픈소스 ERP인 Odoo의 판매 흐름을 끝까지 따라가 봤습니다. 견적서 작성부터 주문 승인까지 화면 뒤에서 14번의 API 호출이 오갔고, 찾아낸 이름으로 공식 API를 부르자 같은 주문과 같은 출고 전표가 만들어졌습니다. 마지막에는 이 방법을 더존 ERP에 그대로 쓰면 안 되는 이유와, 대신 확인해야 할 순서를 정리했습니다.

REA가 브라우저를 직접 조작해 승인까지 마친 Odoo 판매 주문 S00025 화면. 상단에 배송 1건이 생겨 있습니다

이 글은 이런 분께 필요합니다​

  • ERP 데이터를 AI로 꺼내 쓰고 싶은 실무자: 영업·회계·구매 데이터를 AI 에이전트나 n8n 같은 자동화 도구로 가져오고 싶은데, ERP 안이 어떻게 생겼는지부터 막막한 분
  • 더존 ERP 고객사를 맡은 컨설턴트·IT 담당자: 아마란스10이나 iCUBE를 쓰는 회사에서 "어디까지 연동할 수 있는지"를 진단해야 하는 분
  • REA를 실제 업무 시스템에 써 보고 싶은 분: 게임이나 데모 앱 말고, 실무 시스템에서 무엇이 보이고 무엇이 안 보이는지 알고 싶은 분

이 글을 끝까지 따라 하면 연습용 ERP에서 업무 한 건의 API 흐름을 직접 뽑아 볼 수 있습니다. 자기 회사 ERP에는 어떤 순서로 접근해야 하는지도 가져가실 수 있습니다. 반대로 더존 내부 API 주소나 우회 방법을 찾는 분께는 맞지 않는 글입니다. 그런 정보는 법적으로 공개할 수 없어서 싣지 않았습니다.

준비물은 맥 한 대(Homebrew로 PostgreSQL, Python 3.10, Node.js 설치 가능), Google Chrome, 그리고 터미널입니다. 이 글의 명령과 결과는 모두 2026년 10월 28일에 REA 6.8.0, Odoo 18.0 커뮤니티판으로 직접 실행한 기록입니다.

먼저 결론​

  • REA는 왜 화제인가: 6개월 반 만에 GitHub 스타 약 39.9만 개를 모은 도구입니다. 전문가 영역이던 리버스 엔지니어링을 AI 에이전트에게 말로 시킬 수 있게 했습니다.
  • 그만큼 위험한 점: 남의 소프트웨어 복제에 쓰일 수 있고, 분석하다가 실제 데이터가 바뀌며, 증거 파일과 AI 회사 쪽으로 민감한 값이 넘어갈 수 있습니다.
  • REA가 해 주는 일: 업무 순서를 JSON으로 적어 주면 REA가 브라우저를 띄워 그대로 실행합니다. 그동안 오간 요청과 응답 구조를 증거 파일 하나로 남겨 줍니다. 비밀번호는 증거 파일에 [REDACTED]로 가려져 들어갑니다.
  • Odoo에서 찾은 것: 견적서 저장은 sale.order.web_save, 승인 버튼은 sale.order.action_confirm이었습니다. 이 이름들은 Odoo가 공개한 외부 API에서도 그대로 쓸 수 있었습니다.
  • 보이지 않는 것: 승인 요청의 응답은 false 한 단어뿐이었습니다. 출고 전표가 생긴다는 사실은 네트워크만 봐서는 알 수 없고, 다음 화면 조회와 소스 코드, DB를 함께 봐야 확인됩니다.
  • 더존에 적용할 때: 더존은 오픈소스가 아닙니다. 공식 연동 경로(API·엑셀 연동)부터 확인하는 게 순서입니다. 역분석은 저작권법상 호환 목적으로만 허용되고, 그렇게 얻은 정보를 남에게 공개하면 안 됩니다. 그래서 이 글의 실습 대상은 Odoo입니다.

REA가 뭔가요​

REA(Reverse Engineer Anything)는 Claude Code, Codex 같은 코딩 에이전트에 MCP로 붙는 리버스 엔지니어링 도구입니다. 리버스 엔지니어링은 소스 코드 없이 완성된 프로그램만 보고 "이게 안에서 어떻게 돌아가는지"를 거꾸로 알아내는 작업입니다. 예전에는 Ghidra, IDA 같은 전문 도구를 다룰 줄 아는 보안 전문가의 영역이었습니다. REA는 이 도구들을 AI 에이전트가 직접 다루게 해 줍니다. 그래서 "이 앱의 검색 기능이 어떻게 동작하는지 증거와 함께 보여 줘"라고 말로 시키면 됩니다.

공식 README에 따르면 분석 대상은 네이티브 바이너리, JavaScript·Electron 앱, .NET 어셈블리, 웹사이트, 저장된 네트워크 캡처까지 넓습니다. 같은 기능을 터미널 명령(rea)으로도 쓸 수 있어서, 이 글에서는 결과를 하나씩 보여 드리기 쉬운 CLI로 진행했습니다.

왜 이렇게 화제인가요​

숫자로 보면 규모가 이례적입니다.

  • GitHub 스타 약 39.9만 개, 포크 약 10만 개: 2026년 4월 14일에 저장소가 만들어졌으니 6개월 반 만입니다(2026년 10월 28일 GitHub API 기준).
  • npm 주간 다운로드 약 40만 회: rea-agents 패키지의 10월 20일~26일 기록입니다.
  • 복원 정확도: 공식 사례 중 하나는 고전 게임 DX-Ball의 사운드 계산 함수를 C 코드로 되살린 것입니다. InfoQ 보도에 따르면 원본 x86 코드와 비교한 3,205개 테스트를 모두 통과했고, 다시 컴파일한 결과가 원본 함수의 63바이트와 똑같았습니다. Notion 앱의 클립보드 처리 구조를 추적한 사례도 있습니다.

이번 실습에서도 속도가 체감됐습니다. 업무 순서를 적은 JSON 하나로 로그인부터 주문 승인까지 15단계를 실행하고 요청 62건을 기록하는 데 14초가 걸렸습니다. 그중 ERP 데이터를 다루는 호출 14건을 골라내는 데는 짧은 스크립트 하나면 충분했습니다.

그만큼 위험한 이유​

강력한 만큼 잘못 쓰면 피해도 큽니다. ERP처럼 회사 돈과 거래처 정보가 들어 있는 시스템이라면 특히 아래 다섯 가지를 먼저 알고 시작하세요.

  1. 남의 소프트웨어를 베끼는 데 쓰일 수 있습니다. InfoQ는 인디 개발자들이 자기 게임과 앱이 쉽게 복제되고 유료 기능이 뚫릴까 걱정한다는 반응을 함께 전했습니다. REA도 면책 문서에서 DRM·라이선스 우회, 게임 치트, 독점 소프트웨어 복제에는 도움을 주지 않는다고 밝힙니다. 한국에서는 저작권법이 역분석을 좁게 허용합니다(아래 더존 파트에서 자세히 다룹니다).
  2. 분석하다가 실제 데이터가 바뀝니다. 시나리오에 "승인" 클릭을 넣으면 진짜 주문과 진짜 출고 전표가 생깁니다. 이번 실습에서도 주문 S00025와 출고 WH/OUT/00012가 실제로 만들어졌습니다. 면책 문서도 실행·관찰 작업이 애플리케이션 상태, 파일, 계정에 영향을 줄 수 있다고 경고합니다.
  3. 증거 파일에 민감한 값이 남습니다. 비밀번호는 가려졌지만, REA 스스로 증거 파일 안에서 로그인 토큰과 거래처 접근 토큰 등 7개 항목을 찾아 "공유 전에 검토하라"고 경고했습니다(Step 6). 실제 ERP라면 거래처명, 단가, 매출이 그대로 들어갑니다.
  4. 분석 결과가 AI 회사로 넘어갑니다. REA 자체는 로컬에서 돌지만, README는 에이전트가 받은 분석 결과는 그 에이전트를 만든 모델 제공사의 데이터 정책을 따른다고 적고 있습니다. 회사 ERP 데이터를 다룬다면 사내 AI 사용 규정부터 확인해야 합니다.
  5. 가짜가 많습니다. 인기만큼 이름이 비슷한 복제 저장소와 실행 파일 배포처가 생겼습니다. README는 공식 출처를 GitHub morluto/rea, rea.tools, npm rea-agents 세 곳으로 못 박았고, REA 이름을 단 암호화폐 토큰도 프로젝트와 무관하다고 밝혔습니다. 그 밖의 경로로 받은 파일은 실행하지 마세요.

Step 1. 연습용 ERP(Odoo 18) 띄우기​

리버스 엔지니어링 연습은 분석해도 문제없는 대상으로 해야 합니다. Odoo 커뮤니티판은 소스가 공개된 오픈소스 ERP라서 연습에 맞습니다. 견적·주문·재고·회계가 한 시스템에 다 있고, 데모 회사와 거래처, 품목까지 들어 있습니다. 나중에 REA가 찾은 결과를 실제 소스와 맞춰 볼 수 있다는 점도 좋습니다.

PostgreSQL이 실행 중인 맥에서 아래 순서대로 입력합니다.

mkdir rea-erp && cd rea-erp
git clone --depth 1 -b 18.0 https://github.com/odoo/odoo.git odoo

# Python 3.10 가상환경
/opt/homebrew/opt/[email protected]/bin/python3.10 -m venv venv

# requirements.txt의 고정 버전은 최신 macOS에서 일부 빌드가 실패해서 패키지 이름만 뽑아 설치합니다
grep -v '^#' odoo/requirements.txt \
| grep -v -i -E "python-ldap|gevent|greenlet|win32" \
| sed -E 's/[=<>~;].*//; s/ *#.*//; s/ *$//' | grep -v '^$' | sort -u > names.txt
./venv/bin/pip install $(cat names.txt)
./venv/bin/pip install "PyPDF2==1.26.0" # 최신 PyPDF2에서는 데모 데이터 설치가 멈춥니다

설정 파일 odoo.conf를 만듭니다. 로컬 연습용이라 외부에서 접속할 수 없게 127.0.0.1에만 엽니다.

[options]
db_host = /tmp
db_port = 5432
addons_path = odoo/addons,odoo/odoo/addons
http_port = 8069
http_interface = 127.0.0.1
data_dir = ./filestore
db_name = rea_erp_demo
dbfilter = ^rea_erp_demo$
list_db = False

판매(sale_management), 재고(stock), 회계(account) 모듈과 데모 데이터를 설치한 뒤 서버를 켭니다.

./venv/bin/python odoo/odoo-bin -c odoo.conf -d rea_erp_demo \
-i sale_management,stock,account --stop-after-init
./venv/bin/python odoo/odoo-bin -c odoo.conf

브라우저에서 http://127.0.0.1:8069에 들어가 admin / admin으로 로그인하면 됩니다. 저는 화면을 한국어로 보려고 한국어 언어팩을 추가했습니다. 아래 스크립트를 한 번 실행하면 관리자 화면이 한국어로 바뀝니다.

import xmlrpc.client as x
u, db = "http://127.0.0.1:8069", "rea_erp_demo"
uid = x.ServerProxy(u + "/xmlrpc/2/common").authenticate(db, "admin", "admin", {})
m = x.ServerProxy(u + "/xmlrpc/2/object", allow_none=True)
lang = m.execute_kw(db, uid, "admin", "res.lang", "search", [[["code", "=", "ko_KR"], ["active", "in", [True, False]]]])
wid = m.execute_kw(db, uid, "admin", "base.language.install", "create", [{"lang_ids": [(6, 0, lang)], "overwrite": True}])
m.execute_kw(db, uid, "admin", "base.language.install", "lang_install", [[wid]])
m.execute_kw(db, uid, "admin", "res.users", "write", [[uid], {"lang": "ko_KR", "tz": "Asia/Seoul"}])

이번에 따라갈 업무는 영업 담당자가 가장 자주 하는 일입니다. 고객 Azure Interior에게 책상 세트(FURN_7800) 1개를 견적 내고, 승인해서 판매 주문으로 바꾸기입니다.

Odoo 견적서 화면. 고객 Azure Interior, 품목 FURN_7800 책상 세트 1개, 총계 450달러가 입력된 상태

Step 2. REA 설치하고 버전 확인하기​

REA는 Node.js 22.23.4 이상(22.x), 24.22.1 이상(24.x), 또는 26.12.1 이상을 요구합니다. 제 맥의 Node 버전이 이보다 낮아서 rea doctor가 node 항목을 실패로 표시했습니다. 시스템 Node를 바꾸지 않고 작업 폴더 안에 필요한 Node를 같이 설치하는 방법이 가장 간단했습니다.

mkdir rea && cd rea && npm init -y
export PATH="$PWD/node_modules/.bin:$PATH"
node -v # v24.22.1
rea --version # 6.8.0

에이전트에 붙여 쓰려면 npx rea-agents setup을 실행하면 됩니다. rea setup --client claude_code --dry-run으로 미리 보면, Claude Code의 경우 MCP 등록 위치는 ~/.claude.json이고 작업 지침(스킬) 위치는 ~/.claude/skills/reverse-engineer-anything로 표시됩니다. 이 글에서는 결과를 하나씩 보여 드리려고 CLI로 진행합니다.

Step 3. 업무 순서를 시나리오 JSON으로 적기​

REA에게 "이 순서대로 브라우저에서 해 줘"라고 알려 주는 파일입니다. 사람이 하는 클릭과 입력을 한 줄씩 적은 것이라고 보면 됩니다. REA를 에이전트에 연결해 두면 이 파일은 에이전트가 대신 만들고 실행합니다. 이 글에서는 무엇이 들어가는지 보여 드리려고 직접 만든 파일을 그대로 싣습니다. scenario-sale.json으로 저장합니다.

{
"browser": {"mode": "launch", "executable_path": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "headless": true},
"start_url": {"url": "http://127.0.0.1:8069/web/login"},
"environment": {"viewport": {"width": 1280, "height": 800}, "locale": "ko-KR", "timezone": "Asia/Seoul"},
"secrets": [{"secret_id": "odoo_pw", "environment_variable": "ODOO_PW"}],
"actions": [
{"step_id": "login.email", "action": "fill", "locator": {"kind": "css", "selector": "#login"}, "value": {"source": "literal", "value": "admin"}},
{"step_id": "login.password", "action": "fill", "locator": {"kind": "css", "selector": "#password"}, "value": {"source": "secret", "secret_id": "odoo_pw"}},
{"step_id": "login.submit", "action": "click", "locator": {"kind": "css", "selector": "button[type=submit]"}},
{"step_id": "login.done", "action": "wait_for", "locator": {"kind": "css", "selector": ".o_main_navbar"}, "state": "visible", "timeout_ms": 20000},
{"step_id": "quote.open", "action": "goto", "destination": {"url": "http://127.0.0.1:8069/odoo/sales/new"}, "wait_until": "load"},
{"step_id": "quote.ready", "action": "wait_for", "locator": {"kind": "css", "selector": "div[name=partner_id] input"}, "state": "visible", "timeout_ms": 20000},
{"step_id": "quote.customer", "action": "fill", "locator": {"kind": "css", "selector": "div[name=partner_id] input"}, "value": {"source": "literal", "value": "Azure Interior"}},
{"step_id": "quote.customer.pick", "action": "click", "locator": {"kind": "role", "role": "option", "name": "Azure Interior"}, "timeout_ms": 15000},
{"step_id": "quote.line.add", "action": "click", "locator": {"kind": "role", "role": "button", "name": "품목 추가하기"}, "timeout_ms": 15000},
{"step_id": "quote.line.product", "action": "fill", "locator": {"kind": "css", "selector": ".o_data_row div[name=product_template_id] input"}, "value": {"source": "literal", "value": "FURN_7800"}},
{"step_id": "quote.line.pick", "action": "click", "locator": {"kind": "role", "role": "option", "name": "[FURN_7800] 책상 세트"}, "timeout_ms": 15000},
{"step_id": "quote.line.settle", "action": "wait_for_timeout", "duration_ms": 1500},
{"step_id": "order.confirm", "action": "click", "locator": {"kind": "css", "selector": "button[name=action_confirm]"}},
{"step_id": "order.delivery", "action": "wait_for", "locator": {"kind": "css", "selector": "button[name=action_view_delivery]"}, "state": "visible", "timeout_ms": 20000}
],
"capture": {
"after_each_step": ["url"],
"at_end": ["screenshot", "url"],
"events": ["network", "page-errors"],
"network": {"request_body": true, "response_body": true, "header_values": false}
}
}

눈여겨볼 부분이 세 가지 있습니다.

  • 비밀번호는 파일에 적지 않습니다. secrets에 환경변수 이름만 적고, 입력할 때는 {"source": "secret"}으로 가리킵니다. 실제 증거 파일을 열어 보니 로그인 요청 본문에 password=[REDACTED:odoo_pw]로 가려져 있었습니다.
  • 자동완성 목록은 이름으로 정확히 고릅니다. Odoo 고객 입력칸에 글자를 치면 목록 맨 위에 "Deco" 생성 같은 새 거래처 만들기 항목이 먼저 뜨는 경우가 있습니다. 첫 번째 항목을 누르게 하면 엉뚱한 거래처가 생깁니다. 저도 연습 중에 한 번 겪었습니다. role: option과 정확한 이름으로 고르면 안전합니다.
  • 마지막 단계는 결과가 화면에 보일 때까지 기다립니다. 승인이 끝나면 상단에 "배송" 버튼이 생기므로, 그 버튼이 보일 때까지 기다리게 했습니다.

Step 4. 실행하고 API 흐름 읽기​

ODOO_PW=admin rea capture-browser-scenario scenario-sale.json --format json > ev-sale.json

약 14초 뒤에 15개 단계가 모두 completed로 끝나고, 약 490KB짜리 증거 파일 ev-sale.json이 생깁니다. 맨 위 사진이 REA가 끝낸 시점의 화면입니다. 주문 번호 S00025가 "판매 주문" 상태로 바뀌고 배송 1건이 생겼습니다.

증거 파일에는 요청 62건이 들어 있습니다. 대부분은 화면용 스크립트·번역·이미지라서, ERP 데이터를 다루는 요청만 골라 보는 짧은 스크립트를 썼습니다. list-calls.mjs로 저장합니다.

// REA 증거 파일에서 ERP API 호출만 골라 단계별로 보여 줍니다.
import { readFileSync } from "node:fs";

const ev = JSON.parse(readFileSync(process.argv[2] ?? "ev-sale.json", "utf8"));
const { steps, events } = ev.normalized_result;
const stepName = Object.fromEntries(steps.map((s) => [s.step_index, s.step_id]));
const items = events.items;
const bodyOf = (tid) =>
items.find((i) => i.kind === "network-content" && i.transaction_id === tid && i.phase === "request");

for (const r of items.filter((i) => i.kind === "request" && /\/web\/dataset\//.test(i.url.url))) {
const b = bodyOf(r.transaction_id)?.body;
const params = b?.content ? JSON.parse(Buffer.from(b.content, "base64").toString()).params : {};
console.log(
stepName[r.step_index].padEnd(20),
`${params.model}.${params.method}`.padEnd(45),
new URL(r.url.url).pathname,
);
}
node list-calls.mjs ev-sale.json

결과를 사람이 하는 일 기준으로 다시 정리하면 이렇습니다.

사람이 한 일화면 뒤에서 불린 API (모델.메서드)의미
새 견적서 열기sale.order.get_views, sale.order.onchange화면 구조와 기본값(만료일, 지급 조건)을 받아 옴
고객 고르기res.partner.name_search → sale.order.onchange이름으로 거래처 검색, 고객이 바뀌면 주소·조건 다시 계산
품목 고르기product.template.name_search → sale.order.line.onchange판매 가능 품목(sale_ok = true)만 검색, 단가·세금 계산
승인 버튼sale.order.web_save → sale.order.action_confirm먼저 견적서를 저장해 번호(S00025)를 받고, 그다음 승인 실행
결과 화면sale.order.web_read상태 sale, 배송 1건, 청구 상태 no를 다시 읽어 옴

화면에서는 버튼을 한 번 눌렀지만, 실제로는 저장 요청과 승인 요청이 따로 나갔습니다. 저장 요청 본문에는 거래처 ID, 품목 ID, 수량, 단가가 모두 들어 있고, 승인 요청에는 주문 ID 하나만 들어 있습니다.

{"params": {"model": "sale.order", "method": "action_confirm", "args": [[25]], "kwargs": {"context": {"lang": "ko_KR", "...": "..."}}}}

그런데 승인 요청의 응답은 이게 전부입니다.

{"jsonrpc": "2.0", "id": 15, "result": false}

출고 전표가 생겼다는 사실은 이 응답만 봐서는 알 수 없습니다. 다음 web_read 응답의 delivery_count: 1에서야 간접적으로 드러납니다. 실제 이유는 서버 코드에 있습니다. Odoo 소스의 addons/sale_stock/models/sale_order.py를 보면, 주문을 확정할 때 self.order_line._action_launch_stock_rule()을 불러 재고 규칙(출고)을 실행합니다. 이 부분은 브라우저 네트워크에 한 줄도 나오지 않습니다.

주문 S00025에서 자동으로 만들어진 출고 전표 WH/OUT/00012. 책상 세트 1개, 원본 문서 S00025

블랙박스 분석의 한계

REA가 보여 주는 건 "화면이 서버에 무엇을 보내고 무엇을 받는지"입니다. 승인하면 재고가 빠지는지, 회계 전표가 생기는지 같은 업무 규칙은 서버 안에 있습니다. 그래서 반드시 결과 데이터(출고 전표, DB)로 따로 확인해야 합니다. ERP를 에이전트에 연결할 때 가장 위험한 착각이 "API 이름을 알았으니 다 안다"입니다.

Step 5. 찾은 이름으로 공식 API를 불러 재현하기​

Step 4에서 찾은 모델·메서드 이름이 맞는지는 같은 결과를 다시 만들어 보면 알 수 있습니다. Odoo는 외부 API 문서를 공개하고 있고, execute_kw로 모델의 메서드를 직접 부를 수 있습니다. 화면 전용인 web_save 대신 공식 문서에 있는 create를 쓰고, 나머지는 REA가 찾은 이름을 그대로 썼습니다. repro_sale.py로 저장합니다.

"""REA가 관찰한 모델·메서드 이름으로 Odoo 외부 API(JSON-RPC)를 호출해 같은 흐름을 재현한다."""
import json, os, urllib.request

URL = os.environ.get("ODOO_URL", "http://127.0.0.1:8069")
DB, USER, PW = "rea_erp_demo", "admin", os.environ["ODOO_PW"]

def rpc(service, method, *args):
body = {"jsonrpc": "2.0", "method": "call", "params": {"service": service, "method": method, "args": args}, "id": 1}
req = urllib.request.Request(URL + "/jsonrpc", json.dumps(body).encode(), {"Content-Type": "application/json"})
res = json.load(urllib.request.urlopen(req))
if "error" in res:
raise RuntimeError(res["error"]["data"]["message"])
return res["result"]

uid = rpc("common", "login", DB, USER, PW)
def call(model, method, *args, **kw):
return rpc("object", "execute_kw", DB, uid, PW, model, method, list(args), kw)

# 1) REA: res.partner/name_search -> 고객 찾기
partner_id = call("res.partner", "name_search", "Azure Interior", limit=1)[0][0]
# 2) REA: product.template/name_search -> 품목 찾기 (sale_ok 조건까지 화면과 동일)
tmpl_id = call("product.template", "name_search", "FURN_7800", args=[["sale_ok", "=", True]], limit=1)[0][0]
product_id = call("product.product", "search", [["product_tmpl_id", "=", tmpl_id]], limit=1)[0]
# 3) REA: sale.order/web_save -> 견적서 저장 (화면 전용 web_save 대신 공식 create)
order_id = call("sale.order", "create", {"partner_id": partner_id,
"order_line": [[0, 0, {"product_id": product_id, "product_uom_qty": 1}]]})
# 4) REA: call_button/sale.order/action_confirm -> 승인
call("sale.order", "action_confirm", [order_id])
# 5) REA: sale.order/web_read -> 결과 확인
print(call("sale.order", "read", [order_id], fields=["name", "state", "amount_total", "delivery_count", "invoice_status"]))
ODOO_PW=admin ./venv/bin/python repro_sale.py
[{'id': 26, 'name': 'S00026', 'state': 'sale', 'amount_total': 450.0, 'delivery_count': 1, 'invoice_status': 'no'}]

DB에서도 확인했습니다. S00025(REA가 화면으로 만든 주문)에는 출고 전표 WH/OUT/00012가, S00026(스크립트로 만든 주문)에는 WH/OUT/00013이 생겼습니다. 둘 다 WH/Stock에서 Partners/Customers로 책상 세트 1개를 옮기는 이동입니다. 청구서는 두 주문 모두 아직 없습니다. Odoo에서는 승인과 청구서 발행이 별도 단계라는 뜻입니다.

여기서 얻을 교훈은 하나입니다. REA로 찾은 이름이 결국 공식 API에 다 있었습니다. 공식 API가 있는 시스템이라면 역분석은 "공식 문서에서 무엇을 찾아봐야 하는지" 알려 주는 지도 역할이면 충분합니다. 실제 연동은 공식 API로 해야 합니다.

Step 6. 에이전트용 도구로 바꿔 보고, 막히는 지점 확인하기​

REA에는 녹화한 요청을 에이전트가 부를 수 있는 도구 묶음으로 바꿔 주는 environment-build 기능이 있습니다. 파일을 만들지 않고 결과만 보는 --dry-run으로 돌려 봤습니다.

rea environment-build ev-sale.json --dry-run --name odoo-sales --format json

결과는 이랬습니다.

  • 녹화된 요청에서 도구 후보 14개를 찾았습니다.
  • 실제로 답할 수 있는 도구는 읽기 3개뿐이었습니다. 쓰기 도구 11개(web_save, action_confirm 포함)는 전부 "보류"였고, 이유는 모두 no-state-target입니다.
  • 증거 파일 안에 이름이 비밀값처럼 보이는 항목 7개(로그인 csrf_token, password 자리, 거래처 아바타 접근 토큰)가 있으니 공유 전에 검토하라는 경고가 나왔습니다.

쓰기가 보류된 이유는 Odoo API의 생김새에 있습니다. Odoo는 /orders/25처럼 주소에 대상을 적는 방식이 아닙니다. 모든 요청을 /web/dataset/call_kw/... 같은 소수의 주소로 보내고, 무엇을 할지는 본문에 적습니다(JSON-RPC). REA는 "이 요청이 어떤 데이터를 바꾸는지"를 주소로 판단하는데, 이 방식에서는 그걸 알 수 없으니 쓰기를 막아 둔 겁니다. 녹화만으로 쓰기 도구를 자동으로 만들 수 있다고 기대하면 안 됩니다. 오히려 쓰기를 기본으로 막아 두는 이 동작이 ERP에는 맞는 기본값입니다.

더존 ERP에 적용하기 전에 확인할 것​

여기까지가 오픈소스 ERP 이야기입니다. 더존 아마란스10이나 iCUBE에 같은 방법을 쓰기 전에 아래 순서를 꼭 거치세요.

1. 공식 연동 경로부터 확인합니다​

  • 아마란스10: 조달청 나라장터에 올라온 Amaranth10 Cloud 가격제안서에는 "외부시스템 연동 (API 및 EXCEL 연동)"이 옵션 항목으로 들어 있습니다. 쓰고 있는 계약에 이 옵션이 있는지, 없다면 추가할 수 있는지 영업 담당자에게 먼저 묻는 게 순서입니다. 연동 업체 중에는 더존 ERP 연동 API 개방에 별도 비용이 든다고 안내하는 곳도 있습니다.
  • iCUBE: 전표를 바깥에서 넣는 일이라면 자동전표처리 EXCEL IMPORT 매뉴얼 같은 엑셀 가져오기 경로가 이미 있습니다. 읽기는 승인된 엑셀 내보내기로 시작해도 충분한 경우가 많습니다.
  • 정리하면 순서는 ① 공식 API·연동모듈 → ② 승인된 엑셀 내보내기·가져오기 → ③ 그래도 안 될 때만 화면 자동화입니다. Odoo도 클라우드 버전은 외부 API를 Custom 요금제에서만 열어 줍니다. ERP의 API는 원래 계약 조건으로 열리는 경우가 많습니다.

2. 역분석이 허용되는 범위를 압니다​

저작권법 제101조의4(프로그램코드역분석)는 아래 조건을 모두 갖춘 경우에만 저작권자 허락 없는 역분석을 허용합니다.

  • 정당한 권한으로 프로그램을 쓰는 사람(또는 그 허락을 받은 사람)이
  • 호환에 필요한 정보를 쉽게 얻을 수 없고 그 획득이 불가피할 때
  • 호환에 필요한 부분에 한정해서

그리고 이렇게 얻은 정보는 호환 목적 밖으로 쓰거나 제3자에게 제공하면 안 됩니다. 표현이 실질적으로 비슷한 프로그램을 만드는 데 쓰는 것도 금지입니다. 공식 연동 경로가 있으면 "쉽게 얻을 수 없는 경우"라고 주장하기 어렵습니다. 더존 내부 API를 분석해서 블로그나 다른 고객에게 공유하는 일은 제3자 제공에 해당할 수 있습니다. 이 글에서 더존 화면이나 내부 주소를 하나도 싣지 않은 이유입니다. 사용 계약서(이용약관)에 별도 금지 조항이 있는지도 함께 확인해야 합니다. 실제 판단이 필요하면 법률 자문을 받으세요.

3. 분석해야 한다면 범위를 좁힙니다​

  • 회사가 정식으로 쓰는 계정으로, 분석 허락을 문서로 받은 뒤에 합니다.
  • 운영 회사코드가 아니라 테스트 회사코드에서 합니다. Step 3처럼 시나리오에 "승인" 클릭이 들어가면 실제 전표가 생깁니다.
  • 읽기 흐름(조회, 검색)부터 봅니다. 쓰기는 공식 경로로, 사람 승인을 거쳐서만 붙입니다.
  • 증거 파일은 사내에만 둡니다. Step 6의 경고처럼 토큰과 거래처 정보가 들어 있습니다.

자주 막히는 지점​

증상원인해결
npm warn EBADENGINE, rea doctor에서 node 실패REA 6.8.0이 요구하는 Node 버전보다 낮음작업 폴더에 npm i [email protected] 후 node_modules/.bin을 PATH 앞에 둠
capture-browser-scenario가 execution_failure로 끝남(Chrome을 직접 띄워 보면 Abort trap: 6)Chrome을 띄울 수 없는 제한된 실행 환경(샌드박스 등)일반 터미널에서 실행. 저는 REA와 Chrome을 다른 맥에서 돌리고 ssh -R 8069:127.0.0.1:8069로 Odoo에 연결했습니다
pip install -r requirements.txt에서 cbor2 빌드 실패(No module named 'pkg_resources')고정된 옛 버전이 최신 빌드 도구와 맞지 않음Step 1처럼 패키지 이름만 뽑아 설치
데모 설치 중 isEncrypted is deprecated and was removed in PyPDF2 3.0.0최신 PyPDF2와 Odoo 18의 호환 문제pip install "PyPDF2==1.26.0" 후 DB를 지우고 다시 설치
한국어 화면에서 영어 품목명(Desk Combination)이 검색되지 않음품목명이 한국어 번역(책상 세트)으로 검색됨내부 참조 코드(FURN_7800)로 검색
고객 칸에서 엉뚱한 새 거래처가 생김자동완성 첫 항목이 "입력값" 생성이었음role: option과 정확한 이름으로 선택
environment-build에서 쓰기 도구가 전부 보류RPC 방식이라 주소만으로 바뀌는 데이터를 알 수 없음정상 동작입니다. 쓰기는 공식 API로 따로 구현

정리​

ERP를 AI에 붙이는 일의 첫 단계는 "어떤 버튼이 어떤 API를 부르는지"의 지도를 갖는 것입니다. REA는 그 지도를 증거와 함께 빠르게 그려 줍니다. 다만 업무 규칙은 서버 안에 있어서 결과 데이터로 따로 확인해야 했습니다. 그리고 지도를 그린 뒤에는 결국 공식 API로 돌아가야 했습니다. 더존처럼 소스가 닫힌 ERP라면 순서를 뒤집어서, 공식 연동 경로부터 확인하고 역분석은 법이 허용하는 좁은 범위에서만 쓰는 게 맞습니다.

전체 연결 보기