Compare commits

...

8 Commits

Author SHA1 Message Date
1bba8b1af0 Merge branch 'dev' of https://git.hanasys.jp/qcast3/onsitesurvey into feature/survey 2025-06-02 13:33:35 +09:00
749dd30ecb refactor: update survey submission handling and improve data validation 2025-06-02 13:33:23 +09:00
0b4a50e78a chore: add ecosystem configuration for on-site survey application
- Introduced a new configuration file for managing the on-site survey application.
- Defined application settings including script execution and instance management.
2025-06-02 13:03:32 +09:00
46e6bc36f8 Merge branch 'dev' of https://git.hanasys.jp/qcast3/onsitesurvey into feature/survey 2025-06-02 10:16:10 +09:00
454a8a39a9 refactor: rename store to storeId in API and components for consistency 2025-06-02 10:16:00 +09:00
ca45c68b9d refactor: update builderNm field in session management
- Set builderNm in session and response payload based on user data for improved session tracking.
- Ensured consistency in session management structures.
2025-06-02 10:15:50 +09:00
8a1313a964 chore: update environment configuration files to include EMAIL_TITLE_PREFIX
- Added EMAIL_TITLE_PREFIX variable to .env.development for system test identification.
- Cleared EMAIL_TITLE_PREFIX in .env.localhost and .env.production for consistency across environments.
2025-06-02 10:11:22 +09:00
fe96acebec refactor: add builderNm field to session management
- Added builderNm field to both SessionData interface and initial state for enhanced session tracking.
- Ensured consistency across session management structures.
2025-05-30 17:42:11 +09:00
14 changed files with 99 additions and 56 deletions

View File

@ -11,6 +11,7 @@ NEXT_PUBLIC_QSP_API_URL=https://jp-dev.qsalesplatform.com
#1:1문의 api #1:1문의 api
NEXT_PUBLIC_INQUIRY_API_URL=https://jp-dev.qsalesplatform.com NEXT_PUBLIC_INQUIRY_API_URL=https://jp-dev.qsalesplatform.com
EMAIL_TITLE_PREFIX=(System Test)
#QPARTNER 로그인 api #QPARTNER 로그인 api
DB_HOST=202.218.61.226 DB_HOST=202.218.61.226

View File

@ -11,6 +11,8 @@ NEXT_PUBLIC_QSP_API_URL=https://jp-dev.qsalesplatform.com
#1:1문의 api #1:1문의 api
NEXT_PUBLIC_INQUIRY_API_URL=https://jp-dev.qsalesplatform.com NEXT_PUBLIC_INQUIRY_API_URL=https://jp-dev.qsalesplatform.com
EMAIL_TITLE_PREFIX=
#QPARTNER 로그인 api #QPARTNER 로그인 api
DB_HOST=202.218.61.226 DB_HOST=202.218.61.226
DB_USER=readonly DB_USER=readonly

View File

@ -9,6 +9,8 @@ NEXT_PUBLIC_QSP_API_URL=https://jp.qsalesplatform.com
#1:1문의 api #1:1문의 api
NEXT_PUBLIC_INQUIRY_API_URL=http://172.23.4.129:8110 NEXT_PUBLIC_INQUIRY_API_URL=http://172.23.4.129:8110
EMAIL_TITLE_PREFIX=
#QPARTNER 로그인 api #QPARTNER 로그인 api
DB_HOST=202.218.61.226 DB_HOST=202.218.61.226
DB_USER=readonly DB_USER=readonly

10
dev.ecosystem.comfig.js Normal file
View File

@ -0,0 +1,10 @@
module.exports = {
apps: [
{
name: 'on-site-survey',
script: 'node_modules/next/dist/bin/next',
instances: 1,
exec_mode: 'fork',
},
],
}

View File

@ -57,7 +57,7 @@ export async function POST(request: Request) {
session.storeLvl = result.data.data.storeLvl session.storeLvl = result.data.data.storeLvl
session.custCd = result.data.data.custCd session.custCd = result.data.data.custCd
session.builderNo = result.data.data.builderNo session.builderNo = result.data.data.builderNo
session.builderNm = '' session.builderNm = result.data.data.builderNm
session.isLoggedIn = true session.isLoggedIn = true
if (result.data.data.userId === 'T01') { if (result.data.data.userId === 'T01') {
@ -105,7 +105,7 @@ export async function POST(request: Request) {
STORE_LVL: result.data.data.storeLvl, STORE_LVL: result.data.data.storeLvl,
CUST_CD: result.data.data.custCd, CUST_CD: result.data.data.custCd,
BUILDER_NO: result.data.data.builderNo, BUILDER_NO: result.data.data.builderNo,
BUILDER_NM: '', BUILDER_NM: result.data.data.builderNm,
IS_LOGGED_IN: true, IS_LOGGED_IN: true,
ROLE: '', ROLE: '',
} }

View File

@ -115,8 +115,6 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
try { try {
const { id } = await params const { id } = await params
const body = await request.json() const body = await request.json()
if (body.targetId) {
// @ts-ignore // @ts-ignore
const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({ const survey = await prisma.SD_SURVEY_SALES_BASIC_INFO.update({
where: { ID: Number(id) }, where: { ID: Number(id) },
@ -129,7 +127,6 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
}, },
}) })
return NextResponse.json({ message: 'Survey confirmed successfully', data: survey }) return NextResponse.json({ message: 'Survey confirmed successfully', data: survey })
}
} catch (error) { } catch (error) {
console.error('Error updating survey:', error) console.error('Error updating survey:', error)
return NextResponse.json({ error: 'Failed to update survey' }, { status: 500 }) return NextResponse.json({ error: 'Failed to update survey' }, { status: 500 })

View File

@ -11,7 +11,7 @@ type SearchParams = {
sort?: string | null // 정렬 방식 sort?: string | null // 정렬 방식
offset?: string | null offset?: string | null
role?: string | null // 회원권한한 role?: string | null // 회원권한한
store?: string | null // 판매점ID storeId?: string | null // 판매점ID
builderNo?: string | null // 시공ID builderNo?: string | null // 시공ID
} }
@ -75,11 +75,11 @@ const createMemberRoleCondition = (params: SearchParams): WhereCondition => {
where.OR = [ where.OR = [
{ {
// 같은 판매점에서 작성한 제출/제출되지 않은 매물 // 같은 판매점에서 작성한 제출/제출되지 않은 매물
AND: [{ STORE_ID: { equals: params.store } }], AND: [{ STORE_ID: { equals: params.storeId } }],
}, },
{ {
// MUSUBI (시공권한 X) 가 ORDER 에 제출한 매물 // MUSUBI (시공권한 X) 가 ORDER 에 제출한 매물
AND: [{ SUBMISSION_TARGET_ID: { equals: params.store } }, { SUBMISSION_STATUS: { equals: true } }], AND: [{ SUBMISSION_TARGET_ID: { equals: params.storeId } }, { SUBMISSION_STATUS: { equals: true } }],
}, },
] ]
break break
@ -89,18 +89,18 @@ const createMemberRoleCondition = (params: SearchParams): WhereCondition => {
{ {
// MUSUBI (시공권한 X) 같은 판매점에서 작성한 제출/제출되지 않은 매물 // MUSUBI (시공권한 X) 같은 판매점에서 작성한 제출/제출되지 않은 매물
AND: [ AND: [
{ STORE_ID: { equals: params.store } }, { STORE_ID: { equals: params.storeId } },
// { {
// OR: [{ CONSTRUCTION_POINT: { equals: null } }, { CONSTRUCTION_POINT: { equals: '' } }], OR: [{ CONSTRUCTION_POINT_ID: { equals: null } }, { CONSTRUCTION_POINT_ID: { equals: '' } }],
// }, },
], ],
}, },
{ {
// MUSUBI (시공권한 O) 가 MUSUBI 에 제출한 매물 + PARTNER 가 제출한 매물 // MUSUBI (시공권한 O) 가 MUSUBI 에 제출한 매물 + PARTNER 가 제출한 매물
AND: [ AND: [
{ SUBMISSION_TARGET_ID: { equals: params.store } }, { SUBMISSION_TARGET_ID: { equals: params.storeId } },
{ CONSTRUCTION_POINT: { not: null } }, { CONSTRUCTION_POINT_ID: { not: null } },
{ CONSTRUCTION_POINT: { not: '' } }, { CONSTRUCTION_POINT_ID: { not: '' } },
{ SUBMISSION_STATUS: { equals: true } }, { SUBMISSION_STATUS: { equals: true } },
], ],
}, },
@ -109,9 +109,8 @@ const createMemberRoleCondition = (params: SearchParams): WhereCondition => {
case 'Builder': // MUSUBI (시공권한 O) case 'Builder': // MUSUBI (시공권한 O)
case 'Partner': // PARTNER case 'Partner': // PARTNER
// 시공점이 있고 STORE_ID 가 시공ID와 같은 매물 // 시공ID 같은 매물
where.AND?.push({ where.AND?.push({
// CONSTRUCTION_POINT: { not: null },
CONSTRUCTION_POINT_ID: { equals: params.builderNo }, CONSTRUCTION_POINT_ID: { equals: params.builderNo },
}) })
break break
@ -127,7 +126,7 @@ const createMemberRoleCondition = (params: SearchParams): WhereCondition => {
}, },
{ {
STORE_ID: { STORE_ID: {
equals: params.store, equals: params.storeId,
}, },
}, },
] ]
@ -154,7 +153,7 @@ export async function GET(request: Request) {
sort: searchParams.get('sort'), sort: searchParams.get('sort'),
offset: searchParams.get('offset'), offset: searchParams.get('offset'),
role: searchParams.get('role'), role: searchParams.get('role'),
store: searchParams.get('store'), //storeId storeId: searchParams.get('storeId'), //storeId
builderNo: searchParams.get('builderNo'), builderNo: searchParams.get('builderNo'),
} }

View File

@ -10,7 +10,6 @@ import { useSpinnerStore } from '@/store/spinnerStore'
export default function SurveySaleDownloadPdf() { export default function SurveySaleDownloadPdf() {
const params = useParams() const params = useParams()
const id = params.id const id = params.id
const router = useRouter()
const { surveyDetail, isLoadingSurveyDetail } = useSurvey(Number(id)) const { surveyDetail, isLoadingSurveyDetail } = useSurvey(Number(id))
const { setIsShow } = useSpinnerStore() const { setIsShow } = useSpinnerStore()
@ -50,7 +49,7 @@ export default function SurveySaleDownloadPdf() {
generatePDF(targetRef, options).then(() => { generatePDF(targetRef, options).then(() => {
setIsShow(false) setIsShow(false)
router.push(`/survey-sale/${id}`) alert('PDFの生成が完了しました。 ポップアップウィンドウからダウンロードしてください。')
}) })
} }

View File

@ -11,8 +11,8 @@ import { useSpinnerStore } from '@/store/spinnerStore'
interface SubmitFormData { interface SubmitFormData {
saleBase: string | null saleBase: string | null
targetId: string targetId: string | null
targetNm: string targetNm: string | null
sender: string sender: string
receiver: string[] | string receiver: string[] | string
reference: string | null reference: string | null
@ -38,12 +38,12 @@ export default function SurveySaleSubmitPopup() {
const [submitData, setSubmitData] = useState<SubmitFormData>({ const [submitData, setSubmitData] = useState<SubmitFormData>({
saleBase: null, saleBase: null,
targetId: session?.role === 'Builder' ? surveyDetail?.storeId ?? '' : '', targetId: null,
targetNm: session?.role === 'Builder' ? surveyDetail?.store ?? '' : '', targetNm: null,
sender: session?.email ?? '', sender: '',
receiver: [], receiver: [],
reference: null, reference: null,
title: '[HANASYS現地調査] 調査物件が提出. (' + surveyDetail?.srlNo + ')', title: '',
contents: '', contents: '',
}) })
@ -57,6 +57,8 @@ export default function SurveySaleSubmitPopup() {
} }
setSubmitData({ setSubmitData({
...submitData, ...submitData,
targetId: session?.role === 'Builder' ? surveyDetail?.storeId ?? null : null,
targetNm: session?.role === 'Builder' ? surveyDetail?.store ?? null : null,
sender: session?.email ?? '', sender: session?.email ?? '',
title: '[HANASYS現地調査] 調査物件が提出. (' + surveyDetail?.srlNo + ')', title: '[HANASYS現地調査] 調査物件が提出. (' + surveyDetail?.srlNo + ')',
}) })
@ -82,7 +84,7 @@ export default function SurveySaleSubmitPopup() {
const requiredFields = FORM_FIELDS.filter((field) => field.required) const requiredFields = FORM_FIELDS.filter((field) => field.required)
for (const field of requiredFields) { for (const field of requiredFields) {
if (data[field.id]?.length === 0) { if (data[field.id] === '' || data[field.id] === null || data[field.id]?.length === 0) {
alert(`${field.name}は必須入力項目です。`) alert(`${field.name}は必須入力項目です。`)
const element = document.getElementById(field.id) const element = document.getElementById(field.id)
if (element) { if (element) {
@ -105,16 +107,17 @@ export default function SurveySaleSubmitPopup() {
}) })
.then(() => { .then(() => {
if (!isSubmittingSurvey) { if (!isSubmittingSurvey) {
submitSurvey({ targetId: submitData.targetId, targetNm: submitData.targetNm })
alert('提出が完了しました。') alert('提出が完了しました。')
// submitSurvey({ targetId: submitData.targetId, targetNm: submitData.targetNm })
popupController.setSurveySaleSubmitPopup(false) popupController.setSurveySaleSubmitPopup(false)
} }
}) })
.catch((error) => { .catch((error) => {
console.error('Error sending email:', error) console.error('Error sending email:', error)
alert('メール送信に失敗しました。') alert('メール送信に失敗しました。 再度送信してください。')
}) })
.finally(() => { .finally(() => {
submitSurvey({ targetId: submitData.targetId, targetNm: submitData.targetNm })
setIsShow(false) setIsShow(false)
popupController.setSurveySaleSubmitPopup(false) popupController.setSurveySaleSubmitPopup(false)
}) })
@ -137,6 +140,7 @@ export default function SurveySaleSubmitPopup() {
if (field.id === 'targetNm' && session?.role === 'Admin') { if (field.id === 'targetNm' && session?.role === 'Admin') {
return null return null
} }
return ( return (
<div className="data-input-form-bx" key={field.id}> <div className="data-input-form-bx" key={field.id}>
<div className="data-input-form-tit"> <div className="data-input-form-tit">

View File

@ -29,7 +29,6 @@ export default function ButtonForm(props: {
...props.data.basic, ...props.data.basic,
detailInfo: props.data.roof, detailInfo: props.data.roof,
}) })
// -------------------------------------------------------------- // --------------------------------------------------------------
// 권한 // 권한
@ -49,12 +48,12 @@ export default function ButtonForm(props: {
// 1차 판매점(Order) + 2차 판매점(Musubi) => 같은 판매점 제출권한 // 1차 판매점(Order) + 2차 판매점(Musubi) => 같은 판매점 제출권한
case 'Admin': case 'Admin':
case 'Admin_Sub': case 'Admin_Sub':
setIsSubmiter(session.storeNm === props.data.basic.store && session.builderNo === props.data.basic.constructionPoint) setIsSubmiter(session.storeNm === props.data.basic.store && session.builderNo === props.data.basic.constructionPointId)
break break
// 시공권한 User(Musubi) + Partner => 같은 시공ID 제출권한 // 시공권한 User(Musubi) + Partner => 같은 시공ID 제출권한
case 'Builder': case 'Builder':
case 'Partner': case 'Partner':
setIsSubmiter(session.builderNo === props.data.basic.constructionPoint) setIsSubmiter(session.builderNo === props.data.basic.constructionPointId)
break break
default: default:
setIsSubmiter(false) setIsSubmiter(false)
@ -76,7 +75,6 @@ export default function ButtonForm(props: {
const { deleteSurvey, updateSurvey, isDeletingSurvey, isUpdatingSurvey } = useSurvey(Number(id)) const { deleteSurvey, updateSurvey, isDeletingSurvey, isUpdatingSurvey } = useSurvey(Number(id))
const { validateSurveyDetail, createSurvey, isCreatingSurvey } = useSurvey() const { validateSurveyDetail, createSurvey, isCreatingSurvey } = useSurvey()
const handleSave = (isTemporary: boolean, isSubmitProcess: boolean) => { const handleSave = (isTemporary: boolean, isSubmitProcess: boolean) => {
const emptyField = validateSurveyDetail(props.data.roof) const emptyField = validateSurveyDetail(props.data.roof)
const hasEmptyField = emptyField?.trim() !== '' const hasEmptyField = emptyField?.trim() !== ''
@ -189,6 +187,19 @@ export default function ButtonForm(props: {
) )
} }
//TODO: 추가확인 필요 (T01 계정이 어떤 조사매물을 수정/삭제 할 수 있는지)
if (mode === 'READ' && session?.role === 'T01' && (!isSubmit || props.data.basic.submissionTargetId !== session.storeId)) {
return (
<>
<div className="sale-form-btn-wrap">
<div className="btn-flex-wrap">
<ListButton />
</div>
</div>
</>
)
}
return ( return (
<> <>
{mode === 'READ' && ( {mode === 'READ' && (

View File

@ -4,12 +4,15 @@ import { useSurvey } from '@/hooks/useSurvey'
import { useParams, useRouter } from 'next/navigation' import { useParams, useRouter } from 'next/navigation'
import { useEffect } from 'react' import { useEffect } from 'react'
import DetailForm from './DetailForm' import DetailForm from './DetailForm'
import { useSessionStore } from '@/store/session'
export default function DataTable() { export default function DataTable() {
const params = useParams() const params = useParams()
const id = params.id const id = params.id
const router = useRouter() const router = useRouter()
const { session } = useSessionStore()
useEffect(() => { useEffect(() => {
if (Number.isNaN(Number(id))) { if (Number.isNaN(Number(id))) {
alert('間違ったアプローチです。') alert('間違ったアプローチです。')
@ -20,7 +23,25 @@ export default function DataTable() {
const { surveyDetail, isLoadingSurveyDetail } = useSurvey(Number(id)) const { surveyDetail, isLoadingSurveyDetail } = useSurvey(Number(id))
if (isLoadingSurveyDetail) { if (isLoadingSurveyDetail) {
return <></> return null
}
const submitStatus = () => {
const { submissionTargetNm, submissionTargetId } = surveyDetail ?? {}
if (!submissionTargetNm) {
return null
}
if (!submissionTargetId) {
return <div>{submissionTargetNm}</div>
}
return (
<div>
({submissionTargetNm} - {submissionTargetId})
</div>
)
} }
return ( return (
@ -56,9 +77,7 @@ export default function DataTable() {
{surveyDetail?.submissionStatus && surveyDetail?.submissionDate ? ( {surveyDetail?.submissionStatus && surveyDetail?.submissionDate ? (
<> <>
<div>{new Date(surveyDetail.submissionDate).toLocaleString()}</div> <div>{new Date(surveyDetail.submissionDate).toLocaleString()}</div>
<div> {submitStatus()}
({surveyDetail.submissionTargetNm} - {surveyDetail.submissionTargetId})
</div>
</> </>
) : ( ) : (
'-' '-'

View File

@ -66,7 +66,7 @@ export function useSurvey(id?: number): {
createSurvey: (survey: SurveyRegistRequest) => Promise<number> createSurvey: (survey: SurveyRegistRequest) => Promise<number>
updateSurvey: ({ survey, isTemporary, storeId }: { survey: SurveyRegistRequest; isTemporary: boolean; storeId?: string }) => void updateSurvey: ({ survey, isTemporary, storeId }: { survey: SurveyRegistRequest; isTemporary: boolean; storeId?: string }) => void
deleteSurvey: () => Promise<boolean> deleteSurvey: () => Promise<boolean>
submitSurvey: (params: { saveId?: number; targetId?: string; targetNm?: string; storeId?: string; srlNo?: string }) => void submitSurvey: (params: { targetId?: string | null; targetNm?: string | null }) => void
validateSurveyDetail: (surveyDetail: SurveyDetailRequest) => string validateSurveyDetail: (surveyDetail: SurveyDetailRequest) => string
getZipCode: (zipCode: string) => Promise<ZipCode[] | null> getZipCode: (zipCode: string) => Promise<ZipCode[] | null>
refetchSurveyList: () => void refetchSurveyList: () => void
@ -90,7 +90,7 @@ export function useSurvey(id?: number): {
isMySurvey, isMySurvey,
sort, sort,
offset, offset,
store: session?.storeId, storeId: session?.storeId,
builderNo: session?.builderNo, builderNo: session?.builderNo,
role: session?.role, role: session?.role,
}, },
@ -163,14 +163,11 @@ export function useSurvey(id?: number): {
}) })
const { mutateAsync: submitSurvey, isPending: isSubmittingSurvey } = useMutation({ const { mutateAsync: submitSurvey, isPending: isSubmittingSurvey } = useMutation({
mutationFn: async ({ targetId, targetNm, storeId, srlNo }: { targetId?: string; targetNm?: string; storeId?: string; srlNo?: string }) => { mutationFn: async ({ targetId, targetNm }: { targetId?: string | null; targetNm?: string | null }) => {
if (!id) throw new Error('id is required') if (!id) throw new Error('id is required')
const resp = await axiosInstance(null).patch<boolean>(`/api/survey-sales/${id}`, { const resp = await axiosInstance(null).patch<boolean>(`/api/survey-sales/${id}`, {
targetId, targetId,
targetNm, targetNm,
storeId,
srlNo,
role: session?.role ?? null,
}) })
return resp.data return resp.data
}, },

View File

@ -45,6 +45,7 @@ export const defaultSession: SessionData = {
storeLvl: null, storeLvl: null,
custCd: null, custCd: null,
builderNo: null, builderNo: null,
builderNm: null,
isLoggedIn: false, isLoggedIn: false,
role: null, role: null,
} }

View File

@ -41,6 +41,7 @@ const initialState: InitialState = {
storeLvl: null, storeLvl: null,
custCd: null, custCd: null,
builderNo: null, builderNo: null,
builderNm: null,
isLoggedIn: false, isLoggedIn: false,
role: null, role: null,
}, },