Commit Graph

333 Commits

Author SHA1 Message Date
chpark ed746e71a2 fix(inventory/history): 매입 입고 음수 차감은 '매입출고' 로 라벨
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>
2026-06-01 00:53:57 +09:00
chpark 36edafcf16 ui(orders/new): '한정 잔여' → '출고 가능 N개' 배지로 강조
- 카드/리스트 모두: 작은 회색 텍스트 → 에메랄드 배경 테두리 배지로 변경
- 표현: '한정 잔여 N / M' → '출고 가능 N(단위)  / 한정 M'
- 잔여 0 이면 로즈 배경으로 시각 차별
- limit_qty 없는 품목은 표시 안 함 (무제한, 기존 동작 유지)

계산식 그대로: limit_qty - reserved_qty (사이클 누적 합산)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 00:38:13 +09:00
chpark adcc7e3b48 feat(orders): USER 마감 후 수정 차단 + ADMIN 한정수량 우회
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>
2026-06-01 00:35:59 +09:00
chpark 2afb5fecdd fix(cycle): 사이클 종료를 strict less than 으로 — 마감 시각 정각부터는 다음 사이클
이전: 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>
2026-06-01 00:27:00 +09:00
chpark 4f2543686a feat(items): 한정 수량(limit_qty) + 출고일 배지 — 마감 사이클 단위 누적 상한
신규 컬럼: momo_items.limit_qty INTEGER (null/0 = 제한 없음)
  · ensureColumns 에서 ADD COLUMN IF NOT EXISTS 자동 보장
  · 관리자 품목 폼: '한정 수량 (이번 마감 사이클 누적 상한)' 필드 추가

마감 사이클 정의 (sale_end_date 요일 기준):
  · 월요일 마감(DOW=1): 저번주 금~월 마감 시각 (마감일 -3일 00:00 ~ 마감일)
  · 화요일 마감(DOW=2): 저번주 금~화 마감 시각 (마감일 -4일 00:00 ~ 마감일)
  · 그 외: sale_start_date ~ sale_end_date (fallback)

누적 합산 대상: 같은 사이클 안에 등록된 momo_order_items.qty
  · status: REQUESTED/APPROVED/PAID/INVOICED (CANCELLED 제외)
  · kind='ITEM' (택배/용차/환불 제외)

검증 (모든 사용자/관리자/unlimited_qty 권한 무관 적용):
  · orders/save: 이번 요청 합 + reserved <= limit_qty 체크
  · orders/items/add: 동일 검증 (트랜잭션 client 사용)
  · orders/items/update: newQty - oldQty 차이만 비교 (수량 증가 시)

신규 파일:
  · src/lib/momo-cycle.ts — getReservedQty(itemObjid, client?) 헬퍼

사용자 출고요청 화면(/m/orders/new):
  · 카드/리스트에 RESERVED_QTY 받아 '한정 잔여 N / 한정 M' 표시
  · 한정 소진 시 '한정 수량 소진' 배지 + 담기 버튼 비활성
  · 이미지 위 출고일 배지: 월요일 마감 → '수요일 출고', 화요일 마감 → '금요일 출고'
  · limit_qty 가 없는 품목은 무한대 (기존 동작 유지)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 00:17:20 +09:00
chpark 77f2ef2cd5 feat(orders): 모든 품목 재고 무관 출고요청 — 음수 재고는 매입/입고에서 발주 트리거
핵심 정책 변경:
- 기존: 택배전용 품목만 재고 무관 출고요청 가능 / 일반 품목은 재고 ≥ 요청 강제
- 신규: 모든 품목 재고 무관 출고요청 가능. 권한 체크는 sale_start/end_date,
        is_hidden(+view_hidden), max_order_qty(+unlimited_qty) 만 적용

API (재고 체크 제거 — 한도/숨김/판매기간만 유지):
- orders/save: ITEM 재고 초과 검증 제거. needsDelivery 자동 추가는 유지
- orders/items/add: 재고 초과 검증 제거
- orders/items/update: 재고 초과 검증 + stock_qty 조회 자체 제거
- items/list: onlyAvailable 재고 필터 제거(옵션은 호환 위해 no-op로 유지)

사용자 화면 — 재고 표시/품절 제거 (재고 없어도 출고 가능):
- /m/orders/new: 카드/리스트에서 STOCK_QTY 컬럼 + '품절' 배지 제거.
       한도 체크는 MAX_ORDER_QTY(권한자 무제한) 만 적용
- /m/orders 주문 상세 ItemPickerModal: 재고 컬럼 + max=stock 제거,
       stockFilter:'AVAILABLE' → forSale:true 로 교체

관리자 화면 — 현재고 표시 유지하되 음수 강조:
- /m/admin/orders 거래명세표: 현재고 음수면 bg-rose-50 + extrabold,
       '재고 부족' 경고를 '음수 재고가 됩니다' 안내로 톤 변경
- /m/admin/inventory(매입/입고): 재고 매트릭스 음수 셀 bg-rose-50 + extrabold

(approve API의 음수 재고 허용 정책은 이전부터 적용되어 있어 변경 없음)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-31 22:53:17 +09:00
chpark d6f80c187b feat(inventory): 매입 입고에서 음수 수량 허용 — 재고 차감 가능
- 입력 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>
2026-05-31 22:20:04 +09:00
chpark c69e811f46 feat(m/orders/new): 30초 폴링 + 탭 포커스 복귀 시 자동 재조회
- 관리자가 품목을 숨김/노출/재고 변경해도 다른 사용자가 로그아웃/새로고침 없이 자동 반영
- silent 모드 추가해 백그라운드 재조회 시 '불러오는 중' 깜빡임 제거
- visibilitychange + focus 이벤트로 탭 복귀 즉시 최신화

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-31 22:11:07 +09:00
chpark 83ac3a5456 feat(daily-order-inventory): 단일 일자 → 기간(시작~종료) 조회 지원
- 페이지: date 두 개(시작/종료) + '오늘'·'최근 7일' 빠른 버튼
- API: targetDate 대신 startDate/endDate 사용 (단일 호환 유지),
       판매가능 품목은 기간 겹침 조건, 발주수량은 order_date BETWEEN으로 합산

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-31 22:05:23 +09:00
chpark b7bc6b2bbf fix(m/orders/new): 검색조건 동작 / 마감일 범위 검색 / 마감 글자 줄바꿈 개선
- 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>
2026-05-31 22:05:06 +09:00
chpark 7bcfe9ce34 fix(notice-history): 본문 HTML 렌더 — 이미지/서식 정상 표시
기존 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>
2026-05-31 01:15:58 +09:00
chpark 5bd07526e4 fix(push): 알림에 본문/이미지 표시 — FCM image + sw.js image 옵션
증상: 폰에 도착한 푸시 알림이 제목만 보이고 본문/이미지 안 보임
원인:
  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>
2026-05-31 01:04:12 +09:00
chpark b11d0df704 feat(notices): 본문 textarea → Tiptap 리치 에디터 + 공지 페이지 HTML 렌더
푸시알림 게시판:
- 본문 입력을 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>
2026-05-31 00:34:33 +09:00
chpark ec6bf2922f fix(push): 알림 탭 시 앱으로 열림 (Chrome 아닌) + Tiptap 리치 에디터 추가
핵심 수정 (사용자 보고: 알림 탭 → 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>
2026-05-31 00:30:04 +09:00
chpark 2be9792263 feat(push): 네이티브 앱 자동 등록 — 로그인 직후 권한 prompt + FCM 토큰 등록
설치자 액션 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>
2026-05-31 00:16:10 +09:00
chpark 5f1983b0f6 feat(deploy): firebase-sa.json 호스트 마운트 추가 — FCM 발송용
호스트의 /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>
2026-05-31 00:11:43 +09:00
chpark 5d668716f1 feat(push): FCM(Capacitor 네이티브) 발송 통합 — 옛 web-push 와 병행
신규:
- 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>
2026-05-30 23:43:06 +09:00
chpark 294e33b5b3 fix(push): 상단 배너 표시 강화 + 첫 진입 자동 권한 prompt
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>
2026-05-30 22:51:04 +09:00
chpark 3786800f12 fix(push/subscribe): 같은 user_id 의 여러 endpoint 동시 허용
옛 정책: 같은 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>
2026-05-30 22:26:06 +09:00
chpark be86d22a6f fix(notices): 그룹 멤버 테이블에도 '구독중' 배지 표시
좌측 '그룹 멤버' 테이블에서 사용자를 우측 풀에서 옮겨오면 구독중 여부가
안 보였던 문제 — 같은 SUBSCRIBED 플래그 렌더링 추가.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 22:21:50 +09:00
chpark f2e7c03507 fix(push-optin): 삼성 인터넷 지원 + ArrayBuffer applicationServerKey
원인 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>
2026-05-30 22:09:12 +09:00
chpark 0dd392136b ci: Actions 워크플로 완전 제거 — Gitea native Webhook 단독 사용
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>
2026-05-30 18:26:27 +09:00
chpark 46a6d4697e ci: 워크플로 self-hosted 라벨로 즉시 실행 — image pull 0초
Deploy / deploy (push) Failing after 12m13s
act_runner 라벨 [ubuntu-latest:host, self-hosted:host] 둘 다 :host 매핑
이라 컨테이너 없이 호스트 셸 직접 실행. ubuntu image pull 시간(10분+)
제거 → 워크플로 ~10초 안에 .

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 18:20:21 +09:00
chpark 72b6b6873e chore(ci): Gitea Actions 워크플로 제거 — Gitea native Webhook 으로 대체
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>
2026-05-30 18:08:00 +09:00
chpark 76167f0ae5 fix(deploy/webhook): query string token 도 허용 — Gitea native Webhook 지원
Deploy momo-erp / deploy (push) Failing after 11m57s
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>
2026-05-30 18:05:38 +09:00
chpark ef298b381c fix(ci): 워크플로 헬스체크 step 제거 — webhook 호출만 = ~10초 안에
Deploy momo-erp / deploy (push) Failing after 14m27s
이전: 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>
2026-05-30 17:48:07 +09:00
chpark 06b406ba6a fix(ci): 헬스체크 polling 6회×5s 로 단축 — 워크플로 ~1분 안에
Deploy momo-erp / deploy (push) Failing after 13m7s
이전 24회×10s(최대 260s) → 6회×5s(최대 40s)
빌드는 보통 60-90s 라 polling 안에 못 들어오는 게 일반적 →
::notice 로 안내만 하고 step 정상 종료 (continue-on-error 유지)
2026-05-30 17:44:29 +09:00
chpark a897f12116 fix: Gitea Actions 워크플로 보장 + 푸시 토글 강제 클릭 가능
Deploy momo-erp / deploy (push) Failing after 15m53s
자동배포 워크플로 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>
2026-05-30 17:36:42 +09:00
chpark bbadd546ed chore(deploy): 자동배포 사이클 검증 (logged deployer)
Deploy momo-erp / deploy (push) Failing after 12m55s
2026-05-30 16:39:41 +09:00
chpark 1061332fbd fix(deploy): webhook deployer spawn 로그 redirect + base64 quoting
Deploy momo-erp / deploy (push) Failing after 14m17s
이전: spawn(docker, [...], { stdio: 'ignore' }) → 에러 보이지 않음
이제: sh -c 'docker run -d ... >> log 2>&1' → 컨테이너 ID/에러 모두 로그
+ 내부 스크립트 base64 인코딩으로 sh -c 중첩 quoting 안전화

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 16:38:17 +09:00
chpark d30c8ad8d3 chore(deploy): 임시 deployer 패턴 자동 사이클 검증
Deploy momo-erp / deploy (push) Failing after 16m17s
2026-05-30 16:31:17 +09:00
chpark 46eba2996f fix: 자동배포 임시 deployer 컨테이너 분리 + 푸시 알림 denied 안내 강화
Deploy momo-erp / deploy (push) Failing after 12m55s
자동배포 (모든 워크플로 빨강 문제 근본 해결):
- 옛 흐름: 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>
2026-05-30 16:29:41 +09:00
chpark 4933655c26 fix(push-optin): 회원정보 페이지로 위치 이동 + 실패 사유 명확 노출
Deploy momo-erp / deploy (push) Failing after 13m37s
요청 사항:
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>
2026-05-30 16:13:58 +09:00
chpark 9bd81d5fbc chore(deploy): 자동배포 webhook 사이클 검증 — .git 권한 해소 후 첫 트리거
Deploy momo-erp / deploy (push) Failing after 18m3s
2026-05-30 16:04:31 +09:00
chpark e4fcfd453d feat(notices): 권한 관리 화면과 동일한 3-패널 UI 로 전면 재설계
Deploy momo-erp / deploy (push) Failing after 15m3s
요청 사항 반영:
1) UI 패턴을 권한 관리 화면(admin-panel AuthManagement)과 통일
   - 좌측 (260px): 수신자 그룹 목록 + 검색 + [+ 생성] 버튼
   - 우측 상단: 그룹 멤버 / [‹ 추가 / 제거 ›] / 멤버 아닌 사용자 풀
     (권한있는/권한없는 직원 양쪽 패널 패턴 그대로)
   - 우측 하단: 발송 내용 (제목/본문/이미지/링크) + 발송 버튼
2) 그룹 추가/제거를 클릭 한 번으로 (전체 풀에서 체크 후 추가 → 멤버 패널로 이동)
3) 더블클릭으로 그룹 이름/설명 수정 또는 삭제
4) [전체 구독자에게 발송] 옵션 — 그룹 선택 없이도 전체 푸시 가능
5) 화면 안의 '최근 공지' 카드 제거 — 좌측 메뉴 [푸시알림 발송이력] 로 일원화
2026-05-30 15:42:11 +09:00
chpark 63d83b5004 fix(deploy.sh): git safe.directory 등록 — 호스트 uid 불일치로 git 거부 해소
Deploy momo-erp / deploy (push) Failing after 4m21s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 15:27:37 +09:00
chpark 83478fd3e1 chore(deploy): 자동배포 자기재배포 사이클 검증 트리거
Deploy momo-erp / deploy (push) Failing after 16m47s
부트스트랩 후 첫 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>
2026-05-30 15:20:47 +09:00
chpark 8bc7bc50c0 fix(docker): runtime 이미지에 git + docker CLI + dockerhost(GID 988) 그룹 포함
Deploy momo-erp / deploy (push) Failing after 18m29s
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>
2026-05-30 15:14:07 +09:00
chpark e4b64af3da fix(deploy): webhook 자기재배포 흐름 완성 — deploy.sh sh 명시 호출 + build-sha 생성
Deploy momo-erp / deploy (push) Failing after 16m1s
- 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>
2026-05-30 15:06:34 +09:00
chpark f9c7e55eb0 fix(deploy): SSH 제거 + 운영 webhook 직접 호출 방식으로 전환
Deploy momo-erp / deploy (push) Failing after 18m57s
배경:
- 옛 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>
2026-05-30 14:58:35 +09:00
chpark 199ffb56d9 fix(deploy): SSH 비밀번호를 Gitea Secret 으로 분리 + 헬스체크 견고화
Deploy momo-erp / deploy (push) Failing after 12m39s
- 코드 내 평문 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>
2026-05-30 14:44:46 +09:00
chpark 1181940bb8 infra: 운영 서버를 121.156.99.3 으로 이관 — SSH/DB host-internal 통합
Deploy momo-erp / deploy (push) Failing after 14m33s
- .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>
2026-05-30 14:17:59 +09:00
chpark 6ac6807b1b fix(deploy): DB IP를 121.156.99.3으로 갱신 — 운영 .env.production 자동 반영
Deploy momo-erp / deploy (push) Failing after 11m45s
- .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>
2026-05-30 13:55:46 +09:00
chpark 62d2c43e73 docs(db): DB 서버 IP 갱신 (121.156.99.3)
Deploy momo-erp / deploy (push) Failing after 12m21s
README.md / CLAUDE.md 의 DB 표기를 신규 호스트로 업데이트.
포트/유저/DB명/비밀번호 등 나머지는 동일.
(.env.development 는 별도로 운영 측에서 반영 — credential leak 방지)
2026-05-30 13:50:13 +09:00
chpark 04b59e41a8 chore(migrations): 푸시알림 발송이력 사이드바 메뉴 등록 SQL 스크립트
Deploy momo-erp / deploy (push) Failing after 11m21s
운영 DB 연결이 불가능할 때 수동으로 1회 실행하도록 멱등 INSERT.
2026-05-30 13:46:13 +09:00
chpark ecc14561e6 feat(notices): 수신자 그룹 + 발송이력 메뉴 신설
Deploy momo-erp / deploy (push) Failing after 12m27s
수신자 그룹:
- 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).
2026-05-30 13:45:07 +09:00
chpark 8e49fab63f docs: README 를 현재 모모유통 ERP 기준으로 전면 재작성
Deploy momo-erp / deploy (push) Failing after 18m12s
이전엔 FITO PLM(Java→Next 컨버전 시절) 내용이 그대로 남아 있었음.
현 상태(유통/물류 ERP, momotogether.com, com.momotogether.app TWA)에 맞춰
주요 기능·기술 스택·디렉토리·환경변수·Gitea 자동배포·TWA 빌드·코딩 컨벤션
모두 갱신.
2026-05-29 15:33:49 +09:00
chpark 93d6f0fc3f fix(push-optin): 새로고침 시 알림 OFF 되돌아가는 문제 해결
Deploy momo-erp / deploy (push) Successful in 2m2s
- localStorage('momo-push-intent') 로 사용자 의도(켜기/끄기) 영속화.
- 마운트 시: pushManager 가 sub 를 갖고 있으면 ON + 서버에 endpoint 재동기화.
  sub 가 없는데 의도='on' + 권한=granted 면 조용히 재구독해 ON 유지.
- SW 업데이트(v1→v2) 직후 getSubscription 이 일시적으로 null 을 반환해
  토글이 잘못 OFF 표시되던 케이스 방지.
- turnOff 는 의도를 먼저 'off' 로 기록해서 도중 실패해도 자동 재구독 안 함.
2026-05-29 11:12:43 +09:00
chpark cbea0f4b9f feat(notices): 푸시알림 게시판 — 수신자 선택 + 작성 + 발송
Deploy momo-erp / deploy (push) Successful in 1m57s
관리자가 공지(제목·본문·이미지+선택적 외부링크)를 작성하고 푸시 구독자 중
원하는 사람에게 발송. 사용자가 알림 탭하면 자체 공지 페이지(/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) 신규 등록.
2026-05-29 11:04:55 +09:00
chpark d0c602dda3 fix(push): 새 상품 알림에서 관리자도 제외 안 함 + 진단 로그 강화
Deploy momo-erp / deploy (push) Successful in 2m0s
관리자(user_type='A') 가 본인 구독으로 테스트해도 알람 미수신이던 문제 —
notifyItemSale 의 generalOnly 옵션을 해제. 관리자도 본인의 변경분으로 발송 확인 가능.
items/save 와 bulk-sale-range 양쪽에 sent/failed/스킵 사유 로그 추가.
2026-05-29 10:51:26 +09:00