refactor: add service layer to centralize business logic

- 코드 가독성 및 유지보수성 향상을 위한 서비스 레이어 도입
This commit is contained in:
Dayoung 2025-06-17 14:54:42 +09:00
parent 2edde77bae
commit f6729069d7
6 changed files with 139 additions and 157 deletions

View File

@ -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<NextResponse> {
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)

View File

@ -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<NextResponse> {
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)

View File

@ -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<NextResponse> {
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)

View File

@ -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<NextResponse> {
async function getSubmitTargetData(request: NextRequest): Promise<NextResponse> {
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<NextResponse> {
}
}
export const GET = loggerWrapper(getSubmissionSuper)
export const GET = loggerWrapper(getSubmitTargetData)

View File

@ -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<SubmitTargetResponse[] | null> {
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<SubmitTargetResponse[] | null> {
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<SubmitTargetResponse[] | null> {
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<SubmitTargetResponse[] | null> {
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<SubmitTargetResponse[] | null> {
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
}
}

View File

@ -393,21 +393,16 @@ export function useSurvey(
const getSubmitTarget = async (params: { storeId: string; role: string }): Promise<SubmitTargetResponse[] | null> => {
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<SubmitTargetResponse[]>(endpoint)
return data
} catch (error: any) {