← Home

Cin7 Core (Dear Systems) API 연동 검토

Key Insight

Turbo Air는 구글시트 수기 입력 외에 Dear Systems(현재 공식명 Cin7 Core) 라는 클라우드 재고·주문관리(IMS/ERP)로 영업·구매·재무 데이터를 관리하고 서류를 발행한다. 오랫동안 이 시스템의 접근 권한이 없어 TAB의 모든 데이터 분석은 수기로 재입력한 구글시트에 전적으로 의존해왔다. 2026-09-23 자격증명을 확보해 읽기 전용 연결이 실제로 열렸다 — 이제 ERP 원본(SaleList 12,696건)과 구글시트를 인보이스 번호로 조인해 대조할 수 있다. 실측 결과 두 시스템은 InvoiceAmount = (Sales+Freight+Extra−TA$) × 1.10(GST 포함) 관계로 대부분 정확히 일치하며, 일치하지 않는 건들은 시트 입력 누락·부분출고 등으로 유형화돼 건별 확인이 가능해졌다.

배경

  • 로그인 주소 https://inventory.dearsystems.com/로 식별 — Cin7 공식 도움말이 이 도메인을 Cin7 Core 웹앱 주소로 명시하므로, 현재 사용 중인 제품은 Cin7 Core(구 DEAR 계열) 로 판정된다.
  • David는 그동안 Dear Systems DB에 접근권한이 없어 모든 TAB 데이터 분석(Sales Data 등)이 구글시트 수기 입력본에 의존해왔다 — 상세: 영업판매실적 조회 절차, Sales Data 테이블 명세 및 KPI 정의.
  • 2026-09-11, 매니저가 TAB Project 공유드라이브의 Cin7 폴더에 API 조사 자료를 올렸고, David가 2026-09-14에 이를 검토·위키에 반영했다.

이미 확인된 것 (매니저 측 사전 조사, 2026-09-11)

2026-09-11-cin7-core-api-readonly-assessment · 2026-09-11-cin7-core-api-readonly-stage2 요약:

  • API 기초: V1 기본 URL https://inventory.dearsystems.com/ExternalApi/, 인증은 HTTP 헤더 api-auth-accountid + api-auth-applicationkey. 공식 문서는 V2 사용을 권장. 호출 제한 60 calls/min(초과 시 HTTP 503).
  • API Application 발급 완료: Turbo Air Core API - Read Only Pilot이라는 이름으로 생성, active 상태 확인. 자격증명은 매니저 macOS 기기의 로그인 키체인에만 저장.
  • 최소 연결 시험 성공: GET /ExternalApi/Me 1회 호출, HTTP 200 확인(회사명 등 실제 값은 기록하지 않음).
  • 10개 엔드포인트 구조·건수 확인 완료 — 실제 값은 저장하지 않고 구조와 집계 건수만 확인:
영역엔드포인트집계 건수
상품Products737
재고 가용 행ProductAvailability1,540
창고 위치Locations6
고객Customers618
공급자Suppliers271
구매 목록PurchaseList1,516
판매 목록SaleList12,604
회계과목ChartOfAccounts102
결제조건PaymentTerms13
세금규칙TaxationRules7

왜 이 숫자가 중요한가

SaleList 12,604건은 현재 TAB 대시보드가 구글시트(Sales Data)로 관리하는 규모보다 훨씬 큰 원장 데이터가 Cin7 Core 안에 그대로 존재함을 시사한다. 다만 이 문서 자체가 “UI 화면 보고서와 대조하지 않았다”고 명시하므로 정합성이 검증된 숫자는 아니다 — 실제 연결 후 재확인 필요.

  • 업무 영역별 API 커버리지 판정(가능/제한적/UI전용 3단계)도 정리되어 있다 — 판매·구매·결제는 “가능하지만 고위험”(상태 전이가 복잡해 신중한 게이팅 필요), 사용자·역할 관리나 구독 플랜 변경은 “UI 전용”으로 API 자동화 대상에서 제외됨.
  • 다음 단계로 제안된 작업(미승인): TBP·TBH·TBR 총 20개 베이커리 디스플레이 케이스 모델의 최근 2~3년 판매 분석 — 모델별 월간 견적/주문 건수·판매수량·매출 비식별 집계. 조회 기간, 모델 매칭 기준(SKU vs 상품 ID), 매출 정의(주문액 vs invoice amount, 세전/세후) 등이 아직 확정되지 않아 실행 보류 중.

✅ 연결 완료 (2026-09-23)

실제 API 연결 성공 — 이 환경에서 라이브 조회 가능

2026-09-23 David가 Account ID·Application Key를 전달받아, 이 위키/대시보드 파이프라인(Windows)에서 Cin7 Core API 직접 조회가 가능해졌다. GET /ExternalApi/v2/me HTTP 200, 회사명 Turbo Air Pty Ltd(AUD, Australia/Sydney) 확인. 자격증명은 quartz-site/cin7-credentials.json에 보관하며 .gitignore에 등재(turboair-brain-*.json과 동일 취급, 매시간 도는 git add -A 자동커밋에 절대 걸리지 않도록 파일 생성 전에 등재했다).

구축된 것

파일역할
quartz-site/cin7-credentials.jsonAccount ID + Application Key (gitignored, 로컬 전용)
quartz-site/scripts/_cin7-core-auth.mjs읽기 전용 API 클라이언트 — GET 하드코딩 + 엔드포인트 allowlist + 60 calls/min 스로틀 + 503 백오프 + 페이지네이션(fetchAll)
quartz-site/scripts/cin7-reconcile-sales.mjsCin7 ↔ 구글시트 Sales Data 대조 감사 스크립트 (--since YYYY-MM-DD, --list-unmatched)

읽기 전용은 설계로 강제돼 있다

_cin7-core-auth.mjs의 request()는 메서드를 GET으로 하드코딩하고 ENDPOINT_ALLOWLIST 밖의 경로를 거부한다. 매니저 조사 문서가 판매·구매·결제 쓰기를 “가능하지만 고위험(상태 전이 복잡)“으로 분류했기 때문에, 쓰기를 하려면 이 가드를 의도적으로 제거해야만 가능하도록 만들었다 — 실수로 쓰기가 나갈 수 없다.

건수 재검증 (2026-09-23 실측 vs 매니저 2026-09-11 보고)

엔드포인트2026-09-232026-09-11 보고비고
product737737일치
ref/location66일치
customer620618+2 (12일간 신규)
supplier271271일치
purchaseList1,5161,516일치
saleList12,69612,604+92 (12일간 신규, 일 7.7건 수준)
ref/account102102일치
ref/paymentTerm1313일치

전 엔드포인트 HTTP 200. 증가분은 모두 실데이터의 자연 증가로 설명된다 — 매니저 보고 수치가 독립적으로 재현됐다.

🔑 두 시스템을 잇는 키 (2026-09-23 확정, 이 연동의 핵심 성과)

TAB의 모든 수치는 그동안 수기 입력된 구글시트에만 의존했다. 이제 ERP 원본과 대조할 수 있고, 조인 키와 금액 관계가 실측으로 확정됐다:

조인 키

Cin7구글시트 Sales Data예시
OrderNumberOrder NoTA-SO#E360844S (양쪽 동일 체계)
InvoiceNumberInv NoCin7 TA-#E064223 → 시트 E064223 (접두사 TA-# 제거)

금액 관계 — Cin7 InvoiceAmount는 GST 포함·주문 단위, 시트는 GST 제외·라인 단위(운임/Extra/TA$는 별도 컬럼):

(A) InvoiceAmount = (Sales + Freight + Extra) × 1.10
(B) InvoiceAmount = (Sales + Freight + Extra − TA$) × 1.10     ← Cin7에 "Loyalty Rebate" 라인이 있는 건

2026-06-01 이후 생성분 기준, Inv No로 매칭된 423건 중 354건(84.1%)이 위 두 식으로 정확히 일치(오차 $0.75 이내)했다. 단건 대조에서 비율이 소수점까지 정확히 1.1000으로 떨어지는 것을 확인했다 — 두 시스템의 기록은 원칙적으로 일치한다.

Open Question — 남은 차이 (경영 확인 필요)

나머지 약 16%는 오류로 단정할 수 없고 다음 유형이 섞여 있다. 감사 스크립트가 인보이스 번호 단위로 목록을 뽑아주므로 담당자가 건별 확인 가능하다:

  1. 시트 금액 미입력 — 시트에 행은 있으나 Sales가 비어 있고 Cin7에는 실제 금액이 있는 건(예: E063989, Cin7 $11,815.65 / 시트 금액 공란). 시트 입력 누락으로 보인다.
  2. 부분 출고·분할 인보이스 — Cin7 인보이스가 주문의 일부만 담아 시트의 해당 Inv No 라인 합계보다 작은 건.
  3. 단가 불일치 — 예: E064254는 시트 4,230 vs Cin7 실제 청구 2,749.50(+Loyalty Rebate −$126.90). 시트에 RRP가 들어갔을 가능성이 있으나 미확인.
  4. 운임 추가청구 — 차이가 132·176·$352처럼 1.1로 나누면 반올림 수가 되는 건들(운임 성격 추정).

이 중 (1)·(3)은 시트 기반 TAB 대시보드 수치에 직접 영향을 줄 수 있으므로 우선 확인 대상이다.

범위 차이 (오류 아님)

Cin7에는 시트가 의도적으로 다루지 않는 소액 카운터 판매가 있다 — Walk in Customer 등의 부품·액세서리 건(49.50, 77, $159.50 수준). TAB Sales Data는 완제품 딜러 매출 원장이고 부품은 TA Service의 Parts Sales가 담당하므로(TA-Service Log 테이블 명세 및 데이터 카탈로그), 이 건들이 시트에 없는 것은 정상이다.

📤 Cin7 → 구글시트 자동 미러링 (2026-09-23 구축)

Cin7 데이터를 파이프라인 밖에서도(=시트로 일하는 사람도) 쓸 수 있도록, TA Drive의 “Turbo Air” 시트와 같은 위치에 미러 스프레드시트를 만들어 매시간 갱신한다.

항목값
파일명Cin7 Core Data (자동 동기화 · 직접편집 금지)
파일 ID1m30Aw4YJ-PtABJSLrrKO4u-v6Gt7xLg9GOpwpE2efjU
위치TA Drive 루트(0AAj6lhg1kEYWUk9PVA) — “Turbo Air” 시트와 동일
갱신rebuild-site.ps1 스텝 2.5155c, 매시간(09~18시) 전체 새로고침
스크립트scripts/push-cin7-to-sheet.mjs (+ 쓰기 전용 헬퍼 scripts/_google-sheets-write.mjs)

탭 구성 (각 탭은 헤더 고정 + 필터 + 줄무늬의 테이블 서식):

탭Cin7 엔드포인트행 수(2026-09-23)
판매목록 (SaleList)saleList12,696
구매목록 (PurchaseList)purchaseList1,516
상품 (Products)product737
고객 (Customers)customer620
공급자 (Suppliers)supplier271
창고위치 (Locations)ref/location6
회계과목 (ChartOfAccounts)ref/account102
결제조건 (PaymentTerms)ref/paymentTerm13
_동기화정보—마지막 동기화 시각·행수 요약

이 시트는 기계 소유다 — 직접 입력 금지

매 실행마다 각 탭을 clear 후 전량 재기록한다(Cin7은 주문 상태가 제자리에서 변하므로 append 방식은 낡은 중복만 남긴다). 사람이 손으로 넣은 값·수식·추가 열은 다음 동기화 때 사라진다. 가공이 필요하면 이 파일을 참조하는 별도 시트를 만들어 IMPORTRANGE로 끌어다 쓸 것.

권한 구조 (중요)

서비스 계정 tab-wiki-sheets-reader@turboair-brain.iam.gserviceaccount.com은 TA Drive 공유드라이브에서 Viewer일 뿐이라 파일을 만들 수 없다(실측: Drive API 403 insufficientParentPermissions). 그래서:

  1. 파일은 David 계정으로 1회 생성(Google Drive 커넥터)
  2. 그 파일 하나만 서비스 계정에 writer로 공유

공유드라이브 전체 쓰기 권한을 주지 않고 대상 파일에만 권한을 여는 구조라 사고 반경이 최소다. 파일을 새로 만들거나 옮기면 이 공유를 다시 설정해야 한다.

💰 회계 데이터 — 어디까지 가능한가 (2026-09-23 실측)

회계 시스템은 Xero다 (계정체계로부터의 추론)

ref/account 102개 계정이 Xero 기본 계정체계다 — 계정 타입이 Xero 고유 코드(CURRLIAB·CURRENT·DIRECTCOSTS·TERMLIAB·OTHERINCOME·FIXED)이고, 번호도 Xero AU/NZ 기본값(200 Sales, 400 Advertising, 404 Bank Fees, 497 Bank Revaluation, 610 Accounts Receivable, 800 Accounts Payable)이다. 은행계좌는 601 Freedom Business, 602 Petty Cash 2개. 단 API가 연동 회계시스템 이름을 직접 노출하지는 않으므로 이는 추론이며, Cin7 웹 Integrations 화면에서 확정 가능하다.

가능한 것 — 보조원장(sub-ledger) 수준

주문 상세(sale?ID=)에 실제 회계 정보가 들어 있다. 실측 예:

항목실제 값
매출 라인SKU KHR18-3-N, 단가 5620, 할인 58%, GST 236.04, 매출계정 200(Sales), TaxRule GST on Income
원가라인 AverageCost 1459.92 / 주문 COGSAmount 1462.32
결제금액 2651.44, 결제일 2026-09-04, 입금계정 601(Freedom Business)

→ 매출 계정별 금액·GST·제품별 원가와 마진·입금 은행계좌까지 확보 가능.

불가능한 것 — 총계정원장·재무제표

대상상태
journal엔드포인트는 존재하나 Total=0(비어 있음). 주문 상세의 ManualJournals·Transactions도 빈 배열
generalLedger · financialSettings · bankTransaction · payment · saleInvoice · salePayment · purchaseInvoice · ref/taxRule · ref/currency엔드포인트 자체가 없음
손익계산서·재무상태표·시산표·은행대사·급여·BAS·고정자산대장전부 없음
Cin7에서 시작되지 않은 거래(임차료·급여·은행수수료 등)Cin7에 존재하지 않음

조사 시 반드시 알아야 할 함정 — Cin7은 없는 엔드포인트에 HTTP 200을 준다

존재하지 않는 경로에도 HTTP 200 + HTML “Page not found” 페이지를 반환한다. 상태코드만으로 판정하면 “전부 가능”이라는 오판이 나온다. 반드시 응답이 JSON인지로 판정할 것 — 2026-09-23 1차 조사에서 실제로 이 오판이 났고, JSON 여부로 재검증해 바로잡았다.

구조 요약: Cin7 = 보조원장(매출·매입·재고 원천 전표) / Xero = 총계정원장·재무제표. 완전한 회계자료가 필요하면 Xero API 별도 연동(OAuth 2.0, Xero 앱 등록 필요)이 유일한 경로다.

구축한 것 — 라인·결제 상세 증분 수집 (2026-09-23)

scripts/pull-cin7-financials.mjs → 같은 스프레드시트에 탭 2개 추가:

탭내용
매출라인 (InvoiceLines)주문·인보이스·고객·SKU·수량·단가·할인·GST·합계·매출계정코드/명·평균원가·COGS·매출총이익·이익률(%)
결제내역 (Payments)주문·인보이스·고객·결제금액·결제일·입금계정코드/명·참조

왜 증분인가 — 전체 새로고침이 불가능하다

라인·결제 정보는 주문 1건당 1회 호출해야 하는 상세 엔드포인트에만 있다. 12,697건 전체면 60 calls/min 제한에서 약 3.5시간이다. 그래서 이 스크립트는 (1) Cin7의 Updated 타임스탬프를 키로 한 로컬 캐시(cin7-financials-cache.json, gitignored)를 두고 변경된 주문만 다시 받고, (2) 1회 실행당 상세 호출을 300건으로 자체 제한한다. 최초 백필은 여러 번의 시간당 실행에 걸쳐 자연히 완료된다(--backfill-all로 수동 일괄 실행도 가능). 기본 수집 창은 --since 기본값 2026-01-01이다. 더 긴 이력이 필요하면 --since 2025-01-01 --backfill-all로 한 번 돌리면 캐시에 누적된다.

자동 갱신: rebuild-site.ps1 스텝 2.5155d, 매시간(2.5155c 헤더 미러링 직후).

다음 단계 (미착수)

  1. 위 Open Question (1)·(3) 건별 확인 — 감사 스크립트 출력으로 담당자 검토.
  2. 조인 컬럼 노출: pull-sales-data.mjs의 COLUMN_MAP에 Order No를 추가하면 대시보드 스냅샷에서도 주문번호 단위 조인이 가능해진다(현재 20컬럼 투영에는 Inv No만 있음).
  3. Bakery Display Case(TBP·TBH·TBR 20모델) 분석 등 실데이터 조회는 조회 기간·모델 매칭 기준·매출 정의(주문액 vs invoice, 세전/세후)를 David가 먼저 확정해야 진행 가능 — 이제 데이터 접근 자체는 막혀 있지 않다.

Bias Check (2026-09-23 갱신)

Counter-argument: 매니저 조사 수치는 이제 독립 재현됐다(§건수 재검증 — 8개 엔드포인트 중 6개 완전 일치, 2개는 12일치 자연 증가분만큼 차이). 다만 “두 시스템이 일치한다”는 결론의 근거는 Inv No로 매칭된 423건에 한정되며, 매칭 자체가 안 된 196건(주로 Walk in Customer 소액 건)과 2026-06-01 이전 기간은 검증되지 않았다. 84.1%라는 일치율도 $0.75 허용오차라는 임의 기준에 의존한 값이다. Data gap: (1) 남은 ~16% 불일치의 건별 원인은 확인되지 않았다 — 4가지 유형으로 분류만 했고 그중 “단가 불일치”(E064254 등)는 어느 쪽이 맞는지 미확인이다. (2) Cin7 구독 플랜·API add-on 만료 여부, 사용자 권한 범위는 여전히 미확인. (3) ProductAvailability(재고)·TaxationRules는 이번에 응답 형태가 달라 건수 재검증에서 빠졌다. (4) UI 화면 보고서와의 대조는 여전히 수행되지 않았다.