import { NextResponse } from 'next/server' import { prisma } from '@/libs/prisma' import { convertToSnakeCase, ERROR_MESSAGES } from '@/utils/common-utils' import { loggerWrapper } from '@/libs/api-wrapper' import { HttpStatusCode } from 'axios' /** * @description 검색 파라미터 타입 */ type SearchParams = { keyword?: string | null searchOption?: string | null isMySurvey?: string | null sort?: string | null offset?: string | null role?: string | null storeId?: string | null builderId?: string | null } type WhereCondition = { AND: any[] OR?: any[] [key: string]: any } /** 검색 가능한 필드 옵션 */ const SEARCH_OPTIONS = [ 'BUILDING_NAME', 'REPRESENTATIVE', 'STORE', 'STORE_ID', 'CONSTRUCTION_POINT', 'CONSTRUCTION_POINT_ID', 'CUSTOMER_NAME', 'POST_CODE', 'ADDRESS', 'ADDRESS_DETAIL', 'SRL_NO', ] as const /** 페이지당 기본 항목 수 */ const ITEMS_PER_PAGE = 10 /** * @description 키워드 검색 조건 생성 함수 * @param {string} keyword 검색 키워드 * @param {string} searchOption 검색 옵션 * @returns {WhereCondition} 검색 조건 객체 */ const createKeywordSearchCondition = (keyword: string, searchOption: string): WhereCondition => { const where: WhereCondition = { AND: [] } if (searchOption === 'all') { /** 모든 필드 검색 시 OR 조건 사용 */ where.OR = [] where.OR.push( ...SEARCH_OPTIONS.map((field) => ({ [field]: { contains: keyword }, })), ) } else if (SEARCH_OPTIONS.includes(searchOption.toUpperCase() as any)) { /** 특정 필드 검색 */ where[searchOption.toUpperCase()] = { contains: keyword } } return where } /** * @description 회원 역할별 검색 조건 생성 함수 * @param {SearchParams} params 검색 파라미터 * @returns {WhereCondition} 검색 조건 객체 */ const createMemberRoleCondition = (params: SearchParams): WhereCondition => { const where: WhereCondition = { AND: [] } switch (params.role) { case 'Admin': where.OR = [ { AND: [{ STORE_ID: { equals: params.storeId } }], }, { AND: [{ SUBMISSION_TARGET_ID: { equals: params.storeId } }, { SUBMISSION_STATUS: { equals: true } }], }, ] break case 'Admin_Sub': where.OR = [ { AND: [{ STORE_ID: { equals: params.storeId } }, { CONSTRUCTION_POINT_ID: { equals: params.builderId } }], }, { AND: [ { SUBMISSION_TARGET_ID: { equals: params.storeId } }, { CONSTRUCTION_POINT_ID: { not: null } }, { CONSTRUCTION_POINT_ID: { not: '' } }, { SUBMISSION_STATUS: { equals: true } }, ], }, ] break case 'Builder': case 'Partner': where.AND?.push({ CONSTRUCTION_POINT_ID: { equals: params.builderId }, }) break case 'T01': where.OR = [ { NOT: { SRL_NO: { startsWith: '一時保存', }, }, }, { STORE_ID: { equals: params.storeId, }, }, ] break case 'User': break } return where } /** * @description 권한 별 필수 값 존재 여부 확인, 없을 시 빈 데이터 반환 * @param {SearchParams} params 검색 파라미터 * @returns {NextResponse} 세션 체크 결과 */ const checkSession = (params: SearchParams) => { if (params.role === null) { return NextResponse.json({ data: [], count: 0 }) } if (params.role === 'Builder' || params.role === 'Partner') { if (params.builderId === null) { return NextResponse.json({ data: [], count: 0 }) } } else { if (params.storeId === null) { return NextResponse.json({ data: [], count: 0 }) } } return null } /** * @api {GET} /api/survey-sales 설문 목록 조회 API * @apiName GET /api/survey-sales * @apiGroup SurveySales * @apiDescription 설문 목록 조회 API * * @apiParam {String} keyword 검색어 (optional) * @apiParam {String} searchOption 검색 옵션 (optional) * @apiParam {String} isMySurvey 내가 작성한 매물 (optional) * @apiParam {String} sort 정렬 방식 (optional) * @apiParam {String} offset 페이지 오프셋 (optional) * @apiParam {String} role 회원권한 (optional) * @apiParam {String} storeId 판매점ID (optional) * @apiParam {String} builderId 시공점ID (optional) * * @apiSuccess {Object[]} data 설문 목록 데이터 * @apiSuccess {Number} data.count 설문 목록 개수 * * @apiExample {curl} Example usage: * curl -X GET \ * -G "keyword=test&searchOption=all&isMySurvey=true&sort=created&offset=0&role=Admin&storeId=1234567890&builderId=1234567890" \ * http://localhost:3000/api/survey-sales * * @apiSuccessExample {json} Success-Response: * { * "data": [ * { * "id": 1, * "srlNo": "1234567890", * "storeId": "1234567890", * "detailInfo": { * "id": 1, * "memo": "1234567890", * ... * } * ... * } * ], * "count": 1 * } * */ async function getSurveySales(request: Request) { try { /** URL 파라미터 파싱 */ const { searchParams } = new URL(request.url) const params: SearchParams = { keyword: searchParams.get('keyword'), searchOption: searchParams.get('searchOption'), 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 sessionCheckResult = checkSession(params) if (sessionCheckResult) { return sessionCheckResult } /** 검색 조건 구성 */ const where: WhereCondition = { AND: [] } /** 내가 작성한 매물 조건 적용 */ if (params.isMySurvey) { where.AND.push({ REPRESENTATIVE_ID: params.isMySurvey }) } /** 키워드 검색 조건 적용 */ if (params.keyword && params.searchOption) { where.AND.push(createKeywordSearchCondition(params.keyword, params.searchOption)) } /** 회원 유형 조건 적용 */ const roleCondition = createMemberRoleCondition(params) if (Object.keys(roleCondition).length > 0) { where.AND.push(roleCondition) } /** 페이지네이션 데이터 조회 */ //@ts-ignore const surveys = await prisma.SD_SURVEY_SALES_BASIC_INFO.findMany({ where, orderBy: params.sort === 'created' ? { REG_DT: 'desc' } : { UPT_DT: 'desc' }, skip: Number(params.offset), take: ITEMS_PER_PAGE, }) /** 전체 개수만 조회 */ //@ts-ignore const count = await prisma.SD_SURVEY_SALES_BASIC_INFO.count({ where }) return NextResponse.json({ data: { data: surveys, count: count } }) } catch (error) { console.error('❌ API ROUTE ERROR:', error) return NextResponse.json({ error: ERROR_MESSAGES.FETCH_ERROR }, { status: HttpStatusCode.InternalServerError }) } } /** * @api {PUT} /api/survey-sales 설문 상세 정보 추가 API * @apiName PUT /api/survey-sales * @apiGroup SurveySales * @apiDescription 설문 상세 정보 추가 API * * @apiParam {Number} id 설문 목록 ID (required) * @apiBody {Object} detail_info 상세 정보 (required) * * @apiSuccess {String} message 성공 메시지 * * @apiExample {curl} Example usage: * curl -X PUT \ * -H "Content-Type: application/json" \ * -d '{"id": 1, "detail_info": {"memo": "1234567890"}}' \ * http://localhost:3000/api/survey-sales * * @apiSuccessExample {json} Success-Response: * { * "message": "Success Update Survey" * } * * @apiError {Number} 500 서버 오류 */ async function updateSurveySales(request: Request) { try { /** 요청 바디 파싱 */ const body = await request.json() /** 상세 정보 생성을 위한 데이터 구성 */ const detailInfo = { ...body.detail_info, BASIC_INFO_ID: body.id, } /** 상세 정보 생성 */ //@ts-ignore await prisma.SD_SURVEY_SALES_DETAIL_INFO.create({ data: detailInfo, }) return NextResponse.json({ status: HttpStatusCode.Ok }) } catch (error) { console.error('❌ API ROUTE ERROR:', error) return NextResponse.json({ error: ERROR_MESSAGES.FETCH_ERROR }, { status: HttpStatusCode.InternalServerError }) } } /** * @api {POST} /api/survey-sales 설문 상세 정보 추가 API * @apiName POST /api/survey-sales * @apiGroup SurveySales * @apiDescription 설문 상세 정보 추가 API * * @apiParam {Object} survey 설문 목록 데이터 (required) * @apiParam {String} role 회원권한 (required) * @apiParam {String} storeId 판매점ID (required) * @returns * * @apiSuccess {Object} data 설문 목록 데이터 * * @apiExample {curl} Example usage: * curl -X POST \ * -H "Content-Type: application/json" \ * -d '{"survey": {"srlNo": "1234567890", "storeId": "1234567890", "role": "T01", "detail_info": {"memo": "1234567890"}}}' \ * http://localhost:3000/api/survey-sales * * @apiSuccessExample {json} Success-Response: * { * "data": { * "id": 1, * "srlNo": "1234567890", * "storeId": "1234567890", * "detailInfo": { * "id": 1, * "memo": "1234567890", * ... * } * ... * } * } * * @apiError {Number} 500 서버 오류 */ async function createSurveySales(request: Request) { try { const body = await request.json() const role = body.role === 'T01' || body.role === 'Admin' ? 'HO' : body.role === 'Admin_Sub' || body.role === 'Builder' ? 'HM' : body.role === 'Partner' ? '' : null /** 임시 저장 시 임시저장으로 저장 */ /** 기본 저장 시 (HO/HM) + 판매점ID + yyMMdd + 000 으로 저장 */ const baseSrlNo = body.survey.srlNo ?? role + body.storeId + new Date().getFullYear().toString().slice(-2) + (new Date().getMonth() + 1).toString().padStart(2, '0') + new Date().getDate().toString().padStart(2, '0') // @ts-ignore const lastSurvey = await prisma.SD_SURVEY_SALES_BASIC_INFO.findFirst({ where: { SRL_NO: { startsWith: role + body.storeId, }, }, orderBy: { SRL_NO: 'desc', }, }) /** 마지막 번호 추출 */ const lastNumber = lastSurvey ? parseInt(lastSurvey.SRL_NO.slice(-3)) : 0 /** 새로운 srlNo 생성 - 임시저장일 경우 '임시저장' 으로 저장 */ const newSrlNo = baseSrlNo.startsWith('一時保存') ? baseSrlNo : baseSrlNo + (lastNumber + 1).toString().padStart(3, '0') const { detailInfo, ...basicInfo } = body.survey // @ts-ignore const result = await prisma.SD_SURVEY_SALES_BASIC_INFO.create({ data: { ...convertToSnakeCase(basicInfo), SRL_NO: newSrlNo, DETAIL_INFO: { create: convertToSnakeCase(detailInfo), }, }, }) return NextResponse.json(result) } catch (error) { console.error('❌ API ROUTE ERROR:', error) return NextResponse.json({ error: ERROR_MESSAGES.FETCH_ERROR }, { status: HttpStatusCode.InternalServerError }) } } export const GET = loggerWrapper(getSurveySales) export const PUT = loggerWrapper(updateSurveySales) export const POST = loggerWrapper(createSurveySales)