From b0808b1d0aa03497f0cc4c7f8b5a415d15cea5c3 Mon Sep 17 00:00:00 2001 From: chpark Date: Thu, 7 May 2026 16:43:57 +0900 Subject: [PATCH] =?UTF-8?q?feat(momo=20v0.6):=20=EA=B1=B0=EB=9E=98?= =?UTF-8?q?=EB=AA=85=EC=84=B8=ED=91=9C=20=ED=96=89=20=EC=88=9C=EC=84=9C=20?= =?UTF-8?q?+=20=EB=A9=94=EB=89=B4=20=EC=9E=AC=EB=B0=B0=EC=B9=98=20+=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=9E=9C=EB=94=A9=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [거래명세표 행 순서] - 택배(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) --- db/migrations/014_menu_reorder.sql | 33 ++++++++++++++++++++ src/app/(auth)/signup/page.tsx | 3 +- src/app/(main)/m/admin/orders/page.tsx | 11 ++++--- src/app/api/auth/login/route.ts | 15 ++++++--- src/app/api/m/orders/detail/route.ts | 8 ++++- src/app/api/m/orders/statement/[id]/route.ts | 14 +++++++-- 6 files changed, 71 insertions(+), 13 deletions(-) create mode 100644 db/migrations/014_menu_reorder.sql diff --git a/db/migrations/014_menu_reorder.sql b/db/migrations/014_menu_reorder.sql new file mode 100644 index 0000000..901df8f --- /dev/null +++ b/db/migrations/014_menu_reorder.sql @@ -0,0 +1,33 @@ +-- 014_menu_reorder.sql +-- v0.6 (2026-05-07) +-- 메뉴 순서 재배치 + 대시보드 위치 이동 +-- +-- 변경 후 순서: +-- 600 거래처 주문 (9000100) +-- 700 매입/입고 (9000300) +-- 750 출고/정산 (9000400) +-- 800 통계 (9000500) ← 대시보드 자식으로 포함 +-- 900 마스터 관리 (9000200) ← 마지막 +-- +-- 대시보드(9000001) 는 [DASHBOARD] 대메뉴(1837127121) → 통계(9000500) 자식으로 이동 +-- [DASHBOARD] 대메뉴 자체는 비활성화 + +BEGIN; + +-- 1. 대메뉴 seq 재조정 — 마스터 관리를 맨 뒤로 +UPDATE menu_info SET seq = 900 WHERE objid = 9000200; -- 마스터 관리 +-- 거래처 주문(600), 매입/입고(700), 출고/정산(750), 통계(800)는 그대로 + +-- 2. 대시보드(9000001) 를 통계(9000500) 의 첫 자식으로 이동 +UPDATE menu_info + SET parent_obj_id = 9000500, + seq = 5, + menu_name_kor = '대시보드' + WHERE objid = 9000001; + +-- 3. 빈 [DASHBOARD] 대메뉴(1837127121) 비활성화 +UPDATE menu_info + SET status = 'inactive' + WHERE objid = 1837127121; + +COMMIT; diff --git a/src/app/(auth)/signup/page.tsx b/src/app/(auth)/signup/page.tsx index 7bdaa4d..70e6e99 100644 --- a/src/app/(auth)/signup/page.tsx +++ b/src/app/(auth)/signup/page.tsx @@ -61,7 +61,8 @@ export default function SignupPage() { text: "이제 발주를 시작하실 수 있습니다.", confirmButtonColor: "#0f766e", }); - router.push("/m/dashboard"); + // 거래처 가입 직후 — 출고 요청 화면으로 + router.push("/m/orders/new"); } else { Swal.fire({ icon: "error", title: "가입 실패", text: data.message }); } diff --git a/src/app/(main)/m/admin/orders/page.tsx b/src/app/(main)/m/admin/orders/page.tsx index 18ae740..0f7f9ec 100644 --- a/src/app/(main)/m/admin/orders/page.tsx +++ b/src/app/(main)/m/admin/orders/page.tsx @@ -441,7 +441,8 @@ function StatementPreview({ - {items.map((it) => { + {items.map((it, idx) => { + const displaySeq = idx + 1; const isExtra = it.KIND === "DELIVERY" || it.KIND === "CHARTER"; const lack = !isExtra && Number(it.STOCK_QTY) < Number(it.QTY); const kindBadge = it.KIND === "DELIVERY" ? "택배" : it.KIND === "CHARTER" ? "용차" : null; @@ -452,6 +453,7 @@ function StatementPreview({ upsertExtra({ objid: it.OBJID, kind: it.KIND as "DELIVERY" | "CHARTER", ...updated })} onDelete={() => deleteExtra(it.OBJID)} /> @@ -460,7 +462,7 @@ function StatementPreview({ return ( - {it.SEQ} + {displaySeq} {kindBadge && {kindBadge}} {it.ITEM_NAME} @@ -524,8 +526,9 @@ function StatementPreview({ ); } -function ExtraRow({ line, onSave, onDelete }: { +function ExtraRow({ line, displaySeq, onSave, onDelete }: { line: DetailLine; + displaySeq: number; onSave: (data: { label: string; unitPrice: number; qty: number }) => void; onDelete: () => void; }) { @@ -542,7 +545,7 @@ function ExtraRow({ line, onSave, onDelete }: { return ( - {line.SEQ} + {displaySeq} {isDelivery ? "택배" : "용차"} diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts index e96731d..9291a63 100644 --- a/src/app/api/auth/login/route.ts +++ b/src/app/api/auth/login/route.ts @@ -18,12 +18,20 @@ export async function POST(request: NextRequest) { // 이메일 형태이면 MOMO 사용자 우선 시도, 그 외에는 FITO 우선 시도 const looksLikeEmail = /@/.test(userId); + // 역할별 진입 페이지: + // ADMIN(모모유통 임직원/시스템 관리자) → /m/admin/orders (발주서 관리·출고처리) + // USER (거래처) → /m/orders/new (출고 요청) + const landingFor = (u: User): string => + (u.isAdmin || u.role === "ADMIN" || u.userType === "A") + ? "/m/admin/orders" + : "/m/orders/new"; + if (looksLikeEmail) { const momo = await verifyMomoCredentials(userId, password); if (momo.success && momo.user) { const sessionUser: User = momoToSessionUser(momo.user); await createSession(sessionUser); - return NextResponse.json({ success: true, user: sessionUser, redirectTo: "/m/dashboard" }); + return NextResponse.json({ success: true, user: sessionUser, redirectTo: landingFor(sessionUser) }); } // MOMO 실패 시 FITO 폴백 시도 (관리자 마이그레이션 케이스) } @@ -31,8 +39,7 @@ export async function POST(request: NextRequest) { const fito = await verifyCredentials(userId, password); if (fito.success && fito.user) { await createSession(fito.user); - // 모모유통 도메인은 모두 모모 대시보드로 (FITO admin 도 /m/dashboard 로 진입) - return NextResponse.json({ success: true, user: fito.user, redirectTo: "/m/dashboard" }); + return NextResponse.json({ success: true, user: fito.user, redirectTo: landingFor(fito.user) }); } // FITO 도 실패하면 MOMO를 한 번 더 시도 (이메일 형태가 아니지만 MOMO 계정인 경우) @@ -41,7 +48,7 @@ export async function POST(request: NextRequest) { if (momo.success && momo.user) { const sessionUser: User = momoToSessionUser(momo.user); await createSession(sessionUser); - return NextResponse.json({ success: true, user: sessionUser, redirectTo: "/m/dashboard" }); + return NextResponse.json({ success: true, user: sessionUser, redirectTo: landingFor(sessionUser) }); } } diff --git a/src/app/api/m/orders/detail/route.ts b/src/app/api/m/orders/detail/route.ts index 9695d9c..4bc05b5 100644 --- a/src/app/api/m/orders/detail/route.ts +++ b/src/app/api/m/orders/detail/route.ts @@ -64,7 +64,13 @@ export async function POST(req: NextRequest) { FROM momo_order_items OI LEFT JOIN momo_items I ON OI.item_objid = I.objid WHERE OI.order_objid = $1 - ORDER BY OI.seq ASC`, + ORDER BY + CASE COALESCE(OI.kind,'ITEM') + WHEN 'DELIVERY' THEN 0 + WHEN 'CHARTER' THEN 1 + ELSE 2 + END, + OI.seq ASC`, [objid] ); diff --git a/src/app/api/m/orders/statement/[id]/route.ts b/src/app/api/m/orders/statement/[id]/route.ts index 2f340a6..615cef4 100644 --- a/src/app/api/m/orders/statement/[id]/route.ts +++ b/src/app/api/m/orders/statement/[id]/route.ts @@ -26,8 +26,16 @@ export async function GET(req: NextRequest, ctx: { params: Promise<{ id: string const items = await queryRows>( `SELECT seq, item_name_snap, unit_price, qty, is_tax_free, supply_amount, vat_amount, total_amount, + COALESCE(kind, 'ITEM') AS kind, extra_label, (SELECT unit FROM momo_items WHERE objid = OI.item_objid) AS unit - FROM momo_order_items OI WHERE order_objid = $1 ORDER BY seq`, + FROM momo_order_items OI WHERE order_objid = $1 + ORDER BY + CASE COALESCE(kind,'ITEM') + WHEN 'DELIVERY' THEN 0 + WHEN 'CHARTER' THEN 1 + ELSE 2 + END, + seq ASC`, [id] ); @@ -46,8 +54,8 @@ export async function GET(req: NextRequest, ctx: { params: Promise<{ id: string phone: process.env.MOMO_PHONE ?? "010-6624-5315", email: process.env.SMTP_FROM ?? "momo8443@daum.net", }, - items: items.map((it) => ({ - seq: Number(it.seq), + items: items.map((it, idx) => ({ + seq: idx + 1, itemName: String(it.item_name_snap), unit: String(it.unit ?? "EA"), qty: Number(it.qty),