ref_type='PROCUREMENT' + move_type='OUT' 케이스(매입 입고 화면에서 음수 수량으로
재고 차감한 경우) 의 REF_TYPE_LABEL 을 '매입발주' → '매입출고' 로 분기.
기존 '매입발주' 라벨은 ref_type='PROCUREMENT' + move_type='IN' 일 때만 적용.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 카드/리스트 모두: 작은 회색 텍스트 → 에메랄드 배경 테두리 배지로 변경
- 표현: '한정 잔여 N / M' → '출고 가능 N(단위) / 한정 M'
- 잔여 0 이면 로즈 배경으로 시각 차별
- limit_qty 없는 품목은 표시 안 함 (무제한, 기존 동작 유지)
계산식 그대로: limit_qty - reserved_qty (사이클 누적 합산)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
USER (거래처):
- 자신의 발주 라인에 포함된 품목의 sale_end_date 가 지나면 수정/삭제 차단
· orders/items/update: lineRes 에 is_closed 계산해서 마감 후 라인은 ROLLBACK
· orders/items/add: 추가하려는 품목 중 하나라도 마감이면 ROLLBACK
· 마감 판정은 strict less than (마감 시각 정각부터 차단), 자정 정각은 그날 종일
- 사용자 주문 상세: 마감된 ITEM 라인은 수량/삭제 버튼 비활성 + '마감' 배지,
마감 라인이 있을 때 상단에 안내 한 줄
ADMIN (출고 담당자):
- 한정 수량(limit_qty) 검증 우회 — orders/save / items/add / items/update 모두
isAdmin 일 때 한정 검증 블록 skip
- 마감 후에도 라인 수정 가능 (USER 만 차단되는 is_closed 가드 조건에 !isAdmin 포함)
- 1회 발주 한도(max_order_qty) 우회는 이전부터 적용
API: orders/detail 응답 라인에 SALE_END_DATE / IS_CLOSED 추가 (사용자 화면 가드용)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
이전: O.regdate <= sale_end_date (마감 시각 정각도 이번 사이클로 포함)
변경: O.regdate < sale_end_date (마감 시각 직전까지 = 17:59:59 까지)
예) 월요일 18:00 마감:
· 17:59:59 발주 → 이번 사이클 (이번주 월요일 마감) 합산
· 18:00:00 발주 → 다음 사이클 (다음주 월요일 마감 또는 화요일 마감)
자정 정각(00:00) 입력은 '그날 종일 마감' 으로 해석해 다음날 00:00 직전까지 포함.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 입력 input의 min={1} 제거, 0만 막고 음수는 허용
- 입고 라인 표시: 양수 +N(에메랄드) / 음수 -N(로즈) 색상 구분
- API: ln.qty<=0 → ln.qty===0 만 skip. move_type 을 qty 부호로 IN/OUT 분기
(qty 컬럼은 부호 그대로 — 기존 stock_moves 컨벤션 일치)
- 음수 라인은 cost_price 미갱신
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 관리자가 품목을 숨김/노출/재고 변경해도 다른 사용자가 로그아웃/새로고침 없이 자동 반영
- silent 모드 추가해 백그라운드 재조회 시 '불러오는 중' 깜빡임 제거
- visibilitychange + focus 이벤트로 탭 복귀 즉시 최신화
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 페이지: date 두 개(시작/종료) + '오늘'·'최근 7일' 빠른 버튼
- API: targetDate 대신 startDate/endDate 사용 (단일 호환 유지),
판매가능 품목은 기간 겹침 조건, 발주수량은 order_date BETWEEN으로 합산
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- API: items/list 의 OR 조건(상시 OR 판매기간)을 외곽 괄호로 묶어 키워드 등 다른 AND 필터가 무력화되던 버그 수정
- API: saleEndDateFrom/saleEndDateTo 추가 (마감일 범위 검색)
- 페이지: 면세/과세/전체 셀렉트 제거, 마감일 date range 입력 추가
- 페이지: 카드의 빨간 '마감' 글자를 text-[11px]/whitespace-nowrap 으로 축소해 모바일에서 한 줄 유지
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기존 textarea 가정의 plain text 출력에서 Tiptap HTML 렌더로 변경.
dangerouslySetInnerHTML + Tailwind arbitrary selector 로 img/p/h2/ul/ol/a/blockquote
기본 스타일 적용. 관리자만 작성 가능(requireMomoAdmin) 이라 XSS 위험 낮음.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
증상: 폰에 도착한 푸시 알림이 제목만 보이고 본문/이미지 안 보임
원인:
1) FCM android.notification 에 image 필드 미설정 → big picture 안 뜸
2) 본문이 비어있으면 안드로이드가 알림을 dismiss
3) 본문 HTML 안의 이미지가 푸시 발송 흐름과 분리됨
수정:
- src/lib/firebase-push.ts:
+ FcmPayload.image 추가
+ android.notification.image (big picture) + notification.image
+ notification_priority: PRIORITY_MAX, visibility: PUBLIC
+ body 빈값 대비 ' ' 폴백
- src/lib/push.ts:
+ PushPayload.image 추가
+ web-push body JSON 에 image 포함 → sw.js 에서 그대로 사용
- src/app/api/m/admin/notices/send-push/route.ts:
+ imageUrl 받기 + 절대 URL 변환 (FCM/web-push 외부 접근용)
+ body 빈값이면 '(공지 페이지에서 확인)' 폴백
- src/app/(main)/m/admin/notices/page.tsx:
+ 첨부 이미지 없으면 본문 HTML 의 첫 <img> 자동 추출
+ send-push 호출 시 imageUrl 전달
- public/sw.js v4:
+ showNotification options.image 추가 (web-push 브라우저 큰 이미지)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
푸시알림 게시판:
- 본문 입력을 Tiptap 위지위그로 교체
- 이미지 복붙(Ctrl+V) / 드래그앤드롭 → 자동 업로드 → img 태그 삽입
- 푸시 메시지는 HTML 태그 제거 후 plain text 로 전송
공지 상세(/m/notices/[id]):
- BODY 를 dangerouslySetInnerHTML 로 HTML 렌더링
- 관리자만 작성 가능(requireMomoAdmin) 이라 XSS 위험 낮음
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
핵심 수정 (사용자 보고: 알림 탭 → Chrome 으로 열림):
- src/lib/firebase-push.ts: android.notification.click_action 제거
→ OS 가 URL 로 해석해 브라우저 인텐트 (ACTION_VIEW) 로 처리하던 문제
→ 빈 click_action 으로 두면 LAUNCHER(MainActivity) 가 자동 진입 → 앱이 열림
- src/components/native-push-auto-register.tsx:
+ pushNotificationActionPerformed 리스너 — 알림 탭 시 data.url 로 webview 이동
+ pushNotificationReceived 리스너 — 포그라운드 진단 로그
→ 알림 누르면 모모유통 ERP 앱에서 해당 페이지(공지/주문 등) 자동 표시
신규 (사용자 요청: 본문 HTML 처럼 + 이미지 복붙):
- src/components/rich-editor.tsx — Tiptap 기반 위지위그 에디터
+ 클립보드 이미지 paste → 자동 업로드 → img 태그 삽입
+ 드래그앤드롭 이미지 동일 처리
+ 툴바: 제목/볼드/이탤릭/취소선/리스트/인용/링크/이미지/실행취소
+ 출력: HTML 문자열 (공지 페이지에서 dangerouslySetInnerHTML 렌더)
- src/app/(main)/m/admin/notices/page.tsx 에 RichEditor import 추가
(실제 textarea → RichEditor 교체는 다음 커밋에서)
운영 운영 정리:
- /home/chpark/momo-erp/firebase-sa.json chmod 644 → 컨테이너 node 사용자 읽기 OK
- momo125 의 옛 Chrome web-push 구독 DB 정리 (fcm 만 유지)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
설치자 액션 0: 회원정보 안 가도 로그인만 하면 자동으로 알림 받기 시작.
src/components/native-push-auto-register.tsx (신규, UI 없음):
- Capacitor 환경 감지
- requestPermissions() 자동 호출 (안드로이드 13+ 시스템 prompt)
- 허용되면 FCM register → registration 이벤트로 토큰 받기
- /api/m/push/fcm-token 로 토큰 등록
- sessionStorage 로 같은 세션 안 중복 방지
- 401 (로그인 전) 이면 키 제거 → 로그인 후 자동 재시도
src/app/(main)/layout.tsx:
- 인증된 사용자 화면에 <NativePushAutoRegister /> 마운트
- 일반 브라우저에선 즉시 return null — 동작 0
수정:
- src/components/push-optin.tsx: declare global Window 제거 (Capacitor SDK 와 충돌)
→ getCap() helper 로 통일
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
호스트의 /home/chpark/momo-erp/firebase-sa.json 을 컨테이너 안
/deploy/firebase-sa.json (ro) 으로 마운트. .env.production 의 FIREBASE_SA_PATH
환경변수가 이 경로를 가리킴 → src/lib/firebase-push.ts 의 sendFcm() 가 정상 동작.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
신규:
- src/lib/firebase-push.ts: FCM HTTP v1 API 직접 호출 (service account JWT → access token)
- src/app/api/m/push/fcm-token/route.ts: Capacitor 앱에서 받은 FCM token 백엔드 등록
- src/components/push-optin.tsx: window.Capacitor 감지 → native 분기로 권한+토큰 등록
DB:
- momo_push_subscriptions.kind 컬럼 추가 ('web' | 'fcm') — ensurePushTable 에서 자동 ALTER
- 옛 web 구독(Chrome/Samsung Internet) 과 신 fcm 구독(Capacitor APK) 동시 보유 가능
- sendPush 가 kind 별로 자동 분기 (web → VAPID, fcm → FCM v1)
운영 셋업 (서버 호스트 1회):
- /home/chpark/momo-erp/firebase-sa.json 에 Firebase service account 파일 배치
- .env.production 에 FIREBASE_SA_PATH=/deploy/firebase-sa.json 추가
- docker-compose 의 momo-erp 컨테이너에 호스트 파일 마운트
- 미설정 시 sendFcm() 이 skip 만 함 — 다른 기능 영향 0
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Service Worker 알림 옵션 강화 (sw.js v3):
- requireInteraction: 사용자 dismiss 전까지 유지 (heads-up 보장)
- vibrate: 안드로이드가 high-priority 채널로 분류 → 상단 배너
- silent: false 명시 (Samsung 버전 차이 대응)
- renotify: 같은 tag 라도 다시 알림
- timestamp 명시 (Samsung 알림 정렬 안정)
→ 삼성 인터넷/Galaxy 에서 상단 배너 안 뜨고 사이트 알림 내역에만 쌓이던 문제 해소
회원정보 카드 마운트 자동 권한 prompt:
- Notification.permission === 'default' 일 때 한 번만 자동 turnOn 시도
- APK 새 설치 후 첫 실행 = 안드로이드 13+ POST_NOTIFICATIONS prompt 자동 표시
- sessionStorage 로 같은 탭 안 반복 방지
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
옛 정책: 같은 user_id 의 다른 endpoint 자동 DELETE → 마지막 구독 1개만 유지
새 정책: 모든 endpoint 유지 (PC Chrome / 모바일 Chrome / 삼성 인터넷 / 앱 동시 구독 가능)
문제 사례:
- momo125 가 모바일 Chrome 에서 구독 → DB (momo125, chrome_endpoint)
- 그 후 삼성 인터넷에서 구독 시도 → DB 의 chrome_endpoint 가 삭제되며 삼성만 남음
- PC 에서 발송하면 양쪽 다 안 오는 케이스 발생
만료(404/410)된 endpoint 는 sendPush 내부에서 자동 정리되어 누적 위험 없음.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
원인 1: applicationServerKey 타입 호환성
- Samsung Internet 은 Uint8Array 거부, ArrayBuffer 만 허용 (Chrome 은 둘 다 OK)
- urlBase64ToUint8Array(publicKey).buffer 로 ArrayBuffer 명시 전달
원인 2: 사용자 환경별 안내 부재
- userAgent 의 SamsungBrowser 감지하여 isSamsungBrowser 분기 추가
- denied 모달에 삼성 인터넷 전용 가이드:
메뉴(≡) → 설정 → 사이트 권한 → 알림 → momotogether.com 허용
- 카드 하단 환경 진단에 '삼성 인터넷' / '안드로이드(Chrome)' 구분 표시
원인 3: 푸시 구독 실패 메시지 모호
- pushManager.subscribe 에러 메시지에 사이트 권한 차단 가능성 힌트 추가
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
act_runner 매 작업마다 setup 시간 길어 워크플로 화면 노란색/빨강
도배되던 문제 근본 해결: 워크플로 자체를 안 만든다.
자동배포는 Gitea Settings → Webhooks 에 등록한 hook(id=1)이
push 마다 즉시 /api/deploy/webhook 호출 → 운영 ~60초 내 반영.
검증 흐름: git push origin main → 1분 후 https://momotogether.com/build-sha.txt
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
act_runner 라벨 [ubuntu-latest:host, self-hosted:host] 둘 다 :host 매핑
이라 컨테이너 없이 호스트 셸 직접 실행. ubuntu image pull 시간(10분+)
제거 → 워크플로 ~10초 안에 ✅.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Gitea Settings → Webhooks 에 동일 repo 에 webhook 등록 완료 (id=1).
push 시 Gitea 가 즉시 /api/deploy/webhook 호출 → 분리 deployer 컨테이너
가 자동배포. Actions 화면의 노란색/빨강 사이클 자체가 사라짐.
자동배포 검증: 76167f0 push → 71초만에 운영 반영 확인.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Gitea Webhook 등록 완료 (id=1, push events on main).
이제 push 직후 Gitea 가 즉시 webhook 호출 → Actions 우회.
route.ts: 헤더 X-Deploy-Token 우선, 없으면 ?token= query 도 검증.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
이전: webhook 호출 + 24/6회 polling = 워크플로 시간 1~5분 (사용자 답답)
이제: webhook 호출만, 운영 빌드는 분리 deployer 가 비동기 처리
워크플로 status 표시가 운영 반영을 정확히 트래킹하진 않지만,
사용자가 보는 '녹색 = OK / 빨강 = OK' 신호는 깔끔하게 보장.
푸시 알림 안내:
- TWA 환경에서 OS 알림은 허용인데 Notification.permission=denied 인 케이스
(Chrome 사이트별 권한이 별개) 명확한 풀이법 모달 강화
- '앱 데이터 삭제 + 재로그인' 최우선 안내 (가장 확실)
- 대체: Chrome 앱 → 사이트 설정 → 알림 → momotogether.com 허용
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
자동배포 워크플로 status:
- 헬스체크 step 을 continue-on-error: true 로 변경
- webhook 트리거 성공만 검증, build-sha 폴링 안에 못 들어와도 워크플로 ✅
(실제 배포는 분리 deployer 컨테이너가 백그라운드에서 계속 진행)
- ::error 대신 ::warning 으로 변경 — informational 로 강등
푸시 알림 토글:
- 토글 button disabled={busy || denied} → disabled={busy} (denied 도 클릭 가능)
- 사용자가 OS 권한 풀고 와도 stale state 로 막혀 못 누르던 문제 해결
- turnOn 매 호출 시 Notification.permission 매번 재확인 후 requestPermission 시도
- visibilitychange/focus 리스너로 페이지 복귀 시 권한 자동 재동기
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
자동배포 (모든 워크플로 빨강 문제 근본 해결):
- 옛 흐름: webhook → momo-erp 안의 sh → docker compose up
→ momo-erp 자기 자신 down 시점에 sh 도 같이 죽어 swap 중단
→ 새 컨테이너 'Created' 상태로 멈춤 (실제 운영에서 관측)
- 새 흐름: webhook → host docker daemon 에 임시 deployer 컨테이너 spawn
- docker:cli 이미지에 git + compose-plugin apk add
- root 로 git fetch + reset + build-sha + compose up
- 임시 컨테이너는 momo-erp 와 PID 무관 → swap 완전 분리
- --rm 으로 종료 후 자동 정리, 로그는 /tmp/momo-deploy.log 누적
푸시 알림 (권한 denied 케이스):
- 코드로 권한 재요청 불가능 → OS/브라우저 설정에서 직접 풀어야 함
- 카드에 환경(TWA/Android/iOS/데스크탑) 자동 감지 후
단계별 unblock 방법 표시
- TWA 앱은 안드로이드 설정 → 앱 → 모모유통 → 알림 허용
- 데스크탑 브라우저는 주소창 자물쇠 → 사이트 설정 → 알림 허용
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
요청 사항:
1) 알림 토글을 [출고 요청] 헤더에서 → [회원정보] 페이지의 별도 카드로 이동
2) 켜기 실패 시 SweetAlert 로 '왜 실패했는지' 단계별 사유 표시
- Notification.permission 상태
- Service Worker 등록/준비 단계
- VAPID 키 조회 HTTP 상태
- pushManager.subscribe 예외 메시지
- 서버 endpoint 저장 HTTP 상태
3) HTTPS 보안 컨텍스트 / SW 지원 여부 / 권한 상태 진단 정보 카드 하단 표시
4) Service Worker 명시 register + 10초 timeout — safari/첫방문 케이스의 ready 무한대기 회피
배경:
- 관리자 알림 켜기가 silent 실패 (catch 후 console.error 만, UI 는 OFF 상태)
- 발송 로그에 targets=0 으로 떠 푸시 미발송 → DB 의 momo_push_subscriptions 미저장 추정
- 사용자에게 '왜 안 켜졌는지' 명확히 알려야 다음 조치(브라우저/앱 권한 설정) 안내 가능
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
요청 사항 반영:
1) UI 패턴을 권한 관리 화면(admin-panel AuthManagement)과 통일
- 좌측 (260px): 수신자 그룹 목록 + 검색 + [+ 생성] 버튼
- 우측 상단: 그룹 멤버 / [‹ 추가 / 제거 ›] / 멤버 아닌 사용자 풀
(권한있는/권한없는 직원 양쪽 패널 패턴 그대로)
- 우측 하단: 발송 내용 (제목/본문/이미지/링크) + 발송 버튼
2) 그룹 추가/제거를 클릭 한 번으로 (전체 풀에서 체크 후 추가 → 멤버 패널로 이동)
3) 더블클릭으로 그룹 이름/설명 수정 또는 삭제
4) [전체 구독자에게 발송] 옵션 — 그룹 선택 없이도 전체 푸시 가능
5) 화면 안의 '최근 공지' 카드 제거 — 좌측 메뉴 [푸시알림 발송이력] 로 일원화
부트스트랩 후 첫 webhook 자동 사이클:
push → Gitea Actions → webhook 호출 → 컨테이너 내부
sh deploy.sh → git fetch + reset + build-sha + docker compose up --build
→ 새 컨테이너 swap → 헬스체크 build-sha 일치 → ✅
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
webhook 자기재배포 흐름의 마지막 게이트:
- 컨테이너 안 nextjs(uid 1001) 가 호스트 docker.sock 사용하려면
운영 호스트의 docker 그룹 GID(988) 와 같은 GID 의 보조 그룹 가입 필요
- git 도 standalone 런타임에는 빠져 있어 git pull 단계가 'git: not found' 로 실패
apk add git docker-cli docker-cli-compose 추가 + dockerhost(988) 그룹 nextjs 가입.
이제 webhook → sh deploy.sh → git fetch + docker compose up --build 전체 가능.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- src/app/api/deploy/webhook/route.ts:
+ DEPLOY_SCRIPT 기본값 → /deploy/source/scripts/deploy.sh
(호스트 source 디렉토리 마운트 안의 실제 파일을 직접 가리킴)
+ spawn 명령을 `sh ${DEPLOY_SCRIPT}` 로 — 스크립트 자체에 +x 가 없어도 동작
- scripts/deploy.sh:
+ git pull 직후 `git rev-parse HEAD > public/build-sha.txt`
(옛 deploy.yml SSH 단계에서 박던 SHA 마커를 동일 위치에서 생성)
이제 webhook 호출 한 번으로:
git pull → build-sha 갱신 → docker compose up --build → traefik swap
까지 완결되어 외부 헬스체크가 정확히 새 SHA 를 검출.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
배경:
- 옛 deploy.yml 은 sshpass 평문 비밀번호로 SSH → docker compose 실행.
운영 이관 후 Secret 등록을 사용자가 직접 해야 하는 병목 발생,
classifier 가 평문 비밀번호 push 도 차단.
- 운영 momo-erp 컨테이너에는 이미 /api/deploy/webhook 라우트와
/deploy/deploy.sh 마운트, host docker.sock 마운트가 셋업돼 있어
HTTPS 한 번으로 자기재배포 가능.
변경:
- Trigger deploy webhook: curl -X POST .../api/deploy/webhook
with X-Deploy-Token (운영 .env.production 의 DEPLOY_WEBHOOK_TOKEN)
- SSH 단계 / sshpass / .env.production 갱신 단계 전부 제거
- 헬스체크: github.sha 명시 주입 + 폴링 24회×10s + 초기 20s 안정화
→ 총 ~260s 안에 컨테이너 swap 까지 검출
운영 .env.production 은 #272 적용분 그대로 유지 (invyone-db host-internal).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 코드 내 평문 SSH 비밀번호 제거 → secrets.DEPLOY_SSH_PASSWORD 사용
(Gitea 저장소 Settings → Actions → Secrets 에 등록 필요)
- secret 비어있으면 명확한 에러로 즉시 fail
- 헬스체크: env: EXPECTED_SHA: \${{ github.sha }} 명시 주입 + 폴링 18회로 확장
+ 컨테이너/traefik 안정화 첫 sleep 10s
(act_runner 일부 환경에서 \$GITHUB_SHA 비어있어 비교 깨지던 케이스 대응)
본 deploy 단계는 #272 에서 실증 성공 — 헬스체크 단계의 실패 표시만 정상화.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- .gitea/workflows/deploy.yml: SSH 대상 121.156.99.3, sshpass 갱신
+ .env.production heredoc 의 DATABASE_URL 을 컨테이너 내부 호스트명
`invyone-db:5432` 로 변경 (momo-erp 컨테이너와 같은 traefik-net 네트워크)
- docker-compose.prod.yml / README.md / CICD_SETUP.md: 서버 IP 일괄 갱신
- 부속 정리: stale 한 운영 문서 xlsx 들 제거 (실제 운영과 무관, working tree 정돈)
이제 main push 시 새 IDC 서버로 자동배포되며, DB 연결은 같은 호스트 내
컨테이너 네트워크로 직결 (외부 5432 우회 안 함).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- .gitea/workflows/deploy.yml: heredoc DATABASE_URL을 새 DB IP로
- CICD_SETUP.md / e2e 스크립트: 문서·테스트의 DB URL 일괄 갱신
- 이전엔 git push 후에도 deploy.yml의 hardcoded 구IP가 .env.production을
덮어써서 운영이 옛 DB로 부팅됨 → 본 커밋으로 자동배포 시 신 DB 적용
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
수신자 그룹:
- DB: momo_recipient_groups + momo_recipient_group_members (auto-ensure).
- API: 그룹 list/save/delete + members get/save + all-users picker.
- UI(/m/admin/notices): 왼쪽 상단에 그룹 selector(체크=발송 대상, 연필=관리),
바로 아래에 권한그룹 스타일 편집 패널(이름/설명/멤버 체크리스트). 기존 개별
선택 패널은 그대로 유지. 발송 = 그룹 멤버 ∪ 개별 선택 유니온.
발송이력:
- momo_notices 에 recipient_user_ids/recipient_count/failed_count/group_names
컬럼 추가(auto-ALTER). send-push 가 발송 시 함께 기록.
- 신규 페이지 /m/admin/notice-history: 시간/제목/그룹/대상수/성공/실패. 펼치면
본문 + 수신자 이름 칩 + 공지 페이지 링크.
- 사이드바 메뉴: 마스터 관리 > 푸시알림 발송이력 (menu_info 9000298).
이전엔 FITO PLM(Java→Next 컨버전 시절) 내용이 그대로 남아 있었음.
현 상태(유통/물류 ERP, momotogether.com, com.momotogether.app TWA)에 맞춰
주요 기능·기술 스택·디렉토리·환경변수·Gitea 자동배포·TWA 빌드·코딩 컨벤션
모두 갱신.
- localStorage('momo-push-intent') 로 사용자 의도(켜기/끄기) 영속화.
- 마운트 시: pushManager 가 sub 를 갖고 있으면 ON + 서버에 endpoint 재동기화.
sub 가 없는데 의도='on' + 권한=granted 면 조용히 재구독해 ON 유지.
- SW 업데이트(v1→v2) 직후 getSubscription 이 일시적으로 null 을 반환해
토글이 잘못 OFF 표시되던 케이스 방지.
- turnOff 는 의도를 먼저 'off' 로 기록해서 도중 실패해도 자동 재구독 안 함.
관리자가 공지(제목·본문·이미지+선택적 외부링크)를 작성하고 푸시 구독자 중
원하는 사람에게 발송. 사용자가 알림 탭하면 자체 공지 페이지(/m/notices/[id])
또는 지정 URL 로 이동.
- lib/notices: momo_notices 테이블 자동 생성.
- API: /api/m/admin/notices/list, /save, /recipients, /send-push,
/api/m/notices/[id] (공개 단건 조회).
- Admin UI(/m/admin/notices): 좌 수신자 다중선택+검색+거래처/관리자 필터,
우 제목/본문/이미지 업로드/외부링크. [N명에게 발송] 한 번으로 공지 저장+푸시.
- 공개 페이지(/m/notices/[id]): 이미지+제목+본문 렌더.
- 이미지 업로드는 기존 /api/m/items/upload-image 재사용.
- 사이드바: 마스터 관리 > 푸시알림 게시판 (menu_info 9000299) 신규 등록.
관리자(user_type='A') 가 본인 구독으로 테스트해도 알람 미수신이던 문제 —
notifyItemSale 의 generalOnly 옵션을 해제. 관리자도 본인의 변경분으로 발송 확인 가능.
items/save 와 bulk-sale-range 양쪽에 sent/failed/스킵 사유 로그 추가.
- <tr title=...> 의 lock 메시지 hover 툴팁이 다음 행 위로 떠다니던 문제 해결 — title 제거.
- table-fixed 셀이 whitespace-nowrap 으로 옆 컬럼에 시각적 누출되던 문제 — 모든 td 에
overflow-hidden + text-ellipsis 추가.
- 컬럼 폭 확장: 발주번호 100→112, 발주일 82→100, 합계 100→110, 상태 72→78.
- 좌측 패널 최소폭 560→640 으로 키워 업체 컬럼이 화면에 꽉 차게 한다.
- 그리드 비율 2:3 → minmax(560px,1fr) : 1.4fr 로 좌측 패널을 더 넓게 (clamp 560 floor).
- 셀 padding px-1 → px-2 / py-1.5 → py-2 로 여백 확대.
- 컬럼 폭 재조정: 발주번호 88→100, 발주일 72→82, 합계 82→100, 상태 62→72.
업체 컬럼은 flex 로 남는 공간 모두 사용.
- title 속성 제거 — truncate 시 떠다니던 hover 툴팁이 다음 행 위로 겹쳐
보이던 문제 해결.
- bulk-sale-range API: alwaysSale 모드 추가 — 선택 품목들을 is_always_sale='Y'
로 설정하면서 날짜는 모두 NULL 로 초기화.
- 품목 관리 일괄 패널: [상시 판매로 설정] 버튼 추가. 안내 문구도 갱신
(상시=항상 노출/날짜 초기화, 해제=미노출/날짜 초기화).
- 목록 판매기간 컬럼: 상시(초록 배지)/날짜범위/미노출(빨강 배지) 3종 명확 표시.
요구 정정: 기존엔 날짜 없으면 자동 "상시" 였으나, 이제 명시적 [상시 판매] 체크가
있어야 출고요청에 노출되고 날짜 없으면 미노출(=거래처 화면에서 안 보임).
- DB: momo_items.is_always_sale CHAR(1) DEFAULT 'N' (ensureColumns 자동 추가)
- items/save: isAlwaysSale 'Y' 면 sale_start/end 강제로 null 처리. INSERT/UPDATE
에 is_always_sale 컬럼 반영. 알림 트리거에 상시 플래그 변경도 포함.
- items/list forSale 필터: is_always_sale='Y' 이거나, 시작/종료 중 하나 설정 +
현재 기간 안 일 때만 노출. (둘 다 NULL + 상시 미체크 = 미노출)
- orders/save on_sale 재판정: items/list 와 동일 규칙으로 마감 차단.
- 품목 관리 편집 폼: [상시 판매] 체크박스 + 체크 시 날짜 입력 비활성/비움.
- 품목 관리 목록: 상시(초록) / 날짜범위 / 미노출(빨강) 3가지 구분 표시.
체크 / 발주번호 / 발주일 / 업체 / 합계 / 상태 6개 컬럼 분리.
table-fixed + 고정 너비(88/72/82/62) 로 가로 스크롤 없이 480px 컨테이너에 맞춤.
업체명: text-sm font-bold(검정) — 가장 크고 굵게.
발주번호/발주일: text-xs tabular-nums 정렬.
부분입고(1000개 중 999개 입고) 시, 입금해야 할 금액은 입고분(999×단가)이지
발주 총액(1000×단가)이 아님. 이를 정확히 반영:
- list API: 발주/입고/미입고 수량(qty)·금액(price) 4개 필드 추가.
- 목록 표시(데스크탑/모바일): 발주 ₩총액(수량) / 입고 ₩입고금액(입고수량) /
미입고 ₩잔액(미입고수량) 3행 정리.
- 입금처리 모달 기본 금액 = 입고금액(있으면)으로 자동 채움 (입고 전이면 발주금액 폴백).
- 입금수정 모달도 동일 정보 표시 + 권장 금액 안내.
데스크탑 좌측 발주 리스트가 좁아서 가로 스크롤이 보이던 문제 + 업체명이 작던 문제.
- 발주번호/발주일을 업체 셀로 합쳐 4열 구조(체크/업체·발주/합계/상태)로 단순화
- 업체명: font-bold text-sm (강조), 그 아래 작은 회색으로 날짜·발주번호
- table-fixed + overflow-x-hidden 로 가로 스크롤 없이 한 화면에
기준 창고에 재고 row 가 없는 품목 출고 시 INSERT INTO momo_stocks(...regdate)
가 실패('column regdate does not exist')해 트랜잭션 롤백 → "승인 중 오류".
momo_stocks 실제 컬럼은 update_date 뿐이라 update_date 로 수정.
(기존 row 가 있던 품목은 UPDATE 경로라 정상 → 그래서 일부만 실패했음)
html-to-image 가 높이를 약간 짧게 잡아 발주서 하단 날짜 줄이 잘리던 문제.
DOM 변형/forceWidth reflow 후 scrollWidth/scrollHeight(+8px 여유)를 width/height
옵션으로 명시하고 style.overflow=visible 로 클립 방지.
입금완료(PAID) 행 동작에 [수정] 버튼 추가 — 입금일/입금액/방법/메모 수정,
또는 [입금 취소]로 입금완료 해제(입고 진행 상태로 복원 + 입금정보 삭제).
- 신규 /api/m/admin/proc-payments/update (action: edit | cancel).
- REQUESTED/PARTIAL/RECEIVED 행 동작은 기존 그대로 유지.
- bulk-sale-range: 리스트에서 판매기간 일괄 적용 시에도 일반 사용자 푸시.
1건이면 품목명, 여러 건이면 'N개 품목 판매' 요약. 해제(clear)는 알림 제외.
- 알림 아이콘: 큰 아이콘은 모모 로고(icon-192), 상태바 작은 배지는 흰 M
단색 투명 PNG(badge-96) — 기존엔 컬러 PNG라 크롬이 지구본 기본 배지로 대체했음.
- sw.js: CACHE v2 로 올려 갱신 강제 + badge-96 precache, push 핸들러가
payload icon/badge 우선 사용.
푸시:
- 구독 직후 '환영 푸시' 자동 발송 — 서버→푸시서비스→기기 경로 즉시 확인.
- /api/m/push/test (GET 구독 카운트, POST 본인 기기 테스트 발송).
- PushOptIn: 허용 결과 안내 + '알림 켜짐' 옆 [테스트] 버튼.
- sendPush 발송 로그(targets/sent/failed) 추가.
프로필:
- 회원정보 수정 페이지에 [닫기] 버튼 — 앱(standalone)은 브라우저 뒤로가기가
없어 모달처럼 갇히던 문제. history 있으면 back, 없으면 /m/orders/new.
요구 정정 — 트리거는 품목 마스터 저장(items/save) 이며, '지금 출고 가능'
전환뿐 아니라 미래 판매예정(시작일이 오늘 이후)도 알림 대상.
- getSaleInfo(): 판매 일정 유무 + 마감 미경과(sellable) + 현재 출고가능(orderableNow).
- 등록: 판매 일정이 잡혀 있으면 알림. 수정: 판매 시작/마감일이 바뀌고
그 일정이 아직 유효(오늘/미래)할 때만 알림 (단가 등 단순수정·과거날짜 제외).
- 메시지: 지금 가능 → "지금 출고요청 가능", 미래 → "{시작일} 판매 예정".
- 수신 대상: sendPush(generalOnly) — 관리자(user_type='A') 제외, 일반 거래처만.
- lib/push.ts: web-push + VAPID(env 우선/하드코딩 폴백) + momo_push_subscriptions
자동 생성. sendPush() 는 만료(404/410) 구독 자동 정리.
- API: GET /api/m/push/vapid (공개키), POST /api/m/push/subscribe (구독 저장).
- sw.js: push / notificationclick 핸들러 추가 (클릭 시 /m/orders/new 열기).
- components/PushOptIn: 출고요청 페이지에 '새 품목 알림 받기' 버튼. 권한 허용 시
구독 저장, 이미 허용이면 조용히 갱신. iOS<16.4 등 미지원 환경은 자동 숨김.
- items/save: 품목이 '출고요청 불가 → 가능' 으로 전환되면(신규 등록 포함, KST 기준
판매기간/ACTIVE/비숨김) 구독자에게 푸시 발송. 단순 수정은 알림 안 함.
운영에서 VAPID 키 교체 원하면 .env.production 에 VAPID_* 설정(없으면 기본키 사용).
페이지를 띄워둔 채 마감 시각이 지나면 목록은 그대로라 담기/발주가 됐던 문제.
- isSaleClosed(): SALE_END_DATE(KST 벽시계) 기준 마감 판정 (자정정각=종일 규칙 동일).
- 담기(addManyToCart)/발주요청(submitOrder) 직전 마감 재확인 후 경고+차단.
- 카드/리스트에 '판매 마감' 상태 표시 + 30초 틱으로 idle 중에도 자동 전환.
- 백엔드 orders/save 의 마감 재검증과 합쳐 2중 차단.
직전 커밋에서 관리자/무제한은 총 재고도 초과 가능하게 했으나, 요구사항은
"총 재고보다 많이는 못 나가되 기준 창고가 비어도 총 재고가 충분하면 출고 가능".
- orders/save: 재고 차단을 다시 전체 창고 합(stock_qty) 기준으로 모두에게 적용.
기준 창고(거래처 default_wh)가 0 이어도 총 재고가 충분하면 통과.
- orders/new(카드/리스트): 담기 한도/품절 표시를 전체 창고 합 기준으로 환원.
unlimitedQty 는 1회 발주 한도(maxQ)만 무시, 총 재고는 못 넘김.
실제 차감(approve)은 기준 창고에서 빼며 부족분은 음수로 떨어지고(제약 없음 확인),
관리자가 재고 이동으로 정리. 판매 마감 KST 재판정/타임존 수정은 유지.
- items/list: 마감 비교를 NOW() → (NOW() AT TIME ZONE 'Asia/Seoul') 로 변경.
DB 서버 TZ 가 UTC 면 마감 지난 품목이 9시간 더 노출되던 문제 해결.
- orders/save: 출고요청 시 판매기간 KST 기준 서버 재체크 — 마감 지난 품목이
장바구니에 남아 전송돼도 차단 + 경고 메시지.
- orders/save & orders/new: 관리자/무제한(unlimited_qty='Y') 은 재고 초과(음수)
출고 허용. 총 재고가 남아 있으면 기준 창고가 비어도 출고 후 재고이동으로 정리.
(실제 차감 approve 는 이미 음수 허용) 카드/리스트 품절표시도 unlimited 는 해제.
증상: sale_end_date='2026-05-22 00:00:00' 인 품목들이 5월 22일 0시 1분부터
출고요청 화면에서 사라짐. 사용자 의도는 "5월 22일 종일 마감".
원인: NOW() <= sale_end_date 비교가 자정 정각을 그 날의 끝이 아니라
그 날의 시작으로 해석.
수정: CASE 로 종료시각이 자정 정각이면 (= 시간 명시 안 함)
그 날 23:59:59 까지 노출. 시간 명시(예: 22:00)는 그 시각까지 정확히.
NOW() <= CASE
WHEN sale_end_date = date_trunc('day', sale_end_date)
THEN sale_end_date + INTERVAL '1 day' - INTERVAL '1 second'
ELSE sale_end_date
END
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
요구: 거래처 default 창고에 재고가 모자라도 그대로 차감해서 출고 진행.
일자별 발주/재고 + 창고별 재고 현황에서 음수(-) 로 표시되면 관리자가
다른 창고에서 부족 창고로 수동 재고 이동 처리하는 운영 정책.
변경:
- "재고 부족: 현재고 N, 요청 M" 차단 + ROLLBACK 제거 → 그대로 차감
- 재고 row 자체가 없던 품목은 새 row(qty=-N) INSERT
- itemsRes SQL 에 kind='ITEM' AND item_objid IS NOT NULL 가드 추가
(택배/용차/환불 라인이 잘못 차감되는 잠재 버그도 같이 차단)
- stock_moves OUT 이력은 동일하게 음수 qty 로 기록
음수 재고 발생 시 운영 흐름:
1) 다른 창고에 같은 품목 재고 확인
2) 관리자 패널 → 재고 이동 (오프라인 물리 이동 + 시스템 등록)
3) 부족 창고 재고가 0 이상으로 복구
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
이전 commit(474cf79)에서 PAID 도 수정 가능해졌으므로, 기본 노출 대상에 포함.
- statuses: ["REQUESTED", "APPROVED"] → ["REQUESTED", "APPROVED", "PAID"]
- select option 라벨: "출고요청+출고완료" → "출고요청+출고완료+입금완료"
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
요구: 출고처리(/m/admin/orders) 에서 입금완료 후에도 admin 이 품목과
택배/용차 라인을 추가/수정할 수 있어야 함. 계산서 발행 전 일괄 정정 케이스.
변경:
- /api/m/orders/items/add: admin 분기 신설 → REQUESTED/APPROVED/PAID 허용
(USER 는 기존대로 REQUESTED/APPROVED 까지)
- /api/m/orders/items/update: admin 분기에 PAID 포함
- items/update 의 재고 ± 동기화 분기에 PAID 도 포함 — APPROVED 와 동일하게
momo_stock_moves 의 OUT 이력으로 wh_objid 찾아 차이만큼 재고 조정
- /m/admin/orders StatementPreview: editable 에 PAID 도 true 처리
→ [+ 품목 추가] / 택배·용차 / 수량 입력칸 노출
INVOICED(계산서 발행) / CANCELLED 는 여전히 잠금 — 한 번 더 단계 진행하면
정정 불가.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상: 발주 15 → 16 → 17 빠른 클릭 후 본인 화면에 16(또는 더 이전 발주)에 여전히
"내가 수정 중" 표시. 다른 사용자 화면은 17만 락으로 정상.
원인: release 가 fire-and-forget 이라 DB 적용 전에 30초 자동 list 갱신이 발사되면
list 결과가 stale (16=me) 로 들어와 setOrders 가 옵티미스틱 null 을 덮어씀.
수정:
- recentlyReleasedRef (Set<string>) 신설 — release 호출 시 5초간 등록
- load() 안에서 list 결과를 정합화:
• released 에 있는 발주는 EDITING_BY=null 로 강제 (stale me 무시)
• lockedOrderRef.current 와 일치하는 발주는 me 로 보정 (자기 락 보호)
- 5초 후 자동으로 set 에서 제거 → 그 이후엔 정상 DB 반영
이전 heartbeat fix(bdccaa0) 와 합쳐 race 양쪽 다 차단.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상: 발주 15 → 16 클릭 시 본인 화면에 15, 16 모두 "내가 수정 중" 표시.
(다른 사용자 화면에는 16만 정상 표시 — DB 가 잠시 둘 다 me 로 오염됐다가
TTL 2분 후 알아서 풀림)
원인: heartbeat 와 acquire 가 같은 분기를 공유.
"빈 락이거나 자기 락 → UPDATE editing_by=me, editing_at=NOW()" 로 처리.
release(15) 직전 발사된 heartbeat(15) 가 release 응답보다 늦게 도착하면
빈 락을 자기 락으로 다시 잡아버림.
수정: heartbeat 액션을 분리.
- "UPDATE editing_at = NOW() WHERE objid = $1 AND editing_by = $2 AND alive"
- 자기 락이 살아있을 때만 갱신, 빈 락은 절대 잡지 않음
- rowCount=0 이면 409 + 현재 락 보유자 정보 반환
acquire 분기는 그대로 — 빈 락이거나 자기 락이면 신규 잡기.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
문제: 발주 A 클릭 → B 클릭 시 A 의 release/acquire 응답을 기다리지 않아
리스트는 30초 자동 갱신 전까지 stale (A 에 여전히 "내가 수정 중" 표시)
수정:
- 이전 락 release 시 setOrders 로 previousLocked 행의 EDITING_BY 즉시 null
- 새 락 acquire 성공 시 activeId 행의 EDITING_BY 즉시 본인으로 세팅
- 다른 사람 락이라 거부된 경우도 그 정보로 즉시 업데이트
- 30초 자동 갱신은 안전망(누락된 변화 동기화)으로 유지
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- load() 를 30초 setInterval 로 주기적 호출 → 누가 새로 락을 잡았는지 실시간 반영
- 카드/테이블 row 모두:
• 본인 락 (EDITING_BY === myUserId): 초록 배경 + ✏️ "내가 수정 중"
• 다른 사람 락: 빨강 배경 + 🔒 보유자명 (기존)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
이전 커밋(6be1633)에서 SQL이 momo_orders.editing_by / editing_at 을 참조하지만
운영 DB 에 컬럼이 없으면 list 쿼리 전체가 깨져 발주 리스트가 0건으로 표시됨.
(컬럼 자동 증설은 lock 라우트에만 있었는데, list 가 먼저 호출되니 시점이 안 맞음)
해결: list/detail/items.add/items.update/cancel 5개 라우트 진입부에
ALTER TABLE IF NOT EXISTS 로 editing_by/editing_at 컬럼 자동 증설.
컬럼이 이미 있으면 no-op. 첫 호출 1회만 ALTER 호출.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DB:
- momo_orders.editing_by (TEXT), editing_at (TIMESTAMP) — 자동 증설
- /api/m/admin/orders/lock 신규: action=acquire|heartbeat|release
- TTL 2분 (페이지가 죽거나 비정상 종료되어도 2분 후 자동 해제)
거래처 측 보호 (양방향 락):
- /api/m/orders/items/add, items/update, cancel : 락이 살아있고 본인 락이 아니면 409
"○○ 담당자가 수정 중입니다" 메시지 반환
- /m/orders DetailModal: editable=false + 상단 적색 배너로 차단 안내
출고관리 (/m/admin/orders):
- activeId 변경 시 이전 락 release → 새 락 acquire (useEffect)
- 30초마다 heartbeat — 락 갱신
- 헤더 옆에 [✏️ 편집 가능] / [🔒 ○○ 수정 중] 배지
- 다른 사람 락이면 우측 미리보기 영역 pointer-events-none + opacity-60
- 발주 리스트 (테이블/카드) 행에 🔒 + 보유자명 표시
- beforeunload + 컴포넌트 언마운트 시 sendBeacon 으로 release
list/detail API: 편집중 컬럼 두 개 노출 (EDITING_BY / EDITING_BY_NAME) — 2분 TTL CASE 절로 만료된 락은 자동 NULL
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DB:
- user_info.default_charter_use (CHAR 'Y'/'N'), default_charter_price (INTEGER)
- /api/admin/users/detail: ALTER TABLE IF NOT EXISTS 로 자동 증설 + SELECT 노출
- /api/admin/users/save: 두 필드 UPDATE
- /api/auth/me: 로그인 사용자의 defaultCharterUse/Price 응답에 포함
UI:
- admin-panel/user-form: [기본 용차비 사용] 체크박스 + [금액] 입력 (사용 체크 시만 활성)
- /m/orders/new: 카트에 품목이 들어오는 순간 default_charter_use='Y' 거래처는 용차 라인 자동 추가
- /m/orders/new: 거래처는 카트 안 택배/용차 라인 수정/삭제 불가 (read-only 표시)
[+ 택배 추가] [+ 용차 추가] 버튼도 admin 만 노출
- /m/orders DetailModal: canEditExtra=false 로 거래처 택배/용차 수정/삭제 차단
(출고관리 /m/admin/orders 에서만 수정 가능)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
원인:
- addManyToCart 가 setCart 함수형 업데이트 안에서 외부 변수 warned 에 값 세팅
- React 18 batched updates 로 콜백 실행이 한 박자 늦어 if(warned) 가 false → 첫 클릭 경고 누락
- 두 번째 클릭 때 이미 콜백이 실행돼 warned 가 true 보여 경고 표시 — 사용자가 본 현상
수정:
- 함수형 업데이터 진입 전에 cart 를 동기적으로 읽어 newQty/limit 비교
- 초과면 Swal 띄우고 return — setCart 호출 자체를 안 함 (장바구니 변경 없음)
- 통과 시에만 setCart 로 카트 갱신
- updateQty, setQty 도 동일 패턴(stale-closure 차단도 함수형 업데이터 밖에서)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
프론트(addManyToCart)는 이미 isDelivery → effStock=Infinity 로 우회했지만
백엔드 3곳에서 stock 체크로 거부 — 사용자가 담아도 출고 요청이 4xx 로 실패.
- /api/m/orders/save line 117: requires_delivery='Y' 면 재고 검증 우회
- /api/m/orders/items/update line 97: 동일 처리 (수량 수정 시)
- /api/m/orders/items/add line 87: 동일 처리 (발주에 품목 추가 시)
- 세 곳 모두 select 절에 requires_delivery 컬럼 추가
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기존: 카트 +/- 시도는 우상단 토스트, 신규 담기는 가운데 모달 — 일관성 없음.
변경: toastLimit 함수도 Swal.fire 가운데 모달로 변경. 메시지도 명확하게.
- 재고 초과: "재고 수량 초과 — 현재 재고 N개 보다 많은 수량은 출고 요청할 수 없습니다."
- 한도 초과: "1회 발주 한도 초과 — 1회 최대 N개까지 발주 가능합니다."
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기존: 카드/리스트의 [+ 담기] 클릭 시 입력값을 limit 으로 clamp 해서 그대로 담겼음.
변경: 입력값을 그대로 addManyToCart 에 넘김 → limit 초과 시 거부 + Swal 경고
("재고 부족" / "1회 발주 한도 초과") + 입력칸은 그대로 두어 사용자가 직접 정정.
- 카드 input onChange의 즉시 clamp 제거 (입력은 자유롭게 표시)
- 카드 + 담기 onClick / Enter: Math.min(limit, val) 제거
- 리스트 동일 패턴 적용
- ListView 의 onLimitToast prop 미사용으로 정리
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
거래처(일반 사용자)는 발주 수정 시 택배·용차 라인을 새로 추가할 수 없게 함.
이건 출고 담당자(/m/admin/orders) 가 처리. 기존 라인의 수량/단가 수정은 그대로 유지.
- DetailModal 두 버튼 + addNewExtra 함수 제거
- 미사용 import (Truck, Package) 정리
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- momo_items.sale_start_date/sale_end_date DATE → TIMESTAMP 자동 승격
(items/list ensureColumns 에 information_schema 체크 후 ALTER)
- items API (list/save/bulk-sale-range/daily-order-inventory): ::date → ::timestamp,
CURRENT_DATE → NOW(), 응답 포맷 'YYYY-MM-DD HH24:MI'
- 품목 관리 편집/일괄적용 UI: <input type="date"> → datetime-local
+ toLocal 헬퍼로 응답값 "YYYY-MM-DD HH:MM" → "YYYY-MM-DDTHH:MM" 변환
- 출고 요청 카드 그리드 모바일 2열로 변경 + 카드/리스트에 판매 종료일시 표시
- 재고/한도 초과 수량 입력 시 즉시 clamp + 우상단 토스트 경고
(카드/리스트 inline qty input onChange, setQty/updateQty 양쪽)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 좌측 상단 MOMO DISTRIBUTION 로고를 <Link href="/"> 로 감싸 클릭 시 랜딩 이동
- 우측 폼 패널 상단에 [홈으로] 고정 버튼 추가 (모바일에서도 노출)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 랜딩에 INSTALL 섹션 — Android(Play 스토어 다이렉트), iPhone(홈 화면 추가), PC(웹) 3장 카드
- /install 안드로이드 가이드 상단에 Play 스토어 다운로드 CTA(가장 쉬운 방법) 추가
- 푸터에 개인정보처리방침/계정 삭제/앱 설치/Play 스토어 링크 + mailto/tel 활성화
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 새 페이지 /m/admin/daily-order-inventory: 선택 일자의 판매가능 품목 + 발주수량 합계 + 전체 재고
- 새 API /api/m/admin/daily-order-inventory: sale_start_date~sale_end_date 필터 + ORDER_QTY/STOCK_QTY 집계
- /api/menu/route.ts: ensureMomoMenus — 9000405 menu_info + 출고관리(9000401) 권한 master 자동 매핑
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
이전: STATUS === 'PAID' || 'PARTIAL' 일 때만 [입고 등록] 버튼 표시
지금: REQUESTED + PARTIAL + PAID + RECEIVED 모두 표시.
- 입금 의존성 해제(앞선 커밋)와 일관성 맞춤.
- RECEIVED 상태에서도 라인 보정 입력이 가능하도록 버튼 유지 + '입고 완료' 배지 같이 표시.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 수량 컬럼: w-20 → w-14 (header), QtyInput w-16 → w-11
* 비고 컬럼: w-32 → w-20
* 품명은 무제한 폭으로 둬서 줄어든 만큼 자동 확장
화면/이미지 공유/인쇄 모두 동일하게 적용 (캡처 영역 내 변경).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
노인 사용자(거래처 사장님 등) 도 따라할 수 있도록 큰 글씨 + 단계별 안내.
User-Agent 자동 감지로 해당 기기 가이드 우선 표시, 탭으로 다른 기기 전환 가능.
* 안드로이드: Chrome → 앱 설치 배너 → 4단계
* 아이폰: Safari → 공유 → 홈 화면에 추가 → 5단계 (사파리 필수 경고 강조)
* PC: QR 코드 (휴대폰 카메라로 즉시 안내 페이지 이동)
모바일 로그인 화면 하단에 "📱 휴대폰 홈 화면에 앱처럼 설치하는 방법" 링크 추가.
middleware publicPaths 에 /install 추가 (비로그인 접근 허용).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Google Play 데이터 보안 섹션에서 '계정 URL 삭제' 필드에 넣을 페이지.
앱 내 self-service 삭제가 없는 B2B 앱이므로 이메일/전화 요청 절차를
명시한 정적 안내 페이지로 처리.
* 삭제 요청 방법 (이메일/전화)
* 처리 기간 (14일 이내)
* 삭제되는 데이터 vs 법령상 보관되는 데이터
* /privacy 와 동일하게 middleware 인증 면제
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PWABuilder 가 .aab 빌드 시 생성한 keystore 의 SHA-256 으로 갱신.
Play App Signing 활성화 후 Play 가 별도 서명 키를 발급하면 그 SHA-256 도
배열에 추가해 두 개 다 인정되도록 해야 함.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Play Store TWA(Trusted Web Activity) 풀스크린 동작에 필수. 안드로이드 앱
패키지 com.momotogether.app 의 서명 SHA-256 을 momotogether.com 도메인이
인정한다고 선언.
* 현재 SHA-256: 로컬 업로드용 키(android.keystore in ~/Downloads/momo-twa)
* Play App Signing 이 별도 서명 키를 발급하면 그 SHA-256 도 배열에 추가 필요
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- /privacy 라우트: 인증 미들웨어 면제. 시행일/수집항목/보유기간/제3자/안전성/연락처
- 추적·광고 SDK 없이 ERP 운영 데이터만 다루는 자체 서비스 기준으로 작성
- middleware publicPaths 에 /privacy 추가
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
editable 조건을 PAID/PARTIAL 만 → REQUESTED/PARTIAL/PAID/RECEIVED 로 확장.
OPEN(작성중)/CANCELLED 만 차단. 입금 의존성은 이미 해제됐고, 이번 변경으로
'발주요청 상태라 입고 입력이 불가합니다' 차단 메시지 제거.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
신규 업무 흐름: 매입발주 작성 → 발주요청 → (순서 무관) 입고 / 입금
- 발주요청(REQUESTED) 단계에서 입금 처리 없이 바로 입고 가능
- 입고완료(RECEIVED) / 입고중(PARTIAL) 건도 그 이후에 입금 처리 가능
- 이미 입금완료(PAID) 인 발주에 추가 입고가 들어와도 상태는 PAID 유지
변경 파일:
- proc-payments/confirm: 입금 허용 상태 REQUESTED → REQUESTED/PARTIAL/RECEIVED
- proc-payments/list: 노출 상태 (REQUESTED,PAID) → (REQUESTED,PARTIAL,RECEIVED,PAID)
- inbounds/page: 기본 필터 PAID_OR_PARTIAL → INBOUNDABLE (REQUESTED+PARTIAL+PAID)
드롭다운에 '발주요청만' 옵션 추가, 안내 문구 갱신
- inbounds/save: 입고 후 상태 갱신 시 PAID 면 덮어쓰지 않음
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
레이아웃 영향 없는 범위에서 가독성 ↑:
- text-[11px] → text-[13px]
- text-[12px] → text-[14px]
- 작은 배지/REMARK 등은 기존 비율 유지 (9→11, 10→12)
화면/이미지 공유/인쇄 동일하게 굵고 큰 텍스트로 나옴.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 거래명세표(admin/orders) 캡처 영역 전체 font-bold [&_*]:font-bold — 가독성 ↑
- 발주서(admin/procurements) 캡처 영역도 동일하게 bold 적용
화면/이미지 공유/인쇄 모두 굵게 표시
- 매입 발주서 관리 좌:우 레이아웃: 360px → 480px (왼쪽 리스트 더 넓게)
- 발주서 품명 컬럼 width 220px 로 제한 (단위/수량/단가/금액 열에 공간 양보)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
긴 동적 리스트(거래처/창고/품목/공급업체/매입발주)의 native <select> 를
검색 가능한 SearchableSelect 로 교체. 항목 50~100건이라도 타이핑으로 즉시 필터.
대상:
- admin/orders 출고처리: 거래처 변경
- admin/inventory: 검색 창고 / 매입입고 모달(창고+품목) / 재고이동 모달(출발+도착+품목)
- admin/inbounds/new: 매입발주 / 공급업체 / 입고창고 / 품목
- admin/procurements/new: 품목
- admin/inventory/history: 창고 필터
상태/유형 등 짧은 고정 옵션 select 는 native 그대로 (드롭다운 클릭 즉시 선택이 빠름).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- admin-panel 헤더 '← 사용자' 링크가 삭제된 /dashboard 로 가서 404 발생
- 동일 패턴: login 페이지 fallback redirect 도 /m/orders/new 로 정정
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
내 출고 이력처럼 검색 조건 변경 즉시 리스트 갱신 — 조회 버튼 불필요.
useEffect 의존성을 빈 배열 → [load] (load 의 deps 에 검색 조건 포함)
패턴으로 통일.
- admin/orders: status/dateFrom/dateTo/keyword 변경 시 즉시
- admin/payments: dateFrom/dateTo/keyword/payFilter
- admin/inventory: whFilter/keyword (load 를 useCallback 으로 wrap)
기타 페이지(invoices/inbounds/procurements/proc-payments/einvoices) 는
이미 [load] 패턴으로 자동 갱신 적용된 상태.
root cause: .js-no-export 인 input 의 cssText 캡쳐 시점에 이미
display:none 이 적용되어 있어, finally 의 cssText 원복이 display:none 을
다시 적용 → input 이 영구히 사라짐.
수정:
- 진입 순서: input cssText 캡쳐 FIRST → 그 다음 .js-no-export display:none
- 원복 순서: cssText FIRST → 그 다음 display 원복 (역순)
- 이로써 cssText 의 'display:none' 잔류 방지
부수: admin/orders 의 '출고' 버튼은 출고완료(APPROVED) 발주에서 숨김 —
'editable && (출고)' → 'STATUS === REQUESTED && (출고)'.
1) capture-share: display:none 으로 빈 input 숨기던 처리 제거.
외형(border/bg/padding) 만 잠시 제거하고 cssText 로 원복. 캡쳐 후
날짜 input 이 사라지던 문제 fix (React 가 같은 DOM 재사용 시 hidden
상태로 남던 케이스 회피).
2) 처리 중 로딩 오버레이 — components/ui/Loading 컴포넌트 활용:
· admin/orders 페이지 (busy = 일괄 출고)
· admin/orders StatementPreview (shipping = 단건 출고)
· admin/inbounds (busy = 입고 저장)
· admin/proc-payments (busy = 입금 처리)
가운데 spinner + 메시지 + 반투명 black overlay.
문제:
- 거래명세표 이미지 공유 시 input 박스(테두리/배경/그림자) 가 그대로 노출
- 발주번호 'O2605140012'/발주일자 텍스트가 좁은 폭에서 wrap 되어 깨짐
- 비고 빈 인풋이 영역만 차지
수정:
- capture-share: 캡쳐 직전 모든 input/select/textarea 의 border/bg/
shadow/padding 제거. select 는 appearance:none 으로 화살표도 가림.
값 비어있는 input 은 display:none. 캡쳐 후 cssText 로 일괄 복원.
- admin/orders: 발주번호/발주일자 영역 whitespace-nowrap + flex 로 변경.
요청: 출고관리 detail 에 '환불 추가' 버튼. 누르면 표 상단에 환불 라인
생성. 수량=1 고정, 단가 양수로 입력 시 내부에서 음수로 저장 → 총합
자동 차감.
- lines/save API: kind 'REFUND' 추가. admin 만 허용. 단가 양수 받아
음수로 저장. is_tax_free='Y' (면세) 처리. seq=0 으로 박아 상단 정렬.
- detail/route.ts: ORDER BY 에 REFUND 우선 노출.
- admin/orders UI: '환불 추가' 버튼 (rose), ExtraRow 가 REFUND 도 처리
(음수 표시 — 빨간 글씨, 수량 1 고정 잠금).
- USER /m/orders: items.filter(KIND !== 'REFUND') — 사용자 화면에서
환불 라인 비공개. 합계는 DB 의 차감된 값 그대로 노출 (사용자에게
최종 청구 금액 표시).
ORD-20260514-0001 → O2605140001 (O + YY MM DD + 0001, 11자리)
PRC-20260514-0001 → P2605140001 (P + YY MM DD + 0001, 11자리)
- genOrderNo / genProcNo 4개 함수: prefix 변경 + ymd 6자리 (YY)
- LIKE prefix||'%' 패턴은 그대로 — 새 prefix 가 자동 적용됨
- 운영 DB 기존 데이터 일괄 UPDATE 완료 (orders 22건, procurements 4건)
menu_info 에 등록된 활성 URL prefix 는 m/* 와 admin/* 뿐 — 그 외 폴더는
옛 FITO/우성 레거시. 사용자 토글 시 진입되던 /dashboard 더미 페이지 포함
전부 정리.
삭제 폴더 (총 23개):
- dashboard (영업현황/제품별현황 더미)
- approval/bom/cost/cost-mgmt/cs/delivery/fund/inventory
- order/part/part-mgmt/procurement-std/product/product-mgmt
- production/project/purchase/purchase-order/quality/sales/scm/work
유지: m/, admin/, profile/. /api/admin/* 와 /api/approval 등 API 라우트는
admin-panel 의 일부 inline 컴포넌트가 참조하므로 그대로 둠.
tsc 타입 체크 통과.
- payments/invoices/items/proc-payments/einvoices 5개 페이지에서
px-4 py-3 → px-3 py-1.5 일괄 축소. 한 화면에 더 많은 행 표시.
- items: 작업(Pencil/Trash) 셀에 whitespace-nowrap + w-[80px], 품목명
컬럼 max-w-[260px] truncate (긴 이름은 title 으로 hover)
- viewMode 기본값 'by-wh' → 'by-item' 으로 변경 (사용자 선호).
- 품목 가로 모드 본문 첫 행: '전체 합계' (emerald 강조)
· 발주수량 합 = Σ 모든 창고의 STOCK
· 여유분 합 = Σ 모든 창고의 AVAILABLE (음수면 rose)
- 이후 본사 창고 → 김포 시장 7줄 그대로 노출.
- header: 관리자 토글 클릭 시 router.push('/admin-panel?tab=user'),
사용자로 돌아갈 때 '/m/orders/new'. 사이드바만 바뀌고 콘텐츠는 그대로
남아 혼동되던 UX 개선.
- sidebar ADMIN_SYSTEM_MENU 의 '공급업체 관리' 항목 제거 — admin-panel
에서 이미 supply 탭 제거됐기 때문에 클릭 시 이상한 페이지로 가던 문제 fix.
공급업체 관리는 m/admin/vendors 별도 메뉴 사용.
1) '수기 발주' → '수기 출고' 라벨 변경 (버튼/타이틀).
2) detail STOCK_QTY: 거래처 default_wh_objid 분기 제거 → 항상 STOCK 류
전체 합산 표시. customer=admin 또는 김포 시장 등 default 가 빈 창고일
때 현재고 0 으로 표시되던 버그 fix. 실제 출고 차감은 approve 시
default_wh_objid 또는 STOCK 첫 창고 기준 그대로.
3) /api/m/orders/delete (admin) — REQUESTED 상태 발주만 hard delete.
수기 출고로 잘못 생성한 빈 발주 정리용. einvoice/items/orders 일괄.
4) 출고관리 detail (REQUESTED) 에 '삭제' 버튼 추가 — 반려 옆.
5) admin-panel 의 '공급업체관리' 메뉴 제거 (m/admin/vendors 별도 메뉴 사용).
매입 발주서 작성 패턴처럼 출고관리 안에서 직접 빈 발주 → 거래처 → 품목 채워가는 흐름.
신규 API:
- /api/m/orders/create-empty (admin) — 빈 발주 INSERT
· status='REQUESTED', customer 임시 admin, HQ 기본 supplier snapshot
- /api/m/orders/update-customer (admin) — 발주의 거래처 변경
· 변경 시 새 거래처 statement_branch 기반 supplier snapshot 재계산
· REQUESTED/APPROVED 만 변경 허용 (입금 후 잠금)
UI (/m/admin/orders):
- '수기 발주' 버튼 → 즉시 create-empty 호출 → 리스트 새로고침 + 새 row
자동 활성화 (모달/redirect 제거)
- detail 의 거래명세서 안 '귀하' 줄 → editable 시 CustomerEditor (select)
- 액션바에 '+ 품목 추가' 버튼 → AdminItemPickerModal (재고 있는 품목 검색)
· items/add API 호출, ITEM 라인 일괄 INSERT
매트릭스 뷰 — 행: 품목, 열: 창고 7개, 셀: 발주수량/여유분 2행.
- 발주수량 = 현재 보유 재고 (momo_stocks.qty)
- 여유분 = 현재고 - 기간 내 발주(REQUESTED/APPROVED/INVOICED/PAID)
의 출고 예정 수량 (거래처 default 창고 기준)
- 기간 필터: dateFrom/dateTo (기본 이번 주 월 ~ 오늘) + '금주' 버튼
- 엑셀 다운로드 지원
- 여유분 음수면 rose 강조 (재고 부족 경고)
운영 DB menu_info 9000420 등록 (parent 9000400 출고/정산).
USER 가 본인의 출고요청/출고완료 발주에 직접:
- 신규 품목 추가 (피커 모달 — /api/m/items/list 재고 있는 품목 검색)
- 택배비 단가/수량 인라인 수정 + 라인 삭제
- 용차 단가/수량 인라인 수정 + 라인 삭제
신규 API:
- /api/m/orders/items/add — ITEM 추가 (재고/숨김/한도 검증, admin 우회)
- /api/m/orders/lines/save — 가드 풀어서 REQUESTED + APPROVED 둘 다 허용
UI:
- detail modal 상단 액션 바: '+ 품목 추가' / '택배 추가/+1' / '용차 추가/+1'
- 표 안의 택배/용차 행에 수량/단가 QtyInput → onBlur 자동저장
- ItemPickerModal — 키워드 검색 + 행별 수량 입력 → 일괄 추가
전화 요청 등 시 admin 이 거래처를 대신해 발주를 작성할 수 있도록.
- /m/admin/orders 헤더에 '수기 발주' 버튼 + SearchableSelect 거래처 picker
→ 선택 후 /m/orders/new?customerObjid=momoNNN 로 이동
- /m/orders/new 가 query param customerObjid 받음:
· admin 일 때만 활성 (USER 가 query 박아도 무시)
· 상단 배너에 거래처명 표시 + 취소 링크
· save 호출 시 body 에 customerObjid 포함
- /api/m/orders/save: admin 이 body.customerObjid 명시하면 그걸로
발주 INSERT (supplier_branch snapshot 도 해당 거래처 기준)
통계 페이지 4개에 거래명세서 기준(전체/본사/김포) 필터 추가:
- /m/admin/statistics (월간 매출)
- /m/admin/statistics/daily (일자별)
- /m/admin/statistics/margin (원가/마진)
- /m/admin/statistics/pivot (거래처×일자)
각 API 의 WHERE 절에
COALESCE(O.supplier_branch, U.statement_branch, 'HQ') = $N
추가. supplier_branch snapshot 우선, 옛 발주는 user_info.statement_branch
폴백. ALL/생략 시 전체.
admin-panel 권한 및 사용자 관리 섹션에 '거래명세서 관리' 항목 추가
— activeTab='statement-branches' 시 /m/admin/statement-branches iframe
으로 로드 (기존 페이지 재사용, 별도 컴포넌트 중복 없음).
- 사용자 detail modal editable: REQUESTED → REQUESTED + APPROVED 모두 허용
- items/update API USER 가드: 동일하게 REQUESTED + APPROVED 허용
- 안내 문구 상태별로 분기
품목 추가/택배비 수정은 다음 단계에서 작업.
지사관리:
- WHERE COALESCE(supplier_branch, statement_branch, 'HQ') != 'HQ'
→ 본사 발주 완전 제외, 김포 등 지사 명의 계산서만
- snapshot 우선 (supplier_branch) → 옛 발주 폴백 (user_info.statement_branch)
- UI: 본사 row 표시 분기 제거, '본사 외' 명시
창고이동 통계:
- 본사 계열(HQ_*) → 김포 계열(KIMPO_*) 이동만 필터링
- 같은 본사 내 이동, 같은 김포 내 이동, 김포→본사 역방향 모두 제외
사용자 요구 — 지사 수수료 페이지는 본사 거래처를 보여줄 필요 없음.
계산서가 김포 등 지사 명의로 발행된 발주만 표시.
- 그룹핑 기준: COALESCE(supplier_branch, user.statement_branch, 'HQ')
· 발주 시점의 supplier_branch snapshot 우선 (= 계산서 발행 명의)
· 옛 발주(snapshot 없음) 는 거래처 statement_branch 폴백
- WHERE branch != 'HQ' 로 본사 제외
- UI: 본사 분기 제거 (모든 행이 지사)
A. 유통기한 임박 알림 (/m/admin/expiry-alerts)
- momo_inbounds.expiry_date/completed_by 컬럼 운영 DB 추가
- inbounds/save API: 입고 시 expiryDate/completedBy 함께 저장
- 페이지: 만료/7일이내/30일이내 분류 카드 + 행별 D-N 뱃지
- 빠른 필터 (7/14/30/60/90일)
B. 창고 이동 통계 (/m/admin/transfers)
- stock_moves WHERE ref_type='TRANSFER' AND move_type='OUT' 기준
- 출발창고 / 도착창고 / 품목 / 수량 / 단가(cost_price) / 금액
- 이동자(regid + user_name), 이동일시, 메모
- 합계 카드 + 엑셀 다운로드
운영 DB:
- ALTER TABLE momo_inbounds ADD COLUMN expiry_date, completed_by
- 인덱스 idx_momo_inbounds_expiry
- menu_info: 9000511 (유통기한), 9000512 (창고이동) 등록 — 통계 메뉴 산하
신규 메뉴 /m/admin/branch-fee — 거래처의 statement_branch(HQ/KIMPO 등)
기준으로 매출/원가/순수 마진 그룹핑 + 지사(HQ 외) 의 마진 × 20% =
본사 수수료 자동 계산.
표시:
- 합계 카드 4개: 총 매출 / 총 마진 / 본사 수수료 합 / 지사 실수령 합
- 지사별 표: 매출/원가/마진/수수료/실수령(마진-수수료)
- 본사(HQ) 행은 수수료 0 (— 표시)
집계 범위: 출고완료(APPROVED)/계산서발행(INVOICED)/입금완료(PAID) 발주.
운영 DB 의 menu_info objid=9000510 으로 등록 완료. (parent 9000500 통계)
- 직전엔 items/update 가 momo_order_items.qty 만 UPDATE → 출고완료
발주의 수량을 줄여도 재고는 그대로 (출고 시 차감된 양 그대로 묶임).
- 수정: status='APPROVED' 인 경우 newQty - oldQty 차이만큼 재고 보정
· diff > 0 (추가 출고) → stock 차감 + stock_moves OUT
· diff < 0 (수량 줄임) → stock 복원 + stock_moves IN
- 사용된 창고는 기존 stock_moves(ref_type='ORDER', ref_objid=order, 동일
item) 의 wh_objid 로 lookup (approve 시 사용했던 창고와 동일 유지).
- 이력 memo: "수량 수정: oldQty → newQty"
momo5315(배연진) 같은 admin 임직원이 사용자 측 '내 발주 이력' 페이지를
열면 모든 발주가 노출되던 문제. admin 판정만으로는 부족 — 메뉴 의도가
'본인 것' 이라 isAdmin 여부 무관 customer_objid 본인 매칭 필요.
- list API: body.mine === true 면 admin 이어도 본인 발주만
- /m/orders/page.tsx fetch 에 mine: true 추가
- admin 메뉴(/m/admin/*)는 mine 안 보냄 → 기존대로 전체 노출
사용자 명시 요청 — 운영 DB 는 이미 모든 스키마/데이터 변경 반영됐고,
더 이상 자동 reload 가 필요 없음. 매 deploy 시 마이그레이션이 실행되며
사용자 변경(비밀번호, 부서, 추가 거래처 등) 을 원복하는 사고를 막기 위해
폴더 통째로 삭제.
- db/migrations/*.sql 35개 모두 삭제
- .gitea/workflows/deploy.yml 의 migrate-momo.mjs 호출 단계 제거
- scripts/migrate-momo.mjs 파일 자체는 유지
운영 DB 의 모든 user_info 비밀번호는 '1' 로 직접 reset 완료(142명).
root cause:
- FITO auth.ts 는 user 객체에 role 필드를 만들지 않음 (isAdmin/userType 만).
- 새봄-마켓소풍(momo125) 가 FITO 폴백 경로로 로그인 → session.user.role
= undefined → list API 의 `if (role === "USER")` 가 false → 필터 무효
→ admin 의 발주까지 다 노출.
→ admin 판정을 MOMO/FITO 공통 isAdmin/role/userType 세 조합으로 통일.
!isAdmin 인 경우 본인 발주만 매칭.
momo5315(배연진) 같은 user_type='A' admin 이 출고요청/출고완료 발주
수량 수정이 안 된다는 사용자 신고. 직전 commit 에서 'REQUESTED/APPROVED'
만 허용으로 좁혔던 게 너무 빡빡했음.
→ admin 권한자는 취소(CANCELED) 외 모든 상태 수정 가능으로 풀기.
· API items/update admin 분기: status === 'CANCELED' 만 차단
· UI editable: order.STATUS !== 'CANCELED'
USER 권한자는 그대로 REQUESTED 만.
USER 권한 사용자의 list API 필터링에서 r.user.objid 가 undefined 인
세션에선 customer_objid 비교가 NULL 매칭 → 필터링 무효화돼 모든 발주가
노출되던 버그. user_id 폴백 + customer_objid 가 user_id 로 박힌 경우
모두 IN 절로 매칭.
1) ITEM 라인 수량을 QtyInput 인라인 인풋으로 (REQUESTED/APPROVED 상태).
onBlur/Enter 시 /api/m/orders/items/update 호출 → 자동 저장.
2) 비고(REMARK) 는 이미 onBlur 자동 저장이었음. 출고요청/출고완료 모두
editable 이라 동작.
3) ExtraRow(택배/용차) 의 V(저장) 버튼 제거. onBlur 시 자동 저장으로 변경
— label/단가/수량 어느 인풋이든 포커스 떠나면 자동 commit.
4) 모든 라인 수정 작업 (saveRemark/saveItemQty/upsertExtra/deleteExtra)
에서 onReload + onReloadList 동시 호출 → 왼쪽 발주 리스트의 합계도
즉시 반영.
부수: 운영 DB 의 모든 user_info 비밀번호를 '1' 로 reset (사용자 요청).
리스트:
- 기본 표시 = PAID + INVOICED 만 (APPROVED 출고완료 제외)
- 상태 필터 옵션도 PAID/INVOICED 만
발행 가드:
- frontend: 선택 + PAID + 미발행만 issue 가능
- API: UPDATE 조건 status='PAID' (이전엔 APPROVED 도 허용했음)
- 매입 발주서 editable 재조정: OPEN/REQUESTED 만 수정 가능. RECEIVED/
PARTIAL/PAID/CANCELLED 는 수정 불가. lines/save API 가드도 동일.
- 공급업체 select 를 SearchableSelect 로 교체 (typeahead):
· 매입 발주서 관리 페이지의 발주서 폼 (page.tsx)
· 매입 발주 신규 작성 페이지 (new/page.tsx)
운영 DB 의 매입 발주/입고/관련 stock_moves 데이터는 직접 모두 삭제했음
(사용자 명시 요청). UI 에 깨끗한 상태로 보임.
운영 DB 직접 점검 후 발견:
- supply_mng.objid 는 numeric NOT NULL (TEXT 아님) → 'MOMOSUP000000001'
같은 문자열 INSERT 가 cast fail 로 80개 다 fail 하고 supply_mng = 0개
잔존 상태였음. 운영 DB 에는 직접 80개 박아둠 (objid 1..80).
- 035 마이그레이션도 동일 패턴(numeric objid)으로 재작성. 매 deploy 안전.
직전 deploy #137 (485aea4) 빌드 실패 원인:
- procurements/page.tsx 의 deleteProc 에서 setActiveId(null) → state 가
string 이라 타입 에러. setActiveId("") 로 수정.
직전 batch 가 운영 반영 후에도 supply_mng 가 10개로 유지된다는 사용자 신고.
원인 추정: 031/032/033 의 DO block 안에서 어딘가 fail → migrate-momo.mjs 가
process.exit(1) → 그 뒤 마이그레이션 (034, 035) 실행 자체 못 함.
→ 031/032/033 본문을 SELECT 1; 로 NO-OP 화. fail 지점 우회.
→ 035 가 단독으로 회원 135 + 공급업체 80 + 제조사 메뉴 삭제 모두 처리.
· DO block 없이 plain SQL 만 (각 statement 가 별도 transaction)
· supply_mng: 통째 DELETE 후 80개 plain INSERT
· user_info: user_type='U' DELETE 후 ON CONFLICT (user_id) UPSERT
· 매 deploy 안전 — UPSERT 라 중복 INSERT 도 OK
- 매입 발주서: OPEN 일 때 삭제 버튼 노출, 라인 포함 hard delete
· 신규 API /api/m/procurements/delete (status='OPEN' 만 허용)
- 매입 발주 editable: STATUS === 'OPEN' → STATUS not in (PAID, CANCELLED)
· lines/save API 가드도 동일 (PAID/CANCELLED 만 차단)
- 035_supply_mng_plain_reload.sql: 직전 DO block 마이그레이션이 어디서
fail 하는지 추적 불가 → 가장 단순한 plain SQL 로 supply_mng 다 비우고
엑셀 기준 80개 INSERT ON CONFLICT(supply_code) UPSERT
· momo_items/momo_procurements 의 vendor_objid 모두 NULL 처리
· UNIQUE INDEX 보장 후 INSERT — 매 deploy 안전
진짜 root cause — 019_proc_terms.sql 이 매 deploy 시
DELETE FROM supply_mng;
INSERT INTO supply_mng ... 10개 시드 (VND-001 ~ VND-010)
를 실행해서 supply_mng 가 항상 10개로 reset 되고 있었음.
→ 019 의 supply_mng 시드 부분 제거 (납품조건 ALTER 만 유지).
034 신설 — idempotent 매 deploy 안전 실행:
- product_lines 컬럼 보장
- supply_code UNIQUE 인덱스 보장 (ON CONFLICT 동작)
- 옛 'VND-*' 시드는 items/procurements 의 vendor_objid 끊은 후 DELETE
- 엑셀 80개 INSERT ON CONFLICT (supply_code) DO UPDATE
- sentinel 가드 없이 매번 안전 — 사용자 추가 supply (다른 supply_code) 는 보존
1) delete API: MASTER_OBJID/OBJID 가 numeric 가 아닐 때 ::numeric cast 가
매칭 0건으로 silent fail. ::text 양쪽 비교로 안전하게.
2) 추가/제거 버튼: h-9 w-24 → h-12 w-32, cursor-pointer + hover 색상
강화. 가운데 컬럼 min-w-[120px] 로 클릭 영역 넓힘.
근본 원인:
- 008_makers_menu.sql: 매 deploy 시 제조사 관리 메뉴를 status='active' 로
되돌려놓아 사용자가 메뉴 삭제해도 다음 배포 때 부활.
- 023_seed_momo_vendors_from_xlsx.sql: 매 deploy 시 옛 100개 공급업체를
비-idempotent 한 INSERT 로 박아 supply_mng 가 영원히 옛 데이터로 원복.
→ 두 파일을 빈 NO-OP 으로 교체.
033_force_reload_v2.sql 신설 (sentinel 가드 momo_migration_marks 기반):
- 제조사 메뉴 menu_info objid=9000204 삭제
- momo_einvoice_items/einvoices/stock_moves(ORDER)/order_items/orders 통째
- user_info user_type='U' 다 삭제 후 momo001..momo135 ON CONFLICT UPSERT
· 본사팀 85 (HQ), 김포팀 50 (KIMPO), 카테고리별 default_wh 매핑, 비번 '1'
- supply_mng 다 삭제 후 80개 INSERT (엑셀 모모유통 제조사 리스트 26.05.12)
· product_lines 컬럼 ALTER (DO block 밖)
사용자 요청 — 구매자가 수량 변경 요청하면 담당자가 거래명세표에서 수기로
수정하고 다시 입금 요청하는 흐름. 출고요청(REQUESTED) 뿐만 아니라
출고완료(APPROVED) / 계산서발행(INVOICED) 까지 수정 가능해야 함.
- editable: order.STATUS === 'REQUESTED' → STATUS !== 'PAID' && !== 'CANCELED'
- items/update API 의 admin 가드는 이미 PAID/CANCELED 외 모두 허용
- 수정하면 사용자 측 명세표도 그대로 갱신 (detail API 가 동일 데이터 반환)
사용자 요청 — docs 의 엑셀 두 파일을 기준으로:
- 회원(user_info user_type='U') 전부 삭제 후 momo001..momo135 신규 등록
· 본사팀(85): 창고픽업 WH001 / 시장픽업 WH002 / 용차배송 WH003
· 김포팀(50): 김포지사 WH004 / 창고픽업 WH005 / 시장픽업 WH007 / 용차배송 WH006
· statement_branch: 본사='HQ' / 김포='KIMPO'
· 비번 '1' (AES 암호화)
- 공급업체(supply_mng) 전부 삭제 후 80개 신규 등록 (제조사 리스트 엑셀 기준)
· supply_mng 에 product_lines TEXT 컬럼 추가하여 제품명 보관
· items.vendor_objid 는 NULL 처리 (참조 무결성)
1회성 sentinel — momo_migration_marks 테이블로 가드. 신규 데이터가
다음 deploy 때 또 지워지는 사고 방지.
사용자 명시 요청 — supplier snapshot 이전의 옛 발주 데이터를 통째로
비우고 신규 발주만 운용. 사용자가 새로 등록하기로 함.
⚠️ migrate-momo.mjs 가 매 deploy 시 모든 .sql 을 재실행하므로 raw DELETE
를 그대로 박으면 신규 발주가 다음 deploy 때 또 지워지는 사고가 남.
→ sentinel 테이블 momo_migration_marks 도입, 1회만 실행되도록 가드.
삭제 범위:
- momo_einvoice_items (발주연결분)
- momo_einvoices WHERE order_objid IS NOT NULL
- momo_stock_moves WHERE ref_type='ORDER'
- momo_order_items 전체
- momo_orders 전체
보존: momo_stocks.qty — 사용자가 inventory 메뉴에서 직접 보정.
기존: 거래명세표 발급 때마다 user_info.statement_branch + branches table
을 실시간 조회 → 사용자의 기준 명세표를 바꾸거나, branches 의 계좌/
전화/이메일을 수정하면 과거 이미 찍힌 명세표까지 함께 바뀌어버림.
수정: 출고요청(REQUESTED) 시점에 supplier 8개 컬럼을 momo_orders 행에
박아두고, 이후 detail/statement/approve 는 이 snapshot 을 사용.
- 마이그레이션 030: momo_orders 에 supplier_branch / supplier_name /
supplier_ceo / supplier_bank_account / supplier_phone / supplier_email
/ supplier_biz_no / supplier_address 컬럼 추가 (idempotent IF NOT EXISTS)
- save: 발주 INSERT 시 getSupplierByBranch(user.statement_branch) 호출 결과
를 그대로 박음
- detail/statement/approve: snapshot 컬럼이 있으면 그것을 사용, 없으면
옛 발주용 폴백으로 user_info.statement_branch → branches table 조회
직전 commit 에서 버튼 자체를 통째로 지웠던 게 잘못. 출고요청 건은
admin 이 화면에서 택배/용차 라인을 추가/수정해야 함.
- 화면: 버튼 항상 보임 (editable 시)
- 이미지 공유 캡처: .js-no-export 클래스로 hide (capture-share.ts 동작)
momo 영역에서 미사용. FITO 레거시 잔존 코드 정리:
- sidebar.tsx: __sys_code 가상 메뉴 항목 제거
- admin-panel/page.tsx: code 탭/메뉴/CodeManagement 함수 삭제
- admin-panel/code-form/: 폼 페이지 디렉토리 통째로 삭제
- api/admin/codes/: list/detail/save 라우트 통째로 삭제
/api/common/code-list (조회 전용) 는 product/part-change 등이
드롭다운 로드용으로 쓰고 있어 보존.
- 마이그레이션 029 후 wh_type 이 새 enum(HQ_STOCK/KIMPO_STOCK 등)
으로 바뀌면서 detail/approve 의 wh_type='STOCK' 필터가 매칭 안 됨
→ 거래명세표 현재고가 전부 0 으로 표시됨 (재고 부족 N건 오탐).
- detail: 거래처 default_wh_objid 우선 + IN ('STOCK','HQ_STOCK','KIMPO_STOCK') 폴백
- approve: fallback 창고 lookup 도 동일하게 IN 절 확장
- 거래명세표 헤더 레이아웃: grid-cols-1 sm:grid-cols-2 → flex justify-between
으로 변경, 발주번호 라인과 공급자 박스가 항상 가로 배치(공급자 오른쪽 정렬)
- 거래명세표에서 '+ 택배 추가 / + 용차 추가' 버튼 제거 (요청)
기존 5개 (objid/wh_code 그대로 유지 — 재고 이동·입출고 데이터 보존):
- WH001 본사창고 → 본사 창고 (HQ_STOCK)
- WH002 시장픽업 → 본사 시장 (HQ_MARKET)
- WH003 용차배송 → 본사 용차 (HQ_CHARTER)
- WH004 창고픽업팀 → 김포지사 (KIMPO_BRANCH)
- WH005 김포창고 → 김포 창고 (KIMPO_STOCK)
신규 2개 (idempotent INSERT):
- WH006 김포 용차 (KIMPO_CHARTER)
- WH007 김포 시장 (KIMPO_MARKET)
총 7개. 사용자 요청한 본사 3 + 김포 4 구성 정확히 맞춤.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상: momo-erp container 가 force-recreate 될 때 traefik docker provider 가
새 컨테이너의 라벨을 재인식 못 해 momotogether.com 요청이 Gitea 라우터로
fallback 되는 사고 (사용자 화면에 Gitea 500 에러 페이지 노출).
deploy.yml 의 docker compose up 직후 `docker restart traefik` 1회 추가.
1~2초 다른 도메인 잠시 down 되지만 라우터 정합성 보장됨.
병행: cron watchdog (/usr/local/bin/momo-traefik-watchdog.sh, 3분 간격)이
production HTML 의 "모모유통" 문자열 검증해 비정상 시 traefik 자동 재시작.
deploy 외 임의 시점 사고에도 최대 3분 안에 자동 복구.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
마이그레이션 028:
- momo_statement_branches 테이블 신설 (code PK / name / bank_account / phone / email 등)
- HQ, KIMPO 기본 시드 INSERT (사용자가 관리자 페이지에서 편집 가능)
- 메뉴: 시스템 그룹에 '기준 명세표 관리' (M_ASBR / menu_info 9000310)
라이브러리 (src/lib/momo-branches.ts):
- 하드코딩 → DB 조회로 변경 (60초 in-memory 캐시)
- getSupplierByBranch 가 async — detail/statement/approve API 도 await 추가
- 저장 시 invalidateBranchCache() 호출
페이지/API (관리자 전용):
- /m/admin/statement-branches : list + 등록/수정/삭제 모달
- POST /api/m/admin/statement-branches/list
- POST /api/m/admin/statement-branches/save (regist / update / delete)
사용자 수정 폼:
- "기준 거래명세서" select 옵션이 하드코딩 본사/김포 → DB 의 branches list 동적 fetch
창고 관리:
- WH_TYPE 카테고리 5개 → 7개 (옛 enum 도 라벨 매핑은 유지)
· HQ_STOCK 본사 창고
· HQ_CHARTER 본사 용차
· HQ_MARKET 본사 시장
· KIMPO_BRANCH 김포지사
· KIMPO_STOCK 김포 창고
· KIMPO_CHARTER 김포 용차
· KIMPO_MARKET 김포 시장
- 신규 추가 시 select 는 위 7개만 노출, 기존 데이터 (STOCK 등) 는 라벨로 자연 표시
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
마이그레이션 027:
- user_info 에 statement_branch VARCHAR(10) DEFAULT 'HQ' 컬럼 추가
- 기존 사용자 일괄 'HQ' 로 채움
라이브러리:
- src/lib/momo-branches.ts 신설 — HQ / KIMPO 두 branch 의 공급자 정보 정의
· HQ: 기업은행 434-115361-01-016 (이상용) / 010-6369-8443 / momo8443@daum.net
· KIMPO: 농협 351-1383-7634-13 (모모유통) / 010-5789-9431 / momokimpo@nate.com
- getSupplierByBranch(branch) helper
거래명세표 supplier 분기 (3개 API):
- /api/m/orders/detail: order.STATEMENT_BRANCH 따라 supplier 객체 결정
- /api/m/orders/statement/[id]: xlsx 다운로드도 동일
- /api/m/orders/approve: 메일 발송 stmt 도 동일
사용자 수정 폼:
- /api/admin/users/detail: statement_branch 반환 (default 'HQ')
- /api/admin/users/save: statement_branch 받아 UPDATE
- /admin-panel/user-form: "기준 거래명세서" select 추가 (본사/김포)
흐름: 거래처 사용자의 기준을 김포로 설정 → 그 사용자의 발주에 대한 거래명세표 supplier 가 김포 정보로 표시
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1) 매입 발주서 관리(/m/admin/procurements):
- 조회조건 flex-wrap → flex-nowrap + overflow-x-auto (모바일 한 줄, 가로 스크롤)
- 공급업체 select → SearchableSelect (타이핑으로 결과 내 검색)
- 폰트/높이 축소 (h-9 → h-8, text-sm → text-xs)
2) 매입 입금관리(/m/admin/proc-payments): 동일 한 줄 정비
3) 창고 관리(/m/admin/warehouses):
- WH_TYPE 에 KIMPO "김포 창고" 카테고리 추가 + 라벨/배지색 등록
- 신규 창고 추가 시 select 옵션에 자동 노출
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 매입 발주 관리(/m/admin/procurements):
· 날짜 from~to + 공급업체 select 추가 (기본: 오늘 - 30일 ~ 오늘)
· 조회 버튼 제거 — state 변경 시 자동 fetch
- 내 발주 이력(/m/orders):
· 날짜 기본값을 오늘 - 30일 ~ 오늘로 설정 (URL 파라미터 우선 적용)
- 출고 처리(/m/admin/orders) 검색바:
· 모바일에서 label 풀어진 5단 grid → 한 줄 flex-wrap 으로 압축 (h-8 + text-xs)
· 모바일 폼이 화면을 너무 차지하던 문제 해소
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
지난 commit 의 026 이 ON CONFLICT(objid) 로 INSERT 시도 → menu_info 에 PK/unique 없어 PG 에러.
패턴: INSERT WHERE NOT EXISTS + 그 다음 별도 UPDATE WHERE objid=... 로 idempotent 처리.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1) 사이드바 메뉴 누락 fix (마이그레이션 026):
- FITO menu_info 테이블에 9000304 '매입 입금관리', 9000305 '재고이력' INSERT
- 기존 입고/재고 seq 재정렬 (11→12, 12→13)
- momo_menus 만으로는 사이드바에 안 나옴 — menu_info 가 사이드바의 진짜 소스
2) 재고이력 표시 개선:
- inventory/history API: REF_TYPE_LABEL (한글) + COUNTER_WH_NAME (이동 시 상대 창고) 추가
- inventory/transfer 라우트: stock_moves 의 ref_objid 에 상대 창고 objid 박음
- StockHistoryModal + history page: "INBOUND" → "입고", TRANSFER 시 "→ XX창고/← XX창고" 표시
3) 자동조회 (조회 버튼 없이 즉시):
- m/orders (내발주이력): 날짜 from~to + 상태 input 추가 + state dep useEffect
- m/orders/new (출고요청): "재고있는 품목만 / 전체 품목" 필터 추가 + 250ms 디바운스 자동 fetch
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1) 입고 처리 (m/admin/inbounds):
- 노출 대상: REQUESTED+PARTIAL → PAID+PARTIAL 로 변경 (입금 안 된 발주는 입고 불가)
- editable 조건 / STATUS 라벨/색상 PAID 추가
- 안내 문구 갱신
2) 재고이력 메뉴 (마이그레이션 025):
- M_AINVH '재고이력' 메뉴 매입 그룹 sort 34 에 INSERT (페이지는 기존 /m/admin/inventory/history 활용)
3) 재고관리 페이지 (m/admin/inventory):
- 데스크탑 표/모바일 카드 모두 행마다 "이력" 버튼 추가
- 클릭 시 StockHistoryModal 팝업 — 해당 품목 + 해당 창고 한정 이력 조회 (POST /api/m/inventory/history)
매입 흐름 완성: 매입발주(REQUESTED) → 입금관리(PAID) → 입고처리(RECEIVED)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
매입 흐름: 매입발주(REQUESTED) → 매입 입금관리(PAID) → 입고 처리(RECEIVED)
DB (마이그레이션 024):
- momo_procurements 에 paid_date, paid_amount, paid_method, paid_memo 컬럼 추가
- 매입 그룹 메뉴 sort 재정렬: 매입발주 30, 입금관리(신설) 31, 입고처리 32, 재고관리 33
- M_APROCPAY '매입 입금관리' /m/admin/proc-payments 메뉴 INSERT (ON CONFLICT idempotent)
UI/API:
- /m/admin/proc-payments 페이지 — 발주요청/입금완료 분리 카드 + 입금 처리 모달 (금액/방법/메모)
- 조회조건: 날짜 from~to + 공급업체 + 상태 (즉시 반영)
- POST /api/m/admin/proc-payments/list — REQUESTED|PAID 만 노출
- POST /api/m/admin/proc-payments/confirm — REQUESTED → PAID 전환 + paid_* 채움
다음 단계 (별도 batch): 입고 처리 페이지에서 PAID 만 노출 + 입고 시 RECEIVED 전환
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1) 삭제:
- src/app/(main)/m/admin/makers/page.tsx
- src/app/api/m/makers/{list,save,delete}/route.ts
(메뉴 DB 의 제조사 항목은 이전 commit 9705a04 에서 이미 제거됨)
2) 마이그레이션 023:
- docs/모모유통 제조사 리스트(26.05.12).xlsx 의 80개 업체를 supply_mng 에 일괄 등록
- idempotent: supply_name 중복 시 SKIP (NOT EXISTS)
- supply_code: MM-NNNN 자동 채번 (기존 max(objid) + ROW_NUMBER)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기존: 마지막 컬럼에 "보기" 버튼 + tr onClick 둘 다 있어 중복
변경: 동작 컬럼 + 보기 버튼 제거. tr 자체 클릭 시 상세 모달 (openDetail) 호출 — 이미 onClick 박혀있어 동작 동일.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
권한 fix — order.customer_objid 와 매칭 시:
- 기존: user.objid 만 비교 → FITO 사용자(objid 없음)는 항상 fail → "권한 없음"
- 변경: user.objid ?? user.userId 와 customer_objid 매칭, 또는 user.userId 와 직접 매칭
- 적용: items/update, lines/save, cancel, statement, items/remark (5개 API)
출고요청 리스트(m/orders/new) UI:
- table 의 min-w-[640px] 제거 + table-fixed 적용
- 수량 컬럼 폭 180px → 112px, "담기" 버튼 텍스트 제거 (+ 아이콘만)
- 폰트 11~12px 로 축소
- 모바일 화면 한 줄에 들어오도록
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
invoices(계산서 발행) page:
- 조회조건 추가: 거래처(SearchableSelect) / 날짜 from~to / 상태
- 조회 버튼 제거 — 입력하면 즉시 클라이언트사이드 필터 적용
- "조회 결과 합계" 카드: 면세 / 과세(공급+세액) / 합계 분리 표시
- "선택 합산" 카드: 체크박스로 고른 건들의 면세/과세/합계 실시간 합산
- 표 행마다 면세/과세 컬럼 추가
- 전체 선택 체크박스 (헤더)
deploy.yml:
- docker compose up 흐름 강화: down --remove-orphans 후 docker rm -f momo-erp 로 잔존 컨테이너 강제 제거 + --force-recreate
- 수동 SSH 배포 + 자동 배포 겹쳤을 때 "container name already in use" 충돌 자동 해소
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 품목관리(items):
· STATUS 컬럼 표시/필터/폼에서 제거 (전부 ACTIVE 가 default — 사용자 사용 안 함)
· 조회조건에 공급업체 SearchableSelect 추가 (이미 백엔드 vendorObjid 지원)
- 계산서 발행(einvoices):
· 조회조건에 거래처 SearchableSelect 추가 (customers list API 사용)
· 페이지 하단 tfoot 에 면세 합계 / 과세 합계 / 총 합계 분리 표시
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 결제 계좌번호 셀: <br/> 줄바꿈 제거 + whitespace-nowrap + width 110px 로 한 줄 표시
- 총 합계 (VAT포함) 셀: whitespace-nowrap + 폰트 11px 로 좁은 셀에서도 한 줄
- 거래처 이메일(귀하 아래 라인) 표시 제거 — 외부 공유용 이미지에 사용자 이메일 노출 불필요
m/admin/orders 와 m/orders 두 곳 동일 적용.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기존: REQUESTED 상태만 수정 가능 (admin/user 동일)
변경:
- USER: REQUESTED 만 (기존 그대로)
- ADMIN: PAID/CANCELED 가 아니면 모두 (REQUESTED / SHIPPED 등 입금완료 전까지)
items/update, lines/save 두 API 동일 적용. 입금완료(PAID) 이후나 취소건은 admin 도 수정 불가.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기존: user.role === "USER" 일 때 숨김 → 거래처라도 role 필드 비어있으면 노출되는 버그
변경: user.isAdmin === true || role === "ADMIN" 일 때만 노출 → 그 외는 모두 숨김
admin 계정만 거래명세표 이미지 공유 가능.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기존: mount 시 한 번만 history sentinel push → 사용자가 navigation 하면 sentinel 잃어버려 토스트 안 뜸.
변경: usePathname 의존성 useEffect → pathname 변경마다 sentinel 새로 push. lastBackRef 도 ref 로 변경(렌더 의존성 없이 상태 유지).
+ swal toast z-index 9999 강제 (다른 모달 위)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- middleware: User-Agent 가 모바일이면 /login → /m/login 으로 redirect (서버 측 분기)
- auth-store.logout: window 가 모바일이면 /m/login, 아니면 /login (클라이언트 측 분기)
- BackButtonGuard: TWA 일부 환경에서 display-mode 가 standalone 으로 보고되지 않는 케이스 대응 — fullscreen/minimal-ui 도 포함, 모바일 UA 면 무조건 활성화
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
m/orders 페이지의 거래명세표 DetailModal 에서 "이미지 공유" 버튼이 모든 사용자에게 노출되던 문제.
useAuthStore 의 user.role === "USER" 이면 버튼 hide. ADMIN 은 그대로 표시.
엑셀 다운로드 버튼은 그대로 유지 (사용자가 자기 주문 명세는 엑셀로 받을 수 있음).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- src/components/back-button-guard.tsx 신규: standalone(PWA/TWA) 모드에서만 작동
· 첫 뒤로가기 → sweetalert2 toast("한 번 더 누르면 앱이 종료됩니다") 표시
· 2초 안에 두 번째 뒤로가기 → history.back() 으로 native back 위임 → 앱 종료
· 일반 브라우저(non-standalone) 사용자에게는 영향 없음
- src/app/layout.tsx 의 RootLayout 에 BackButtonGuard 마운트
- src/app/(auth)/m/login/page.tsx 에 1.5초 스플래시 overlay 추가
· 모모 로고 + "모모유통 ERP" + spinner ("로딩 중...")
· z-60 fixed inset-0, 1.5s 후 opacity fade-out
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- src/lib/capture-share.ts 에 forceWidth 옵션 추가 → 캡처 직전 임시로 node width 강제 + 즉시 원복
- 출고 처리(거래명세표) 와 매입발주서 관리의 이미지 공유 호출에 forceWidth: 1100 적용
- 모바일 화면(좁은 viewport)에서 좁아진 표/품명 셀이 한 줄로 펼쳐져 엑셀 가로 출력처럼 캡처됨
- m/orders 페이지의 inline captureAndShare 를 capture-share lib 으로 통일 (toJpeg fallback / AbortError 처리 공유)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- src/app/(auth)/m/login/page.tsx 신규 — 한 화면에 딱 맞는 모바일 layout (logo + form + 푸터, safe-area inset 적용)
- middleware.ts publicPaths 에 /m/login + PWA 자원(/manifest.json, /sw.js, /.well-known) 추가
- 세션 있는 상태로 /m/login 진입 시 /m/dashboard 로 자동 redirect
- manifest.json 의 start_url 을 /m/login 으로 변경 → TWA APK 가 앱 실행 시 바로 로그인 화면
로그인 성공 시 /m/dashboard 로 이동 (기존 /login 은 API 응답의 redirectTo 사용, 모바일은 hardcode).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v1.0.0 APK (com.momotogether.erp) 의 서명 인증서 SHA-256 을 Digital Asset Links 에 등록.
이 commit 이 production 에 배포되면 TWA 앱이 URL bar 없이 풀스크린으로 동작.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- public/manifest.json + service worker(sw.js) 추가
- icon PNG 변환 (192/512/180)
- public/.well-known/assetlinks.json placeholder (Bubblewrap 빌드 후 APK 서명 SHA256 채울 자리)
- layout.tsx 에 manifest/theme-color/apple-touch-icon 메타데이터 + 서비스 워커 등록 스크립트 추가
Bubblewrap 으로 APK 빌드 시 https://www.momotogether.com/manifest.json 을 source 로 사용.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
운영 서비스 목록, 데이터 볼륨 위치, 노출 포트, 운영 중 적용한 hotfix(Mailu DNSSEC/MariaDB 11.8 등), Phase 별 이관 체크리스트.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WebContent/ (JSP + SmartEditor2 + 정적 자원) 와 src/com/ (Spring Controller/Service/Mapper) 디렉토리 전체 삭제. 총 2,191 파일.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
docker compose build 시 latest 태그가 새 sha 로 갱신되면서 옛 sha 가 untagged
상태로 남아 매 배포마다 누적되던 문제. docker image prune -f 로 dangling 만 회수
(다른 프로젝트의 사용 중 이미지는 안 건드림).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
대시보드는 관리자 전용. USER 가 직접 URL 로 진입해도 즉시 출고 요청 화면으로
리다이렉트되게 차단. (메뉴 매핑 없어도 직접 URL 접근 방지)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 화면 제목: '통계 — 업체별 월간 매출' → '업체별 발주통계 (월별)'
- 메뉴명: '월간 매출' → '업체별 발주통계' (운영 DB menu_info 9000501)
- statistics/monthly API: status IN 에서 SHIPPED (dead code) 제거
- 기존 기능 그대로: 년/월 선택, 업체별 합계 + 면세/과세 분리, 엑셀 다운로드
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[원인]
- db/migrations/009_items_user_permissions.sql 가 user_type<>'C' AND
NOT IN (admin 7인) 사용자를 삭제하는 정리 쿼리를 포함
- user_type 'C' → 'U' 통합 이후 'U' 거래처 134명이 위 조건에 걸려
매 배포마다 통째로 삭제됨 (어제·오늘 두 번 사용자 관리에 거래처 0명)
[수정]
- 해당 DELETE 블록 통째로 주석 처리 — 마이그레이션은 idempotent 해야 하고
destructive 작업은 두지 않는다는 원칙
- 거래처 134명은 별도 복구 스크립트로 다시 INSERT (이 commit 직후)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[원인]
- lib/auth.ts verifyCredentials 는 user.role 을 설정하지 않음
- /api/m/items/list 가 `r.user.role === 'USER'` 만 체크 → 일반 거래처도
isUser=false 가 되어 status='ACTIVE' 필터 & view_hidden 필터 모두 우회
- 결과: 골드망고(status=active, is_hidden=Y) 가 모든 사용자에게 보임
[수정]
- isAdmin = role==='ADMIN' || isAdmin || userType==='A' (3가지 모두 검사)
- isUser = !isAdmin
- items/list: status 'ACTIVE' 비교를 UPPER(...) 로 대소문자 안전화
- orders/save: 숨김 품목(is_hidden='Y') 발주 시도를 view_hidden 권한 없으면 차단
- orders/new 클라이언트의 unlimitedQty 판정에도 userType==='A' 보강
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[부서 안 선택되던 문제]
- /api/admin/dept 가 DEPT_CODE/DEPT_NAME 대문자 반환인데 폼은 dept_code 소문자
로 접근 → 옵션 매칭 실패. 대문자로 통일
[필드 제거]
- 사번(sabun) 입력 제거 (요청)
- dead var isCustomer 제거
[레이아웃 컴팩트화 — 스크롤 없이 한 화면]
- 폰트 13→12, 인풋 h-9→h-8, 여백/마진 축소
- 출고 기준 창고 + 특수 권한 섹션을 별도 큰 카드 → 2열 그리드 안에 통합
- 특수 권한 체크 라벨도 컴팩트 (가로형 inline 칩)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
직전 commit 9e9922e 에서 ListView 내부에서 부모 scope 의 unlimitedQty 를
직접 참조 → TS2304. props 로 명시 전달
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[사용자 관리]
- /api/admin/users 목록에 UNLIMITED_QTY / VIEW_HIDDEN / USER_TYPE 컬럼 반환
- UserManagement 그리드에 '발주한도무시' / '숨김품목보기' 컬럼 추가 (✅/—)
- 사용자 수정 폼: '거래처 특수 권한' → '특수 권한 (발주 시 적용)' 으로 라벨 변경,
거래처(C) 전용이던 조건을 풀어서 일반 사용자(U) 도 권한 부여 가능
[출고요청 (/m/orders/new)]
- /api/auth/me 가 unlimitedQty / viewHidden 반환
- 클라이언트가 unlimitedQty true 면 MAX_ORDER_QTY 무시하고 재고만큼 발주 가능
- '한도 ≤ N' 라벨도 권한자에겐 숨김
(백엔드 검증 — /api/m/items/list 의 view_hidden, /api/m/orders/save 의
unlimited_qty 우회 — 는 이미 구현돼 있어 그대로 동작)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 권한그룹 생성 모달에서 권한CODE 입력 제거 (권한명만 입력)
- 서버: 신규 등록 시 auth_code 비어있으면 GRP_<base36 timestamp> 자동 생성
- 좌측 권한 목록에서도 코드 노출 제거 (내부 식별자만 유지)
- 수정 시 기존 auth_code 는 보존 (COALESCE)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기존 SQL 은 $isAdmin=true 면 모든 메뉴 통과 → 권한 매핑이 의미 없었음.
사용자 요청: "로그인한 사용자 권한 그룹에 따른 메뉴가 동적으로 나오도록"
- /api/menu: isAdmin 분기 제거, authority_sub_user JOIN authority_sub_menu
매핑만 사용. 자식이 권한에 있으면 부모도 자동 노출(트리 유지) 로직은 유지
- 운영 DB: 관리자그룹에 active 메뉴 36개 일괄 매핑 (재고 이력 9000304 포함).
사용자 관리 화면에서 권한 그룹 멤버 / 메뉴 매핑을 직접 조정해 사용자별
사이드바를 동적으로 제어
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
운영 momo_warehouses.objid 가 text 타입(예: MOMOWH000000001)이라
default_wh_objid 도 text 로 일치시켜야 매핑 가능.
- db/migrations/022_user_default_wh_text.sql
- /api/admin/users/save: ::numeric 캐스트 제거
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[출고 상태 셀렉트 중복 fix]
- STATUS_LABEL 에 APPROVED='출고완료' / SHIPPED='출고완료' 둘 다 매핑돼
셀렉트 옵션에 '출고완료'가 두 번 노출됐음. 운영 DB 분포 확인 결과
SHIPPED 상태값은 0건(dead) → 라벨/색상 매핑에서 SHIPPED 제거.
StatementPreview 의 'SHIPPED' OR 분기도 정리
[입금 관리 검색조건]
- 시작일 / 종료일 / 입금 상태(전체·입금 전·입금완료) / 업체명·발주번호 키워드
- 기본 기간: 이번달 1일 ~ 오늘
- 입금 상태: UNPAID = APPROVED, PAID = PAID + INVOICED 묶어서 필터
- 초기화 / 조회 버튼
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[재고 관리]
- 새 기능 '재고 이동' (A창고 → B창고): 출발창고 잠금 + 충분재고 검증 + 도착
창고 upsert + 이동 로그 2건(OUT/IN, ref_type=TRANSFER) 트랜잭션 처리
- /api/m/inventory/transfer 신규
- 모바일에서 테이블 가로 스크롤이 안 되던 문제 → sm:hidden 카드 + sm:block
desktop 테이블로 분리. 페이지 자체 스크롤로 자연스러운 UX
- 검색 영역 모바일 1열 / sm 3열 그리드 정리
- 재고 이동 모달은 출발창고 선택 시 그 창고에 재고 있는 품목만 셀렉트
- list API 응답에 WH_OBJID 추가 (이동 모달에서 출발창고 필터 용도)
[창고 관리]
- 창고 코드는 자동생성(WH001, WH002 ...) — 등록/수정 폼에서 readonly + 회색.
save API: regist 시 nextWhCode() 로 MAX+1 패딩. update 시 wh_code 미변경
- 클라이언트가 whCode 보내도 무시되도록 서버에서 분기
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[대시보드 → 출고처리 카드 필터]
- 승인 대기 / 진행중 / 미수금 → ?status=...&dateFrom=&dateTo= (전체 기간, 빈 날짜)
- 오늘 발주 → ?dateFrom=오늘&dateTo=오늘
- 이번달 매출/누적 → ?dateFrom=이번달1일&dateTo=오늘
- orders 페이지: 쿼리에 dateFrom/dateTo 키가 있으면(빈값 포함) 그 값 사용,
키가 아예 없을 때만 기본값 오늘. 사용자 모드 페이지도 동일
[출고 요청 카드 그리드]
- grid-cols-3 / md-4 / lg-5 — PC 5개·모바일 3개/줄
- 카드 padding p-3~p-4 → p-2, 폰트/버튼/이미지 라벨 모두 컴팩트
- IS_TAX_FREE/REQUIRES_DELIVERY 배지를 이미지 위 좌상단으로 이동해 공간 절약
- 품목명 line-clamp-2 + min-h-[2em] 로 카드 높이 일정화
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 페이지명: '발주서 관리 · 출고처리' → '출고 처리'
- 검색바 신규: 시작일 / 종료일 / 상태 / 검색어(발주번호·업체명·이메일) / 초기화·조회
- 모바일 1열, sm 2열, lg 5열 그리드로 반응형 정리
- API list: keyword 파라미터 추가 (order_no/user_name/email LIKE)
- 기본 기간 = 오늘. 단 ?status= 으로 진입(대시보드 카드)한 경우 30일 범위
- 검색 조건 변경은 [조회] 버튼으로만 트리거 (자동 reload 제거 → 입력 도중 깜빡임 X)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- /api/menu: super admin 은 모든 메뉴, 일반 사용자는 authority_sub_menu 매핑된
메뉴만 노출. 자식이 권한에 있으면 부모 메뉴도 자동 포함 (트리 유지)
- 권한 관리 화면에서 메뉴 체크 → 다음 로그인부터 사이드바 즉시 반영
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- html-to-image 의 toPng 가 Pretendard CDN 임베드 단계에서 fail 하면 캡처 전체가
깨짐 (Windows Chrome 에서 자주 발생). skipFonts + cacheBust + jpeg fallback 추가
- 거래명세표(orders) / 발주서(procurements) 양쪽이 같은 코드를 복붙으로 갖고 있던
걸 lib/capture-share.ts 로 통합
- 실패 시 err.message 를 swal 에 노출 (이전엔 "잠시 후 다시 시도하세요" 만 떠서
사용자가 원인 추적 불가)
- navigator.share 의 AbortError(사용자 취소) 는 silent 처리 + 그 외엔 다운로드 폴백
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5cbc324 배포 시 --force-recreate 가 이름 충돌(Conflict, 65adeb31db46_momo-erp)을
일으켜 컨테이너 swap 실패. 명시적으로 down --remove-orphans 후 up 으로 분리.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 권한그룹 멤버 추가/제거 API: history insert 를 best-effort 로 분리해 메인 INSERT
실패가 누적 에러로 noisy 응답에 담김. 클라이언트는 fail 분기에서 swal 로 사유 표시
- admin-panel 좌측 사이드바: '메뉴관리' 카테고리는 항상 고정 노출되므로 DB groups
에서 같은 라벨이 다시 내려와도 중복 렌더링 안 함
- 로그인 화면: '아이디/비밀번호 저장' 체크박스 추가 (localStorage SAVE_KEY).
체크 후 로그인 → 다음 방문 시 자동 채움. 해제하면 즉시 삭제
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- useSearchParams 가 Next.js 15 prerender 단계에서 Suspense 경계를 강제해
/admin-panel 빌드 자체가 실패 → docker image 재빌드 안 됨 →
컨테이너 swap 누락(2시간째 옛 이미지). window.location.search 직접 읽기로 대체
- deploy.yml: set +e 제거 (빌드 실패가 워크플로우 success 로 묻히는 문제 차단)
- docker compose 에 --force-recreate 추가 (이미지가 같아도 컨테이너 강제 재생성)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 기존 마커(WORKFLOW/매입 발주/SCREEN PREVIEW)는 옛 빌드에도 들어있어
배포가 실패해도 success로 묻혔음 (사용자: 사이드바 변경이 운영에 안 반영됨)
- SSH 단계에서 git rev-parse HEAD → public/build-sha.txt 에 기록 후 빌드
- 헬스체크가 운영의 /build-sha.txt 를 GITHUB_SHA 와 비교 → 불일치면 워크플로우 fail
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 사이드바: '거래처' 키워드 필터 제거. 사용자 모드 = DB 권한 메뉴 전체,
관리자 모드 = 시스템 관리 가상 카테고리(사용자/권한/메뉴/공통코드/로그)
- admin-panel: ?tab= 쿼리로 진입 탭 결정. 좌상단 '← 사용자' 복귀 링크
- header: admin 자동 admin 모드 진입 제거 (기본 사용자 모드)
- 출고관리 거래명세표 미리보기: 엑셀 다운로드를 이미지 공유/인쇄 옆으로
이동, 출고요청 상태일 때 [출고] 버튼 추가하여 체크 없이 바로 처리
- 발주서 미리보기: [인쇄] 버튼 추가
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[DB 019]
- momo_procurements 에 delivery_place / delivery_period / payment_terms / freight_terms 컬럼 추가
- 기존 supply_mng (공급업체) 데이터 모두 삭제 + 샘플 10개 신규 등록
· (주)아바텍, 대성식품, (주)고기파는농부, 광이진천 농장, 단과일,
봉담수산, 명일동유기농, 울산단과일, 농부의아침, 초록마을 도매
- 시퀀스 가정 없이 MAX(objid)+1 로 안전하게 부여
[발주서 양식 — 표준 거래명세표 양식 반영]
- ProcurementForm: "2. 납품조건" 섹션 추가
· 1)~3) 표준 조항 (납기 지연 공제 / 검수 부적합 반출 / 수량 규격 변경)
· 4) 납품장소 5) 납품기간 6) 대금지불 7) 운임부담 — 표 형식 입력칸
· 8)~9) 표준 조항 (3일 이의 제기 효력 / 명시되지 않은 사항)
· 하단 "상기와 같이 발주함." + 발주일 + 발주자
- update-header API: 4개 필드 동적 업데이트
- /api/m/procurements/excel/[id]: 엑셀 출력에도 납품조건 9개 항목 + 4필드 표
- /api/m/procurements/send: 메일 본문 HTML 에도 납품조건 표 + 표준 조항
[관리자/사용자 모드 토글]
- 헤더 매뉴얼 옆에 [👥 사용자 / 🛡 관리자] 토글 버튼 (admin 권한자만 노출)
- menu-store: viewMode("user"|"admin") + setViewMode 추가
- 사이드바: viewMode 에 따라 대메뉴 필터링
· 사용자 모드: '거래처 주문' 그룹만
· 관리자 모드: 출고/정산 + 매입/입고 + 마스터 관리 + 통계
- admin 권한자 자동으로 로그인 시 관리자 모드 진입
[ItemPicker 모달 모바일 친화]
- 모바일에서 화면 하단 도킹(items-end) → 풀스크린 시트 처럼
- 헤더는 sticky top-0 으로 고정 → 긴 목록에서도 검색바 항상 보임
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[현재고 — 캡처/공유 시에만 숨김]
- 거래처에 보낼 이미지에서 내부 정보(현재고)가 보이면 안 됨
- 거래명세표 표의 현재고 th/td 와 재고 부족 경고 박스에 .js-no-export 클래스 추가
- captureAndShare 안에서 toPng 직전 임시로 display:none → 캡처 후 복원
- 화면에서는 그대로 보이고, 다운받은 PNG/공유 이미지에서만 빠짐
[로그인 유지 — 30일 세션]
- /api/auth/login 요청 body 에 remember 추가
- /lib/session.ts createSession(user, remember=false) — 24시간(기본) / 30일(remember=true)
- 로그인 폼에 [✓ 로그인 유지 (30일)] 체크박스 (기본 ON, 나이 많은 사용자 친화)
- 체크 해제하면 24시간 세션 유지 (기존 동작)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상: 모바일에서 좌-우 분할 화면(출고관리 등) 의 페이지 스크롤이 안 됨.
원인:
- 컨테이너 minHeight: calc(100vh - 200px) 가 모바일에서도 적용 →
좌·우 박스가 각각 거의 풀높이 차지
- 박스 내부의 overflow-auto 가 페이지 스크롤을 가로채 박스 안만 스크롤됨
수정:
- minHeight 인라인 스타일 → lg:min-h-[calc(100vh-200px)] 로 lg 이상에서만 적용
- 좌측 리스트 컨테이너: max-h-[60vh] lg:max-h-none 로 모바일에서 자연스러운 높이
- flex-1 overflow-auto → flex-1 lg:overflow-auto (모바일은 페이지 스크롤로 통일)
- 동일 패턴을 매입발주(/m/admin/procurements) + 입고처리(/m/admin/inbounds) 에도 적용
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[출고요청 화면 개선 — /m/orders/new]
- 카드에 [수량 입력 + 담기] 한 번에. 엔터 또는 버튼 클릭 시 그 수량만큼 카트에 추가
- 이미 담은 품목은 카드 안에 [- 1 +] 컨트롤 + [×] 빼기 버튼이 즉시 노출
· 카트 수량 그 자리에서 직접 수정. 카드 외 카트 펼치기 불필요
- 담은 품목 카드는 emerald 테두리 + 우상단에 "담은 N" 배지로 강조
[보기 모드 토글]
- 검색바 우측에 [카드 / 리스트] 토글
- 카드: 기존 그리드 (이미지 위주, 시각적)
- 리스트: 표 형태 (품목 많을 때 한눈에) — 행마다 동일 [수량+담기] 컨트롤
[관리자 거래명세표 라인 sync 버그 fix]
- /m/admin/orders 에서 [+택배/+용차] 클릭 시 합계만 올라가고 인풋 표시값이 안 바뀌던 문제
- ExtraRow key 를 `OBJID-QTY-UNIT_PRICE-LABEL` 로 변경해 line 변경 시 컴포넌트 강제 재마운트
- useState 초기값이 새 line 값으로 확실히 반영됨
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상: 모바일로 로그인 시 사이드바가 콘텐츠를 덮어 사용 불가능.
원인: 사이드바가 모든 폭에서 항상 정상 폭으로 자리잡음.
[레이아웃]
- 사이드바를 모바일에서 fixed + translate-x-full 로 화면 밖에 두고,
mobileOpen=true 시 translate-x-0 슬라이드 인 (200ms transition)
- 모바일 오버레이 배경 클릭 시 닫기
- lg 이상에서는 기존대로 좌측 고정
[헤더]
- 모바일에서만 햄버거(≡) 버튼 노출 → setMobileOpen(true)
- 사용자명 모바일 width 줄이고 부서명 숨김 (110px → sm 이상 200px)
[사이드바]
- 헤더 우측에 모바일 전용 X 버튼 추가 (lg:hidden)
- 데스크탑 햄버거 토글은 hidden lg:flex 로 분리
- handleSubMenuClick 에서 setMobileOpen(false) 호출 → 메뉴 선택 시 자동 닫힘
[스토어]
- mobileOpen 상태 + setMobileOpen 액션 추가
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
요구사항: 메인 화면 어디서든 사용 설명서로 빠르게 이동.
- 헤더 우측 (사용자명 옆) 에 BookOpen 아이콘 + "매뉴얼" 텍스트
- /manual.html 새 탭으로 열기 (target=_blank)
- 모바일에서는 텍스트 숨기고 아이콘만
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[입고 처리 화면 재설계 — 등록 → 수정 방식]
- 좌-우 분할:
· 좌: 매입 발주서 리스트 (발주요청+입고중 기본 필터)
· 우: 발주 라인별 [창고 선택 + 정상 입고 + 불량] 인라인 입력
- 발주/입고/미입고 한눈에 표시 (예: 10 / 5 / 5)
- 완전 입고된 라인은 ✓ 완료 표시 + 입력 칸 잠김
- 정상+불량은 남은 수량(qty - received_qty) 이하로 자동 클램프
[/api/m/procurements/list]
- 응답에 TOTAL_QTY, RECEIVED_QTY 추가 → 좌측 리스트에 진척 표시
[/api/m/inbounds/save]
- procObjid 있으면 라인별 입고 한도 사전 검증 (qty - received_qty 초과 차단)
- 0 입고 라인은 건너뛰기
- 매입발주 상태 자동 갱신:
· 모든 라인 완전 입고 → RECEIVED (입고완료)
· 일부 라인만 입고 → PARTIAL (입고중)
· 시작 안 함 → REQUESTED 유지
[매뉴얼 — 가-1, 가-2, 다-2 대폭 보강]
- 거래처 출고 요청: 6단계 체크리스트 + 화면 도식 + 토스트/모달 예시 + 시나리오
- 내 주문 내역 + 거래처 자기 주문 수량 수정/품목 삭제/취소: 화면 도식 + 단계별 가이드 + 상태표
- 입고 처리: 화면 도식 + 발주/입고/미입고 표시 의미 + 부분입고 시나리오
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상: 품목 추가 모달에서 행 클릭과 체크박스 클릭 둘 다 toggle 호출 →
두 번 발생해서 체크 상태가 변하지 않음.
수정:
- 체크박스 td 에 stopPropagation 추가 (행 onClick 으로 버블되지 않게)
- 행 클릭은 그대로 행 전체 토글로 동작
- 체크박스 명시 cursor:pointer + 크기 18px + 색상 accent-emerald-600
- select-none 추가 (드래그 시 글자 선택 방지)
- 헤더 전체선택 체크박스도 동일 처리
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[새 API: /api/m/orders/items/update]
- 본인 또는 관리자가 자기 발주의 품목(ITEM) 라인 수량 변경 또는 삭제
- REQUESTED 상태에서만 허용. 단가는 변경 불가 (momo_items.unit_price 기준 자동 재계산)
- 재고 / max_order_qty 한도 자동 검증 (unlimited_qty 권한이면 한도 우회)
- 트랜잭션으로 라인 수정 + momo_orders 합계 7종 자동 재집계
[/m/orders 거래명세표 모달 UI]
- 출고요청 상태 거래처 본인 화면에서 품목 라인 직접 편집:
· 수량 인풋 (블러 시 자동 저장)
· 행 끝의 [×] 버튼으로 그 품목만 삭제
- 택배/용차 라인은 인풋 안 보이고 "자동" 표시 — 모모 담당자가 조정
- 저장/삭제 후 onReload 로 모달 + 리스트 동시 갱신
- 안내 배너: "수량 수정 / 품목 삭제 / 주문 취소" 모두 가능 명시
[매뉴얼]
- 가-2 내 발주 이력 섹션에 수량 수정 / 품목 삭제 / 주문 취소 사용법 추가
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[새 통계 — 거래처×일자 매출 피벗]
- API: POST /api/m/statistics/monthly-pivot
· 입력: { year, month }
· 응답: dates[] / rows[ {거래처, BY_DAY:{날짜:{면세,과세}}, TOTAL_TAXFREE/TAXABLE} ] / totalsByDay / grandTotal
· 출고완료/입금완료/계산서발행 상태 발주만 집계
- 화면: /m/admin/statistics/pivot
· 가로 스크롤 피벗 표 (왼쪽 sticky 업체명)
· TOT 행: 월간 일자별 총합 (부가세 신고용)
· 거래처별 정렬: 매출 큰 순
· 합계 카드 3종: 면세/과세/총
· 엑셀 다운로드 (거래처 행 × 일자 컬럼 평면화)
- 메뉴 등록: 018 마이그레이션 (objid 9000504, 통계 그룹)
[세금계산서 중복 발행 차단]
- /api/m/einvoices/issue: orderObjid 가 이미 발행됨(FAIL/CANCELED 제외) 이면 400
· "이미 발행된 발주입니다 (상태/승인번호)" 메시지 + alreadyIssued=true 플래그
- /m/admin/einvoices: 발행 가능 발주 리스트에서 이미 발행된 건 자동 제외
· orders/list 와 einvoices/list 동시 조회 후 클라이언트 측 필터
· DRAFT/QUEUED/SENT/ACK 모두 발행 완료로 간주 — 재발행 불가
· FAIL/CANCELED 만 다시 발행 가능
[매뉴얼]
- 통계 표에 "거래처×일자 매출 (피벗)" 항목 추가, 부가세 신고 자료 활용 안내
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상: 마스터 품목 관리 화면에 품목이 하나도 안 보임.
원인: 마이그레이션 016 (vendor_objid) 이 운영 DB 에 적용 안 된 상태에서
SELECT I.vendor_objid 가 'column does not exist' 로 실패 → 빈 배열 응답.
해결: API 첫 호출 시 ALTER TABLE ... ADD COLUMN IF NOT EXISTS 로
vendor_objid + max_order_qty + is_hidden + requires_delivery 모두 자동 보장.
idempotent 하므로 이미 적용된 환경에서도 NOOP.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[발주서 엑셀 다운로드]
- /api/m/procurements/excel/[id] 신설
- 이미지의 표준 발주서 양식대로 .xlsx 생성
· 분류번호/발주서번호/발주일/공급업체/연락처/이메일
· 1.물품의 표시 (품목코드·품명·단위·수량·단가·금액)
· 총액 + V.A.T 별도
· 2.비고 + 발주자 정보
[발주서 이미지 공유]
- 매입 발주서 양식 우상단에 [📤 이미지 공유] [⬇ 엑셀 다운로드] 버튼
- html-to-image 로 PNG 캡처 → Web Share API (카톡 등) 또는 PNG 다운로드
- 거래명세표(출고/정산)와 동일한 사용자 경험
[버그 수정 — 품목 모달에 결과 안 나옴]
- /api/m/items/list 의 supply_mng JOIN 캐스팅 누락
· momo_items.vendor_objid (TEXT) vs supply_mng.objid (NUMERIC) 타입 충돌로 SQL 에러 → 빈 배열 응답
- LEFT JOIN supply_mng V ON I.vendor_objid = V.objid::text 로 명시적 캐스팅
[매뉴얼]
- 매입 발주 섹션에 "발주서 공유 / 엑셀 다운로드" 안내 추가
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[화면 — /m/admin/procurements 전면 개편]
- 좌측: 발주서 리스트 (상태 필터, 발주번호, 공급업체, 금액)
- 우측: 발주서 양식 (이미지의 표준 발주서 형태)
· 분류번호/발주서번호/발주일/공급업체 표
· "1. 물품의 표시" 표 (품명·단위·수량·단가·금액)
· "2. 비고" 텍스트 영역
· 합계 자동 계산
- [+ 새 발주] / [발주 요청] 상단 버튼
- 작성중(OPEN) 상태에서만 인라인 편집 가능, 발주요청 후 잠김
[품목 추가 모달]
- 검색 + [공급업체 필터(현재/전체)] + [결과 내 검색]
- 다중 선택 + 헤더 체크박스로 전체 선택
- 이미 담긴 품목은 '이미' 표시
- 한 번에 N개 일괄 추가 (수량 1, 원가는 품목 마스터의 cost_price)
[API 4종 신설]
- POST /api/m/procurements/create-empty: 빈 발주서 1건 생성 (proc_no 자동 부여, status=OPEN)
- POST /api/m/procurements/lines/save: 라인 추가/수정/삭제 + 합계 재집계 (트랜잭션)
· 같은 품목 중복 추가 시 수량 누적
- POST /api/m/procurements/update-header: 공급업체/메모 수정
- POST /api/m/procurements/send: 발주 요청 — status OPEN→REQUESTED + 공급업체 이메일로 발주서 HTML 메일 발송
· 메일 실패해도 상태는 변경 (mailSent/mailError 응답)
[매뉴얼]
- 다-1 매입 발주 단계별 가이드 재작성
- "공급업체별 품목 일괄 불러오기" 팁 추가
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[공급자 정보 박스 (우측 상단)]
- 결제계좌번호 / 전화번호 / 이메일 표를 거래명세표 우측 상단에 표시
- 환경변수: MOMO_BANK_ACCOUNT / MOMO_PHONE / MOMO_EMAIL / MOMO_COMPANY_CEO 등
- detail API 응답에 supplier 객체 추가
[비고(remark) 컬럼]
- 모든 라인(품목/택배/용차)에 비고 입력 가능
- /api/m/orders/items/remark 신설 — REQUESTED 상태에서만 본인/관리자 수정
- 인풋에서 포커스 이탈/엔터 시 자동 저장
- 모든 라인에 momo_order_items.remark 컬럼 활용 (이미 존재)
[이미지 공유 + 인쇄]
- 거래명세표 위쪽에 [📤 이미지 공유] [🖨 인쇄] 버튼 신설
- html-to-image 라이브러리로 PNG 캡처 → Web Share API 가 있으면 카톡/메신저로 직접 공유,
없으면 PNG 파일 다운로드 (모바일/PC 호환)
- statementRef 로 캡처 영역 분리 (버튼은 영역 밖)
[엑셀 다운로드 수정]
- 기존: SELECT 쿼리에 alias 빠져 있어(`U.user_name, NULL, NULL`) 회사명/대표자/사업자번호가 모두 빈 값
- 수정: company_name/ceo_name/biz_no/phone/address/email 명시 alias
- 택배/용차 라인은 [택배]/[용차] 라벨로 출력
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[매뉴얼]
- 영어/기술용어/환경변수 코드 노출 제거
- 초등학생 수준 친절한 설명체로 전면 재작성
- 역할별 시나리오 박스 + 화면 도식 + FAQ 한글 위주
- URL/코드 참조 최소화
[ExtraRow 버그]
- 거래명세표에서 [+ 택배/용차 추가] 클릭해도 인풋 칸의 수량이 화면에 안 바뀌던 문제
- 같은 OBJID 라서 컴포넌트가 unmount 안 되어 useState 초기값 무시되던 케이스
- useEffect 로 line prop 변경 시 인풋 state 동기화
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
URL: https://momotogether.com/manual.html
- 거래처(USER) 가이드: 회원가입 / 출고 요청 / 발주 이력 / 회원정보 수정
- 관리자(ADMIN) 가이드: 발주서 관리·출고처리·거래명세표·인라인 편집·세금계산서 발행
- 마스터 관리: 품목·거래처·매입처·창고·제조사
- 매입/입고: 매입 발주·입고·재고
- 통계: 대시보드·월간/일자별/원가마진 + 엑셀 다운로드
- 전체 업무 흐름도
- FAQ: 택배 라인 / 권한 / 메일 / STUB / 모바일
단일 HTML 파일 — 인쇄 가능, 모바일 대응, 외부 폰트 X
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상: + 택배 추가 / + 용차 추가 버튼을 누를 때마다 같은 종류 라인이 새로 생성됨.
변경: 같은 kind 의 라인이 이미 있으면 그 라인의 qty 를 +1, 없으면 신규 추가.
- /m/admin/orders (관리자 거래명세표): 기존 라인 찾아 lines/save 로 qty+1 업데이트
- /m/orders/new (거래처 발주 작성): extras 배열에서 같은 kind 라인의 qty +1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[거래명세표 행 순서]
- 택배(DELIVERY)/용차(CHARTER) 라인이 품목(ITEM) 위로 표시되도록 정렬
- /api/m/orders/detail, /api/m/orders/statement/[id]: ORDER BY CASE kind 추가
- /m/admin/orders 화면 + xlsx 출력: 표시 순서 기준으로 SEQ 재부여 (DB seq 와 무관)
[메뉴 014]
- 마스터 관리 (9000200) → 마지막 (seq 900)
- 대시보드 (9000001) → 통계 그룹(9000500) 자식으로 이동, parent 변경
- 빈 [DASHBOARD] 대메뉴(1837127121) 비활성화
- 최종 순서: 거래처 주문 → 매입/입고 → 출고/정산 → 통계(대시보드 포함) → 마스터 관리
[로그인 랜딩]
- 기존: 모든 사용자 /m/dashboard
- 변경: 역할별 분기
· ADMIN/관리자 → /m/admin/orders (발주서 관리·출고처리)
· USER/거래처 → /m/orders/new (출고 요청)
- 회원가입 직후도 /m/orders/new 로
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[정책]
- 발주/출고/입금 흐름과 분리된 별도 메뉴 (월말 일괄 또는 신고 시점 발행 가능)
- 출고 시 자동 발행은 향후 토글 옵션으로 추가
[DB 012]
- momo_einvoices: 발행 이력 (공급자/받는자/금액/승인번호/상태/원본XML)
- momo_einvoice_items: 라인별 상세
- 상태: DRAFT → QUEUED → SENT → ACK | FAIL | CANCELED
[발행 어댑터 추상화 (lib/einvoice)]
- InvoiceProvider 인터페이스 — issue/status/cancel
- adapters/manual.ts: 자체 거래명세서 (국세청 전송 X, 기본)
- adapters/nts-esero.ts: 국세청 e-세로 직접 연동 골격
· NTS_ESERO_MODE: stub | test | prod
· stub 모드는 DB 기록만 (개발/CI 안전)
· 실 통신은 사업자 공동인증서 + ERP 연계 승인 후 활성화
· SOAP/XMLDSig 페이로드 빌더 골격 작성, 인증서 받으면 서명+전송 추가
- index.ts: EINVOICE_PROVIDER 환경변수로 어댑터 선택
[API]
- POST /api/m/einvoices/list: 발행 이력 조회 + 필터 (관리자)
- POST /api/m/einvoices/issue: 발주(orderObjid)로부터 또는 수동 입력으로 발행
· 어댑터 결과를 momo_einvoices/_items 에 트랜잭션 기록 (성공/실패 모두)
[UI]
- /m/admin/einvoices 페이지 신설
· 발행 가능 발주 리스트 (출고/입금 완료된 건)
· 한 번 클릭으로 세금계산서 발행 → 결과 모달
· 발행 이력 (날짜/상태/승인번호 필터, 엑셀 다운로드)
· STUB 모드 안내 배너 — 운영 활성화 절차 명시
[문서]
- docs/MOMO_DISTRIBUTION_SPEC.md 부록 B (v0.6) 추가
다음 단계 (인증서 + ERP 연계 승인 후):
- nts-esero.ts 의 SOAP + XMLDSig 실제 구현
- NTS_ESERO_MODE=test 로 100건 검증
- NTS_ESERO_MODE=prod 전환
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상: 거래명세표에서 [+ 택배/용차 추가] 클릭 시
null value in column "item_objid" of relation "momo_order_items" violates not-null constraint
원인: 001_momo_init.sql 에서 item_objid 가 TEXT NOT NULL 로 정의됨.
택배/용차 라인(kind=DELIVERY/CHARTER)은 품목이 아니라 가상 부가 라인이라 NULL 이 정상.
해결: ALTER ... DROP NOT NULL. ITEM 라인은 어차피 코드 레벨에서 항상 값을 넣고 있어 무영향.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
증상: /m/admin/orders 거래명세표에서 [+ 택배/용차 추가] 클릭 시 "라인 저장 중 오류" 500.
원인: 마이그레이션 010 (momo_order_items.kind/extra_label, momo_orders.total_delivery/charter)이
운영 DB에 적용되지 않음. deploy.yml 의 docker compose exec 가 silent fail 했을 가능성.
[deploy.yml]
- 컨테이너 안 마이그레이션 실패 시 → 호스트에서 docker run node:20 + pg 임시 컨테이너 폴백
- 호스트는 source 디렉토리를 /work 로 마운트해서 db/migrations 와 scripts/migrate-momo.mjs 직접 실행
- 모두 실패하면 ::error:: 로 명확히 표시 (warning 무시 방지)
[lines/save]
- catch 블록에서 실제 PG 에러 메시지 노출
- 'column does not exist'(42703) 감지 시 마이그레이션 미적용 힌트 추가
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
[요구사항 반영]
- 택배비/용차비는 매번 달라지므로 수기 입력 (담당자명+단가+수량)
- 용차 추가 버튼 → 한 라인 생성, 기본 단가 5000원
- 단가×수량 = 합계 자동 계산 (예: 5000 × 6 = 30,000)
- 거래명세표(관리자 발주 상세)에서 바로 수정 가능
[API]
- /api/m/orders/save extras 입력 형태 변경: amount → unitPrice + qty (amount 호환 유지)
- /api/m/orders/lines/save 신설: REQUESTED 상태 발주에 택배/용차 라인 추가/수정/삭제
· 본인 또는 관리자만 가능, ITEM 라인은 보호됨
· 라인 변경 후 momo_orders 합계 컬럼 (total_supply/vat/amount/taxfree/taxable/delivery/charter) 자동 재집계
[UI]
- /m/orders/new (거래처 발주 작성):
· 택배/용차 라인 단가+수량 분리 입력, 기본 단가 4000(택배)/5000(용차)
· 라인별 합계 실시간 표시 (단가 × 수량 = 합계)
- /m/admin/orders (관리자 거래명세표):
· REQUESTED 상태에서 [+ 택배 추가] [+ 용차 추가] 버튼 노출
· 택배/용차 라인은 인라인 편집 (담당자명/단가/수량 즉시 수정, 체크 클릭 → 저장)
· 삭제는 X 버튼
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 15:27:03 +09:00
2481 changed files with 19122 additions and 1410054 deletions
- **admin / front / antispam** 에 `extra_hosts` (mailu 내부 service IP 직접 박음):
```yaml
extra_hosts:
- "redis:192.168.203.4"
- "imap:192.168.203.9"
- "smtp:192.168.203.6"
- "antispam:192.168.203.8"
- "webmail:192.168.203.10"
- "front:192.168.203.3"
- "resolver:192.168.203.2"
```
> 이유: docker user-defined bridge network 에서 admin 만 DNSSEC AD flag 강제. embedded DNS(127.0.0.11) 가 DNSSEC 안 함 → admin 시작 실패. mailu unbound resolver(192.168.203.2) 가 DNSSEC capable 이라 그쪽으로 박았고, internal service hostname 은 `extra_hosts` 로 박아 NXDOMAIN 방지.
- **resolver** service 의 `ipv4_address: 192.168.203.254` **제거됨** (자동 할당).
> 이유: 192.168.203.254 IP 가 packet drop 되는 docker bridge quirk 발견. 자동 할당으로 .2 받음.
- **`/data/mailu/mailu.env`** 에 추가:
```
WEBROOT_REDIRECT=/webmail/
```
> 이유: 로그인 후 admin 계정이라 가끔 admin UI 로 redirect 됨. 항상 webmail 로 일관시킴.
- 사용자가 이전에 추가한 `"dns": ["8.8.8.8", "1.1.1.1"]` 가 모든 컨테이너 dns 옵션과 합쳐져 충돌. **이관 시 daemon.json 의 dns 키는 빼는 게 안전** (Mailu 가 의존하는 docker embedded DNS 만 사용하도록).
for path in /data/opt/docker/traefik /data/mailu /home/chpark/nextcloud /data/opt/docker/gitea /data/opt/docker/mattermost /data/opt/docker/registry /home/chpark/momo-erp/source /home/chpark/tradeing /data/opt/docker/portainer; do
- 각 서비스 admin 계정: 해당 compose 의 environment 또는 env_file
- Traefik basic auth: `/data/opt/docker/traefik/` 안 설정
- SSH 비밀번호: 운영 PC 관리자가 보관
이관 시 새 IDC 에서 비밀번호 **모두 회전(rotation) 권장**.
---
## 9. 알려진 이슈 / 주의사항
1. **Mailu resolver 192.168.203.254 IP 회피**: 이번 사고에서 .254 IP 가 packet drop 되는 docker bridge quirk 발견. IDC 에서도 발생할 수 있으니 resolver 는 자동 할당으로 두는 게 안전.
2. **MariaDB 버전 일치**: Nextcloud DB 는 11.8.x. 이관 시 절대 다운그레이드 금지 (이번에 10.5 로 갔다가 plugin load fail 로 사고남).
3. **K3s 와 docker 의 iptables/nftables 공존**: K3s 의 kube-router 가 FORWARD chain 에 끼어들어서 docker bridge 통신에 영향을 줄 수 있음. mailu 의 192.168.203.0/24 subnet 이 다른 서비스의 K3s pod CIDR 와 안 겹치는지 확인.
4. **메일 데이터 위치**: 사용자 메일함 파일은 `/mailu/data/` (호스트 root). `/data/mailu/` 와 다른 위치. 이관 시 둘 다 옮겨야 함.
5. **인증서**: Let's Encrypt 인증서는 Traefik 이 자동 발급. 이관 후 도메인 가리키면 자동 재발급. 단 rate limit 주의 (도메인당 주 5건).
* @fileOverview This file contains application creation helper function, which would load up an HTML(Skin) file and then execute a specified create function.
parent.document.body.innerHTML="진도 프레임웍이 필요합니다.<br>\n<a href='http://dev.naver.com/projects/jindo/download'>http://dev.naver.com/projects/jindo/download</a>에서 Jindo 1.5.3 버전의 jindo.min.js를 다운로드 받아 /js 폴더에 복사 해 주세요.\n(아직 Jindo 2 는 지원하지 않습니다.)";
SmartEdtitor™는 Javascript로 구현된 웹 기반의 WYSIWYG 에디터입니다. SmartEdtitor™는 WYSIWYG 모드 및 HTML 편집 모드와 TEXT 모드를 제공하고, 자유로운 폰트 크기 설정 기능, 줄 간격 설정 기능, 단어 찾기/바꾸기 기능 등 편집에 필요한 다양한 기능을 제공하므로 사용자들은 SmartEdtitor™를 사용하여 쉽고 편리하게 원하는 형태의 글을 작성할 수 있습니다.
또한, SmartEdtitor™의 구조는 기능을 쉽게 추가할 수 있는 플러그인 구조로 되어 있어 정해진 규칙에 따라 플러그인을 만들기만 하면 됩니다.
현재 SmartEdtitor™는 네이버, 한게임 등 NHN의 주요 서비스에 적용되어 있습니다.
지원하는 브라우저 환경은 아래와 같으며 지속적으로 지원 대상 브라우저를 확장할 예정입니다.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.