refactor: improve error handling in API routes and services
- API 에러 시 반환 타입 통일 - Prisma 에러 검증 추가
This commit is contained in:
parent
b116e6e5c1
commit
d690c3e774
@ -3,6 +3,7 @@ import { SubmissionService } from './service'
|
||||
import { HttpStatusCode } from 'axios'
|
||||
import { ERROR_MESSAGES } from '@/utils/common-utils'
|
||||
import { loggerWrapper } from '@/libs/api-wrapper'
|
||||
import { ApiError } from 'next/dist/server/api-utils'
|
||||
|
||||
/**
|
||||
* @api {GET} /api/submission 제출 대상 조회
|
||||
@ -50,8 +51,11 @@ async function getSubmitTargetData(request: NextRequest): Promise<NextResponse>
|
||||
}
|
||||
|
||||
const submissionService = new SubmissionService(storeId, role)
|
||||
const data = await submissionService.tryFunction(() => submissionService.getSubmissionTarget())
|
||||
return NextResponse.json(data)
|
||||
const result = await submissionService.tryFunction(() => submissionService.getSubmissionTarget())
|
||||
if (result instanceof ApiError) {
|
||||
return NextResponse.json({ error: result.message }, { status: result.statusCode })
|
||||
}
|
||||
return NextResponse.json(result)
|
||||
}
|
||||
|
||||
export const GET = loggerWrapper(getSubmitTargetData)
|
||||
|
||||
@ -2,7 +2,8 @@ import { prisma } from '@/libs/prisma'
|
||||
import { ERROR_MESSAGES } from '@/utils/common-utils'
|
||||
import { SubmitTargetResponse } from '@/types/Survey'
|
||||
import { HttpStatusCode } from 'axios'
|
||||
import { NextResponse } from 'next/server'
|
||||
import { ApiError } from 'next/dist/server/api-utils'
|
||||
import { Prisma } from '@prisma/client'
|
||||
|
||||
export class SubmissionService {
|
||||
private storeId: string
|
||||
@ -18,18 +19,26 @@ export class SubmissionService {
|
||||
this.role = role
|
||||
}
|
||||
|
||||
async getSubmissionTarget(): Promise<SubmitTargetResponse[] | null> {
|
||||
/**
|
||||
* @description 제출 대상 조회
|
||||
* @returns {Promise<SubmitTargetResponse[] | ApiError>} 제출 대상 데이터
|
||||
*/
|
||||
async getSubmissionTarget(): Promise<SubmitTargetResponse[] | ApiError> {
|
||||
switch (this.role) {
|
||||
case 'Admin_Sub':
|
||||
return this.getSubmissionTargetAdminSub()
|
||||
case 'Builder':
|
||||
return this.getSubmissionTargetBuilder()
|
||||
default:
|
||||
return null
|
||||
return new ApiError(HttpStatusCode.BadRequest, ERROR_MESSAGES.BAD_REQUEST)
|
||||
}
|
||||
}
|
||||
|
||||
private async getSubmissionTargetAdminSub(): Promise<SubmitTargetResponse[] | null> {
|
||||
/**
|
||||
* @description 2차점의 매핑 된 제출 대상 판매점 조회 (Admin_Sub - Musubi)
|
||||
* @returns {Promise<SubmitTargetResponse[]>} 제출 대상 데이터
|
||||
*/
|
||||
private async getSubmissionTargetAdminSub(): Promise<SubmitTargetResponse[]> {
|
||||
const query = `
|
||||
SELECT
|
||||
MCS.STORE_ID AS targetStoreId
|
||||
@ -51,7 +60,11 @@ export class SubmissionService {
|
||||
return await prisma.$queryRawUnsafe(this.BASE_QUERY + query, this.storeId)
|
||||
}
|
||||
|
||||
private async getSubmissionTargetBuilder(): Promise<SubmitTargetResponse[] | null> {
|
||||
/**
|
||||
* @description 2차점 시공권한 user의 매핑 된 제출 대상 판매점 조회 (Builder - Musubi)
|
||||
* @returns {Promise<SubmitTargetResponse[]>} 제출 대상 데이터
|
||||
*/
|
||||
private async getSubmissionTargetBuilder(): Promise<SubmitTargetResponse[]> {
|
||||
const query = `
|
||||
SELECT
|
||||
MCAS.AGENCY_STORE_ID AS targetStoreId
|
||||
@ -74,12 +87,31 @@ export class SubmissionService {
|
||||
return await prisma.$queryRawUnsafe(this.BASE_QUERY + query, this.storeId)
|
||||
}
|
||||
|
||||
handleRouteError(error: unknown): NextResponse {
|
||||
/**
|
||||
* @description API ROUTE 에러 처리
|
||||
* @param error 에러 객체
|
||||
* @returns {ApiError} 에러 객체
|
||||
*/
|
||||
handleRouteError(error: any): ApiError {
|
||||
console.error('❌ API ROUTE ERROR : ', error)
|
||||
return NextResponse.json({ error: ERROR_MESSAGES.FETCH_ERROR }, { status: HttpStatusCode.InternalServerError })
|
||||
if (
|
||||
error instanceof Prisma.PrismaClientInitializationError ||
|
||||
error instanceof Prisma.PrismaClientUnknownRequestError ||
|
||||
error instanceof Prisma.PrismaClientRustPanicError ||
|
||||
error instanceof Prisma.PrismaClientValidationError ||
|
||||
error instanceof Prisma.PrismaClientUnknownRequestError
|
||||
) {
|
||||
return new ApiError(HttpStatusCode.InternalServerError, ERROR_MESSAGES.PRISMA_ERROR)
|
||||
}
|
||||
return new ApiError(error.statusCode ?? HttpStatusCode.InternalServerError, error.message ?? ERROR_MESSAGES.FETCH_ERROR)
|
||||
}
|
||||
|
||||
async tryFunction(func: () => Promise<any>): Promise<any> {
|
||||
/**
|
||||
* @description 비동기 함수 try-catch 처리 함수
|
||||
* @param func 비동기 함수
|
||||
* @returns {Promise<ApiError | any>} 에러 객체 또는 함수 결과
|
||||
*/
|
||||
async tryFunction(func: () => Promise<any>): Promise<ApiError | any> {
|
||||
try {
|
||||
return await func()
|
||||
} catch (error) {
|
||||
|
||||
@ -4,9 +4,9 @@ import { sessionOptions } from '@/libs/session'
|
||||
import { cookies } from 'next/headers'
|
||||
import type { SessionData } from '@/types/Auth'
|
||||
import { HttpStatusCode } from 'axios'
|
||||
import { ERROR_MESSAGES } from '@/utils/common-utils'
|
||||
import { loggerWrapper } from '@/libs/api-wrapper'
|
||||
import { SurveySalesService } from '../service'
|
||||
import { ApiError } from 'next/dist/server/api-utils'
|
||||
|
||||
/**
|
||||
* @api {GET} /api/survey-sales/:id 조사 매물 조회 API
|
||||
@ -51,22 +51,12 @@ async function getSurveySaleDetail(request: NextRequest): Promise<NextResponse>
|
||||
const { searchParams } = new URL(request.url)
|
||||
const isPdf = searchParams.get('isPdf') === 'true'
|
||||
|
||||
const service = new SurveySalesService({})
|
||||
const survey = await service.tryFunction(() => service.fetchSurvey(Number(id)))
|
||||
|
||||
if (!survey) {
|
||||
return NextResponse.json({ error: ERROR_MESSAGES.NOT_FOUND }, { status: HttpStatusCode.NotFound })
|
||||
const service = new SurveySalesService({}, session)
|
||||
const result = await service.tryFunction(() => service.fetchSurvey(Number(id), isPdf))
|
||||
if (result instanceof ApiError) {
|
||||
return NextResponse.json({ error: result.message }, { status: result.statusCode })
|
||||
}
|
||||
|
||||
if (isPdf || service.checkRole(survey, session)) {
|
||||
return NextResponse.json(survey)
|
||||
}
|
||||
|
||||
if (!session?.isLoggedIn || session?.role === null) {
|
||||
return NextResponse.json({ error: ERROR_MESSAGES.UNAUTHORIZED }, { status: HttpStatusCode.Unauthorized })
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: ERROR_MESSAGES.NO_PERMISSION }, { status: HttpStatusCode.Forbidden })
|
||||
return NextResponse.json(result)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -107,8 +97,11 @@ async function updateSurveySaleDetail(request: NextRequest): Promise<NextRespons
|
||||
const body = await request.json()
|
||||
const service = new SurveySalesService({})
|
||||
|
||||
const survey = await service.tryFunction(() => service.updateSurvey(Number(id), body.survey, body.isTemporary, body.storeId, body.role))
|
||||
return NextResponse.json(survey)
|
||||
const result = await service.tryFunction(() => service.updateSurvey(Number(id), body.survey, body.isTemporary, body.storeId, body.role))
|
||||
if (result instanceof ApiError) {
|
||||
return NextResponse.json({ error: result.message }, { status: result.statusCode })
|
||||
}
|
||||
return NextResponse.json(result)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -132,7 +125,10 @@ async function updateSurveySaleDetail(request: NextRequest): Promise<NextRespons
|
||||
async function deleteSurveySaleDetail(request: NextRequest): Promise<NextResponse> {
|
||||
const id = request.nextUrl.pathname.split('/').pop() ?? ''
|
||||
const service = new SurveySalesService({})
|
||||
await service.tryFunction(() => service.deleteSurvey(Number(id)))
|
||||
const result = await service.tryFunction(() => service.deleteSurvey(Number(id)))
|
||||
if (result instanceof ApiError) {
|
||||
return NextResponse.json({ error: result.message }, { status: result.statusCode })
|
||||
}
|
||||
return NextResponse.json({ status: HttpStatusCode.Ok })
|
||||
}
|
||||
|
||||
@ -176,8 +172,11 @@ async function submitSurveySaleDetail(request: NextRequest): Promise<NextRespons
|
||||
const body = await request.json()
|
||||
const service = new SurveySalesService({})
|
||||
|
||||
const survey = await service.tryFunction(() => service.submitSurvey(Number(id), body.targetId, body.targetNm))
|
||||
return NextResponse.json({ status: HttpStatusCode.Ok, data: survey })
|
||||
const result = await service.tryFunction(() => service.submitSurvey(Number(id), body.targetId, body.targetNm))
|
||||
if (result instanceof ApiError) {
|
||||
return NextResponse.json({ error: result.message }, { status: result.statusCode })
|
||||
}
|
||||
return NextResponse.json({ status: HttpStatusCode.Ok, data: result })
|
||||
}
|
||||
|
||||
export const GET = loggerWrapper(getSurveySaleDetail)
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { ERROR_MESSAGES } from '@/utils/common-utils'
|
||||
import { loggerWrapper } from '@/libs/api-wrapper'
|
||||
import { HttpStatusCode } from 'axios'
|
||||
import { SurveySearchParams } from '@/types/Survey'
|
||||
import { SurveySalesService } from './service'
|
||||
import { ApiError } from 'next/dist/server/api-utils'
|
||||
import { getIronSession } from 'iron-session'
|
||||
import { sessionOptions } from '@/libs/session'
|
||||
import { cookies } from 'next/headers'
|
||||
import { SessionData } from '@/types/Auth'
|
||||
|
||||
/**
|
||||
* @api {GET} /api/survey-sales 설문 목록 조회 API
|
||||
@ -48,7 +51,9 @@ import { SurveySalesService } from './service'
|
||||
*
|
||||
*/
|
||||
async function getSurveySales(request: Request) {
|
||||
/** URL 파라미터 파싱 */
|
||||
const cookieStore = await cookies()
|
||||
const session = await getIronSession<SessionData>(cookieStore, sessionOptions)
|
||||
|
||||
const { searchParams } = new URL(request.url)
|
||||
const params: SurveySearchParams = {
|
||||
keyword: searchParams.get('keyword'),
|
||||
@ -56,19 +61,13 @@ async function getSurveySales(request: Request) {
|
||||
isMySurvey: searchParams.get('isMySurvey'),
|
||||
sort: searchParams.get('sort'),
|
||||
offset: searchParams.get('offset'),
|
||||
role: searchParams.get('role'),
|
||||
storeId: searchParams.get('storeId'),
|
||||
builderId: searchParams.get('builderId'),
|
||||
}
|
||||
const surveySalesService = new SurveySalesService(params)
|
||||
const surveySalesService = new SurveySalesService(params, session)
|
||||
|
||||
/** 세션 체크 결과 처리 */
|
||||
const sessionCheckResult = surveySalesService.checkSession()
|
||||
if (sessionCheckResult) {
|
||||
return sessionCheckResult
|
||||
const result = await surveySalesService.tryFunction(() => surveySalesService.getSurveySales())
|
||||
if (result instanceof ApiError) {
|
||||
return NextResponse.json({ error: result.message }, { status: result.statusCode })
|
||||
}
|
||||
const where = surveySalesService.createFilterSurvey()
|
||||
const result = await surveySalesService.tryFunction(() => surveySalesService.getSurveySales(where))
|
||||
return NextResponse.json({ data: result })
|
||||
}
|
||||
|
||||
@ -112,6 +111,9 @@ async function createSurveySales(request: Request) {
|
||||
const body = await request.json()
|
||||
const surveySalesService = new SurveySalesService({})
|
||||
const result = await surveySalesService.tryFunction(() => surveySalesService.createSurvey(body.survey, body.role, body.storeId))
|
||||
if (result instanceof ApiError) {
|
||||
return NextResponse.json({ error: result.message }, { status: result.statusCode })
|
||||
}
|
||||
return NextResponse.json(result)
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { prisma } from '@/libs/prisma'
|
||||
import { SurveyBasicInfo, SurveyRegistRequest, SurveySearchParams } from '@/types/Survey'
|
||||
import { convertToSnakeCase, ERROR_MESSAGES } from '@/utils/common-utils'
|
||||
import { NextResponse } from 'next/server'
|
||||
import { Prisma } from '@prisma/client'
|
||||
import type { SessionData } from '@/types/Auth'
|
||||
import { HttpStatusCode } from 'axios'
|
||||
import { ApiError } from 'next/dist/server/api-utils'
|
||||
|
||||
type WhereCondition = {
|
||||
AND: any[]
|
||||
@ -36,13 +36,15 @@ const ITEMS_PER_PAGE = 10
|
||||
*/
|
||||
export class SurveySalesService {
|
||||
private params!: SurveySearchParams
|
||||
private session?: SessionData
|
||||
|
||||
/**
|
||||
* @description 생성자
|
||||
* @param {SurveySearchParams} params 검색 파라미터
|
||||
*/
|
||||
constructor(params: SurveySearchParams) {
|
||||
constructor(params: SurveySearchParams, session?: SessionData) {
|
||||
this.params = params
|
||||
this.session = session
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,17 +52,17 @@ export class SurveySalesService {
|
||||
* @param {SearchParams} params 검색 파라미터
|
||||
* @returns {NextResponse} 세션 체크 결과
|
||||
*/
|
||||
checkSession() {
|
||||
if (this.params.role === null) {
|
||||
return NextResponse.json({ data: [], count: 0 })
|
||||
checkSession(): ApiError | null {
|
||||
if (!this.session?.isLoggedIn || this.session?.role === null) {
|
||||
return new ApiError(HttpStatusCode.Unauthorized, ERROR_MESSAGES.UNAUTHORIZED)
|
||||
}
|
||||
if (this.params.role === 'Builder' || this.params.role === 'Partner') {
|
||||
if (this.session?.role === 'Builder' || this.session?.role === 'Partner') {
|
||||
if (this.params.builderId === null) {
|
||||
return NextResponse.json({ data: [], count: 0 })
|
||||
return new ApiError(HttpStatusCode.Forbidden, ERROR_MESSAGES.NO_PERMISSION)
|
||||
}
|
||||
} else {
|
||||
if (this.params.storeId === null) {
|
||||
return NextResponse.json({ data: [], count: 0 })
|
||||
if (this.session?.storeId === null) {
|
||||
return new ApiError(HttpStatusCode.Forbidden, ERROR_MESSAGES.NO_PERMISSION)
|
||||
}
|
||||
}
|
||||
return null
|
||||
@ -107,19 +109,21 @@ export class SurveySalesService {
|
||||
private createRoleCondition(): WhereCondition {
|
||||
const where: WhereCondition = { AND: [] }
|
||||
|
||||
switch (this.params.role) {
|
||||
switch (this.session?.role) {
|
||||
case 'Admin':
|
||||
where.OR = [
|
||||
{ AND: [{ STORE_ID: { equals: this.params.storeId } }] },
|
||||
{ AND: [{ SUBMISSION_TARGET_ID: { equals: this.params.storeId } }, { SUBMISSION_STATUS: { equals: true } }] },
|
||||
{ AND: [{ STORE_ID: { equals: this.session?.storeId } }] },
|
||||
{ AND: [{ SUBMISSION_TARGET_ID: { equals: this.session?.storeId } }, { SUBMISSION_STATUS: { equals: true } }] },
|
||||
]
|
||||
break
|
||||
case 'Admin_Sub':
|
||||
where.OR = [
|
||||
{ AND: [{ STORE_ID: { equals: this.params.storeId } }, { CONSTRUCTION_POINT_ID: { equals: this.params.builderId } }] },
|
||||
{
|
||||
AND: [{ STORE_ID: { equals: this.session?.storeId } }, { CONSTRUCTION_POINT_ID: { equals: this.session?.builderId } }],
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{ SUBMISSION_TARGET_ID: { equals: this.params.storeId } },
|
||||
{ SUBMISSION_TARGET_ID: { equals: this.session?.storeId } },
|
||||
{ CONSTRUCTION_POINT_ID: { not: null } },
|
||||
{ CONSTRUCTION_POINT_ID: { not: '' } },
|
||||
{ SUBMISSION_STATUS: { equals: true } },
|
||||
@ -129,10 +133,10 @@ export class SurveySalesService {
|
||||
break
|
||||
case 'Builder':
|
||||
case 'Partner':
|
||||
where.AND.push({ CONSTRUCTION_POINT_ID: { equals: this.params.builderId } })
|
||||
where.AND.push({ CONSTRUCTION_POINT_ID: { equals: this.session?.builderId } })
|
||||
break
|
||||
case 'T01':
|
||||
where.OR = [{ NOT: { SRL_NO: { startsWith: '一時保存' } } }, { STORE_ID: { equals: this.params.storeId } }]
|
||||
where.OR = [{ NOT: { SRL_NO: { startsWith: '一時保存' } } }, { STORE_ID: { equals: this.session?.storeId } }]
|
||||
break
|
||||
}
|
||||
return where
|
||||
@ -162,7 +166,6 @@ export class SurveySalesService {
|
||||
if (Object.keys(roleCondition).length > 0) {
|
||||
where.AND.push(roleCondition)
|
||||
}
|
||||
|
||||
return where
|
||||
}
|
||||
|
||||
@ -171,7 +174,13 @@ export class SurveySalesService {
|
||||
* @param {WhereCondition} where 조사 매물 검색 조건
|
||||
* @returns {Promise<{ data: SurveyBasicInfo[], count: number }>} 조사 매물 데이터
|
||||
*/
|
||||
async getSurveySales(where: WhereCondition) {
|
||||
async getSurveySales(): Promise<{ data: SurveyBasicInfo[]; count: number } | ApiError> {
|
||||
const sessionCheckResult = this.checkSession()
|
||||
if (sessionCheckResult) {
|
||||
return sessionCheckResult
|
||||
}
|
||||
|
||||
const where = this.createFilterSurvey()
|
||||
/** 조사 매물 조회 */
|
||||
//@ts-ignore
|
||||
const surveys = await prisma.SD_SURVEY_SALES_BASIC_INFO.findMany({
|
||||
@ -245,12 +254,20 @@ export class SurveySalesService {
|
||||
* @param {number} id 조사 매물 ID
|
||||
* @returns {Promise<SurveyBasicInfo>} 조사 매물 데이터
|
||||
*/
|
||||
async fetchSurvey(id: number) {
|
||||
async fetchSurvey(id: number, isPdf: boolean): Promise<SurveyBasicInfo | ApiError> {
|
||||
// @ts-ignore
|
||||
return (await prisma.SD_SURVEY_SALES_BASIC_INFO.findFirst({
|
||||
const result = await prisma.SD_SURVEY_SALES_BASIC_INFO.findFirst({
|
||||
where: { ID: id },
|
||||
include: { DETAIL_INFO: true },
|
||||
})) as SurveyBasicInfo
|
||||
})
|
||||
if (!result) {
|
||||
return new ApiError(HttpStatusCode.NotFound, ERROR_MESSAGES.NOT_FOUND)
|
||||
}
|
||||
if (!isPdf) {
|
||||
if (!this.session?.isLoggedIn) return new ApiError(HttpStatusCode.Unauthorized, ERROR_MESSAGES.UNAUTHORIZED)
|
||||
if (!this.checkRole(result, this.session as SessionData)) return new ApiError(HttpStatusCode.Forbidden, ERROR_MESSAGES.NO_PERMISSION)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
@ -339,7 +356,7 @@ export class SurveySalesService {
|
||||
* @returns {boolean} 해당 매물의 조회 권한 여부 (true: 권한 있음, false: 권한 없음)
|
||||
*/
|
||||
checkRole(survey: any, session: SessionData): boolean {
|
||||
if (!survey || !session.isLoggedIn) return false
|
||||
if (!survey || !session) return false
|
||||
|
||||
const roleChecks = {
|
||||
T01: () => this.checkT01Role(survey),
|
||||
@ -352,15 +369,38 @@ export class SurveySalesService {
|
||||
return roleChecks[session.role as keyof typeof roleChecks]?.() ?? false
|
||||
}
|
||||
|
||||
/**
|
||||
* @description T01 권한 체크
|
||||
* - 임시저장 매물을 제외한 전 매물 조회 가능
|
||||
*
|
||||
* @param {any} survey 조사 매물 데이터
|
||||
* @returns {boolean} 해당 매물의 조회 권한 여부 (true: 권한 있음, false: 권한 없음)
|
||||
*/
|
||||
private checkT01Role(survey: any): boolean {
|
||||
return survey.SRL_NO !== '一時保存'
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Admin 권한 체크 (1차점 - Order)
|
||||
* - 같은 판매점에서 작성한 매물, 제출 받은 매물 조회 가능
|
||||
*
|
||||
* @param {any} survey 조사 매물 데이터
|
||||
* @param {string | null} storeId 판매점 ID
|
||||
* @returns {boolean} 해당 매물의 조회 권한 여부 (true: 권한 있음, false: 권한 없음)
|
||||
*/
|
||||
private checkAdminRole(survey: any, storeId: string | null): boolean {
|
||||
if (!storeId) return false
|
||||
return survey.SUBMISSION_STATUS ? survey.SUBMISSION_TARGET_ID === storeId || survey.STORE_ID === storeId : survey.STORE_ID === storeId
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Admin_Sub 권한 체크 (2차점 - Musubi)
|
||||
* - 같은 판매점에서 작성한 매물, 시공권한 user에게 제출받은 매물 조회 가능
|
||||
*
|
||||
* @param {any} survey 조사 매물 데이터
|
||||
* @param {string | null} storeId 판매점 ID
|
||||
* @returns {boolean} 해당 매물의 조회 권한 여부 (true: 권한 있음, false: 권한 없음)
|
||||
*/
|
||||
private checkAdminSubRole(survey: any, storeId: string | null): boolean {
|
||||
if (!storeId) return false
|
||||
return survey.SUBMISSION_STATUS
|
||||
@ -368,17 +408,44 @@ export class SurveySalesService {
|
||||
: survey.STORE_ID === storeId && !survey.CONSTRUCTION_POINT_ID
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Partner 또는 Builder 권한 체크
|
||||
* - 같은 시공점에서 작성한 매물 조회 가능
|
||||
*
|
||||
* @param {any} survey 조사 매물 데이터
|
||||
* @param {string | null} builderId 시공점 ID
|
||||
* @returns {boolean} 해당 매물의 조회 권한 여부 (true: 권한 있음, false: 권한 없음)
|
||||
*/
|
||||
private checkPartnerOrBuilderRole(survey: any, builderId: string | null): boolean {
|
||||
if (!builderId) return false
|
||||
return survey.CONSTRUCTION_POINT_ID === builderId
|
||||
}
|
||||
|
||||
handleRouteError(error: unknown): NextResponse {
|
||||
/**
|
||||
* @description API ROUTE 에러 처리
|
||||
* @param {any} error 에러 객체
|
||||
* @returns {ApiError} 에러 객체
|
||||
*/
|
||||
handleRouteError(error: any): ApiError {
|
||||
console.error('❌ API ROUTE ERROR : ', error)
|
||||
return NextResponse.json({ error: ERROR_MESSAGES.FETCH_ERROR }, { status: HttpStatusCode.InternalServerError })
|
||||
if (
|
||||
error instanceof Prisma.PrismaClientInitializationError ||
|
||||
error instanceof Prisma.PrismaClientUnknownRequestError ||
|
||||
error instanceof Prisma.PrismaClientRustPanicError ||
|
||||
error instanceof Prisma.PrismaClientValidationError ||
|
||||
error instanceof Prisma.PrismaClientUnknownRequestError
|
||||
) {
|
||||
return new ApiError(HttpStatusCode.InternalServerError, ERROR_MESSAGES.PRISMA_ERROR)
|
||||
}
|
||||
return new ApiError(error.statusCode ?? HttpStatusCode.InternalServerError, error.message ?? ERROR_MESSAGES.FETCH_ERROR)
|
||||
}
|
||||
|
||||
async tryFunction(func: () => Promise<any>): Promise<any> {
|
||||
/**
|
||||
* @description 비동기 함수 try-catch 처리 함수
|
||||
* @param {() => Promise<any>} func 비동기 함수
|
||||
* @returns {Promise<ApiError | any>} 에러 객체 또는 함수 결과
|
||||
*/
|
||||
async tryFunction(func: () => Promise<any>): Promise<ApiError | any> {
|
||||
try {
|
||||
return await func()
|
||||
} catch (error) {
|
||||
|
||||
@ -7,13 +7,14 @@ import { useSurvey } from '@/hooks/useSurvey'
|
||||
import { radioEtcData, roofMaterial, selectBoxOptions, supplementaryFacilities } from '../survey-sale/detail/RoofForm'
|
||||
import { useSpinnerStore } from '@/store/spinnerStore'
|
||||
import { useSessionStore } from '@/store/session'
|
||||
import { SURVEY_ALERT_MSG } from '@/types/Survey'
|
||||
|
||||
export default function SurveySaleDownloadPdf() {
|
||||
const params = useParams()
|
||||
const id = params.id
|
||||
const router = useRouter()
|
||||
|
||||
const { surveyDetail, isLoadingSurveyDetail } = useSurvey(Number(id), true)
|
||||
const { surveyDetail, isLoadingSurveyDetail, showSurveyAlert } = useSurvey(Number(id), true)
|
||||
const { setIsShow } = useSpinnerStore()
|
||||
const { session } = useSessionStore()
|
||||
|
||||
@ -23,11 +24,6 @@ export default function SurveySaleDownloadPdf() {
|
||||
/** 페이지 랜더링 이후 PDF 생성 */
|
||||
useEffect(() => {
|
||||
if (isLoadingSurveyDetail || isGeneratedRef.current) return
|
||||
if (surveyDetail === null) {
|
||||
alert('データが見つかりません。')
|
||||
router.replace('/')
|
||||
return
|
||||
}
|
||||
isGeneratedRef.current = true
|
||||
handleDownPdf()
|
||||
}, [surveyDetail?.id, isLoadingSurveyDetail])
|
||||
@ -65,10 +61,11 @@ export default function SurveySaleDownloadPdf() {
|
||||
} else {
|
||||
router.replace('/')
|
||||
}
|
||||
alert('PDFの生成が完了しました。 ポップアップウィンドウからダウンロードしてください。')
|
||||
showSurveyAlert('PDFの生成が完了しました。 ポップアップウィンドウからダウンロードしてください。')
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error('error', error)
|
||||
console.error('❌ PDF GENERATION ERROR', error)
|
||||
showSurveyAlert(SURVEY_ALERT_MSG.PDF_GENERATION_ERROR)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ import { useEffect, useState } from 'react'
|
||||
import { useParams, useRouter } from 'next/navigation'
|
||||
import { requiredFields, useSurvey } from '@/hooks/useSurvey'
|
||||
import { usePopupController } from '@/store/popupController'
|
||||
import { ALERT_MESSAGES } from '@/types/Survey'
|
||||
import { SURVEY_ALERT_MSG } from '@/types/Survey'
|
||||
|
||||
interface ButtonFormProps {
|
||||
mode: Mode
|
||||
@ -117,7 +117,7 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
|
||||
router.push(`/survey-sale/${savedId}`)
|
||||
}
|
||||
}
|
||||
showSurveyAlert(ALERT_MESSAGES.TEMP_SAVE_SUCCESS)
|
||||
showSurveyAlert(SURVEY_ALERT_MSG.TEMP_SAVE_SUCCESS)
|
||||
}
|
||||
|
||||
/** 입력 필드 포커스 처리 */
|
||||
@ -130,7 +130,7 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
|
||||
const saveProcess = async (emptyField: string | null, isSubmitProcess?: boolean) => {
|
||||
if (emptyField?.trim() === '') {
|
||||
if (!isSubmitProcess) {
|
||||
showSurveyConfirm(ALERT_MESSAGES.SAVE_CONFIRM, async () => {
|
||||
showSurveyConfirm(SURVEY_ALERT_MSG.SAVE_CONFIRM, async () => {
|
||||
await handleSuccessfulSave(isSubmitProcess)
|
||||
})
|
||||
} else {
|
||||
@ -155,7 +155,7 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
|
||||
if (isSubmitProcess) {
|
||||
popupController.setSurveySaleSubmitPopup(true)
|
||||
} else {
|
||||
showSurveyAlert(ALERT_MESSAGES.SAVE_SUCCESS)
|
||||
showSurveyAlert(SURVEY_ALERT_MSG.SAVE_SUCCESS)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -165,7 +165,7 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
|
||||
await router.push(`/survey-sale/${savedId}?show=true`)
|
||||
} else {
|
||||
await router.push(`/survey-sale/${savedId}`)
|
||||
showSurveyAlert(ALERT_MESSAGES.SAVE_SUCCESS)
|
||||
showSurveyAlert(SURVEY_ALERT_MSG.SAVE_SUCCESS)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -173,10 +173,10 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
|
||||
/** 필수값 미입력 처리 */
|
||||
const handleFailedSave = (emptyField: string | null) => {
|
||||
if (emptyField?.includes('Unit')) {
|
||||
showSurveyAlert(ALERT_MESSAGES.UNIT_REQUIRED)
|
||||
showSurveyAlert(SURVEY_ALERT_MSG.UNIT_REQUIRED)
|
||||
} else {
|
||||
const fieldInfo = requiredFields.find((field) => field.field === emptyField)
|
||||
showSurveyAlert(ALERT_MESSAGES.REQUIRED_FIELD, fieldInfo?.name || '')
|
||||
showSurveyAlert(SURVEY_ALERT_MSG.REQUIRED_FIELD, fieldInfo?.name || '')
|
||||
}
|
||||
focusInput(emptyField as keyof SurveyDetailInfo)
|
||||
}
|
||||
@ -184,10 +184,10 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
|
||||
/** 삭제 로직 */
|
||||
const handleDelete = async () => {
|
||||
if (!Number.isNaN(id)) {
|
||||
showSurveyConfirm(ALERT_MESSAGES.DELETE_CONFIRM, async () => {
|
||||
showSurveyConfirm(SURVEY_ALERT_MSG.DELETE_CONFIRM, async () => {
|
||||
await deleteSurvey()
|
||||
if (!isDeletingSurvey) {
|
||||
showSurveyAlert(ALERT_MESSAGES.DELETE_SUCCESS)
|
||||
showSurveyAlert(SURVEY_ALERT_MSG.DELETE_SUCCESS)
|
||||
router.push('/survey-sale')
|
||||
}
|
||||
})
|
||||
@ -197,16 +197,16 @@ export default function ButtonForm({ mode, setMode, data }: ButtonFormProps) {
|
||||
/** 제출 로직 */
|
||||
const handleSubmit = async () => {
|
||||
if (data.basic.srlNo?.startsWith('一時保存') && Number.isNaN(id)) {
|
||||
showSurveyAlert(ALERT_MESSAGES.TEMP_SAVE_SUBMIT_ERROR)
|
||||
showSurveyAlert(SURVEY_ALERT_MSG.TEMP_SAVE_SUBMIT_ERROR)
|
||||
return
|
||||
}
|
||||
|
||||
if (mode === 'READ') {
|
||||
showSurveyConfirm(ALERT_MESSAGES.SUBMIT_CONFIRM, async () => {
|
||||
showSurveyConfirm(SURVEY_ALERT_MSG.SUBMIT_CONFIRM, async () => {
|
||||
popupController.setSurveySaleSubmitPopup(true)
|
||||
})
|
||||
} else {
|
||||
showSurveyConfirm(ALERT_MESSAGES.SAVE_AND_SUBMIT_CONFIRM, async () => {
|
||||
showSurveyConfirm(SURVEY_ALERT_MSG.SAVE_AND_SUBMIT_CONFIRM, async () => {
|
||||
handleSave(false, true)
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useState } from 'react'
|
||||
import type { Mode, SurveyDetailInfo, SurveyDetailRequest } from '@/types/Survey'
|
||||
import { useSurvey } from '@/hooks/useSurvey'
|
||||
import { ALERT_MESSAGES } from '@/types/Survey'
|
||||
import { SURVEY_ALERT_MSG } from '@/types/Survey'
|
||||
|
||||
type RadioEtcKeys =
|
||||
| 'structureOrder'
|
||||
@ -755,7 +755,7 @@ const MultiCheck = ({
|
||||
if (isRoofMaterial) {
|
||||
const totalSelected = selectedValues.length + (isOtherSelected || isOtherCheck ? 1 : 0)
|
||||
if (totalSelected >= 2) {
|
||||
showSurveyAlert(ALERT_MESSAGES.ROOF_MATERIAL_MAX_SELECT_ERROR)
|
||||
showSurveyAlert(SURVEY_ALERT_MSG.ROOF_MATERIAL_MAX_SELECT_ERROR)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -769,7 +769,7 @@ const MultiCheck = ({
|
||||
if (isRoofMaterial) {
|
||||
const currentSelected = selectedValues.length
|
||||
if (!isOtherCheck && currentSelected >= 2) {
|
||||
showSurveyAlert(ALERT_MESSAGES.ROOF_MATERIAL_MAX_SELECT_ERROR)
|
||||
showSurveyAlert(SURVEY_ALERT_MSG.ROOF_MATERIAL_MAX_SELECT_ERROR)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { ALERT_MESSAGES, type SubmitTargetResponse, type SurveyBasicInfo, type SurveyDetailRequest, type SurveyRegistRequest } from '@/types/Survey'
|
||||
import { SURVEY_ALERT_MSG, type SubmitTargetResponse, type SurveyBasicInfo, type SurveyDetailRequest, type SurveyRegistRequest } from '@/types/Survey'
|
||||
import { useMemo } from 'react'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { useSurveyFilterStore } from '@/store/surveyFilterStore'
|
||||
@ -7,6 +7,8 @@ import { useAxios } from './useAxios'
|
||||
import { queryStringFormatter } from '@/utils/common-utils'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
|
||||
|
||||
export const requiredFields = [
|
||||
{
|
||||
field: 'installationSystem',
|
||||
@ -93,7 +95,7 @@ export function useSurvey(
|
||||
refetchSurveyList: () => void
|
||||
refetchSurveyDetail: () => void
|
||||
getSubmitTarget: (params: { storeId: string; role: string }) => Promise<SubmitTargetResponse[] | null>
|
||||
showSurveyAlert: (message: (typeof ALERT_MESSAGES)[keyof typeof ALERT_MESSAGES] | string, requiredField?: string) => void
|
||||
showSurveyAlert: (message: (typeof SURVEY_ALERT_MSG)[keyof typeof SURVEY_ALERT_MSG] | string, requiredField?: string) => void
|
||||
showSurveyConfirm: (message: string, onConfirm: () => void, onCancel?: () => void) => void
|
||||
} {
|
||||
const queryClient = useQueryClient()
|
||||
@ -106,7 +108,8 @@ export function useSurvey(
|
||||
* @description 조사 매물 목록, 상세 데이터 조회 에러 처리
|
||||
*
|
||||
* @param {any} error 에러 객체
|
||||
* @returns {void} 라우팅 처리
|
||||
* @param {boolean} isThrow 에러 Throw 처리 여부
|
||||
* @returns {void} 라우팅 처리 / 에러 Throw 처리
|
||||
*/
|
||||
const handleError = (error: any, isThrow?: boolean) => {
|
||||
const status = error.response?.status
|
||||
@ -175,7 +178,7 @@ export function useSurvey(
|
||||
isLoading: isLoadingSurveyList,
|
||||
refetch: refetchSurveyList,
|
||||
} = useQuery({
|
||||
queryKey: ['survey', 'list', keyword, searchOption, isMySurvey, sort, offset, session?.storeNm, session?.builderId, session?.role],
|
||||
queryKey: ['survey', 'list', keyword, searchOption, isMySurvey, sort, offset],
|
||||
queryFn: async () => {
|
||||
return await tryFunction(
|
||||
() =>
|
||||
@ -186,15 +189,13 @@ export function useSurvey(
|
||||
isMySurvey,
|
||||
sort,
|
||||
offset,
|
||||
storeId: session?.storeId,
|
||||
builderId: session?.builderId,
|
||||
role: session?.role,
|
||||
},
|
||||
}),
|
||||
true,
|
||||
false,
|
||||
)
|
||||
},
|
||||
enabled: !isPdf,
|
||||
})
|
||||
|
||||
/**
|
||||
@ -436,7 +437,10 @@ export function useSurvey(
|
||||
* @param {string} message 알림 메시지
|
||||
* @param {string} [requiredField] 필수 필드 이름
|
||||
*/
|
||||
const showSurveyAlert = (message: (typeof ALERT_MESSAGES)[keyof typeof ALERT_MESSAGES] | string, requiredField?: (typeof requiredFields)[number]['field']) => {
|
||||
const showSurveyAlert = (
|
||||
message: (typeof SURVEY_ALERT_MSG)[keyof typeof SURVEY_ALERT_MSG] | string,
|
||||
requiredField?: (typeof requiredFields)[number]['field'],
|
||||
) => {
|
||||
if (requiredField) {
|
||||
alert(`${requiredField} ${message}`)
|
||||
} else {
|
||||
|
||||
@ -325,7 +325,7 @@ export type SurveySearchParams = {
|
||||
}
|
||||
|
||||
|
||||
export const ALERT_MESSAGES = {
|
||||
export const SURVEY_ALERT_MSG = {
|
||||
/** 기본 메세지 */
|
||||
/** 저장 성공 - "저장되었습니다." */
|
||||
SAVE_SUCCESS: '保存されました。',
|
||||
@ -351,4 +351,7 @@ export const ALERT_MESSAGES = {
|
||||
REQUIRED_FIELD: '項目が空です。',
|
||||
/** 최대 선택 오류 메세지 - "지붕재는 최대 2개까지 선택할 수 있습니다." */
|
||||
ROOF_MATERIAL_MAX_SELECT_ERROR: '屋根材は最大2個まで選択できます。',
|
||||
|
||||
/** PDF 생성 오류 - "PDF 생성에 실패했습니다." */
|
||||
PDF_GENERATION_ERROR: 'PDF 生成に失敗しました。',
|
||||
}
|
||||
@ -155,7 +155,7 @@ export const unescapeString = (str) => {
|
||||
*/
|
||||
|
||||
while (regex.test(str)) {
|
||||
str = str.replace(regex, (matched) => chars[matched] || matched);
|
||||
str = str.replace(regex, (matched) => chars[matched] || matched)
|
||||
}
|
||||
return str
|
||||
}
|
||||
@ -186,29 +186,28 @@ function isObject(value) {
|
||||
return value !== null && typeof value === 'object'
|
||||
}
|
||||
|
||||
|
||||
// 카멜케이스를 스네이크케이스로 변환하는 함수
|
||||
export const toSnakeCase = (str) => {
|
||||
return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
|
||||
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
|
||||
}
|
||||
|
||||
// 객체의 키를 스네이크케이스로 변환하는 함수
|
||||
export const convertToSnakeCase = (obj) => {
|
||||
if (obj === null || obj === undefined) return obj;
|
||||
|
||||
if (obj === null || obj === undefined) return obj
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map((item) => convertToSnakeCase(item))
|
||||
}
|
||||
|
||||
if (typeof obj === 'object') {
|
||||
return Object.keys(obj).reduce((acc, key) => {
|
||||
const snakeKey = toSnakeCase(key).toUpperCase();
|
||||
acc[snakeKey] = convertToSnakeCase(obj[key]);
|
||||
return acc;
|
||||
}, {});
|
||||
const snakeKey = toSnakeCase(key).toUpperCase()
|
||||
acc[snakeKey] = convertToSnakeCase(obj[key])
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
return obj;
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
/**
|
||||
@ -225,4 +224,6 @@ export const ERROR_MESSAGES = {
|
||||
FETCH_ERROR: 'データの取得に失敗しました。',
|
||||
/** 잘못된 요청입니다. */
|
||||
BAD_REQUEST: '間違ったリクエストです。',
|
||||
/** 데이터베이스 오류가 발생했습니다. */
|
||||
PRISMA_ERROR: 'データベース エラーが発生しました。',
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user