From 89503ebf03070b206c7a846ef1af2111e095f4e0 Mon Sep 17 00:00:00 2001 From: chpark Date: Wed, 27 May 2026 00:43:45 +0900 Subject: [PATCH] =?UTF-8?q?fix(push):=20=EC=9D=BC=EA=B4=84=20=ED=8C=90?= =?UTF-8?q?=EB=A7=A4=EA=B8=B0=EA=B0=84=20=EC=A0=81=EC=9A=A9=EB=8F=84=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EB=B0=9C=EC=86=A1=20+=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=AA=A8=EB=AA=A8=20?= =?UTF-8?q?=EB=A1=9C=EA=B3=A0/=EB=8B=A8=EC=83=89=20=EB=B0=B0=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - bulk-sale-range: 리스트에서 판매기간 일괄 적용 시에도 일반 사용자 푸시. 1건이면 품목명, 여러 건이면 'N개 품목 판매' 요약. 해제(clear)는 알림 제외. - 알림 아이콘: 큰 아이콘은 모모 로고(icon-192), 상태바 작은 배지는 흰 M 단색 투명 PNG(badge-96) — 기존엔 컬러 PNG라 크롬이 지구본 기본 배지로 대체했음. - sw.js: CACHE v2 로 올려 갱신 강제 + badge-96 precache, push 핸들러가 payload icon/badge 우선 사용. --- public/badge-96.png | Bin 0 -> 1014 bytes public/sw.js | 8 ++-- src/app/api/m/items/bulk-sale-range/route.ts | 45 +++++++++++++++++++ 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 public/badge-96.png diff --git a/public/badge-96.png b/public/badge-96.png new file mode 100644 index 0000000000000000000000000000000000000000..ac4df2d8fce3b2633ee8bb53b72e89491e85d0ee GIT binary patch literal 1014 zcmVhVdS_df`)+< z5oJkbuW{t8CPfeqLW zaLh8yX%1}gO28JwoVFU}Zxs*8nCMNDg~AI4Tb4_=Sm3xCXwgBEB1OCz%ydL!EkNAx1eA+<~})o9E*I z0#JMc*h{lZhV93pOFlgT!B7$E0DLEk_6a~uLh-8rydmasYt=~rhM$125O>Mn+!llO z{Xq->ZlryRBw%#=kU0n374wvlxJY~< zY++X7YqZTN0TJB0Ir$sBHX0Yky1!}}`by%o1&!d|&B-DjD+c|EJwnFrEs2wW5dbVF zi}t)u{6A%T! zezKUSBQeLxn2u44NdU?{0j*>)U#4t7I+`V*vVcOkS2N57FqLdSelB;9q%Ein0B({3 z+EcV2ug0vUGoTcAS3?%{LW=g|uD=$N05rrDrG+f&M*uI#v5O^u7f>MYby!>_f=I1NGTv?hbxJT zK?2%>5^#6-WBpZE2}r=*-5UsKs<4I*;W*s8d-x8YmG?>ef4GJOB;oGxa|k$}IJpF% zfhf?P90ImUKz;$uIRq?_fcyfgGoAzPU({4dKu!S;0M{}I=nV*T2ot3E%x9E#a&El^ zRRX}03<9WsF)0BA;W^-%FD!4F{Xd*rPXL4K>822Hj+%5l3J^jFA%qY@2qA { e.waitUntil(caches.open(CACHE).then((c) => c.addAll(PRECACHE)).catch(() => {})); @@ -33,8 +33,8 @@ self.addEventListener('push', (e) => { const title = data.title || '모모유통'; const options = { body: data.body || '', - icon: '/icon-192.png', - badge: '/icon-192.png', + icon: data.icon || '/icon-192.png', // 큰 아이콘 = 모모 로고(초록 M) + badge: data.badge || '/badge-96.png', // 상태바 작은 아이콘 = 흰 M 단색(투명 배경) tag: data.tag || undefined, data: { url: data.url || '/m/orders/new' }, }; diff --git a/src/app/api/m/items/bulk-sale-range/route.ts b/src/app/api/m/items/bulk-sale-range/route.ts index 23e4d72..31c8395 100644 --- a/src/app/api/m/items/bulk-sale-range/route.ts +++ b/src/app/api/m/items/bulk-sale-range/route.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from "next/server"; import { pool } from "@/lib/db"; import { requireMomoAdmin } from "@/lib/momo-guard"; +import { sendPush } from "@/lib/push"; export async function POST(req: NextRequest) { const g = await requireMomoAdmin(); @@ -28,5 +29,49 @@ export async function POST(req: NextRequest) { WHERE objid IN (${placeholders})`, [...objids, start, end, userId] ); + + // 일괄 적용한 판매 일정이 유효(오늘/미래)한 품목이 있으면 일반 사용자에게 알림. + // 판매기간 해제(clear)는 알림 대상 아님. (상세 수정과 동일 정책) + if (!clear) { + try { + const sell = await pool.query<{ item_name: string; start_txt: string | null; orderable_now: boolean }>( + `SELECT item_name, + TO_CHAR(sale_start_date,'YYYY-MM-DD HH24:MI') AS start_txt, + (sale_start_date IS NULL OR (NOW() AT TIME ZONE 'Asia/Seoul') >= sale_start_date) AS orderable_now + FROM momo_items + WHERE objid IN (${placeholders}) + AND COALESCE(is_del,'N') != 'Y' + AND UPPER(COALESCE(status,'')) = 'ACTIVE' + AND COALESCE(is_hidden,'N') != 'Y' + AND (sale_start_date IS NOT NULL OR sale_end_date IS NOT NULL) + AND ( + sale_end_date IS NULL + OR (NOW() AT TIME ZONE 'Asia/Seoul') <= 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 + )`, + objids + ); + const rows = sell.rows; + if (rows.length === 1) { + const r = rows[0]; + const body = r.orderable_now + ? `${r.item_name} — 지금 출고요청할 수 있어요.` + : `${r.item_name} — ${r.start_txt ?? "곧"} 판매 예정입니다.`; + await sendPush({ title: "판매 품목 안내", body, url: "/m/orders/new", tag: "item-bulk" }, undefined, { generalOnly: true }); + } else if (rows.length > 1) { + await sendPush( + { title: "판매 품목 안내", body: `${rows.length}개 품목 판매가 시작/예정되었어요. 지금 확인해보세요.`, url: "/m/orders/new", tag: "item-bulk" }, + undefined, + { generalOnly: true } + ); + } + } catch (e) { + console.error("[bulk-sale-range notify]", e); + } + } + return NextResponse.json({ success: true, count: res.rowCount ?? 0 }); }