개발일지 2026-07-30
피아노 학원 플랫폼 개발일지 (2026-07-30)
개요
하루 종일 "결제 경로"와 "강사 상세" 두 축을 정리했다.
첫째, 매출 화면에서 결제가 어디서 일어났는지(수강생 앱 / 학원 사이트 / 수기 입력)를 구분할 수 있게 ticket_orders.source 에 HQ(학원) 값을 도입했다. 그동안 학원 사이트에서 원장이 카드 결제하거나 입금 대기를 등록해도 앱 결제(APP)와 섞여 경로 구분이 안 됐는데, 이제 매출 내역에서 학원 / 앱 / 수기로 딱 나뉜다. 내부 코드에서 이 값을 '본사(HQ)'라고 부르던 잔재도 전부 '학원'으로 정리했다.
둘째, 강사 상세 팝업의 담당 수강생 목록에 와이어프레임에 있던 이번 달 레슨 / 수강권 잔여 두 컬럼을 추가했다. 강사가 자기 학생별로 이번 달 몇 회 했고 수강권이 얼마나 남았는지를 한눈에 볼 수 있게 됐다.
그 밖에 원장-강사 겸임 계정 backfill, 상담에 담당 강사 지정, 메뉴 비고 컬럼, 입금대기관리 메뉴 등 자잘한 개선을 함께 얹었다.
수정/생성 파일 목록
백엔드 (piano-backend)
.../student/ticket/TicketPaymentService.java— 카드결제 준비/입금대기 등록에source파라미터 도입(학원 사이트=HQ, 앱=APP), 결제완료 알림톡에 학원명 변수 추가, 수기결제도 알림톡 발송.../academy/teacher/AcademyTeacherService.java— 강사 상세 담당 수강생 조회 확장.../resources/mapper/AcademyTeacherMapper.xml—findStudentsByTeacherId에이번 달 레슨(lessons COUNT),수강권 잔여(student_tickets ACTIVE SUM) 서브쿼리 추가.../academy/student/Student.java—monthLessons/remainingSessionsjoin 필드 추가.../config/OwnerTeacherBackfill.java— 원장(OWNER)을 강사로 겸임 등록하는 backfill.../consultation/*— 상담에 담당 강사(teacher_id) 컬럼/조회 추가.../admin/menu/*— 메뉴 비고(memo) 컬럼- 마이그레이션:
teacher-user-unique,transfer-orders-menu,payment-method-cash,manual-pay-reason,menu-memo,consultation-teacher
프론트엔드 (academy)
src/pages/Sales.tsx— 결제 경로학원/앱/수기표시, 환불 행 라벨 정리src/pages/TeacherDetail.tsx— 담당 수강생 테이블에이번 달 레슨/수강권 잔여컬럼 추가src/api/academyTeachers.ts—TeacherStudentItem에 두 필드 추가
파일별 상세
1. 결제 경로에 '학원(HQ)' 도입
배경: 앱 결제와 학원 결제가 섞여 있었다
ticket_orders.source 는 원래 APP(수강생 앱 결제)와 MANUAL(수기 등록) 두 값만 있었다. 그런데 학원 사이트(어드민)에서 원장이 직접 카드 결제를 하거나 현금/계좌이체 입금 대기를 등록하는 경우가 APP 로 잡혀, 매출 화면에서 "이게 학생이 앱으로 낸 건지, 학원 창구에서 받은 건지" 구분이 안 됐다.
해결: source=HQ 추가
카드 결제 준비(prepareCardPayment)와 입금 대기 등록(createPendingOrder)에 source 를 명시적으로 넘겨, 학원 사이트발 결제는 HQ 로 기록하도록 했다. 프론트에서는 이 코드를 한글 라벨로 변환한다.
// 결제경로: HQ=학원(학원 사이트 결제/입금대기), MANUAL=수기, APP=앱(수강생 앱 결제)
type Route = '앱' | '수기' | '학원';
const routeOf = (source?: string): Route =>
source === 'MANUAL' ? '수기' : source === 'HQ' ? '학원' : '앱';
이제 매출 내역과 엑셀 다운로드 모두 원시 코드(HQ/APP/MANUAL)가 아니라 학원 / 앱 / 수기 한글 라벨로 나온다.
덤: '본사' → '학원' 용어 정리
코드 주석과 일부 로직에서 이 값을 '본사(HQ)'라고 부르던 잔재가 있었다. 실제로는 프랜차이즈 본사가 아니라 개별 학원 창구를 뜻하므로, 사용자에게 보이는 문구와 내부 주석을 모두 '학원'으로 통일했다.
2. 강사 상세 — 담당 수강생에 '이번 달 레슨 / 수강권 잔여'
배경
강사 상세 팝업의 담당 수강생 탭은 이름/연락처/레벨/상태만 보여줬는데, 와이어프레임에는 이번 달 레슨 과 수강권 잔여 컬럼이 더 있었다. 강사가 자기 학생 관리를 하려면 "이번 달 몇 번 왔고, 수강권이 얼마 남았나"가 핵심 정보다.
해결: 조회 쿼리에 서브쿼리 2개 추가
findStudentsByTeacherId 에 학생별 집계 서브쿼리를 붙였다.
-- 이번 달 완료 레슨 수
(SELECT COUNT(*) FROM lessons l
WHERE l.student_id = s.id AND l.completed = TRUE
AND l.lesson_date >= DATE_FORMAT(CURDATE(), '%Y-%m-01')
AND l.lesson_date < DATE_FORMAT(CURDATE(), '%Y-%m-01') + INTERVAL 1 MONTH
) AS month_lessons,
-- 보유 활성 수강권 잔여 횟수 합계
(SELECT COALESCE(SUM(t.remaining_sessions), 0) FROM student_tickets t
WHERE t.student_id = s.id AND t.status = 'ACTIVE'
) AS remaining_sessions
Student.java 에 monthLessons / remainingSessions join 필드를 추가하니, 서비스에서 별도 가공 없이 그대로 JSON 으로 직렬화돼 프론트로 내려간다. 프론트는 테이블에 두 컬럼({n}회)을 추가했다.
이
수강권 잔여합산이 환불/취소된 티켓을 제대로 빼는지는 다음 날(7-31) 별도로 검증했다.status='ACTIVE'필터라 환불(CANCELLED)·소진(DEPLETED)은 자동 제외되지만, 만료일이 지난 티켓은 status 가 아직 ACTIVE 로 남아있을 수 있다는 걸 발견해 만료 정리 배치를 추가하게 된다.
3. 기타 개선
- 원장-강사 겸임 backfill: 학원 원장(OWNER) 계정을 강사 목록에도 잡히게 겸임 등록.
teachers유니크 제약도 추가. - 상담 담당 강사: 상담 건에
teacher_id를 지정/조회할 수 있게 컬럼과 API 확장. - 메뉴 비고: 메뉴에
memo컬럼 추가. - 입금대기관리: 현금/계좌이체 판매를 즉시 발급하지 않고 PENDING 으로 적재 후, 입금 확인 시 발급 + 알림톡 발송하는 흐름.
배포
- 백엔드: ECR 도커 이미지 재빌드(
cb20e92) → EC2 배포. DB 마이그레이션 6건은 배포 전 dev RDS 에 먼저 반영. - 프론트(academy): 빌드 → S3 업로드 → CloudFront
/academy/*무효화.
회고
"결제 경로"처럼 사용자에겐 단순해 보이는 라벨 하나가, 실제로는 결제 진입점마다 source 를 일관되게 심어야 성립한다는 걸 다시 느꼈다. 카드/입금/수기/앱 각 경로에 값을 빠짐없이 박고, 화면에선 코드가 아니라 라벨로만 보이게 하는 것 — 데이터는 코드로, 표시는 사람 말로. 강사 상세의 집계 컬럼도 "조회 한 방에 서브쿼리로 붙이기"가 깔끔했지만, 그게 다음 날 만료 처리 배치라는 숙제를 남겼다.
댓글 0
- 첫 번째 댓글을 남겨보세요.