edm유학센터 · 세부 파일럿
자사몰에서 받은 주문을 접수부터 정산까지 관리하는 시스템의 제품 요구사항 — 백오피스 우선
대상 edm유학센터 유학본부 · 개발팀 · 필리핀 지사 / 개정 2판
02/29
SECTION 01 · CONTEXT
앞선 문서들이 무엇을 할지를 정했습니다. 이 문서는 그중 주문 인프라만 골라 어떻게 만들지를 정합니다. 이번 개정에서 프런트가 자사몰로 바뀌었고 데이터 저장 방식이 확정됐습니다.
| 문서 | 정한 것 | 이 PRD 와의 관계 |
|---|---|---|
| 운영 안내서 | 7대 서비스와 현장 운영 기준 | 품목·마감 시각·안전 재고 등 업무 규칙의 출처 |
| 추가 제안서 | 현지인 풀타임 고용 확정안 | 시스템을 쓸 현지 사용자 2명의 근거 |
| 기능 정의서 | 8개 모듈 57개 기능 | 상위 문서. 이 PRD 는 M1·M2·M7 을 구현 수준으로 펼침 |
| 액션 플랜 | 9/1 착수 5주, 10/6 오픈 | 맞물려야 하는 바깥 일정 |
| 가격 가안 | 품목·판매가·마진 초안 | 기준 정보에 넣을 품목 마스터의 원본 |
| 이 PRD | 자사몰 + 백오피스 | 화면·데이터·상태·권한·릴리스를 개발 착수 가능한 수준으로 |
1판과 달라진 곳 — ① 주문 유입이 자사몰(쇼핑몰) 프런트로 확정, ② 회원은 비회원 우선, ③ 데이터는 edm Open Database 대행 API, ④ 백오피스를 먼저 만든다.
03/29
SECTION 01 · GIVENS
설계를 가르는 갈림길입니다. 이 여섯이 바뀌면 아래 요구사항의 상당 부분이 함께 바뀝니다.
| # | 항목 | 결정 | 설계에 미치는 영향 |
|---|---|---|---|
| 1 | 프런트 | 자사몰(쇼핑몰) 형태 | 상품·장바구니·주문 흐름이 필요. 디자인은 edmuhak.com 스타일 가이드를 따름 |
| 2 | 회원 | 비회원 우선 | 고객 계정 체계를 만들지 않음. 멤버십은 추후 연동이라 식별 키를 갈아끼울 수 있게 설계 |
| 3 | 결제 | 계좌 이체 · 한화(KRW) | 입금 확인이 주문 확정의 기준. 계좌번호는 임시값, 가상계좌 발급 후 교체 |
| 4 | 데이터 저장 | edm Open Database (MySQL 9 · 서버 대행 API) |
DB 컨테이너를 직접 띄우지 않음. HTTPS 로 SQL 을 대행시키므로 API 키를 가진 서버 측 코드가 반드시 필요 |
| 5 | 배포 위치 | 이 저장소에 함께 | 문서·자사몰은 정적, 백오피스와 주문 API 는 앱 서비스로 분리 |
| 6 | 작업 순서 | 백오피스 먼저 | 자사몰 UI 는 나왔지만 주문을 받아서 처리할 곳이 없음. 받는 쪽을 먼저 만든다 |
04/29
SECTION 01 · ORDER OF WORK
자사몰 화면은 이미 있습니다. 없는 것은 들어온 주문을 처리하는 쪽입니다.
이유 01
신청 폼만 있으면 지금도 구글폼으로 됩니다. 손해가 나는 곳은 입금 대조·발주 수량·검수 누락이고 전부 백오피스입니다.
이유 02
주문을 저장할 테이블과 상태 규칙이 없으면 프런트가 보낼 데이터의 모양을 정할 수 없습니다. 백오피스가 스키마의 주인입니다.
이유 03
10/6 오픈 시점에는 주문서 화면 + 백오피스로 운영이 됩니다. 자사몰은 고객 접점을 넓히는 것이지 오픈의 조건이 아닙니다.
| 단계 | 이때 고객은 | 이때 담당자는 |
|---|---|---|
| 지금 ~ R1 | 카카오톡·전화로 신청 | 백오피스에 직접 입력해 접수 — 주문서 화면으로도 가능 |
| R2 | 자사몰에서 직접 주문 | 들어온 주문을 백오피스에서 확인·처리 |
| R4 | 멤버십으로 로그인해 내역 조회 | 동일 |
05/29
SECTION 02 · PROBLEM
접수는 지금도 됩니다. 깨지는 곳은 접수 다음이고, 그래서 백오피스가 먼저입니다.
| 깨지는 지점 | 지금 무슨 일이 생기는가 | 남는 비용 |
|---|---|---|
| 입금과 주문이 따로 | 주문은 시트에, 입금은 통장에 있어 사람이 눈으로 대조 | 미입금 발송, 입금 후 누락 발송. 둘 다 클레임 |
| 수요 취합이 수기 | 요일별 주문을 학원 단위로 손으로 합산 | 수량 오류가 그대로 손해. 재발주로 배송 하루 지연 |
| 재고가 장부에만 | 판매·대여로 나간 수량이 실시간 반영되지 않음 | 품절 상태로 주문 접수. 수요일 실사까지 모름 |
| 인도 증빙이 흩어짐 | 검수 사진이 담당자 휴대폰에 남음 | 분실·파손 분쟁에서 근거 부재 |
| 이력이 안 남음 | 시트를 덮어써 변경 내역이 사라짐 | 정산 불일치의 원인 추적 불가 |
공통점은 하나입니다 — 기록이 한 곳에 모이지 않는다. R1 의 목표는 기능 수가 아니라 주문 한 건의 전 생애가 한 줄로 남는 것입니다.
06/29
SECTION 02 · SUCCESS
기능 완성이 아니라 측정되는 결과로 씁니다. 파일럿 3개월 시점에 판정합니다.
ORDER TRACE
100%
접수된 주문이 DB 에 남는 비율 — 시트 이중 입력 없이
PAYMENT MATCH
1영업일
입금 확인부터 배송 확정까지 걸리는 시간
STOCKOUT
0건
재고 없는 품목을 접수해 취소한 건수
HANDLING
5분
주문 한 건 처리에 담당자가 쓰는 시간
| 판정 항목 | 측정 방법 | 통과 기준 |
|---|---|---|
| 주문 누락 | DB 건수 vs 통장 입금 건수 | 월 단위 차이 0건 |
| 정산 일치 | 일별 기록 합계 vs 월 집계 | 차이 발생 시 원인 추적 가능 |
| 담당자 전환 | 현지 담당자가 시트를 다시 쓰는지 관찰 | 시트 없이 하루를 마감할 수 있음 |
07/29
SECTION 03 · USERS
자사몰이 생기면서 고객이 시스템 안으로 들어옵니다. 다만 로그인은 하지 않습니다.
| 사용자 | 인원 | 인증 | 하는 일 | 주 화면 |
|---|---|---|---|---|
| 현지 담당자 | 2 | 사내 게이트 | 입고 검수·사진 첨부·인도 체크·재고 실사. 휴대폰으로 현장에서 | 오늘 할 일, 검수 |
| 본사 운영 | 1~2 | 사내 게이트 | 입금 확인, 발주 승인, 주문 대리 입력, 학생 안내 | 입금 대기, 주문 목록 |
| 본사 관리자 | 1 | 사내 게이트 | 품목·단가·권한 설정, 정산 열람 | 기준 정보, 리포트 |
| 고객 (비회원) | - | 없음 | 자사몰에서 상품을 담고 이름·어학원·연락처로 주문 | 자사몰 스토어·장바구니·주문 |
| 고객 (회원) | - | 멤버십 | R4 예정 — 로그인 후 주문 내역 조회 | 주문 조회 |
비회원이라 고객을 무엇으로 식별할지가 문제가 됩니다. R1~R3 은 주문번호 + 연락처 뒤 4자리를 조회 키로 쓰고, 멤버십이 붙는 R4 에서 회원 고유번호로 갈아끼울 수 있게 주문 테이블에 빈 칼럼을 미리 둡니다.
08/29
SECTION 03 · FLOW
아홉 단계입니다. 초록은 R1 백오피스가 만드는 구간, 파란 글씨는 R2 자사몰 연동에서 붙습니다.
| # | 단계 | 주체 | 하는 일 | 다음으로 가는 조건 |
|---|---|---|---|---|
| 1 | 상품 담기 | 고객 | 자사몰에서 품목·수량 선택 R2 | 장바구니 확정 |
| 2 | 주문 제출 | 고객 | 이름·어학원·연락처·배송 시작일 입력 R2 | 제출 |
| 3 | 접수 | 시스템 | 주문 생성, 마감 시각·단위·재고 검증, 주문번호 발급 | 검증 통과 |
| 4 | 입금 확인 | 본사 | 입금자명·금액 대조 후 확인 처리 | 입금 확인 = 주문 확정 |
| 5 | 수요 취합 | 시스템 | 마감 시각 기준으로 학원·품목별 합산 | 마감 도달 |
| 6 | 발주 | 현지 | 공급업체별 발주·결제 기록, 단가 이력 보관 | 발주 완료 입력 |
| 7 | 입고 검수 | 현지 | 박스 수 검수, 사진 첨부, 보관소 입고 | 검수 완료 |
| 8 | 배송·인도 | 현지 | 학원별 배송, 인도 체크. 과일은 3시간 초과 표시 | 인도 완료 |
| 9 | 정산 기록 | 시스템 | 일별 매출·원가·기여마진 기록 | - |
R1 에서는 1·2 번을 담당자가 대신 합니다 — 고객이 카카오톡으로 보낸 내용을 백오피스에 입력합니다. R2 에서 자사몰이 붙으면 3번부터가 자동으로 시작됩니다.
09/29
SECTION 04 · ARCHITECTURE
데이터 저장이 원격 관리형 DB로 확정되면서 구조가 단순해졌습니다. 다만 API 키를 가진 서버가 반드시 필요해졌습니다.
FRONT
정적 페이지. 상품·장바구니·주문 폼. DB 를 직접 부르지 않는다 — 키가 노출되므로
API
얇은 서버. 자사몰의 주문을 받아 검증하고 DB 에 넣는다. 공개 경로 필요
BACK
사내 게이트 뒤. 접수·입금·취합·발주·검수·인도·리포트. R1 의 전부
DATA
MySQL 9. HTTPS 로 SQL 을 대행. 컨테이너·볼륨·포트 개방이 필요 없다
| 결정 사항 | 선택 | 이유 |
|---|---|---|
| DB | edm Open Database 대행 API | 1판의 "DB 컨테이너 분리 + _data 볼륨" 계획을 대체.
재배포 때 데이터가 사라질 위험이 원천적으로 없어짐 |
| API 키 위치 | 서버 환경변수만 | EDM_OPENDATA_API_KEY 는 브라우저로 절대 내려가지 않음.
그래서 자사몰과 DB 사이에 서버가 한 겹 필요 |
| 배포 단위 | 정적(문서·자사몰) + 앱(API·백오피스) | 문서 한 줄 고칠 때 앱이 재시작되면 안 됨 |
| 파일(검수 사진) | 앱 볼륨 저장 + 경로만 DB | 사진을 DB 에 넣으면 1000행 조회 상한과 용량에 걸림 |
| 실시간성 | 폴링 | 게이트가 WebSocket 과 90초 초과 연결 미지원 |
10/29
SECTION 04 · DATA LAYER
일반 MySQL 드라이버와 다릅니다. 제약을 알고 짜야 나중에 고칠 일이 없습니다.
| 제약 | 내용 | 설계에서 대응 |
|---|---|---|
| SQL 한 문장 | 세미콜론으로 여러 문 금지 | 트랜잭션을 쓸 수 없음 → 단일 문장으로 원자성 확보
(INSERT ... ON DUPLICATE KEY, 조건부 UPDATE ... WHERE status=?) |
| 조회 1000행 상한 | 초과 시 truncated:true |
목록은 반드시 LIMIT. 전량 순회는 커서(마지막 id 기준)로 이어 읽기 |
| 호출 한도 | 동시 10 · 분당 300 · 일 5000 (키마다 다름 — 헤더로 확인) |
클라이언트가 동시 5개로 묶고, 429 면 Retry-After 만큼 멈춤.
화면 진입마다 전체 재조회 금지 — 캐시 + 증분 |
| 키 노출 금지 | 헤더 X-API-KEY 만, 쿼리스트링 금지 |
.env 에서 읽고 서버에서만 호출. 코드·로그에 남기지 않음 |
| slug 소유권 | 등록된 내 앱 slug 만 (403) | slug = philippines-service. 최초 provision 1회(멱등) |
| 트랜잭션 없음 | 여러 표를 함께 바꿀 수 없음 | 상태 전이를 한 문장으로 만들고, 두 표를 바꿔야 하면 먼저 기록 → 나중에 집계 순서로 짜서 중간에 끊겨도 복구 가능하게 |
클라이언트 모듈은 server/edm-opendata.mjs 한 파일입니다 —
provisionDatabase / runSql / listDatabases / dropDatabase
에 동시 5개 제한, 429 대기, 5xx 지수 백오프, 읽기 캐시가 들어 있습니다.
11/29
REQUIREMENT · R1
여기가 비면 나머지가 전부 막힙니다. 가격 가안 문서가 이 화면의 입력값입니다.
| ID | 요구사항 | 상세 · 판정 기준 | 릴리스 |
|---|---|---|---|
| M-01 | 품목 마스터 | 품목명·구분(구매/대여)·단위·판매가·보증금·안전 재고·현재 수량·노출 여부. 자사몰 상품 목록도 이 표에서 나온다 | R1 |
| M-02 | 학원 마스터 | 학원명·주소·보관 장소·담당자 연락처·배송 요일 | R1 |
| M-03 | 학생 마스터 | 이름·학원·체류 기간·귀국 예정일·보호자 연락처·카카오톡 ID | R1 |
| M-04 | 공급업체 | 업체명·품목·연락처·주/예비 구분 | R1 |
| M-05 | 마감 규칙 | 품목별 마감 요일·시각과 배송 요일. 접수 검증의 기준값 (생수 월·목 09시 / 과일 화·금 09시) | R1 |
| M-06 | 단가 이력 | 품목·업체·날짜별 매입 단가. 판매가 재산정의 근거 | R1 |
| M-07 | 사용자 역할 | 게이트 사용자 고유번호에 역할을 지정 | R1 |
12/29
REQUIREMENT · R1
접수에서 막지 못한 잘못은 발주 수량 오류로 끝납니다. 검증을 접수에 몰아 넣습니다.
| ID | 요구사항 | 상세 · 판정 기준 | 릴리스 |
|---|---|---|---|
| O-01 | 주문 생성 (대리 입력) | 담당자가 고객 대신 입력. 학생·품목·수량·배송 시작일 → 주문번호 발급 | R1 |
| O-02 | 마감 시각 자동 적용 | 마감 이후 접수는 다음 회차로 자동 배정하고 화면에 알림 | R1 |
| O-03 | 주문 단위 검증 | 생수는 박스 단위만, 과일은 규격 패키지만. 단위 외 수량 저장 거부 | R1 |
| O-04 | 재고 확인 | 재고형 품목은 가용 재고 초과 접수를 차단하고 대체 안내 표시 | R1 |
| O-05 | 수정·취소 | 마감 전 자유 수정. 마감 후에는 취소만 되고 이력이 남음 | R1 |
| O-06 | 변경 이력 | 누가 언제 무엇을 바꿨는지 전 필드 기록 | R1 |
| O-07 | 자사몰 주문 수신 | 공개 API 로 들어온 주문을 같은 검증을 거쳐 저장. 출처(web/manual)를 구분해 기록 | R2 |
| O-08 | 중복·스팸 차단 | 같은 연락처의 짧은 시간 반복 제출 제한, 봇 방지 | R2 |
13/29
REQUIREMENT · R1
계좌 이체이므로 입금과 주문을 잇는 일이 사람 손에 남습니다. 그 손을 줄이는 것이 목표입니다.
| ID | 요구사항 | 상세 · 판정 기준 | 릴리스 |
|---|---|---|---|
| P-01 | 입금 대기 목록 | 미입금 주문을 배송 시작일 임박 순으로 한 화면에. 본사의 하루 시작점 | R1 |
| P-02 | 입금 확인 처리 | 입금자명·금액·입금일 입력 → 주문이 확정으로 넘어가 취합 대상에 들어감 | R1 |
| P-03 | 금액 불일치 표시 | 합계와 입금액이 다르면 차액과 방향을 표시하고 확정을 막음 | R1 |
| P-04 | 입금자명 불일치 | 신청자명과 다른 경우가 흔함. 별칭을 학생에 매핑해 다음부터 자동 인식 | R3 |
| P-05 | 미입금 자동 보류 | 마감까지 미입금이면 자동 보류. 기간·처리 방식은 본사 확정 필요 | R3 |
| P-06 | 보증금 분리 | 대여 보증금은 매출이 아니라 예수금으로 따로 집계하고 환급 상태 관리 | R3 |
| P-07 | 계좌 명세 대조 | 은행 거래내역 CSV 업로드로 미확인 입금 자동 후보 매칭 | R4 |
R1 은 수동 확인으로 시작합니다. 월 50건 규모에서는 수동이 더 빠르고, 매칭 규칙은 실제 입금 데이터를 본 뒤에 정해야 정확합니다.
14/29
REQUIREMENT · R1
손으로 하는 합산을 시스템이 대신합니다. 확정된 주문만 집계 대상입니다.
| ID | 요구사항 | 상세 · 판정 기준 | 릴리스 |
|---|---|---|---|
| A-01 | 회차별 취합표 | 마감 후 학원 × 품목 격자로 합산표 생성. 발주서의 원본 | R1 |
| A-02 | 발주 기록 | 공급업체별 발주 수량·단가·결제 금액·결제 수단 기록 | R1 |
| A-03 | 단가 이력 반영 | 발주 시 매입 단가를 남겨 판매가 재산정 근거로 사용 | R1 |
| A-04 | 취합 후 변경 잠금 | 취합표 생성 시 해당 회차는 수량 수정 불가. 취소만 가능하고 손실로 기록되며 취합표에서 사라지지 않음 | R1 |
| A-05 | 예비 업체 전환 | 주 업체 발주 실패 시 예비 업체 전환 기록 | R3 |
| A-06 | 발주서 내보내기 | 업체에 보낼 내역을 텍스트·이미지로 생성 (현지 업체는 채팅으로 받음) | R3 |
15/29
REQUIREMENT · R1
이 구간은 학원 현장에서 휴대폰으로 처리됩니다. 화면 수와 터치 수가 요구사항의 일부입니다.
| ID | 요구사항 | 상세 · 판정 기준 | 릴리스 |
|---|---|---|---|
| D-01 | 입고 검수 | 도착 박스 수 입력 + 사진 첨부. 발주 수량과 다르면 차이·사유 필수 | R1 |
| D-02 | 사진 처리 | 업로드 전 압축, 실패 시 재시도. 통신이 끊겨도 입력값 유지 | R1 |
| D-03 | 오늘 할 일 화면 | 오늘 검수·배송·인도 대상을 한 화면에. 현지 담당자의 기본 화면 | R1 |
| D-04 | 인도 처리 | 학생별 인도 완료 체크. 과일은 수령 후 3시간 초과 시 자동 경고 | R1 |
| D-05 | 지연 기록 | 예정일 대비 지연 일수·사유 기록 → 배송 지연률의 원천 데이터 | R1 |
| D-06 | 보관소 관리 | 학원별 보관 장소·담당자를 기준 정보로 두고 인도 대기 현황 표시 | R3 |
| D-07 | 인도 안내 생성 | 인도 준비 완료 시 카카오톡 문안 자동 생성 | R3 |
16/29
REQUIREMENT · R1
집계는 뒤로 미루되 원천 기록은 R1 부터 남깁니다. 기록이 없으면 어떤 집계도 만들 수 없습니다.
| ID | 요구사항 | 상세 · 판정 기준 | 릴리스 |
|---|---|---|---|
| L-01 | 일별 주문 기록 | 건당 매출·원가·기여마진을 일별로 계산해 남김 (가격 가안 기준 건당 20,000원 · 마진 8,000원) | R1 |
| L-02 | 시트 내보내기 | 구글시트 형식으로 내보내 기존 보고 방식을 그대로 유지 | R1 |
| L-03 | 기간·학원 필터 | 기간·학원·품목·상태로 걸러 보기. 목록은 반드시 LIMIT(1000행 상한) | R1 |
| L-04 | 월별 손익 집계 | 월 고정비 대비 기여마진과 월 순부담 | R3 |
| L-05 | KPI 자동 판정 | 5대 KPI 와 확장 트리거 판정 | R4 |
L-02 를 R1 에 넣는 이유 — 내보내기가 없으면 담당자가 시스템과 시트에 두 번 입력하게 되고, 그러면 시스템을 쓰지 않게 됩니다. 시트를 당장 없애지 않고 병행합니다.
17/29
SECTION 05 · STATE
상태는 일곱 개입니다. 화면·알림·집계가 이 값으로 움직이므로 임의로 늘리지 않습니다.
| 상태 | DB 값 | 의미 | 이 상태로 만드는 행동 | 할 수 있는 일 |
|---|---|---|---|---|
| 접수 | received | 주문은 들어왔고 입금은 아직 | 자사몰 제출 / 담당자 입력 | 수정·취소 자유 |
| 확정 | paid | 입금 확인되어 취합 대상 | 본사 입금 확인 | 취합 전까지 수정 가능 |
| 취합 | batched | 회차 합산에 포함 | 마감 시각 도달 | 수량 수정 불가 · 취소만 |
| 발주 | ordered | 업체에 주문됨 | 현지 발주 완료 입력 | 취소 시 손실 기록 |
| 입고 | received_stock | 검수 통과해 보관 중 | 검수 완료 (사진 필수) | 인도 처리 |
| 인도 | delivered | 학생에게 전달 완료 | 인도 체크 | 정산 대상 · 이후 변경 불가 |
| 취소 | cancelled | 진행 중단 | 단계별 취소 | 사유 필수 · 손실 여부 기록 |
되돌리기 규칙 — 인도 이후에는 상태를 되돌리지 않습니다.
잘못 처리한 경우는 되돌림이 아니라 보정 기록으로 남깁니다.
트랜잭션이 없으므로 상태 전이는 UPDATE ... WHERE id=? AND status=?
한 문장으로 만들어 중복 실행을 막습니다.
18/29
SECTION 05 · SCHEMA
MySQL 9 테이블 기준입니다. 초록이 R1 에서 만드는 표입니다.
| 테이블 | 주요 칼럼 | 관계 · 인덱스 | 릴리스 |
|---|---|---|---|
item | id · name · kind(buy/rent) · unit · price · deposit · safety_stock · stock · visible | 자사몰 상품 목록의 원본 | R1 |
school | id · name · address · storage · contact · delivery_days | student.school_id | R1 |
student | id · name · school_id · stay_from · stay_to · return_date · guardian · kakao | idx(school_id), idx(return_date) | R1 |
orders | id · order_no · student_id · member_no(NULL) · source(web/manual) · status · total · deposit_total · start_date · created_by | uniq(order_no), idx(status,start_date) | R1 |
order_item | id · order_id · item_id · qty · unit_price · amount · deposit | idx(order_id) · orders 1:N | R1 |
payment | id · order_id · payer_name · amount · paid_at · confirmed_by · diff | idx(order_id) · 분할 입금 대비 1:N | R1 |
batch | id · cycle_date · item_id · school_id · qty · locked_at | uniq(cycle_date,item_id,school_id) | R1 |
purchase | id · batch_cycle · supplier_id · item_id · qty · cost_unit · cost_total · paid_by | idx(batch_cycle) | R1 |
inspection | id · purchase_id · qty_arrived · diff · reason · photo_path · by · at | purchase 1:1 | R1 |
audit_log | id · table_name · row_id · field · before · after · user_no · at | idx(table_name,row_id) | R1 |
stock_move | id · item_id · kind(in/out/adjust) · qty · reason · at | idx(item_id,at) | R3 |
rental | id · student_id · item_id · out_at · due_at · in_at · deposit · refund_state · damage_cut | idx(student_id), idx(due_at) | R3 |
orders.member_no 를 지금 NULL 로 넣어 두는 것이 핵심입니다.
비회원으로 시작하지만 R4 에서 멤버십이 붙을 때 테이블을 바꾸지 않고 회원 주문을 이어붙일 수 있습니다.
source 칼럼도 같은 이유입니다 — 자사몰 주문과 대리 입력을 나중에 구분해 분석할 수 있습니다.
19/29
SECTION 06 · BACK OFFICE
R1 은 화면 여덟 개로 끝냅니다. 현지용은 휴대폰, 본사용은 데스크톱 기준입니다.
| # | 화면 | 사용자 | 기기 | 담는 것 |
|---|---|---|---|---|
| 1 | 오늘 할 일 | 현지 | 모바일 | 오늘 검수·배송·인도 대상. 진입 후 첫 화면 |
| 2 | 입금 대기 | 본사 | 데스크톱 | 미입금 주문, 입금 확인 입력, 금액 불일치 표시 |
| 3 | 주문 목록 · 상세 | 본사 | 데스크톱 | 상태·기간·학원 필터, 주문 1건의 전 이력과 변경 로그 |
| 4 | 주문 등록 | 양쪽 | 모바일 | 대리 입력. 마감·단위·재고 검증 즉시 표시 |
| 5 | 회차 취합 · 발주 | 현지 | 데스크톱 | 학원 × 품목 합산표, 발주 기록 입력 |
| 6 | 검수 | 현지 | 모바일 | 수량 입력, 사진 첨부, 차이 사유. 3 터치 이내 |
| 7 | 기준 정보 | 관리자 | 데스크톱 | 품목·학원·학생·공급업체·마감 규칙·권한 |
| 8 | 일별 기록 · 내보내기 | 본사 | 데스크톱 | 일별 주문·매출·원가, 구글시트 형식 내보내기 |
화면마다 진입 시 전체 재조회를 하지 않습니다. 목록은 LIMIT 을 걸고 캐시를 두며, 갱신은 만료 시점이나 사용자가 새로 고칠 때만 합니다 — API 호출 한도 때문입니다.
20/29
SECTION 06 · STOREFRONT
화면은 이미 만들어져 있습니다(index.html).
남은 것은 주문을 서버로 보내는 일이고 그것이 R2 입니다.
| 구성 | 상태 | 지금 되는 것 | R2 에서 붙는 것 |
|---|---|---|---|
| 상품 목록 | 완료 | 카테고리별 상품 카드, 가격·보증금 표시 | item 표에서 실제 재고·가격을 불러오기 |
| 장바구니 | 완료 | 담기·수량 조절·삭제, 합계와 보증금 분리 계산, 브라우저에 유지 | 재고 초과 시 담기 차단 |
| 비회원 주문 폼 | 완료 | 이름·어학원·연락처·배송일·입금자명 입력, 필수값 검증 | 서버로 제출 → orders 저장, 주문번호 회신 |
| 주문 내역 생성 | 완료 | 주문서 텍스트 생성 · 복사 · 인쇄. 지금은 카카오톡으로 보내면 접수 | 제출 즉시 접수되므로 복사 안내가 확인 화면으로 바뀜 |
| 이용 방법 · FAQ | 완료 | 3단계 안내, 6개 FAQ | 결제·환불 규정 확정 후 문구 교체 |
| 주문 조회 | R2 | - | 주문번호 + 연락처 뒤 4자리로 상태 조회 |
| 멤버십 로그인 | R4 | - | 회원 식별 후 member_no 연결, 내역 목록 |
디자인은 edmuhak.com 스타일 가이드를 따랐습니다 —
Pretendard 계열 폰트, 0.9 배율 타입 스케일(14.4 · 16.2 · 18 · 28.8 · 36),
라운드 3.6 · 7.2 · 10.8 · 21.6, 파랑 #006BC8 과 그린 #1EC95B,
회색 섹션 #F5F5F7. 상단에 비회원 주문 안내 바를 상시 노출합니다.
21/29
SECTION 06 · LINEUP
경쟁사가 여섯 갈래로 팔고 있습니다. 우리 문서의 7대 서비스를 같은 축에 놓고 보면 비어 있는 두 칸이 보입니다 — 도착 시점과 귀국 시점입니다.
| # | 라인 | 우리 상태 | 담는 상품 | 메인 배치와 판단 |
|---|---|---|---|---|
| 1 | 출국 준비 팩 | 신규 | 유심 · 생수 1박스 · 화장지 · 세면용품 | 맨 위. 도착 첫날을 잡는 상품이고 출국 전 선결제라 입금 확인 시간이 넉넉합니다 |
| 2 | 정기 배송 | 판매 | 생수 1박스 · 과일 규격 패키지 | 둘째 칸. 매주 반복되는 핵심이라 재방문의 이유가 됩니다 |
| 3 | 생활용품 | 판매 | 샤워헤드·필터 구매 / 멀티탭 대여 | 셋째 칸. 구매와 대여를 한 라인에서 구분 표시 |
| 4 | 구독 | 일부 | 과일 4주 정기 (판매 중) · 반찬 4주 (2단계) | 넷째 칸. 반찬은 운영 안내서에서 2단계 검토 품목이라 '준비 중'으로 노출 |
| 5 | 택배 · 보관 | 판매 | 수령 대행 · 발송 대행 · 물품 보관 | 다섯째 칸. 경쟁사에 없는 우리 고유 라인입니다 |
| 6 | 귀국 선물 팩 | 신규 | 건망고·간식 세트 · 대용량 박스 | 여섯째 칸. 연수 마지막 주를 잡는 상품. 마트가 그대로여서 마진은 낮지만 재구매 없이 끝나는 고객에게 마지막 접점 |
| 7 | 액티비티 · 예약대행 | 숨김 | 투어 · 골프 · 마사지 | 지금은 숨김. 제휴처 정산가와 면책 문구가 정해지면 한 줄로 켭니다 |
라인을 늘리는 것이 목적이 아닙니다. 고객이 우리를 만나는 시점이 출국 전 · 연수 중 · 귀국 전 셋인데 지금은 가운데만 있습니다. 1번과 6번은 새 공급망이 필요해 R3 이후지만, 메인에 자리를 먼저 잡아 두고 '준비 중'으로 노출해 수요를 봅니다.
경쟁사 상품명·구성·문구는 쓰지 않습니다. 참고한 것은 어떤 시점에 어떤 상품군이 팔리는지이고, 상품 구성과 가격은 우리 손익 기준으로 다시 짭니다.
22/29
SECTION 06 · ROLES
백오피스는 사내 게이트 뒤, 자사몰은 게이트 밖입니다. 이 둘을 한 앱에서 가르는 것이 요점입니다.
| 역할 | 판별 방법 | 할 수 있는 일 | 할 수 없는 일 |
|---|---|---|---|
| 관리자 | 게이트 헤더 X-Edm-User-Role = super-admin |
품목·단가·안전 재고·권한 설정, 전체 열람 | - |
| 본사 운영 | 관리자가 앱에서 지정 (X-Edm-User-Id 기준) |
입금 확인, 발주 승인, 주문 취소, 내보내기 | 단가·권한 변경 |
| 현지 담당자 | 관리자가 앱에서 지정 | 접수·취합·발주 기록·검수·인도·실사 입력 | 입금 확인, 가격 변경 |
| 고객 (비회원) | 인증 없음 — 공개 경로 | 상품 조회, 주문 제출, 주문번호+연락처로 조회 | 남의 주문 조회, 가격·재고 변경, 목록 열람 |
| 그 외 사내 사용자 | 사내 계정으로 로그인한 나머지 | 본인 관련 내역 조회만 | 모든 입력 |
주의 네 가지 — ① 관리자 판별은 Role 값으로만 하고 회원 등급(level)으로 하지 않습니다. ② 헤더 값은 URL 인코딩되어 오므로 디코딩 후 씁니다. ③ 사용자별 데이터는 사용자 고유번호를 키로 저장합니다 — 아이디와 이름은 바뀝니다. ④ 공개 경로는 쓰기만 열고 읽기는 열지 않습니다 — 주문 제출은 되지만 목록 조회는 안 됩니다.
23/29
REQUIREMENT · R3
품목이 적어 실사로 버틸 수 있습니다. 다만 가용 재고 한 숫자는 R1 부터 필요합니다 — 없는 물건을 자사몰에 팔면 안 되기 때문입니다.
| ID | 요구사항 | 상세 · 판정 기준 | 릴리스 |
|---|---|---|---|
| S-01 | 가용 재고 계산 | 현재 수량 − 확정된 미인도 수량. 접수 차단(O-04)과 자사몰 품절 표시의 기준값 | R1 |
| S-02 | 입출고 원장 | 구매 입고·판매 출고·대여 출고·반납 입고·폐기를 한 줄씩 남겨 현재 수량을 도출 | R3 |
| S-03 | 안전 재고 감지 | 샤워헤드 5 · 샤워필터 20 · 멀티탭 5 이하 도달 시 발주 대상 자동 표시 | R3 |
| S-04 | 주간 실사 | 수요일 실사 화면에서 실물 수량 입력, 장부 대비 차이와 조정 사유 기록 | R3 |
| R-01 | 대여대장 | 누가·언제·무엇을 대여했고 반납 예정일이 언제인지 실시간 관리 | R3 |
| R-02 | 보증금 현황 | 수령·보관·환불 상태 관리, 미반납 목록 별도 표시 | R3 |
| R-03 | 반납 검수 | 정상 확인 후 환불, 파손 시 차감 금액·사유·사진 기록 | R3 |
| R-04 | 귀국일 연동 알림 | 귀국 예정일 기준 미반납 사전 경고. 귀국 후에는 회수가 사실상 불가 | R3 |
R-04 가 이 절의 핵심입니다. student.return_date 가 있으니
대여 회수는 자동으로 예측 가능한 유일한 리스크입니다. 놓치면 보증금 분쟁과 재고 손실이 동시에 발생합니다.
24/29
SECTION 07 · AUTOMATION
게이트가 실시간 푸시를 지원하지 않으므로 화면 폴링과 예약 작업으로 만듭니다.
| 알림 · 자동화 | 시점 | 받는 사람 | 방식 · 비고 |
|---|---|---|---|
| 마감 30분 전 미입금 | 마감 −30분 | 본사 | 화면 배지 + 목록 상단 고정. 가장 손해를 막는 알림 |
| 회차 취합 생성 | 마감 시각 | 현지 | 예약 작업이 취합표를 만들고 오늘 할 일에 표시 |
| 검수 미완료 | 배송일 12시 | 현지·본사 | 배송 예정인데 검수 기록이 없는 건 |
| 과일 3시간 초과 | 수령 +3시간 | 현지 | 인도 미완료 건에 경고 표시 |
| 자사몰 신규 주문 | 제출 즉시 | 본사 | 입금 대기 목록에 배지로 표시 R2 |
| 안전 재고 미달 | 매일 09시 | 현지 | 기준치 이하 품목을 발주 대상으로 R3 |
| 귀국 전 미반납 | 귀국 −7일 | 현지·본사 | 대여 물품 회수 경고 R3 |
| 주간 보고 초안 | 금요일 17시 | 본사 | 한 주 주문·매출·이슈 요약 R4 |
카카오톡 발송은 자동화하지 않습니다. 문안만 시스템이 만들고 사람이 보냅니다 — 채널 연동 비용이 크고, 초기에는 문구를 상황에 맞게 손보는 편이 낫습니다.
25/29
SECTION 07 · CONSTRAINTS
플랫폼과 DB API 의 제약이 설계를 결정한 부분입니다. 어기면 데이터가 새거나 화면이 멈춥니다.
01 · 데이터
Open Database 가 앱 밖에 있어 재배포와 무관합니다. 1판의 컨테이너 분리 요건은 해소됐습니다.
02 · 비밀
EDM_OPENDATA_API_KEY 는 .env 에서 읽고
브라우저로 내려보내지 않습니다. 코드·로그에 남기지 않습니다.
03 · 호출 한도
순간 burst 가 사고를 만듭니다. 병렬도를 올리지 않고
429 는 Retry-After 만큼 멈춥니다.
04 · 현장 사용성
검수·인도는 학원 현장에서 휴대폰으로. 사진은 업로드 전 압축, 저장 실패 시 재시도.
05 · 통신
WebSocket 과 90초 초과 연결 미지원. 긴 집계는 백그라운드로 돌립니다.
06 · 개인정보
보호자 연락처·카카오톡 ID 의 보관 기한과 삭제 기준을 본사와 정해야 합니다. 미결 사항.
26/29
SECTION 08 · SCHEDULE
액션 플랜의 5주와 나란히 갑니다. 10/6 오픈에 시스템을 맞추지 않습니다 — 오픈은 주문서와 시트로 열고 백오피스를 그 뒤에 세웁니다.
| 구분 | 기간 | 개발 | 이때 실제 운영은 |
|---|---|---|---|
| W1~W2 | 9/7 ~ 9/18 | DB provision · 스키마 생성 · 기준 정보(M-01~M-07) · 주문 접수(O-01~O-06) | 주문서 화면으로 접수, 시트에 옮김 |
| W3 | 9/21 ~ 9/23 | 입금 확인(P-01~P-03) · 가용 재고(S-01) | 추석 연휴 — 실작업 3일 |
| W4 | 9/28 ~ 10/2 | 취합·발주(A-01~A-04) · 검수·인도(D-01~D-05) | 시범 운영과 병행해 실제 데이터로 검증 |
| W5 | 10/5 ~ 10/9 | 일별 기록·내보내기(L-01~L-03) · 안정화 | 10/6 오픈 — 주문서·시트로 운영 |
| R1 | 10월 중순 | 백오피스 전환 — 현지 담당자가 시스템으로 하루를 마감 | 시트 병행 유지 |
| R2 | 11월 | 자사몰 연동 — 주문 API, 재고·가격 연동, 주문 조회 | 고객이 직접 주문 |
| R3 | 11월 말 ~ 12월 | 재고 원장·실사·대여·보증금·월 집계 | 시스템 단독 |
| R4 | 12월 이후 | 멤버십 연동 · 계좌 명세 대조 · KPI 자동 판정 | - |
이 일정의 전제 — 물품 내역과 확정 가격이 W1 안에 들어와야 합니다. 품목·단가가 없으면 기준 정보를 채울 수 없고, 그러면 주문 접수도 붙일 데이터가 없습니다. 가안은 준비돼 있으니 확정만 필요합니다.
27/29
SECTION 08 · SCOPE
R1 은 백오피스에서 주문 한 건이 끝나는 것까지입니다. 자사몰 연동은 그다음입니다.
R1 · 10월 중순
R2 · 11월
R3 · 11월 말~12월
R4 · 12월 이후
R1 에서 빼는 것과 이유 — 예약대행·응급·정기방문은 기능 정의서의 확장 단계에 그대로 둡니다. 빈도가 낮아 시스템 없이도 관리됩니다. 자사몰에서도 예약대행은 숨겨 두었습니다(필요해지면 한 줄로 다시 켭니다).
28/29
SECTION 09 · OPEN
개발 진행에 필요한 답입니다. 주황색은 W1~W2 안에 정해져야 막히지 않습니다.
| # | 항목 | 정해야 하는 내용 | 기한 |
|---|---|---|---|
| 1 | 물품 내역과 가격 확정 | 가안은 준비됨. 대여 정책·단품 대응·수질 구분·후보 품목 채택 등 7건의 결정 필요 | W1 |
| 2 | API 키 등록 | .env 의 EDM_OPENDATA_API_KEY 가 비어 있음.
키가 없으면 DB provision 부터 막힘 |
즉시 |
| 3 | 앱 slug 등록 확인 | DB provision 은 apps.edmedu.com 에 등록된 내 앱 slug만 허용.
philippines-service 가 배포·소유 확정 상태인지 확인 필요 |
즉시 |
| 4 | 자사몰 공개 경로 | 고객은 사내 계정이 없음. 게이트 밖에서 접근 가능한 경로와 주문 제출 API 의 공개 방식 확정 필요 | W2 |
| 5 | 가상계좌 발급 | 지금은 임시 계좌번호. 발급 후 자사몰·주문서 두 곳 교체 | R2 |
| 6 | 미입금 처리 기준 | 며칠 뒤 자동 보류·취소할지, 부분 입금은 어떻게 볼지 | W2 |
| 7 | 취소·환불 규정 | 발주 후 취소 시 고객 부담 범위. 자사몰 FAQ 문구와 함께 확정 | W2 |
| 8 | 개인정보 보관 기한 | 보호자 연락처·카카오톡 ID 의 보관 기간과 삭제 기준 | W2 |
| 9 | 멤버십 연동 방식 | 회원 고유번호를 어디서 받는지, 비회원 주문을 회원에 사후 연결할지 | R4 |
29/29
자사몰 화면은 이미 있습니다. 없는 것은 들어온 주문을 처리하는 곳이고, 손해가 나는 곳도 전부 거기입니다. R1 은 주문 한 건의 전 생애가 한 줄로 남는 것이 전부입니다. 데이터는 Open Database 로 확정됐고 클라이언트도 준비됐습니다. 지금 막혀 있는 것은 개발이 아니라 API 키, 앱 slug 확인, 그리고 확정 가격입니다.