휴무일 일괄 세팅 (다중 공급사 · 통합 캘린더)
공급사별 주간 발주 마감 · 주간 배송 · 월간 커스텀 일정은 어드민 공급사 팝업의 배송 정보 화면에 이미 있다. MD-21은 신규 화면 없이 기존 공급사 관리 목록 /partnerList 툴바에 버튼 2개를 얹는다 — [휴무일 일괄 설정](체크한 공급사 N개에 특정 날짜·주간 마감·주간 배송을 한 번에) · [휴무 통합 캘린더](전 공급사 read-only). 개별 팝업의 월간 커스텀 패널에는 일괄과 동일한 폼을 임베드한다. 기존 3테이블 컬럼 변경 없음 · 신규 테이블 0.
/partnerList 툴바 [휴무일 일괄 설정] · [휴무 통합 캘린더] · 개별: 목록 공급사 ID 클릭 → 팝업(새창) /partnerInfo/delivery?partnerId&partnerName › 🚚 배송 정보 · 노출 권한 = 팝업과 동일 PARTNER_POPUP한눈 결론
/partnerList 툴바에 버튼 2 — [휴무일 일괄 설정](행 체크 N ≥ 1이면 활성 → 모달 탭 3) · [휴무 통합 캘린더](항상 활성 → 전폭 모달). 노출 권한은 기존 공급사 팝업과 동일 PARTNER_POPUP — 팝업이 안 보이는 계정엔 버튼 2개도 숨긴다.seller_custom_schedule, 기존 값 있는 (공급사×날짜)는 건너뜀. 주간 발주 마감 — 요일 7칸(변경 없음/휴무/시각), 값 넣은 요일만 덮어쓰기 → seller_weekly_deadline. 주간 배송 — 요일 7칸(변경 없음/가능/불가) → seller_weekly_delivery.scheduleForm({mode:'bulk'|'single'}). 유일한 차이: 개별은 기존 값 프리필 후 upsert(덮어쓰기), 일괄은 건너뛰기.get-all-schedule 1개. 선택 인덱스 1.seller_order_close* 레거시 여부.기존 화면 — 스테이지 실측
어드민 공급사 관리 목록 /partnerList › 공급사 ID 클릭 → 팝업(새창) /partnerInfo/delivery?partnerId&partnerName › 🚚 배송 정보 — 공급사 1곳씩 편집하는 화면. 이 절은 실측 기록이라 v2 이후 내용을 유지한다
▲ 스테이지 어드민 실제 화면(2026-09 캡처). 편집 주체는 어드민(created_by에 어드민 계정). 패널 1·2는 MD-21에서 바꾸지 않고, 패널 3의 입력 방식만 같은 폼으로 교체한다(§03 ⑤).
"HH:MM" 또는 NONE(=그 요일 발주 휴무). 캡처에선 일요일 칸이 비어 있고(placeholder) 월~금이 16:00 — 일요일 발주휴무 패턴. 빈 칸이 NONE 저장값의 표시인지는 확인 필요. 토요일 칸이 화면 폭 밖으로 잘리는 건 기존 화면 결함(MD-21 범위 밖, 언급만).| 항목 | 실측 | MD-21에서 |
|---|---|---|
| 목록 화면 | /partnerList — 검색 프리셋·필터(공급사 ID/명·사용 여부·등록일)·행 체크박스(헤더 전체선택 포함)·툴바 배송 예정일 계산기 · 공급사 코드 등록 · 공급사 등록 · 공급사 삭제 · 엑셀. 휴무 관련 액션 없음. 어드민 전체에서 '휴무' 기능은 팝업 배송 정보(월간 커스텀)만 존재. | 툴바에 버튼 2 추가(§03 ①) |
| 개별 팝업 | 목록의 공급사 ID 클릭 → 새창 /partnerInfo/delivery?partnerId&partnerName. 좌측 아이콘 레일 ⚡ 공급사 정보 / 🗎 정산 / 🚚 배송 정보. 배송 정보 = 위 3패널. | 패널 3만 폼 교체(§03 ⑤) |
| 권한 | 공급사 ID 링크(팝업)는 계정 권한 PARTNER_POPUP이 있을 때만 노출. 팝업 안 저장 권한이 같은 코드로 묶이는지는 코드 확인 필요 | 버튼 2개 노출 조건 = 같은 PARTNER_POPUP(정책 10) |
| # | 패널 | 입력 | 저장 테이블 | 의미 | MD-21 |
|---|---|---|---|---|---|
| 1 | 주간 발주 마감시간 | 일~토 select(HH:MM | NONE) | seller_weekly_deadline seller당 1행 · sunday…saturday varchar(5) NOT NULL | NONE = 발주 휴무. 나머지는 그 요일 마감 시각 | WRITE(일괄 탭 ②·주간 마감) + READ(통합 캘린더) |
| 2 | 주간 배송 일정 | 일~토 체크 | seller_weekly_delivery seller당 1행 · sunday…saturday boolean NOT NULL | true = 배송 가능 | WRITE(일괄 탭 ③·주간 배송) + READ(통합 캘린더) |
| 3 | 월간 커스텀 일정 | 날짜 클릭 → 마감 시간 + 배송 여부 | seller_custom_schedule PK(seller_id, event_date) · deadline varchar(5) NULL · delivery boolean NOT NULL | 특정 날짜 예외. 발주휴무(NONE)와 배송가능(boolean)이 한 행에 독립 컬럼 | WRITE(일괄 탭 ①·특정 날짜 + 팝업 임베드 폼) + READ(통합 캘린더) |
| 동작 | 메서드 · 경로 | 바디 / 파라미터 |
|---|---|---|
| 주간 마감 조회 | GET get-weekly-deadline/{sellerId} | — |
| 주간 마감 저장 | POST upsert-weekly-deadline | {sellerId, sunday, …, saturday} |
| 주간 배송 조회 | GET get-weekly-delivery/{sellerId} | — |
| 주간 배송 저장 | POST upsert-weekly-delivery | {sellerId, sunday, …, saturday} |
| 커스텀 조회 | GET get-custom-schedule | params(sellerId, 기간) 파라미터 상세 미확인 |
| 커스텀 저장 | POST upsert-custom-schedule | {sellerId, eventDate, deadline, delivery} |
| 커스텀 삭제 | DELETE delete-custom-schedule | {sellerId, eventDate} |
seller_order_close(seller_id, day_week, close_hour time, use_flag)·seller_order_close_custom(seller_id, event_date, close_hour time)는 별개 테이블이고, 위 3테이블과 값이 불일치한다 → 이 화면은 읽지 않는다. v1 초기안은 이 둘을 저장소로 오인했다. 레거시로 추정하지만 어떤 코드(앱 발주 마감 계산 등)가 아직 읽는지는 개발 확인 필요. MD-21은 이 두 테이블을 건드리지 않는다. 레거시가 맞으면 정리 대상.
이 문서에서 실측한 것
- 스테이지 어드민 팝업 배송 정보 화면 3패널 구성·입력 방식·NONE 의미(캡처)
- 3테이블 컬럼·타입·PK·감사 컬럼 — 개발DB DDL, 구조 확인용만(행 수 등 수치는 인용하지 않음)
- 기존 partner API 7개의 이름·바디
- 레거시 2테이블의 존재와 값 불일치
/partnerList툴바 구성(09-07): 휴무 관련 액션 없음 · 행 체크박스 컬럼 존재(스크린샷) · 공급사 ID 클릭 → 새창 팝업 · 팝업 링크 노출 권한PARTNER_POPUP
확인 못 한 것 (문장으로 표시)
- 앱 발주 마감·배송예정일 계산 코드가 어느 테이블을 읽는지 (weekly/custom vs 레거시)
custom_schedule.deadline이 NULL일 때의 의미 — "마감은 주간 규칙 유지, 배송만 예외"로 추정. 일괄·임베드 폼은 그래서 NULL을 쓰지 않는다(§02 정책 8)- 특정 날짜의 유효값 우선순위(custom 우선 → weekly)가 코드에 그렇게 구현돼 있는지 — 추정
- 기존 화면이 지난 날짜 커스텀 저장을 막는지 · 주간 행이 없는 공급사의 기본값(팝업이 처음 저장할 때 7칸 기본값)
get-custom-schedule파라미터 ·upsert-custom-schedule이 기존 행을 덮어쓰는지(이름·PK로 보아 덮어쓰기로 추정, 동작 실측 안 함) ·PARTNER_POPUP이 저장 권한까지 포함하는지- 공급사 수 — 수치는 스테이지/운영 기준으로 별도 확인, 이 문서는 "150+"라는 요구사항 표현만 승계
- 목록 체크 선택이 페이지 넘김 후에도 유지되는지 · 목록 컬럼 '매칭 센터'가 통합 캘린더 센터 필터와 같은 값인지 — 코드 재실측 필요(§04)
정의 · 정책
용어를 실제 컬럼에 못 박고, 일괄과 개별의 규칙 차이를 한 표로 고정한다
| 용어 | 정의 | 저장 위치 |
|---|---|---|
| 공급사 | 식자재 매입처(seller). 이 문서는 '유통사·파트너' 대신 '공급사'로 통일. API·경로에서는 partner로 표기됨. | seller.id (varchar(100)) |
| 발주 휴무 | 해당 요일/날짜의 발주 마감값이 NONE → 그날 발주 접수 없음. 별도 플래그가 아니라 마감 시간의 특수값이다. | seller_weekly_deadline.{요일} = 'NONE' seller_custom_schedule.deadline = 'NONE' |
| 발주 마감 시간 | "HH:MM" 문자열(varchar(5)). 기존 화면 select의 옵션 집합을 그대로 쓴다. | 위와 같은 컬럼 |
| 배송 가능 | boolean true. 발주 휴무와 독립 — "발주는 쉬지만 기존 물량 배송은 감"(NONE + true) 조합이 저장 가능. | seller_weekly_delivery.{요일} seller_custom_schedule.delivery |
| 주간 고정 규칙 | 요일 단위 반복. seller당 1행에 7요일 컬럼. 마감(①)과 배송(②)이 별도 테이블. 일괄 모달 탭 ②·③의 대상 — "전 공급사 일요일 발주 휴무" 같은 패턴 통일용. 공급사별 미세 조정은 팝업에서. | seller_weekly_deadline / seller_weekly_delivery |
| 월간 커스텀(게릴라) | 특정 날짜 1회성 예외. (seller, 날짜) 1행에 마감+배송. 공휴일·창고 이전 등. 일괄 모달 탭 ①과 팝업 임베드 폼의 대상. | seller_custom_schedule |
| 날짜별 값 | 특정 날짜 폼에서 선택한 날짜마다 {마감, 배송}을 따로 갖는다. "전체 동일 적용" 행은 입력을 빨리 채우는 도구일 뿐, 저장 단위는 항상 (날짜, 값) 쌍이다. | 요청 schedules[{eventDate, deadline, delivery}] |
| 변경 없음 | 주간 탭 7칸의 기본 상태. 그 요일은 요청에 싣지 않고 기존 값을 유지한다. 값(휴무/시각 · 가능/불가)을 고른 요일만 덮어쓴다. | 요청 days{}에 해당 요일 키 없음 |
| 건너뛰기 | 일괄 특정 날짜 탭에만 적용. 같은 (seller, 날짜) 행이 이미 있으면 그 건은 쓰지 않는다(고정 정책, 선택지 없음). 팝업 임베드 폼과 주간 탭은 덮어쓴다. | 저장 없음 — 결과 skipped 건수·목록으로만 안내 |
| 유효 일정 [추론] | 특정 날짜의 실제 값 = custom 행이 있으면 그 값, 없으면 그 요일의 weekly 값. 통합 캘린더는 이 합성값을 보여준다. 계산 코드의 실제 우선순위는 확인 필요. | 저장 없음(조회 시 합성) |
| 통합 캘린더 | 전 공급사의 유효 일정을 날짜 축으로 합쳐 보는 read-only 뷰. 목록 툴바 [휴무 통합 캘린더] → 전폭 모달. 필터는 센터만. | 저장 없음 |
| # | 규칙 | 내용 | 상태 |
|---|---|---|---|
| 1 | 구현 위치 · 진입 | 신규 화면·메뉴 없음. /partnerList 툴바에 [휴무일 일괄 설정] · [휴무 통합 캘린더] 버튼 2개, 각각 모달. 일괄 설정은 목록 행 체크박스로 공급사 N ≥ 1 선택해야 활성(라벨 "휴무일 일괄 설정 (N)"), 캘린더는 항상 활성. 대상 선택 UI·타입 필터는 새로 만들지 않는다 — 목록의 기존 검색·필터·체크박스를 쓴다. | 확정 · v4 |
| 2 | 권한 = PARTNER_POPUP | 버튼 2개의 노출 조건은 기존 공급사 팝업 링크와 같은 권한 코드 PARTNER_POPUP. 팝업이 안 보이는 계정엔 일괄 버튼도 안 보인다(진입점 불일치 방지). 별도 권한 코드를 만들지 않고 신설 API도 같은 체크. | 확정 · v4.2 저장 권한 포함 여부 확인 필요 |
| 3 | 일괄 모달 탭 3 | ① 특정 날짜(기본 탭) → seller_custom_schedule · ② 주간 발주 마감 → seller_weekly_deadline · ③ 주간 배송 → seller_weekly_delivery. 대상 공급사 칩(개별 제거 가능)·적용 바·확인 모달은 3탭 공용. 한 번의 [적용]은 현재 탭 하나만 저장한다. | 확정 · v4.3 |
| 4 | 특정 날짜 = 날짜별 값 | 달력 다중 선택(지난 날짜 차단) → 선택 날짜가 행으로 나열 → 행마다 마감 select {NONE(휴무, 기본) · HH:MM} + 배송 가능 체크. "전체 동일 적용" 행의 값은 새로 클릭한 날짜의 기본값이자 [모든 날짜에 적용] 버튼으로 일괄 채움. 저장 = (공급사 N × 날짜 M) 행, 값은 날짜별. | 확정 · v4.4 |
| 5 | 충돌 = 건너뛰기 (일괄 특정 날짜만) | 같은 (seller, 날짜) 행이 있으면 그 건은 쓰지 않는다(기존 값 보존). 덮어쓰기 선택지·미리보기 단계 없음. 확인 모달에 "N개 공급사 × M일 · 기존 값 K건 건너뜀", 결과에 applied / skipped. 덮어써야 하면 팝업 임베드 폼에서(정책 7). | 확정 · v3 |
| 6 | 주간 탭 = 값 넣은 요일만 덮어쓰기 | 7칸 기본 "변경 없음". 값을 고른 요일만 요청에 실어 기존 값이 있어도 덮어쓴다(건너뛰기 규칙 적용 안 함). "변경 없음" 요일은 기존 값 유지 — 서버가 기존 행과 병합해 upsert(공급사당 1행). 마감 탭 값 = 휴무(NONE) | 시각, 배송 탭 값 = 가능 | 불가. | 확정 · v4.3 |
| 7 | 개별 팝업 = 같은 폼, upsert | 팝업 › 배송 정보 › 월간 커스텀 패널의 단일 날짜 다이얼로그를 특정 날짜 탭과 완전히 같은 폼으로 교체(달력 다중 선택 + 날짜별 값). 그리드 날짜 클릭 = 폼 달력 클릭과 동기(같은 날짜 토글). 기존 값이 있는 날짜는 프리필 후 upsert(덮어쓰기). 두 진입점 = 동일 컴포넌트 scheduleForm({mode:'bulk'|'single'}), 차이는 대상 수(N vs 1)·충돌 처리(건너뜀 vs upsert) 둘뿐. | 확정 · v4.4 |
| 8 | deadline은 항상 값 전송 | 기존 행의 deadline NULL 의미가 미확인(§01)이라 커스텀 저장은 NONE 또는 HH:MM만 쓰고 NULL 옵션을 두지 않는다. 주간 탭도 "변경 없음"은 요청에서 제외할 뿐 NULL을 보내지 않는다. | 제안 |
| 9 | 지난 날짜 차단 | event_date < 오늘(Asia/Seoul) 저장 차단(달력에서 선택 불가). 오늘은 허용하되 경고(제안). 조회는 과거 허용. | 확정 |
| 10 | 통합 캘린더 = read-only · 센터 필터만 | 편집 없음. 날짜 셀 "휴무 N · 배송불가 N · 마감변경 N", 클릭 → 공급사 목록(공급사·마감·배송·출처) + 행별 [배송 정보 열기] → 그 공급사 팝업. 필터는 센터 select 하나. 공급사 타입 필터·검색은 두지 않는다. | 확정 · v4 🟠 기본 필터 |
| 11 | 이력 · 되돌리기 없음 | 감사 흔적은 기존 테이블의 created_by / updated_by(어드민 계정)로 충분. 되돌리기 = 팝업 폼에서 해당 날짜 삭제(기존 delete-custom-schedule) 또는 주간 패널 재수정. | 확정 · v3 |
| 12 | 즉시 정합 | 일괄·개별이 같은 3테이블에 쓰므로 일괄 결과가 팝업 달력·주간 패널에 바로 보이고, 개별 수정도 통합 캘린더에 바로 반영. 모달 제목도 팝업 패널명과 통일("월간 커스텀 일정 세팅 (일괄)" 등). | 확정 |
| 13 | 2속성 독립 · 고정/게릴라 분리 | 발주휴무(NONE)와 배송가능(boolean)은 이미 별도 컬럼/테이블. weekly(seller당 1행)와 custom(PK seller_id+event_date)도 이미 분리. UI에서 함께 바꿔도 저장은 독립 — 스키마 작업 없음. | 기구현 · 해소 |
화면 흐름 — 목록 툴바 버튼 2 → 모달 2 · 팝업 폼 교체
번호 1~5는 클릭 프로토 우측 설명 레일의 핀 번호와 같다. 프로토에서 카드를 누르면 그 자리로 이동한다
▲ 프로토 v4.4 = 공급사 관리 목록 재현(15행: 스테이지 실값 6 + 목업 9) + 툴바 버튼 2 + 일괄 모달 탭 3 + 통합 캘린더 + 공급사 팝업(배송 정보 3패널, 월간 커스텀에 같은 폼 임베드). 우측 레일 ①~⑤ 카드 클릭 → 해당 화면으로 이동. 값은 시연용 샘플.
▲ 툴바 목업(스테이지 툴바 구성 실측 + 추가 버튼). 기존 버튼의 순서·명칭은 스테이지 기준, 추가 버튼의 배치 위치(맨 앞)는 제안. 행 체크박스는 기존 다중 선택 컬럼 재사용. v3의 별도 메뉴·2탭·공급사 선택표는 폐기 — 목록이 이미 검색·필터·체크박스를 갖고 있다.
- 좌 미니 달력 다중 클릭(지난 날짜 차단, 오늘 허용+경고)
- 우 "전체 동일 적용" 행: 마감 select(기본 휴무 NONE) + 배송 가능 체크 → [모든 날짜에 적용]
- 아래 선택 날짜 행 목록 — 행마다 마감·배송 따로 조정(예: 9/9 10:00·배송불가, 9/10 12:00·배송가능)
- 기존 값 있는 (공급사×날짜)는 행에 "기존 K/N 건너뜀" 표시
- 저장
seller_custom_scheduleN×M, 날짜별 값
- 팝업 패널 1과 같은 요일 7칸 select
- 옵션 = 변경 없음(기본) / 휴무(NONE) / 시각(HH:MM)
- 값을 고른 요일만 덮어쓰기, "변경 없음" 요일은 기존 값 유지
- 기존 값이 있어도 덮어씀 — 건너뛰기 규칙 적용 안 함
- 저장
seller_weekly_deadline공급사당 1행 upsert(서버 병합)
- 팝업 패널 2와 같은 요일 7칸, 3상태 변경 없음(기본) / 가능 / 불가
- 지정한 요일만 덮어쓰기
- 마감(휴무)과 독립 — 휴무여도 배송은 가능으로 둘 수 있음
- 저장
seller_weekly_delivery공급사당 1행 upsert(서버 병합)
▲ 특정 날짜 탭 목업(예시값·프로토 샘플). 날짜마다 값이 다른 채로 한 번에 저장된다. 적용 바 문구는 프로토 v4.4와 같다.
▲ 주간 발주 마감 탭 목업. 주간 배송 탭은 같은 7칸에 값이 변경 없음/가능/불가 3상태.
- 목록에서 공급사 선택 — 기존 검색·필터로 대상을 좁히고 행 체크박스로 N개 선택 → [휴무일 일괄 설정 (N)] → 모달. 모달 하단 대상 공급사 칩에서 개별 제거 가능. 페이지 넘김 시 선택 유지 여부는 기존 목록 동작 확인 필요(§04). 모달 상단에 대상 N과 이름 일부를 보여 오해를 막는다.
- 탭에서 값 입력 — 특정 날짜: 날짜 M개 + 날짜별 값 · 주간: 요일별 값. [적용] 버튼은 쓸 건이 1건 이상일 때만 활성, 라벨 "적용 · T건". 특정 날짜 탭의 "건너뜀 K"는 적용 전에 계산해 적용 바와 확인 모달에 미리 보여준다(§05 dryRun 또는 기존 GET).
- 확인 → 저장 → 결과 — 확인 모달(아래 목업) [적용] → 저장 → 토스트 "{N}개 공급사 × {M}일({요약}) → {applied}건 반영 · {skipped}건 건너뜀(기존 값 유지)" → 모달 닫힘 · 목록 선택 해제 · 열려 있는 통합 캘린더·팝업은 즉시 갱신. 서버가 적용 시 다시 판정하므로 확인 모달의 K와 결과 skipped가 다를 수 있다(§04 동시 편집). 주간 탭은 건너뜀 없이 "N개 × 요일 k개 덮어씀".
| 항목 | 건수 | 비고 |
|---|---|---|
| 공급사 | 3 | 삼성웰스토리 · 서림 · 도시곳간 (목록 체크) |
| 날짜별 값 | 4일 | 09/09(수) 10:00·배송불가 / 09/10(목) 12:00·배송가능 / 09/24(목) 휴무·배송불가 / 09/25(금) 휴무·배송불가 |
| 건너뜀 (기존 값 있음) | 5 | 삼성웰스토리 09/24·09/25 / 서림 09/25 / 도시곳간 09/24·09/25 — 기존 값 유지 고정 정책 |
| 적용 | 7 | 신규 행 seller_custom_schedule INSERT |
▲ 적용 확인 목업(프로토 샘플값). 공급사명·수치는 예시이며 실데이터 아님.
| 요소 | 동작 | 비고 |
|---|---|---|
| 열기 | 툴바 [휴무 통합 캘린더] — 목록 선택과 무관하게 항상 활성. 전폭 모달, 닫으면 목록 상태 그대로. 헤더: ‹ 월 › · 오늘 · 센터 select · 대상 공급사 수. | 별도 라우트·메뉴 없음 |
| 월 그리드 | 날짜 셀마다 집계 뱃지 "휴무 N(마감=NONE) · 배송불가 N(delivery=false) · 마감변경 N(커스텀 마감이 그 요일 주간 값과 다름)". 주간 NONE/false 요일을 날짜로 펼치고, 같은 날 커스텀 행이 있으면 커스텀 우선(유효 일정). | 합성 우선순위는 §02 '유효 일정' 추론 — 계산 코드 확인 후 확정 |
| 날짜 클릭 | 우측 패널에 그 날짜의 공급사 목록: 공급사(센터) · 마감(HH:MM/휴무, 변경 태그) · 배송(가능/불가) · 출처(주간/커스텀) + 행별 [배송 정보 열기] → 그 공급사 팝업(5). | 출처 구분은 custom 행 존재 여부 |
| 필터 | 센터만(단일 select: 전체 / 각 센터 / 미매칭). 공급사 검색·타입 필터는 두지 않는다(정책 10). | 🟠 기본 필터(전체 vs 특정 센터) 미정 |
| 편집 | 없음. 일괄 적용 결과는 열려 있어도 즉시 반영. 편집은 모달을 닫고 2 또는 5. 셀 → "이 날짜로 일괄 설정" 바로가기는 공급사 선택이 목록 체크박스에 있어 두지 않는다. | 2차 검토 |
- 주간 발주 마감시간 세팅 7칸 select · 주간 배송 일정 세팅 7칸 체크 — 변경 없음
- 일괄 주간 탭(2)으로 넣은 값이 여기 그대로 보임 = 같은 저장소
- 진입: 목록 공급사 ID 클릭 → 새창
/partnerInfo/delivery?partnerId&partnerName› 🚚 · 권한PARTNER_POPUP
- FullCalendar 월 그리드는 유지, 날짜 클릭 → 단일 다이얼로그를 제거
- 그리드 아래에 특정 날짜 탭과 완전히 같은 폼 임베드(달력 다중 선택 + "전체 동일 적용" + 날짜별 행) — 그리드 날짜 클릭 = 폼 달력 클릭과 동기(같은 날짜 토글)
- 기존 값 있는 날짜는 행에 프리필(태그 "기존 값 프리필 → 적용 시 바꿈") → 적용 시 upsert. 확인 모달 "{공급사} × M일 · 이미 값이 있는 K건은 새 값으로 바꿉니다"
- 일괄로 넣은 날짜가 그리드에 그대로 표시 = 같은 저장소
seller_custom_schedule - 삭제는 기존
delete-custom-schedule유지(행 ✕)
scheduleForm 1개, F.bulk·F.pop 인스턴스 2개).
규칙 · 검증
프론트 차단 + 서버 재검증. 단계 번호는 §03 3 기준(1 목록 체크 · 2 탭 입력 · 3 적용)
| 검증 | 조건 | 처리 | 적용 범위 | 단계 |
|---|---|---|---|---|
| 공급사 미선택 | 목록 체크 N = 0 (또는 모달 칩을 전부 제거) | 툴바 [휴무일 일괄 설정] 비활성(툴팁 "공급사를 선택하세요"). 모달 안에서 칩을 다 지우면 [적용] 비활성 "대상 공급사가 없습니다". [휴무 통합 캘린더]는 영향 없음 | 일괄 | 1 |
| 선택 범위 · 페이지 | 목록이 페이지네이션이라 "전체선택"이 현재 페이지 행만인지, 페이지를 넘겨도 체크가 유지되는지 | 기존 목록 동작을 따른다. 유지되지 않으면 1회 대상 = 현재 페이지 행 — 모달 대상 칩으로 확인시킨다. 기존 목록의 선택 유지 동작 실측 안 함 | 일괄 | 1 |
| 날짜 · 값 미입력 | 특정 날짜: 날짜 0개 / 주간: 7칸 전부 "변경 없음" | [적용] 비활성 — "날짜를 고르세요" / "요일별 값을 고르세요('변경 없음' 요일은 그대로)" | 일괄 · 개별 | 2 |
| 지난 날짜 | event_date < 오늘(Asia/Seoul) | 달력에서 선택 자체를 막음. 오늘은 허용 + "이미 마감 시각이 지났을 수 있음" 경고(제안). 서버도 재검증 | 일괄 · 개별 | 2 · 3 |
| 마감값 형식 | "HH:MM" 또는 "NONE", varchar(5) | 기존 화면 select 옵션 집합만 허용. NULL 미전송(정책 8). 주간 요청의 요일 키는 sunday…saturday만. 서버에서 정규식 재검증 | 일괄 · 개별 | 2 · 3 |
| 충돌 (특정 날짜) | 같은 (seller, 날짜) 행 존재 | 일괄: 건너뛰기 고정 — 적용 바·확인 모달에 K건 표시(적용 전 조회) → 서버는 INSERT … ON CONFLICT DO NOTHING으로 다시 판정, 결과에 skipped 목록. 개별: upsert — 프리필된 값을 바꾸는 건수 표시 후 덮어씀 | 일괄=skip · 개별=upsert | 3 |
| 주간 병합 | 주간 탭에서 일부 요일만 지정 | 서버가 공급사별 기존 행을 읽어 지정 요일만 바꿔 upsert. 기존 upsert-weekly-*는 7요일 전부(NOT NULL)를 받으므로 프론트가 N회 호출하려면 공급사마다 GET 후 병합해야 한다(§05) | 일괄 주간 | 3 |
| 주간 행 없는 공급사 | 선택 공급사에 seller_weekly_* 행이 아직 없음(7컬럼 NOT NULL이라 "변경 없음" 요일을 채울 기존 값이 없음) | 제안: 그 공급사는 건너뛰고 결과에 "주간 설정 없음 K곳"으로 표시 → 팝업에서 먼저 저장하게 안내. 대안: 팝업의 기본값으로 행을 만든 뒤 덮어쓰기 — 기본값이 무엇인지 미확인(§01)이라 1차는 건너뛰기 | 일괄 주간 | 3 |
| 동시 편집 | 확인 모달 표시 후 적용 전에 팝업에서 같은 (seller, 날짜) 행이 생김 | 서버 재판정으로 그 건도 건너뜀. 모달의 K ≠ 결과 skipped면 결과 토스트에 차이를 표시. 주간은 마지막 저장이 이긴다(덮어쓰기) | 일괄 | 3 |
| 부분 실패 | T건 중 1건이라도 DB 오류 | bulk 엔드포인트(권고): 전체 롤백 + 오류 안내. 기존 upsert N회 호출: 실패 건 나열 + "다시 적용" 안내 — 특정 날짜는 건너뛰기 고정이라 재실행이 멱등, 주간은 덮어쓰기라 재실행해도 결과 동일 | 일괄 | 3 |
| 존재하지 않는 seller | 3테이블 모두 seller.id FK 미선언(DDL 실측) | 서버에서 sellerIds 존재 검증 후 진행. FK 추가는 이 티켓 범위 밖 | 일괄 | 3 |
| 대상 상한 | 목록에서 대량 선택 | 허용. 상한을 두지 않되 확인 모달에 "N곳 × M일 = T건" 강조 | 일괄 | 3 |
| 팝업 그리드 ↔ 폼 동기 | 팝업에서 그리드 날짜 클릭과 폼 달력 클릭 | 같은 선택 상태를 토글(한쪽에서 고르면 다른 쪽도 표시). 기존 값 있는 날짜를 고르면 그 값이 행에 프리필 | 개별 | 2 |
백엔드 · API
기존 upsert 3종을 N회 호출할지, bulk 엔드포인트 3종을 신설할지 — 둘 다 가능하고 신설을 권고한다. 통합 조회 1개는 공통. 팝업 임베드 폼은 기존 API로 충분하다
| A. 프론트가 기존 API를 N회 호출 | B. bulk 엔드포인트 3종 신설 (권고) | |
|---|---|---|
| 백엔드 변경 | 없음 (통합 조회 1개만 신설) | 엔드포인트 3 + 통합 조회 1 신설 |
| 트랜잭션 | 없음 — 중간 실패 시 일부만 반영. 특정 날짜는 건너뛰기 고정이라 재실행 안전(멱등), 주간은 덮어쓰기라 재실행 결과 동일 | 탭당 단일 트랜잭션, all-or-nothing |
| 특정 날짜 · 건너뛰기(정책 5) | 기존 upsert-custom-schedule은 덮어쓰기(추정)라, 프론트가 먼저 GET get-custom-schedule(공급사당 1회)로 기존 행을 알아내 그 (seller, 날짜)는 호출을 생략 | 서버가 INSERT … ON CONFLICT DO NOTHING 1문. dryRun으로 K 사전 계산 |
| 주간 · 변경 없음 병합(정책 6) | 기존 upsert-weekly-*가 7요일 전부를 받으므로 공급사마다 GET get-weekly-* → 지정 요일만 바꿔 → POST. 행 없는 공급사 처리 로직이 프론트에 생김 | 서버가 기존 행 SELECT → 지정 요일만 UPDATE(없으면 건너뛰거나 정책대로 INSERT) 1트랜잭션 |
| 요청 수 (150+ 공급사 × 연휴 3일) | 특정 날짜: GET 150+ 회 + POST 최대 450+ 회 · 주간: GET 150+ 회 + POST 150+ 회 | 탭당 2회 (dryRun + 적용) 또는 1회 |
| 판정 | 가능 — 소규모(수십 곳)·1차 데모용. 요청 수와 정합성이 약점 | 권고 |
| 메서드 · 경로 | 요청 | 응답 | 비고 |
|---|---|---|---|
| POST bulk-upsert-custom-schedule | {sellerIds[], schedules:[{eventDate, deadline, delivery}], dryRun} | {targets, applied, skipped:[{sellerId, eventDate}]} | 탭 ① 특정 날짜. 날짜별 값 배열 × sellerIds. 단일 @Transactional · ON CONFLICT DO NOTHING. dryRun=true면 쓰기 없이 같은 형태로 응답(적용 바·확인 모달의 K용). K를 적용 후 결과에서만 보여주기로 하면 dryRun은 빼도 된다 |
| POST bulk-upsert-weekly-deadline | {sellerIds[], days:{sunday:"NONE", saturday:"12:00"}} | {applied, noWeeklyRow:[sellerId]} | 탭 ② 주간 발주 마감. days에 든 요일만 갱신, 나머지 컬럼은 기존 값 유지(서버 병합). 주간 행 없는 공급사는 건너뛰고 응답에 나열(§04 제안) |
| POST bulk-upsert-weekly-delivery | {sellerIds[], days:{sunday:false}} | {applied, noWeeklyRow:[sellerId]} | 탭 ③ 주간 배송. 위와 동일 규칙, 값은 boolean |
| GET get-all-schedule?yearMonth=YYYY-MM¢erId= | — | {days:[{date, orderClosed:n, noDelivery:n, deadlineChanged:n}], sellers:[{sellerId, name, centerId, deadline, delivery, source}]} | 통합 캘린더(4). 필터 파라미터는 센터 1개만. 집계만 줄지 상세까지 줄지는 🟠 성능(§07). 권한 체크 = PARTNER_POPUP |
팝업 임베드 폼(개별, mode single)은 신설 없이 기존 upsert-custom-schedule을 날짜 수만큼 호출(덮어쓰기)·delete-custom-schedule로 충분하다. 트랜잭션이 필요하면 bulk-upsert-custom-schedule에 onConflict:'upsert' 옵션을 두고 sellerIds=[1개]로 재사용해도 된다(선택). v2의 preview-bulk-schedule·get-bulk-schedule-logs·revert-bulk-schedule은 v3에서 철회.
// 탭 ① 특정 날짜 — 요청. 날짜별 값 배열 × 선택 공급사. deadline은 항상 값(NULL 미전송, 정책 8) { "sellerIds": ["1258544565", "2148812077", "3058710095"], "schedules": [ { "eventDate": "2026-09-09", "deadline": "10:00", "delivery": false }, { "eventDate": "2026-09-10", "deadline": "12:00", "delivery": true }, { "eventDate": "2026-09-24", "deadline": "NONE", "delivery": false }, { "eventDate": "2026-09-25", "deadline": "NONE", "delivery": false } ], "dryRun": false } // 응답 — skipped = 기존 행이 있어 쓰지 않은 (seller, 날짜). dryRun=true도 같은 형태 { "targets": 12, "applied": 7, "skipped": [ { "sellerId": "1258544565", "eventDate": "2026-09-24" }, … ] } // 탭 ② 주간 발주 마감 — 요청. days에 든 요일만 덮어쓰기, 나머지 요일은 기존 값 유지 { "sellerIds": ["1258544565", "2148812077", "3058710095"], "days": { "sunday": "NONE", "saturday": "12:00" } } // 응답 { "applied": 3, "noWeeklyRow": [] } // 탭 ③ 주간 배송 — 요청. 값은 boolean(가능 true / 불가 false), "변경 없음" 요일은 키 자체를 뺀다 { "sellerIds": ["1258544565"], "days": { "sunday": false } }
트랜잭션 · 처리 순서 (제안)
- 공통: sellerIds 존재 검증(FK 미선언이라 애플리케이션에서) · 값 형식(HH:MM | NONE · boolean · 요일 키) 검증 · 권한
PARTNER_POPUP. - 특정 날짜: eventDates ≥ 오늘(Asia/Seoul) 검증 → 대상 (seller × 날짜) 목록 생성 → 기존 행 SELECT → skipped 확정.
dryRun=true면 여기서 응답. 나머지를INSERT … ON CONFLICT (seller_id, event_date) DO NOTHING1문으로 배치(값은 행마다 다름 = 날짜별). 영향 행 수 = applied. 2단계 이후 생긴 행도 자동으로 건너뛰어지므로 applied가 targets − skipped보다 작을 수 있고 그 차이를 결과에 표시. - 주간: 공급사별 기존 행 SELECT … FOR UPDATE → 요청 요일 컬럼만 UPDATE(updated_*). 행이 없으면 noWeeklyRow에 넣고 건너뜀(§04 제안).
- created_* / updated_* 기록은 기존 개별 upsert와 동일 규칙(어드민 계정).
- 예외 시 해당 요청 전체 롤백.
성능 [추론 — 부하 실측 안 함]
- 일괄 적용: 공급사 수백 × 날짜 수 개 = 수백~수천 행 INSERT 1문, 주간은 공급사 수만큼 UPDATE. 단일 트랜잭션·배치면 초 단위 이내로 충분하다고 본다. 상한을 두지 않는 근거.
- 통합 캘린더: weekly 2테이블은 공급사 수만큼의 행이라 전체 로드 후 메모리 합성. custom은 월 범위 조회 — PK가
seller_id선행이라 event_date 범위 스캔에 쓸 인덱스가 없다(DDL 실측: PK 외 인덱스 없음) →idx_seller_custom_schedule_event_date추가 제안(§06). 테이블이 작으면 당장 체감은 없을 수 있다. - 응답 형태: 집계(날짜별 n)만 먼저 주고 날짜 클릭 시 상세를 따로 받는 2단 구조가 안전. 한 번에 상세까지 줄지는 🟠.
ERD / DDL
쓰기는 기존 3테이블(컬럼 변경 없음), 신규 테이블 0 · 선택 인덱스 1. 컬럼·타입은 개발DB DDL로 구조만 확인
"HH:MM" | "NONE"(발주 휴무). 일괄 탭 ②가 지정 요일 컬럼만 UPDATE(공급사당 1행, 7컬럼 NOT NULL이라 병합 필수). 통합 캘린더 READ.INSERT … ON CONFLICT DO NOTHING(건너뜀) · 팝업 임베드 폼 = upsert(덮어쓰기). 발주휴무(deadline=NONE)와 배송가능(delivery)이 한 행에 독립. 인덱스는 PK뿐(실측).-- ========================================================= -- MD-21 휴무일 일괄 세팅 · v4.4 (2026-09-07) -- 기존 3테이블은 컬럼 변경 없음. 신규 테이블 0. 신설은 선택 인덱스 1개뿐. -- ========================================================= -- 0) 참고 — 기존 테이블(개발DB DDL로 구조 확인, 변경 없음) -- seller_weekly_deadline (seller_id PK, sunday..saturday varchar(5) NOT NULL -- 'HH:MM' | 'NONE', created/updated_*) -- WRITE(부분 UPDATE) · READ -- seller_weekly_delivery (seller_id PK, sunday..saturday boolean NOT NULL, created/updated_*) -- WRITE(부분 UPDATE) · READ -- seller_custom_schedule (PK(seller_id, event_date), deadline varchar(5) NULL, delivery boolean NOT NULL, created/updated_*) -- WRITE · READ -- 1) [선택] 통합 캘린더 월 범위 조회용 — PK가 seller_id 선행이라 event_date 범위 조회에 인덱스가 없음(실측) CREATE INDEX IF NOT EXISTS idx_seller_custom_schedule_event_date ON seller_custom_schedule (event_date); -- 2) 참고 DML(제안) — 탭 ① 특정 날짜 일괄. 기존 행은 건너뜀(고정). 선택 공급사 × 날짜별 값을 1문으로 배치. -- created_* 채우는 규칙은 기존 개별 upsert와 동일(어드민 계정). 팝업 임베드 폼은 같은 INSERT에 ON CONFLICT DO UPDATE. INSERT INTO seller_custom_schedule (seller_id, event_date, deadline, delivery, created_at, created_by) VALUES (:seller_id, :event_date, :deadline, :delivery, :now_kst, :admin_id), -- × (N × M), 값은 날짜별 … ON CONFLICT (seller_id, event_date) DO NOTHING; -- 3) 참고 DML(제안) — 탭 ② 주간 발주 마감 일괄. 요청에 든 요일 컬럼만 갱신(예: 일=NONE, 토=12:00). 탭 ③은 boolean 컬럼으로 동일. UPDATE seller_weekly_deadline SET sunday = 'NONE', saturday = '12:00', updated_at = :now_kst, updated_by = :admin_id WHERE seller_id = ANY(:seller_ids); -- 행 없는 공급사는 영향 0 → noWeeklyRow로 응답 -- 철회 이력 -- v2(2026-09-03): seller_order_close_custom ALTER(delivery_yn·holiday_source·bulk_group_id 추가) — 대상 테이블 자체가 틀렸고, 실제 테이블엔 이미 delivery가 있음. -- v3(2026-09-04): seller_schedule_bulk_log(선택 이력 테이블) — 이력·되돌리기 제거로 불필요.
미정 🟠 2 · 해소 ✅ 2 · 개발 확인 1
미정은 개발 전 결정이 필요한 것. 해소는 실측으로 닫힌 것(신규 개발 아님)
| # | 항목 | 선택지 | 영향 · 제안 |
|---|---|---|---|
| 🟠 1 | 공휴일 출처 | (a) 운영자가 일괄 설정 달력에서 수동 클릭 / (b) 공공데이터 특일정보 API로 달력에 공휴일 표시·자동 선택 | (b)면 "공휴일=어떤 값?"(NONE+배송불가?) 정책과 동기화 배치가 추가 범위. 제안: 1차는 (a) — 특정 날짜 탭이 바로 그 도구다. v1의 holiday_source 컬럼 선반영은 철회(기존 테이블 미접촉). 출처 기록이 필요해지면 후속. |
| 🟠 2 | 통합 캘린더 기본 필터 · 성능 | 센터 필터 기본값 = 전체 vs 특정 센터(강제) / 셀 뱃지 기본 = 전체 vs 발주휴무만 / 월 조회 응답을 집계만 vs 상세 포함 | 제안: 센터 기본 "전체", 셀은 "휴무·배송불가·마감변경 있는 날만 뱃지", 응답은 집계 + 클릭 시 상세 2단. 인덱스 §06. 150+ 공급사 × 31일 전체 조회가 기본이라 부하가 문제면 센터 강제로 전환. |
| ✅ 1 | 2속성 독립 | v1 "delivery_yn 신설" 불필요. seller_weekly_delivery(요일)·seller_custom_schedule.delivery(날짜)로 이미 발주휴무와 독립 저장. 프로토 미정 항목 "발주휴무↔배송불가 독립성"은 닫힘. | |
| ✅ 2 | 고정 / 게릴라 저장 분리 | weekly(seller당 1행) / custom(PK seller_id+event_date)로 이미 분리. v1의 "seller_order_close_custom 컬럼 추가"는 대상 테이블부터 틀렸으므로 철회. | |
| 개발 확인 | seller_order_close / seller_order_close_custom | 이 화면과 값 불일치 → 레거시 추정. 앱 발주 마감 계산 등이 아직 읽는지 확인 후, 레거시가 맞으면 정리 대상. MD-21 미접촉(§01). | |
산출물
링크 · 상태
| 산출물 | 위치 | 상태 |
|---|---|---|
| 클릭 프로토 v4.4 | md-g67-proto.pages.dev/21/ 소스 archive_live/md-g67-proto/21 · 엄브렐라 폴더째 배포 | v4.4 반영 공급사 관리 목록 재현 + 툴바 버튼 2 + 일괄 모달 탭 3(날짜별 값) + 통합 캘린더 + 공급사 팝업(월간 커스텀에 같은 폼 임베드) + 설명 레일 ①~⑤(§1-B 엔진). 갱신 시 같은 주소에 교체 배포. |
| 이 문서 | MD-21 기획·개발 문서 v4.4 최종 정리판 (2026-09-07) → md-21-holiday-bulk-setup.pages.dev에 교체 배포 | 본문 전체 v4.4 v3 본문 + 배너 덧붙임 상태를 정리 — 배너 내용은 본문에 녹이고 §09 이력으로만 남김 |
| 이전 판 | 같은 주소에 순차 배포됨 — v1 초기안(7/30) → v2 실측 정정(9/3) → v3 단순화(9/4) → v4 구현 위치 정정(9/7, 배너 덧붙임) | v1 ERD 오류 · v2 범위 과다 · v3 신규 화면 전제 · v4 배너 누적 이 문서로 대체 |
| 티켓 수정안 | docs/TICKET_MD21_PLD-1038_수정안.md | v4.4 반영됨 탭 3 · 날짜별 값 · 팝업 동일 폼 · 권한 PARTNER_POPUP 반영(2026-09-07). 지라 PLD-1038 본문 반영 여부는 미확인 |
| 변경 대상 화면 | 스테이지 어드민 /partnerList (공급사 관리 목록) | 툴바 버튼 2 추가 · 모달 2 — 기존 툴바 구성 실측(09-07) |
| 변경 대상 팝업 | 스테이지 어드민 /partnerInfo/delivery?partnerId=&partnerName= | 패널 1·2 변경 없음 · 패널 3(월간 커스텀) 입력 폼 교체 — 실측 근거 |
변경 이력
이전 판에 배너로 덧붙였던 정정·결정은 전부 본문에 반영했고 여기에만 남긴다
| 일자 | 버전 | 내용 |
|---|---|---|
| 2026-07-30 | v1 | 초기안 — 요구사항 기반, DB·화면 실측 없음. ERD를 seller_order_close·seller_order_close_custom + delivery_yn·holiday_source 신설로 작성 — 틀린 테이블. |
| 2026-09-03 | v2 | 초기안 ERD → 실측 정정. 스테이지 화면·DDL 실측으로 저장소를 seller_weekly_deadline·seller_weekly_delivery·seller_custom_schedule(기존 3테이블, 컬럼 변경 없음)로 교체. 2속성 독립·고정/게릴라 분리는 기구현으로 해소. 일괄 유형 3개(기존 3패널과 1:1)·통합 조회·이력(선택)·미리보기 엔드포인트 제안. 레거시 2테이블 미사용 표기. 미정 4·해소 2. |
| 2026-09-04 | v3 | 단순화(사용자 결정 "복잡하다 → 우선 특정 날짜 일괄만"). 주간 마감·주간 배송 일괄 제거 → 쓰기 대상 seller_custom_schedule 1테이블. 충돌=건너뛰기 고정(확인 모달 "K건 건너뜀"), 미리보기·충돌 정책 선택 제거. 이력·되돌리기 탭·bulk_log 제안 제거 → 신규 테이블 0. 신설 엔드포인트 5→2. 미정 4→2. 구현 위치는 아직 신규 화면 2탭 전제. |
| 2026-09-07 | v4 | 구현 위치 = 신규 화면 → 공급사 관리 목록 툴바 버튼 2개(사용자 결정), 타입 필터 제거. 진입점 = /partnerList 체크박스 선택 → [휴무일 일괄 설정] 모달 · [휴무 통합 캘린더] 전폭 모달(필터 센터만). 스테이지 재확인: 목록 툴바에 휴무 관련 액션 없음. 문서는 v3 본문 + v4 배너. |
| 2026-09-07 | v4.1 | 프로토에 §1-B 설명 레일 엔진 이식 — 점선 연결선·후광·빨간 마커·카드 클릭 → 모달/이동. 레일 번호 ①~⑤ 확정(문서 §03과 동일 번호). |
| 2026-09-07 | v4.2 | 프로토에 공급사 ID·명 클릭 → 팝업(새창) 배송 정보 3패널 목업 추가 — 일괄 저장값이 월간 커스텀 달력에 그대로 표시(같은 저장소). 노출 권한 = 팝업과 동일 PARTNER_POPUP, 통합 캘린더 공급사 행 [배송 정보 열기] 연결. |
| 2026-09-07 | v4.3 | 주간 발주 마감·주간 배송 일괄 복원(사용자 결정) — v3에서 뺐던 2종을 일괄 모달의 탭으로. 값 넣은 요일만 덮어쓰기("변경 없음" 유지), seller_weekly_deadline·seller_weekly_delivery 공급사당 1행 서버 병합 upsert. 모달 탭 3. |
| 2026-09-07 | v4.4 | 특정 날짜 = 날짜별 값 개별 지정(전체 동일 적용 + 행별 조정) · 개별 팝업 월간 커스텀에 같은 폼 임베드(단일 날짜 다이얼로그 제거, 기존 값 프리필 후 upsert) — 두 진입점 = 동일 컴포넌트 scheduleForm({mode}), 차이는 대상 수·충돌 처리뿐(사용자 피드백). bulk 엔드포인트 3종 + 통합 조회 1로 API 제안 정리. 문서 본문 전체를 이 모델로 재작성(배너 → 본문·이력). |
[실측]으로 표시하지 않은 설계 항목(신설 엔드포인트·dryRun·트랜잭션·주간 행 없는 공급사 처리·성능·유효 일정 우선순위)은 제안·추론이며 개발 검토 대상.