Skip to content

납기 20영업일·공휴일 · 차량 도착 예정일 · 특장 착수 · PDI 체크리스트 · 영문화 게이트 - #385

Merged
Jay722J merged 6 commits into
mainfrom
feat/ops-batch
Sep 10, 2026
Merged

납기 20영업일·공휴일 · 차량 도착 예정일 · 특장 착수 · PDI 체크리스트 · 영문화 게이트#385
Jay722J merged 6 commits into
mainfrom
feat/ops-batch

Conversation

@Jay722J

@Jay722J Jay722J commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Closes #380
Closes #382
Closes #384

한 번에 배포하기로 해 네 건을 한 PR 로 모았습니다(#381 · #383 은 여기로 합치고 닫았습니다). 커밋은 주제별로 나뉘어 있습니다 — 커밋 단위로 보시는 편이 읽기 쉽습니다.


1. 영문화 — 사전에 없는 문구를 CI 가 막는다 (57e0bd7)

t() 는 사전에 없으면 한국어를 그대로 돌려준다. 화면이 안 깨지니 눈으로는 못 잡고, 영어로 보는 사람에게만 한국어가 나간다. 검사를 붙이니 이미 19개가 새고 있었다.

지시대로 「배포 전에 잘 보자」가 아니라 검사로 못 박았다. 이번 PR 에서 새로 쓴 문구들도 실제로 이 검사에 걸려 채웠다.

곁들여: 숨긴 고객 {0}명EN 쪽에 있어(tf 는 EN_FMT 만 본다) 번역이 있는데도 한국어가 나가고 있었고, 자리표시자가 {-1} 인 죽은 항목이 88개 있었다.

2. 납기 20영업일 + 공휴일 (885d5c8)

기산점은 바뀌지 않는다. 예전부터 assigned_at(배정을 누른 순간)부터 셌다 — 수락하며 새 창이 열리는 것이 아니라, 수락이 늦으면 제작 기간이 줄어든다.

상태 한도
이미 납기를 찍음 15일 그대로 — 발주서에 문서로 찍혀 나갔다
아직 수락 안 됨 20일 — 연휴에 막혀 날짜를 못 고르던 건들

한도를 주문에 얼린다(order.due_limit_days). 공휴일은 holiday 표에서 읽어 shared 계산에 주입한다 — 서버와 화면이 같은 목록을 봐야 「고를 수 있는데 저장이 거부」가 안 난다.

⚠️ 무료 API 두 곳(nager.at · holidays.hyunbin.page) 모두 제헌절(7/17)을 공휴일로 준다. 2008년에 빠진 날이다. 그래서 확인한 2026년 21일치를 migration 으로 넣었다.

3. 차량 도착 예정일 (a04e6c3)

관리자가 찍고 특장사는 본다. 단계가 아니라 주문에 둔다 — 단계에 매달면 수락 전에 쓸 자리가 없다. 상태로 막지 않는다.

바뀌면 다시 알린다. 현장은 이 날짜에 맞춰 사람을 뺀다. 무엇에서 무엇으로 바뀌었는지는 그 단계 대화에 남긴다(특장사가 이미 보고 있는 자리다). 같은 날짜를 다시 보내면 알리지 않는다.

4. 특장 제작 착수 (a04e6c3)

「받아는 놨는데 손도 안 댔다」와 「만들고 있다」를 가른다. 착수에는 증빙을 두지 않는다 — 시작을 사진으로 증명하라고 하면 누르는 것 자체가 미뤄진다.

⚠️ 진행 중인 주문이 「완료됐는데 착수는 안 함」이 되지 않게 기존 행을 채웠다. 반영 뒤 모순 0건 확인.

5. PDI 체크리스트 (24060b4)

차량 도착 · 특장 제작 완료(특장사) · 인도(관리자) 셋에 붙는다.

  • 규칙은 코드, 서식은 데이터. 항목은 관리자 > 체크리스트 탭에서 고친다.
  • 작성 시점 사본을 얼린다. 서식을 고쳐도 이미 작성한 것은 안 바뀐다.
  • 서식이 비면 막지 않는다. 안 정한 단계에서 주문이 서면 안 된다.
  • 불합격은 끝이 아니다. 고쳐서 재검 합격을 주면 열리고, 그 과정이 항목별 이력으로 남는다.
  • 제출하면 앱 알림을 켠 관리자에게 알린다. 알림은 켜고 끄는 것 하나만 본다(직무별 구분 없음).
  • 서식에서 지운 항목은 꺼질 뿐 사라지지 않는다.
  • 새 기능모듈 checklist.manage — 기본 꺼짐, 계정별로 켠다.

검증

검사 702(백엔드) + 316(shared) 통과 · 프론트 클린 빌드 · db:drift 일치(테이블 44 · 컬럼 350).

되돌려 걸리는 것을 확인한 변이 20개:

주제 변이
영문화 사전 없는 문구 / tf 틀을 EN 에 / {-1} 부활 / 정규식 눈멀기
납기·공휴일 공휴일 미제외 / 납기일 공휴일 허용 / 얼린 한도 무시 / 달력 미로드
도착 예정일 특장사 쓰기 허용 / 알림 미발송 / 이력 미기록 / 같은 날짜에도 알림 / 화면 권한
체크리스트 관문 제거 / 빈 서식에도 생성 / 이력 미기록 / 지운 항목 실제 삭제 / 적는 사람 미검증 / 알림 미발송 / 불합격에도 제출 유지

브라우저로 확인한 것: 납기 달력에 추석·개천절·한글날이 이름과 함께 뜨고 고를 수 있는 날이 정확히 20칸 · 관리자가 도착 예정일을 찍고 특장사가 읽기 전용으로 보는 것 · 서식 작성 → 특장사 관문 → 모두 합격 → 제출 열림.

스키마

추가만 했다. 새 표 holiday · checklist_item · order_checklist · order_checklist_line · order_checklist_line_log, 새 컬럼 order.due_limit_days · order.car_arrival_planned_at(+set_by/at). 기존 표·컬럼은 건드리지 않았다.

데이터 채우기 셋(모두 migration 안에서, 전체 seed 를 돌리지 않는다):

  • 기존 주문의 due_limit_days — 수락 완료 15 / 미수락 20
  • 2026 공휴일 21일 (ON CONFLICT DO NOTHING)
  • 기존 주문의 build_started 행 — 이미 완료된 건은 done 으로

준성의 Macbook Pro added 6 commits September 10, 2026 17:26
`t()` 는 사전에 없으면 한국어를 그대로 돌려준다. 화면이 깨지지 않으니
눈으로는 못 잡고, 영어로 보는 사람에게만 한국어가 나간다. 검사를 붙이니
이미 **19개**가 그렇게 배포돼 있었다 — MyPage 「계정」·「언어」,
Header 「일반」, SalesPage 「지연」, 어제 넣은 대화 탭 증빙 버튼까지.

지시(2026-09-10): 앞으로 모든 수정 배포는 영문화를 검사한 뒤 진행한다.
사람이 매번 보는 규칙은 이미 한 번 실패했으므로 검사로 못 박는다.

  · t()·tc() 원문이 EN 에 있는가
  · tf() 틀이 EN_FMT 에 있는가

뒤지다 나온 것 둘도 함께 고쳤다.

  · 「숨긴 고객 {0}명」이 EN 쪽에 있었다. tf() 는 EN_FMT 만 보므로
    번역이 사전에 있는데도 한국어가 나가고 있었다.
  · 자리표시자가 `{-1}` 인 항목 88개. tf() 가 채우는 규칙은 `{0}`·`{1}` 이라
    채워지지도 찾아지지도 않는 죽은 항목이다. 걷어내고, 다시 생기면
    걸리도록 검사를 하나 더 뒀다.

「한국어」 라벨은 t() 를 벗겼다 — 언어 이름은 그 언어로 적는 자리라
옮길 대상이 아니다(옆의 English 와 같은 취급).

검사 3개. 넷을 되돌려 각각 걸리는 것을 확인했다 — 사전 없는 새 문구,
tf 틀을 EN 에 잘못 넣기, {-1} 항목 부활, 정규식이 눈머는 경우.

영어로 켠 화면을 DOM 으로 훑어 라벨에 남은 한국어가 없는 것도 확인했다
(남은 것은 고객명·특장사명 — 데이터라 한국어가 맞다).
주말만 빼고 세다 보니 연휴가 낀 달에 고를 수 있는 날짜가 실제보다 적었다.
추석(9/24~26)을 앞두고 납기를 찍지 못하는 일이 생겼다(제보).

**한도 15 → 20 영업일.** 기산점은 예전과 같은 **배정일**이다(수락일이 아니다) —
수락이 늦으면 남는 제작 기간이 그만큼 줄어든다.

  · 이미 납기를 찍은 건(수락 완료) → 15일 그대로. 발주서에 「15일 이내」가
    문서로 찍혀 나갔으므로 소급해 고치지 않는다.
  · 아직 수락 안 된 배정 → 20일 창을 연다. 연휴에 막혀 있던 건들이 이 대상이다.

그래서 한도를 **주문에 얼려 둔다**(`order.due_limit_days`). 상수 하나로 두면
다음에 또 바꿀 때 이미 나간 서류가 같이 바뀐다.

## 공휴일

`holiday` 표를 새로 만들고 영업일 계산에 **주입**한다. 코드에 박으면 임시공휴일
하나에 배포를 해야 한다. 서버는 DB 에서, 화면은 `GET /holidays` 에서 같은 목록을
받아 같은 함수(shared/schedule)에 물린다 — 어긋나면 「고를 수 있는데 저장이
거부되는」 예전 사고가 다시 난다.

⚠️ 무료 공개 API 두 곳(nager.at · holidays.hyunbin.page)을 대조해 보니 **둘 다**
   제헌절(7/17)을 공휴일로 넣고 있었다. 2008년에 빠진 날이다. 그래서 불러온 값을
   그대로 쓰지 않고, 확인한 2026년 값을 migration 으로 넣었다. 자동 조회는
   관리자 화면에서 **초안**으로만 쓸 것이다(다음 PR).

받아오지 못해도 멈추지 않는다 — 달력이 비면 주말만 빼는 예전 계산이 되고,
한도가 짧게 나온다. 한도는 넘기면 안 되는 쪽이라 안전한 방향이다.

## 달력 화면

연휴는 회색으로 가라앉히지 않고 **이름을 적는다.** 「왜 이 날은 못 고르지」가
남으면 전화가 온다 — 추석이라 못 고르는 것과 한도를 넘어 못 고르는 것은 다른
이야기다.

달력이 늦게 도착하면 격자를 **다시 센다.** 처음엔 한 번 그리고 말아서, 10/14
까지여야 할 달력이 10/10 에서 끊겨 있었다(브라우저에서 잡았다).

검사: shared 8개 · 실제 API 6개. 넷을 되돌려 각각 걸리는 것을 확인했다.
db:drift 일치(테이블 40 · 컬럼 317).
## 차량 도착 예정일

관리자가 특장사에게 **차가 언제 가는지** 알려 주는 날짜다. 특장사가 완료
처리하는 「차량 도착」 단계와 다르다 — 이건 예정이고, 아는 사람은 차를
보내는 쪽이다.

단계가 아니라 **주문에** 둔다(`order.car_arrival_planned_at`). 단계에 매달면
수락 전에는 쓸 자리가 없는데, 배정 직후에 정해질 수도 제작 중에 바뀔 수도
있어야 한다. 상태로 막지 않는다.

바뀌면 **다시 알린다.** 현장은 이 날짜에 맞춰 사람을 뺀다 — 조용히 바뀌면
그날 아무도 없는 공장에 차가 간다. 무엇에서 무엇으로 바뀌었는지는 그 단계
대화에 남긴다. 표를 새로 만들지 않은 이유는, 특장사가 이미 보고 있는 자리가
거기이기 때문이다.

알림은 배정된 특장사에게만 간다. 같은 날짜를 다시 보내면 알리지 않는다 —
안 바뀐 것은 소식이 아니다.

## 특장 제작 착수

완료 하나만 두면 「받아는 놨는데 손도 안 댔다」와 「만들고 있다」가 구분되지
않는다. 납기가 다가올 때 물어봐야 하는 것이 바로 그 차이다.

착수에는 **증빙을 두지 않는다.** 시작했다는 것을 사진으로 증명하라고 하면
누르는 것 자체가 미뤄지고, 그러면 이 단계를 만든 이유가 사라진다.

⚠️ 진행 중인 주문이 모순 상태가 되지 않게 기존 행을 채웠다. 새 단계가 완료의
   선행조건이라, 이미 제작 완료인 주문에 착수가 비어 있으면 「완료됐는데 착수는
   안 함」이 된다. 이미 완료한 건은 실제로 착수도 했으므로 같은 시각·같은 사람으로
   채우고, 아직 전인 건에는 빈 칸을 깔아 둔다. 반영 뒤 모순 0건 확인.

장착은 여전히 **완료**를 기다린다 — 착수만으로는 못 단다.

검사 7 + 3개. 다섯을 되돌려 각각 걸리는 것을 확인했다.
브라우저에서 관리자가 찍고 특장사가 보는 것까지 확인했다(특장사에게는
고치는 자리가 없다). db:drift 일치(테이블 40 · 컬럼 320).
차량 도착 · 특장 제작 완료 · 인도 세 단계에 체크리스트를 붙인다. 앞의 둘은
특장사가, 인도는 넘겨받는 관리자가 적는다.

## 규칙은 코드, 서식은 데이터

무엇을 확인하는지는 관리자가 화면에서 고친다(관리자 > 체크리스트 탭).
어느 단계에 붙는지·누가 적는지·「채워야 넘어간다」는 shared 카탈로그에 둔다 —
규칙까지 데이터로 내리면 검증할 곳이 흩어진다.

⚠️ 서식을 고쳐도 **이미 작성한 체크리스트는 바뀌지 않는다.** 작성을 시작할 때
   그 시점의 항목을 주문에 사본으로 얼린다. 발주서 단가와 같은 원칙 —
   3개월 전에 합격 처리한 항목의 뜻이 소급해서 바뀌면 근거가 되지 못한다.

⚠️ 서식이 **비어 있으면 막지 않는다.** 항목을 아직 안 정한 단계에서 완료가
   막히면 진행 중인 주문이 통째로 선다. 체크리스트는 있을 때만 관문이다.

## 불합격은 끝이 아니다

합격이 아닌 항목이 하나라도 있으면 그 단계를 완료할 수 없다. 불합격을 놔두고
넘어갈 수 있으면 체크리스트는 형식이 된다.

대신 고쳐서 **재검으로 합격**을 주면 열리고, 그 과정이 항목별 이력으로 남는다.
결과 칸 하나만 두면 마지막 판정만 남아 「한 번에 통과」와 「고쳐서 통과」가
같아진다 — 무엇이 왜 걸렸었는지가 다음 차를 만들 때 쓰인다.

제출한 뒤에 불합격이 생기면 제출이 거둬진다. 「제출됨」인데 안에 불합격이 있는
상태가 남으면 관문이 무엇을 보는지 흐려진다.

## 그 밖에

· 제출하면 앱 알림을 켠 관리자에게 알린다. 알림은 켜고 끄는 것 하나만 본다 —
  무슨 알림인지로 가르지 않는다(지시: 2026-09-10).
· 서식에서 「지운」 항목은 **꺼질 뿐** 사라지지 않는다. 그 항목으로 합격 처리한
  옛 체크리스트가 무엇을 봤는지 되짚을 수 있어야 한다.
· 서식을 고치는 권한은 새 기능모듈 `checklist.manage` — 기본은 꺼 두고
  계정별로 켠다.

검사 9개. 일곱을 되돌려 각각 걸리는 것을 확인했다 — 관문 제거, 빈 서식에도
체크리스트 생성, 이력 미기록, 지운 항목 실제 삭제, 적는 사람 미검증,
알림 미발송, 불합격에도 제출 유지.

브라우저에서 서식 작성 → 특장사 화면 관문 → 모두 합격 → 제출 열림까지 확인했다.
db:drift 일치(테이블 44 · 컬럼 350).
`npx tsc --noEmit` 은 통과하는데 배포의 `npm run build`(clean tsc)가
`unknown` 을 supertest 의 `send()` 에 넘긴 것을 잡았다. 검사 파일도 빌드
대상이라, 여기서 나는 타입 오류는 배포를 멈춘다.
손으로 적은 제약 이름(`order_checklist_order_fkey`)이 Prisma 가 짓는
이름(`order_checklist_order_id_fkey`)과 달라 migration 검증이 어긋났다.
maker_price 때와 같은 실수다 — 이름은 `<표>_<컬럼>_fkey` 다.
@Jay722J
Jay722J merged commit fa86e2d into main Sep 10, 2026
4 checks passed
@Jay722J
Jay722J deleted the feat/ops-batch branch September 10, 2026 09:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant