Skip to content

발주서 금액: 계약가는 DB·자동 기입, 계약 밖은 입력 칸이 저절로 - #374

Merged
Jay722J merged 3 commits into
mainfrom
feat/maker-price-admin
Sep 9, 2026
Merged

발주서 금액: 계약가는 DB·자동 기입, 계약 밖은 입력 칸이 저절로#374
Jay722J merged 3 commits into
mainfrom
feat/maker-price-admin

Conversation

@Jay722J

@Jay722J Jay722J commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Closes #373

옵션과 단가표를 빠짐없이 맞물린다

maker_price 행이 옵션마다 하나씩 미리 있다(활성 옵션 27개 전부 · 탑크기별 분화 포함 32행).
value_code 에 외래키를 걸어 없는 옵션을 가리키는 행 자체가 생기지 않고, 만들거나 지우는
API 도 없다. 관리자는 값만 고친다.

분류가 셋인 이유 — EV& 직접해당 없음은 발주서에 안 실리는 결과가 같지만 뜻이 다르다.
앞은 우리가 하는 일이고 뒤는 아무도 안 하는 일이다. 나중에 「이거 누가 하기로 했더라」를
표에서 답하려면 구분돼 있어야 한다.

분류
특장사 15 계약가 8 · 미책정 7
EV& 직접 6 차량 트림 포함 — 베이스 차량은 갑이 지급(계약 제7조)
해당 없음 11 「없음」류 · 기본형에 포함된 여닫이 도어

입력 칸이 저절로 생긴다

예전엔 계약에 없는 항목이면 「+ 항목 추가」를 눌러 품목명부터 타이핑해야 했다.
이제 고른 옵션 중 특장사 몫인데 계약가가 없으면 그 줄이 금액만 빈 칸으로 뜬다.
품목명·단위·수량은 옵션이 정하고 관리자는 금액만 채운다 — 서버도 그렇게 받아서,
품목명을 딴 것으로 보내도 옵션이 정한 이름이 남는다(시험으로 확인).

빈 금액으로는 배정되지 않는다. 화면은 어느 줄이 비었는지 먼저 말하고 버튼을 잠그며,
서버도 같은 것을 본다. 0 원짜리 줄은 특장사에게 「무상으로 해 주기로 했다」로 읽힌다.

unit_price 를 NULL 허용으로 넓혔다 — 「계약에 값이 없다」와 「0 원에 하기로 했다」는 다른 말이다.

🔴 덤으로 찾은 것: 시드가 운영 모듈을 지운다

feature_module.csv 에 11개뿐인데 운영에는 17개다. 시드는 이 표를 deleteMany({})
통째로 비우고 CSV 대로 다시 만든다 — 로컬에서 시드를 한 번 돌렸더니 basedata.manage ·
quote.edit · quote.delete · doc.send.* · notify.push · stats.*권한과 함께
사라졌고
, 옵션DB·무게상수·영업 성과 탭이 통째로 없어졌다(그래서 발견했다).

운영 값으로 맞췄고, permission-seed-sync.test.ts 로 다시는 어긋나지 않게 했다 —
라우트가 요구하는 모듈이 시드에 다 있는가, 권한이 없는 모듈을 가리키지 않는가.

⚠️ 이 시드는 운영에서 돌리면 안 된다(모듈과 계정별 권한이 전부 지워진다).
이 PR 이 그걸 바꾸지는 않는다 — 별도로 다룰 일이다.

DB — 더하기만 한다

unit_price NOT NULL 해제 · value_code 외래키 추가. DROP · DELETE 없음.

배포 뒤 필요한 일

운영 maker_price 에는 계약 8줄만 있다. 나머지 24줄(분류 정보)은 배포 뒤 격리 SQL 로 넣어야
관리 화면이 온전히 보인다 — 전체 시드는 돌리지 않는다.

검증

  • 실 API 10 · 시드 맞물림 9 · 권한 동기화 5 · 소스 가드 다수
  • 가드는 전부 버그를 되살려 확인 — 옵션 추가 후 미등록 · 계약가 오기 · EV& 몫에 단가 ·
    모듈 이름 오타 · 시드에서 모듈 제거
  • 브라우저에서 확인: 관리 화면에서 320,000 입력 → 발주서에 잠긴 계약 줄로 반영 →
    다시 비우면 「선택 옵션」 태그와 함께 빈 금액 칸 + 배정 잠김 → 채우면 열림
  • 로컬 667/667 초록

**1. 옵션과 단가표를 빠짐없이 맞물린다**

`maker_price` 행이 **옵션마다 하나씩** 미리 있다(활성 옵션값 27개 전부 · 32행).
행을 만들거나 지우지 않는다 — 아무 코드로나 행을 만들 수 있으면 어느 선택에도 걸리지
않는 유령 줄이 쌓이고, 「이 옵션은 누가 하기로 했더라」를 표에서 답할 수 없게 된다.
`value_code` 에 외래키를 걸어 없는 옵션을 가리키는 행 자체가 생기지 않는다.

분류는 셋이다. **EVN 과 NONE 은 발주서에 안 실리는 결과가 같지만 뜻이 다르다** —
앞은 우리가 하는 일이고 뒤는 아무도 안 하는 일이다.
  · MAKER 15 — 특장사가 한다(계약가 8 · 미책정 7)
  · EVN    6 — 인도 뒤 EV& 가 직접(차량 트림 포함 — 계약 제7조: 베이스 차량은 갑이 지급)
  · NONE  11 — 「없음」·「추가없음」, 기본형에 포함된 여닫이 도어

**2. 입력 칸이 저절로 생긴다**

예전엔 계약에 없는 항목이면 관리자가 「+ 항목 추가」를 눌러 **품목명부터 타이핑**해야 했다.
무엇을 적어야 하는지 화면이 이미 아는데 사람에게 다시 묻던 셈이다(제보).

이제 고른 옵션 중 특장사 몫인데 계약가가 없으면 그 줄이 **금액만 빈 칸으로** 뜬다.
품목명·단위·수량은 옵션이 정하고 관리자는 금액만 채운다. 서버도 그렇게 받는다 —
품목명을 딴 것으로 보내도 옵션이 정한 이름이 남는다.

**3. 빈 금액으로는 배정되지 않는다**

0 원짜리 줄이 실린 발주서는 특장사에게 「무상으로 해 주기로 했다」로 읽힌다.
화면은 어느 줄이 비었는지 먼저 말하고 배정 버튼을 잠그며, 서버도 같은 것을 본다.

`unit_price` 를 NULL 허용으로 넓혔다 — 「계약에 값이 없다」와 「0 원에 하기로 했다」는
다른 말이라 0 으로 채워 두지 않는다.

**4. 관리 화면**(관리자 > 특장사 단가)

특장사를 고르면 그 계약의 표가 뜨고, 분류·단가·품목명·단위·수량을 **그 자리에서** 고친다.
저장 버튼을 따로 두면 고쳐 놓고 안 누른 채 떠나 옛 단가가 발주서에 나간다.
옵션과의 연결은 고칠 수 없다(행이 옵션에 매여 있다).

**5. `top_code` 를 NULL 대신 빈 문자열로**

Postgres 는 유일 인덱스에서 NULL 을 **서로 다른 값으로** 본다. NULL 로 두면
「같은 옵션·같은 탑에 단가는 하나뿐」이 그 줄들에는 걸리지 않아 같은 자리에 행이
여럿 생길 수 있었다(Prisma 도 복합 유일키 조회에 NULL 을 받지 않는다).

## DB — 더하기만 한다
`unit_price` NOT NULL 해제(넓히는 쪽) · `value_code` 외래키 추가.
`DROP` · `DELETE` · 컬럼 삭제 없음.

## 검증
- 실 API 10 · 시드 맞물림 가드 9(옵션 누락 · 유령 행 · 분류 · 0원 · 중복 · 계약가 대조)
- 가드는 버그를 되살려 확인했다 — 옵션 추가 후 미등록 · 계약가 오기 · EV& 몫에 단가
- 로컬 662/662 초록
**1. 없는 모듈을 요구하고 있었다**

새로 만든 「특장사 단가」 라우트가 `requirePermission('basedata.edit')` 를 걸었는데
실제 모듈은 `basedata.manage` 다. 없는 모듈을 요구하면 **아무도 그 기능을 못 쓴다** —
화면은 그럭저럭 그려지고 「권한 없음」만 나와 원인을 찾기 어렵다.

**2. 🔴 시드가 운영 모듈을 지우고 있었다**

`feature_module.csv` 에 11개뿐인데 운영에는 17개다. 시드는 이 표를 `deleteMany({})` 로
**통째로 비우고** CSV 대로 다시 만든다 — 그래서 로컬에서 시드를 한 번 돌렸더니
`basedata.manage` · `quote.edit` · `quote.delete` · `doc.send.email` · `doc.send.sign` ·
`notify.push` · `stats.own` · `stats.all` 이 **권한과 함께 사라졌고**, 옵션DB·무게상수·
영업 성과 탭이 통째로 없어졌다(그래서 찾았다).

운영 값을 그대로 옮겨 맞췄다. `access_control.csv` 도 마찬가지로 맞췄다 —
이미 없어진 `view.all` · `subsidy.manage` 를 가리키고 있어 시드가 외래키에서 멈췄다
(그 둘은 운영에도 없고 코드도 안 쓴다. 시험 주석에 「켜고 꺼도 아무 일 없었다」고 남아 있다).

⚠️ 이 시드를 **운영에서 돌리면 안 된다**. 모듈과 계정별 권한이 전부 지워진다.
   (memory/feedback_prod_seed 의 경고가 이 경우다)

**3. 되풀이되지 않게**

`permission-seed-sync.test.ts` — 라우트가 요구하는 모듈이 시드에 다 있는가,
권한 부여가 없는 모듈을 가리키지 않는가, 세 역할 모두 기본 권한이 있는가.
시드가 「지우고 다시 만든다」는 사실 자체도 검사에 박아 뒀다 — upsert 로 바뀌면
이 검사들이 지키던 것도 뜻을 잃으므로 그때 같이 고쳐야 한다.

두 버그를 되살려 빨간불을 확인했다(모듈 이름 오타 · 시드에서 모듈 제거).

**4. 관리 화면 마무리** — 「분류」 칸이 좁아 「특장사」가 「사」로 잘렸다. 칸을 넓혔다.
**1. `onDelete` 를 안 적어 migration 과 스키마가 어긋났다**

migration SQL 은 `ON DELETE RESTRICT` 로 만들었는데 스키마에는 안 적었다.
선택 관계(`OptionValue?`)의 Prisma 기본값은 **SetNull** 이라, 배포 전 검증이
「외래키를 지웠다가 다시 만든다」는 drift 로 잡았다(CI 에서 걸렸다).

Restrict 가 맞다 — SetNull 이면 옵션을 지웠을 때 `value_code` 가 NULL 인
**유령 줄**이 남아 어느 선택에도 걸리지 않는다. 옵션은 지우지 않고 `active` 로
끄는 것이 이 저장소의 방식이라, 지우기를 막는 쪽이 그 방식과도 맞는다.

**2. high 취약점 3건 — 이 변경과 무관하다**

`@xmldom/xmldom` · `multer` · `nodemailer`. 어제까지 초록이었고 이 브랜치는
의존성을 건드리지 않았다(`package.json` 무변경) — 그 사이 새로 공지된 것이다.
`npm audit fix` 로 **패치만** 올렸다(`--force` 없음 · `package-lock.json` 만 변경).
남은 6건은 moderate 라 검사 기준(high) 밖이다.
@Jay722J
Jay722J merged commit a8ec8f5 into main Sep 9, 2026
4 checks passed
@Jay722J
Jay722J deleted the feat/maker-price-admin branch September 9, 2026 02:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

발주서 금액: 계약가는 DB에서 자동 기입, 계약 밖 항목은 입력 칸이 저절로 생기게

1 participant