- 카드/리스트 모두: 작은 회색 텍스트 → 에메랄드 배경 테두리 배지로 변경
- 표현: '한정 잔여 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 으로 키워 업체 컬럼이 화면에 꽉 차게 한다.