diff --git a/public/badge-96.png b/public/badge-96.png new file mode 100644 index 0000000..ac4df2d Binary files /dev/null and b/public/badge-96.png differ diff --git a/public/sw.js b/public/sw.js index 7b17628..47de9ba 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,6 +1,6 @@ // 모모유통 ERP — Service Worker (PWA install criteria 충족용) -const CACHE = 'momo-erp-v1'; -const PRECACHE = ['/', '/manifest.json', '/icon-192.png', '/icon-512.png']; +const CACHE = 'momo-erp-v2'; +const PRECACHE = ['/', '/manifest.json', '/icon-192.png', '/icon-512.png', '/badge-96.png']; self.addEventListener('install', (e) => { 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 }); }