From f6729069d7a3b945ccda7f2b1efc44568e1e84e5 Mon Sep 17 00:00:00 2001 From: keyy1315 Date: Tue, 17 Jun 2025 14:54:42 +0900 Subject: [PATCH] refactor: add service layer to centralize business logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 코드 가독성 및 유지보수성 향상을 위한 서비스 레이어 도입 --- src/app/api/submission/admin-sub/route.ts | 45 ------- src/app/api/submission/admin/route.ts | 50 -------- src/app/api/submission/builder/route.ts | 47 -------- src/app/api/submission/{super => }/route.ts | 17 +-- src/app/api/submission/service.ts | 126 ++++++++++++++++++++ src/hooks/useSurvey.ts | 11 +- 6 files changed, 139 insertions(+), 157 deletions(-) delete mode 100644 src/app/api/submission/admin-sub/route.ts delete mode 100644 src/app/api/submission/admin/route.ts delete mode 100644 src/app/api/submission/builder/route.ts rename src/app/api/submission/{super => }/route.ts (68%) create mode 100644 src/app/api/submission/service.ts diff --git a/src/app/api/submission/admin-sub/route.ts b/src/app/api/submission/admin-sub/route.ts deleted file mode 100644 index 53812f8..0000000 --- a/src/app/api/submission/admin-sub/route.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { prisma } from '@/libs/prisma' -import { NextRequest, NextResponse } from 'next/server' -import { loggerWrapper } from '@/libs/api-wrapper' -import { SubmitTargetResponse } from '@/types/Survey' -import { ERROR_MESSAGES } from '@/utils/common-utils' -import { HttpStatusCode } from 'axios' - -// 2차점이 자신에게 매핑 된 1차 판매점과 관리자 정보 조회 -async function getSubMissionAdminSub(request: NextRequest): Promise { - try { - const { searchParams } = new URL(request.url) - const storeId = searchParams.get('storeId') - - if (!storeId) { - return NextResponse.json({ error: ERROR_MESSAGES.BAD_REQUEST }, { status: HttpStatusCode.BadRequest }) - } - - const query = ` - OPEN SYMMETRIC KEY SYMMETRICKEY DECRYPTION BY CERTIFICATE CERTI_QSPJP; - SELECT - MCS.STORE_ID AS targetStoreId - , MCS.STORE_QCAST_NM AS targetStoreNm - , MCP.EOS_LOGIN_ID AS repUserId - , CONVERT(NVARCHAR(100), DecryptByKey(MCP.EMAIL)) AS repUserEmail - , MCP.AUTHORITY AS auth - FROM MS_CUST_STOREID MCS WITH(NOLOCK) - LEFT OUTER JOIN MS_CUST_PERSON MCP WITH(NOLOCK) - ON MCS.COMP_CD = MCP.COMP_CD - AND MCS.STORE_ID = MCP.STORE_ID - AND MCP.DEL_YN = 'N' - WHERE MCS.COMP_CD = '5200' - AND MCS.STORE_ID = (SELECT STORE_ID FROM MS_CUST_AGENCY_STOREID WHERE AGENCY_STORE_ID = '${storeId}' AND DEL_YN = 'N') - AND MCP.EMAIL IS NOT NULL - AND MCS.DEL_YN = 'N'; - CLOSE SYMMETRIC KEY SYMMETRICKEY; - ` - const data: SubmitTargetResponse[] = await prisma.$queryRawUnsafe(query) - return NextResponse.json(data) - } catch (error) { - console.error('❌ API ROUTE ERROR:', error) - return NextResponse.json({ error: ERROR_MESSAGES.FETCH_ERROR }, { status: HttpStatusCode.InternalServerError }) - } -} - -export const GET = loggerWrapper(getSubMissionAdminSub) diff --git a/src/app/api/submission/admin/route.ts b/src/app/api/submission/admin/route.ts deleted file mode 100644 index 0318e1a..0000000 --- a/src/app/api/submission/admin/route.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { prisma } from '@/libs/prisma' -import { NextRequest, NextResponse } from 'next/server' -import { loggerWrapper } from '@/libs/api-wrapper' -import { ERROR_MESSAGES } from '@/utils/common-utils' -import { HttpStatusCode } from 'axios' - -type SuperPerson = { - storeId: string - salesOfficeCd: string - fromEmail: string - toEmail: string -} - -async function getSubmissionAdmin(request: NextRequest): Promise { - try { - const { searchParams } = new URL(request.url) - const storeId = searchParams.get('storeId') - - const query = ` - OPEN SYMMETRIC KEY SYMMETRICKEY DECRYPTION BY CERTIFICATE CERTI_QSPJP; - SELECT - MCSA.STORE_ID - , BCL.CODE AS SALES_OFFICE_CD - , REF_CHR1 AS FROM_E_MAIL - , REF_CHR2 AS TO_E_MAIL - FROM MS_CUST_STOREID_ADDITNL MCSA WITH(NOLOCK) - LEFT OUTER JOIN IF_PERSON_OFFICE_MAPPING IPOM WITH(NOLOCK) - ON MCSA.KAM_ID = IPOM.LIFNR - AND IF_STS = 'R' - AND VKBUR IS NOT NULL - AND VKBUR != '' - INNER JOIN BC_COMM_L BCL WITH(NOLOCK) - ON BCL.CODE = IPOM.VKBUR - AND BCL.HEAD_CD = '103200' - AND BCL.DEL_YN = 'N' - WHERE MCSA.COMP_CD = '5200' - AND MCSA.STORE_ID = '${storeId}' - AND MCSA.DEL_YN = 'N' - ; - CLOSE SYMMETRIC KEY SYMMETRICKEY; - ` - const data: SuperPerson[] = await prisma.$queryRawUnsafe(query) - return NextResponse.json(data) - } catch (error) { - console.error('❌ API ROUTE ERROR:', error) - return NextResponse.json({ error: ERROR_MESSAGES.FETCH_ERROR }, { status: HttpStatusCode.InternalServerError }) - } -} - -export const GET = loggerWrapper(getSubmissionAdmin) diff --git a/src/app/api/submission/builder/route.ts b/src/app/api/submission/builder/route.ts deleted file mode 100644 index 6c831f6..0000000 --- a/src/app/api/submission/builder/route.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { prisma } from '@/libs/prisma' -import { SubmitTargetResponse } from '@/types/Survey' -import { NextRequest, NextResponse } from 'next/server' -import { loggerWrapper } from '@/libs/api-wrapper' -import { ERROR_MESSAGES } from '@/utils/common-utils' -import { HttpStatusCode } from 'axios' - -// 2차점의 시공권한 user가 해당 판매점의 관리자 정보 조회 -// N == 일반유저, S == 수퍼유저, B == 시공권한유저 -async function getSubmissionBuilder(request: NextRequest): Promise { - try { - const { searchParams } = new URL(request.url) - const id = searchParams.get('id') - - if (!id) { - return NextResponse.json({ error: ERROR_MESSAGES.BAD_REQUEST }, { status: HttpStatusCode.BadRequest }) - } - - const query = ` - OPEN SYMMETRIC KEY SYMMETRICKEY DECRYPTION BY CERTIFICATE CERTI_QSPJP; - SELECT - MCAS.AGENCY_STORE_ID AS targetStoreId - , MCAS.AGENCY_QCAST_NM AS targetStoreNm - , BQU.USER_ID AS repUserId - , CONVERT(NVARCHAR(100), DecryptByKey(BQU.EMAIL)) AS repUserEmail - , BQU.USER_AUTH_CD AS auth - FROM MS_CUST_AGENCY_STOREID MCAS WITH(NOLOCK) - LEFT OUTER JOIN BC_QM_USER BQU WITH(NOLOCK) - ON MCAS.COMP_CD = BQU.COMP_CD - AND MCAS.AGENCY_STORE_ID = BQU.AGENCY_STORE_ID - AND MCAS.DEL_YN = 'N' - WHERE MCAS.COMP_CD = '5200' - AND MCAS.AGENCY_STORE_ID = '${id}' - AND BQU.EMAIL IS NOT NULL - AND BQU.USER_AUTH_CD != 'B' - AND MCAS.DEL_YN = 'N'; - CLOSE SYMMETRIC KEY SYMMETRICKEY; - ` - const data: SubmitTargetResponse[] = await prisma.$queryRawUnsafe(query) - return NextResponse.json(data) - } catch (error) { - console.error('❌ API ROUTE ERROR:', error) - return NextResponse.json({ error: ERROR_MESSAGES.FETCH_ERROR }, { status: HttpStatusCode.InternalServerError }) - } -} - -export const GET = loggerWrapper(getSubmissionBuilder) diff --git a/src/app/api/submission/super/route.ts b/src/app/api/submission/route.ts similarity index 68% rename from src/app/api/submission/super/route.ts rename to src/app/api/submission/route.ts index 76c5fc9..bc4fc4a 100644 --- a/src/app/api/submission/super/route.ts +++ b/src/app/api/submission/route.ts @@ -1,17 +1,20 @@ import { NextRequest, NextResponse } from 'next/server' -import { loggerWrapper } from '@/libs/api-wrapper' -import { ERROR_MESSAGES } from '@/utils/common-utils' +import { SubmissionService } from './service' import { HttpStatusCode } from 'axios' -import { SubmissionService } from '../service' +import { ERROR_MESSAGES } from '@/utils/common-utils' +import { loggerWrapper } from '@/libs/api-wrapper' -async function getSubmissionSuper(request: NextRequest): Promise { +async function getSubmitTargetData(request: NextRequest): Promise { try { const { searchParams } = new URL(request.url) const storeId = searchParams.get('storeId') - if (!storeId) { + const role = searchParams.get('role') + + if (!storeId || !role) { return NextResponse.json({ error: ERROR_MESSAGES.BAD_REQUEST }, { status: HttpStatusCode.BadRequest }) } - const submissionService = new SubmissionService(storeId, 'Super') + + const submissionService = new SubmissionService(storeId, role) const data = await submissionService.getSubmissionTarget() return NextResponse.json(data) } catch (error) { @@ -20,4 +23,4 @@ async function getSubmissionSuper(request: NextRequest): Promise { } } -export const GET = loggerWrapper(getSubmissionSuper) +export const GET = loggerWrapper(getSubmitTargetData) diff --git a/src/app/api/submission/service.ts b/src/app/api/submission/service.ts new file mode 100644 index 0000000..4431553 --- /dev/null +++ b/src/app/api/submission/service.ts @@ -0,0 +1,126 @@ +import { prisma } from '@/libs/prisma' +import { SubmitTargetResponse } from '@/types/Survey' + +export class SubmissionService { + private storeId: string + private role: string + + constructor(storeId: string, role: string) { + this.storeId = storeId + this.role = role + } + + async getSubmissionTarget(): Promise { + switch (this.role) { + case 'Admin': + return this.getSubmissionTargetAdmin() + case 'Admin_Sub': + return this.getSubmissionTargetAdminSub() + case 'Builder': + return this.getSubmissionTargetBuilder() + case 'Super': + return this.getSubmissionTargetSuper() + default: + return null + } + } + + private async getSubmissionTargetAdmin(): Promise { + const query = ` + OPEN SYMMETRIC KEY SYMMETRICKEY DECRYPTION BY CERTIFICATE CERTI_QSPJP; + SELECT + MCSA.STORE_ID AS targetStoreId + , BCL.CODE AS salesOfficeCd + , REF_CHR1 AS fromEmail + , REF_CHR2 AS toEmail + FROM MS_CUST_STOREID_ADDITNL MCSA WITH(NOLOCK) + LEFT OUTER JOIN IF_PERSON_OFFICE_MAPPING IPOM WITH(NOLOCK) + ON MCSA.KAM_ID = IPOM.LIFNR + AND IF_STS = 'R' + AND VKBUR IS NOT NULL + AND VKBUR != '' + INNER JOIN BC_COMM_L BCL WITH(NOLOCK) + ON BCL.CODE = IPOM.VKBUR + AND BCL.HEAD_CD = '103200' + AND BCL.DEL_YN = 'N' + WHERE MCSA.COMP_CD = '5200' + AND MCSA.STORE_ID = '${this.storeId}' + AND MCSA.DEL_YN = 'N'; + CLOSE SYMMETRIC KEY SYMMETRICKEY; + ` + const data: SubmitTargetResponse[] = await prisma.$queryRawUnsafe(query) + return data + } + + + private async getSubmissionTargetAdminSub(): Promise { + const query = ` + OPEN SYMMETRIC KEY SYMMETRICKEY DECRYPTION BY CERTIFICATE CERTI_QSPJP; + SELECT + MCS.STORE_ID AS targetStoreId + , MCS.STORE_QCAST_NM AS targetStoreNm + , MCP.EOS_LOGIN_ID AS repUserId + , CONVERT(NVARCHAR(100), DecryptByKey(MCP.EMAIL)) AS repUserEmail + , MCP.AUTHORITY AS auth + FROM MS_CUST_STOREID MCS WITH(NOLOCK) + LEFT OUTER JOIN MS_CUST_PERSON MCP WITH(NOLOCK) + ON MCS.COMP_CD = MCP.COMP_CD + AND MCS.STORE_ID = MCP.STORE_ID + AND MCP.DEL_YN = 'N' + WHERE MCS.COMP_CD = '5200' + AND MCS.STORE_ID = (SELECT STORE_ID FROM MS_CUST_AGENCY_STOREID WHERE AGENCY_STORE_ID = '${this.storeId}' AND DEL_YN = 'N') + AND MCP.EMAIL IS NOT NULL + AND MCS.DEL_YN = 'N'; + CLOSE SYMMETRIC KEY SYMMETRICKEY; + ` + const data: SubmitTargetResponse[] = await prisma.$queryRawUnsafe(query) + return data + } + + private async getSubmissionTargetBuilder(): Promise { + const query = ` + OPEN SYMMETRIC KEY SYMMETRICKEY DECRYPTION BY CERTIFICATE CERTI_QSPJP; + SELECT + MCAS.AGENCY_STORE_ID AS targetStoreId + , MCAS.AGENCY_QCAST_NM AS targetStoreNm + , BQU.USER_ID AS repUserId + , CONVERT(NVARCHAR(100), DecryptByKey(BQU.EMAIL)) AS repUserEmail + , BQU.USER_AUTH_CD AS auth + FROM MS_CUST_AGENCY_STOREID MCAS WITH(NOLOCK) + LEFT OUTER JOIN BC_QM_USER BQU WITH(NOLOCK) + ON MCAS.COMP_CD = BQU.COMP_CD + AND MCAS.AGENCY_STORE_ID = BQU.AGENCY_STORE_ID + AND MCAS.DEL_YN = 'N' + WHERE MCAS.COMP_CD = '5200' + AND MCAS.AGENCY_STORE_ID = '${this.storeId}' + AND BQU.EMAIL IS NOT NULL + AND BQU.USER_AUTH_CD != 'B' + AND MCAS.DEL_YN = 'N'; + CLOSE SYMMETRIC KEY SYMMETRICKEY; + ` + const data: SubmitTargetResponse[] = await prisma.$queryRawUnsafe(query) + return data + } + + private async getSubmissionTargetSuper(): Promise { + const query = ` + OPEN SYMMETRIC KEY SYMMETRICKEY DECRYPTION BY CERTIFICATE CERTI_QSPJP; + SELECT + MCSA.STORE_ID AS targetStoreId + , BU.USER_ID AS repUserId + , CONVERT(NVARCHAR(100), DecryptByKey(BU.E_MAIL)) AS repUserEmail + FROM MS_CUST_STOREID_ADDITNL MCSA WITH(NOLOCK) + LEFT OUTER JOIN BC_USER bu WITH(NOLOCK) + ON MCSA.COMP_CD = BU.COMP_CD + AND MCSA.KAM_ID = BU.KAM_ID + AND BU.STAT_CD = 'A' + AND BU.DEL_YN = 'N' + WHERE MCSA.COMP_CD = '5200' + AND MCSA.STORE_ID = '${this.storeId}' + AND MCSA.DEL_YN = 'N'; + CLOSE SYMMETRIC KEY SYMMETRICKEY; + ` + const data: SubmitTargetResponse[] = await prisma.$queryRawUnsafe(query) + return data + } +} diff --git a/src/hooks/useSurvey.ts b/src/hooks/useSurvey.ts index bee304a..d6581f9 100644 --- a/src/hooks/useSurvey.ts +++ b/src/hooks/useSurvey.ts @@ -393,21 +393,16 @@ export function useSurvey( const getSubmitTarget = async (params: { storeId: string; role: string }): Promise => { try { if (!params.storeId) { + /** 판매점 ID 없는 경우 */ alert('販売店IDがありません。') return null } - - const endpoints = { - Admin_Sub: `/api/submission/admin-sub?id=${params.storeId}`, - Builder: `/api/submission/builder?id=${params.storeId}`, - } as const - - const endpoint = endpoints[params.role as keyof typeof endpoints] + const endpoint = `/api/submission?storeId=${params.storeId}&role=${params.role}` if (!endpoint) { + /** 권한 오류 */ alert('権限が間違っています。') return null } - const { data } = await axiosInstance(null).get(endpoint) return data } catch (error: any) {