사이트픽 블로그
← 목록으로

개발일지 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 / remainingSessions join 필드 추가
  • .../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

  • 첫 번째 댓글을 남겨보세요.