From a29d1f0cc2554c1e29d06ad484c959a67179a2b6 Mon Sep 17 00:00:00 2001 From: ljpark Date: Mon, 6 Jul 2026 17:37:28 +0900 Subject: [PATCH] =?UTF-8?q?wr:=20=EC=88=98=EC=9A=94=EC=B2=98=20pk=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/dm/.env.local | 9 +- apps/dm/app/(popup-layout)/map/popup/page.tsx | 4 +- .../dm/app/(with-layout)/apply/dmdco/page.tsx | 68 +++--- .../app/(with-layout)/apply/dmdinfo/page.tsx | 9 +- .../app/(with-layout)/apply/status/page.tsx | 10 +- apps/dm/app/store/sdmMng/index.tsx | 10 +- apps/wr/.env.local | 4 +- .../matMng/coMng/coSttn/page.tsx | 8 +- apps/wr/app/store/common/searchCode/index.ts | 1 + apps/wr/app/store/matDmd/coMatRvw/index.tsx | 2 +- apps/wr/app/store/matDmd/coMatRvw/type.tsx | 5 + apps/wr/app/store/matDmd/coSttn/index.tsx | 6 +- .../ruseFcltMng/fcltReg/pbnstReg/type.tsx | 2 + .../ui/form/coMat/conCostForm/conCostForm.tsx | 18 +- .../fcltReg/pbnstReg/buseReg/bugRegForm.tsx | 5 + .../sttn/pbnstSttnModal/pbnstSttnModal.tsx | 1 - ... 수요처 비밀번호 확인 dmdCoSn 전환_20260706.md | 46 ++++ .../dmd_info_id 제거 대응 계획_20260706.md | 211 ++++++++++++++++++ .../수요처 사업자번호 수정 대응 계획_20260703.md | 97 ++------ .../src/ui/sdmInsertForm/form.tsx | 9 +- .../src/ui/sdmInsertForm/index.tsx | 1 + 21 files changed, 383 insertions(+), 143 deletions(-) create mode 100644 apps/wr/docs/작업계획/DM 수요처 비밀번호 확인 dmdCoSn 전환_20260706.md create mode 100644 apps/wr/docs/작업계획/dmd_info_id 제거 대응 계획_20260706.md diff --git a/apps/dm/.env.local b/apps/dm/.env.local index 7e0ec15fc..a8c8a1034 100644 --- a/apps/dm/.env.local +++ b/apps/dm/.env.local @@ -9,16 +9,15 @@ NEXT_PUBLIC_CM_LOGIN_PAGE_URL = http://dev.onasys.co.kr:3000/auth/login NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_DM_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 -# NEXT_PUBLIC_FO_SERVER_API_URL = http://192.168.1.222:8883/operation-service/api/v1 -NEXT_PUBLIC_FO_SERVER_API_URL = http://con.mind-one.co.kr:11083/operation-service/api/v1 -NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 +# NEXT_PUBLIC_FO_SERVER_API_URL = http://con.mind-one.co.kr:11083/operation-service/api/v1 +# NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr NEXT_PUBLIC_NGII_PARAM = 04trYP9_xwLAfALjwZ-B8g&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={x}&tilerow={y} -NEXT_PUBLIC_GATEWAY_URL=http://con.mind-one.co.kr:11083 # 개발 서버 API -# NEXT_PUBLIC_GATEWAY_URL=http://localhost:8883 # 개발 서버 API +# NEXT_PUBLIC_GATEWAY_URL=http://con.mind-one.co.kr:11083 # 개발 서버 API +NEXT_PUBLIC_GATEWAY_URL=http://localhost:8883 # local CONTENTS_INTRO = 31 CONTENTS_EXAMPLE = 32 diff --git a/apps/dm/app/(popup-layout)/map/popup/page.tsx b/apps/dm/app/(popup-layout)/map/popup/page.tsx index bdce68a87..f31c49bca 100644 --- a/apps/dm/app/(popup-layout)/map/popup/page.tsx +++ b/apps/dm/app/(popup-layout)/map/popup/page.tsx @@ -133,7 +133,9 @@ const Map = () => { //수요처 ID const urlParams = new URLSearchParams(window.location.search); const dmdInfoId = urlParams.get("dmdInfoId"); + const dmdCoSn = urlParams.get("dmdCoSn"); console.log("[하이라이트] dmdInfoId:", dmdInfoId); + console.log("[하이라이트] dmdCoSn:", dmdCoSn); //수요처 위치로 화면 이동 const maps = gisMaps.current; @@ -207,7 +209,7 @@ const Map = () => { { width: 30 } ); } else { - console.warn("수요처의 좌표정보를 찾을 수 없습니다. dmdInfoId:", dmdInfoId); + console.warn("수요처의 좌표정보를 찾을 수 없습니다. dmdInfoId:", dmdInfoId, "dmdCoSn:", dmdCoSn); } const formatNumber = (val: any) => { diff --git a/apps/dm/app/(with-layout)/apply/dmdco/page.tsx b/apps/dm/app/(with-layout)/apply/dmdco/page.tsx index a450e8582..86262be44 100644 --- a/apps/dm/app/(with-layout)/apply/dmdco/page.tsx +++ b/apps/dm/app/(with-layout)/apply/dmdco/page.tsx @@ -15,6 +15,7 @@ import { getCookie } from "@common/components/lib"; type Option = { value: string; label: string }; type SessionUser = { + dmdCoSn?: number; dmdInfoId?: string; dmdCoNm?: string; zip?: string; @@ -311,10 +312,8 @@ export default function DemandPlaceRegisterPage() { } } if (!password) return alert("기존 비밀번호를 입력해 주세요."); - - const fullDmdInfoId = bizDigits + (noWorkerNo ? "0000" : workerDigits); const payload = { - dmdInfoId: fullDmdInfoId, + dmdCoSn: editMode ? sessionUser?.dmdCoSn : undefined, dmdAplyPswd: password, }; @@ -411,6 +410,7 @@ export default function DemandPlaceRegisterPage() { const myUUID = getCookie(TOKEN_ID_COOKIE) || FALLBACK_UUID; const payload: Record = { + dmdCoSn: editMode ? sessionUser?.dmdCoSn : undefined, ctpvCd: sido, sggCd: sigungu, dmdCoNm: company.trim(), @@ -443,12 +443,14 @@ export default function DemandPlaceRegisterPage() { alert("수요처 수정 완료!"); } else { - await postFetch("/reuseFc/request", payload); + const response = await postFetch("/reuseFc/request", payload); + const responseJson = await response.json().catch(() => null); + const savedUser = responseJson?.data ?? payload; if (typeof window !== "undefined") { const nextSessionUser = { ...(sessionUser ?? {}), - ...payload, + ...savedUser, }; delete nextSessionUser.dmdAplyPswd; sessionStorage.setItem("user", JSON.stringify(nextSessionUser)); @@ -526,34 +528,7 @@ export default function DemandPlaceRegisterPage() {

- - { - setSido(v); - setSigungu(""); - }} - options={sidoOptions} - /> - - - - - - - - - +
@@ -633,7 +608,34 @@ export default function DemandPlaceRegisterPage() { + + { + setSido(v); + setSigungu(""); + }} + options={sidoOptions} + /> + + + + + + + +
{ e.preventDefault(); e.stopPropagation(); - const dmdInfoId = JSON.parse( + const sessionUser = JSON.parse( sessionStorage.getItem("user") || "{}" - )?.dmdInfoId?.toString(); + ) as { dmdInfoId?: string; dmdCoSn?: number }; + const dmdInfoId = sessionUser?.dmdInfoId?.toString() ?? ""; + const dmdCoSn = sessionUser?.dmdCoSn?.toString() ?? ""; - const url = `/map/popup?dmdInfoId=${encodeURIComponent(dmdInfoId)}`; + const url = `/map/popup?dmdInfoId=${encodeURIComponent(dmdInfoId)}&dmdCoSn=${encodeURIComponent(dmdCoSn)}`; const width = window.screen.availWidth; const height = window.screen.availHeight; const left = 0; diff --git a/apps/dm/app/(with-layout)/apply/status/page.tsx b/apps/dm/app/(with-layout)/apply/status/page.tsx index 40241ee4f..bad342e20 100644 --- a/apps/dm/app/(with-layout)/apply/status/page.tsx +++ b/apps/dm/app/(with-layout)/apply/status/page.tsx @@ -11,7 +11,7 @@ import { useEffect, useMemo, useRef, useState } from "react"; type TopField = { label: string; key: string, value: string; required?: boolean }; type PairField = { label: string; key: string, value: string }; -type SessionUser = { dmdInfoId?: string }; +type SessionUser = { dmdInfoId?: string; dmdCoSn?: number }; const PAGE_SIZE = 10; @@ -315,11 +315,13 @@ export default function DemandStatusPage() { const searchCandidate = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - const dmdInfoId = JSON.parse( + const sessionUser = JSON.parse( sessionStorage.getItem("user") || "{}" - )?.dmdInfoId?.toString(); + ) as SessionUser; + const dmdInfoId = sessionUser?.dmdInfoId?.toString() ?? ""; + const dmdCoSn = sessionUser?.dmdCoSn?.toString() ?? ""; - const url = `/map/popup?dmdInfoId=${encodeURIComponent(dmdInfoId)}`; + const url = `/map/popup?dmdInfoId=${encodeURIComponent(dmdInfoId)}&dmdCoSn=${encodeURIComponent(dmdCoSn)}`; const width = window.screen.availWidth; const height = window.screen.availHeight; const left = 0; diff --git a/apps/dm/app/store/sdmMng/index.tsx b/apps/dm/app/store/sdmMng/index.tsx index de345f0d7..0041f5a93 100644 --- a/apps/dm/app/store/sdmMng/index.tsx +++ b/apps/dm/app/store/sdmMng/index.tsx @@ -10,6 +10,7 @@ import { z } from "zod"; =====================================*/ export const sdmInfoFormSchema = z.object({ + dmdCoSn: z.number().optional(), dmdInfoId: z.string().optional(), dmndTmng: z.date().optional(), dmndQty: z.number().min(1, "요구수량을 입력해주세요"), @@ -46,6 +47,7 @@ export type SdmInfoFormData = z.infer; // 수요처 정보 export type DemandCompBizType = { index: number | ''; + dmdCoSn?: number | ''; dmdInfoId: string | ''; dmdInfoFk: string | ''; bzmnNo: string | ''; @@ -443,8 +445,10 @@ export const upHeadSdmInfoStatus: TableUpperProps[] = [ // 수요처 등록 const postSdmData = async (data: DemandCompType) => { try { - await postFetch("/reuseFc/request", data); - sessionStorage.setItem("user", JSON.stringify(data)); + const response = await postFetch("/reuseFc/request", data); + const responseJson = await response.json().catch(() => null); + const savedUser = responseJson?.data ?? data; + sessionStorage.setItem("user", JSON.stringify(savedUser)); toast.success("수요처 등록에 성공하였습니다."); return true; } catch (error) { @@ -487,6 +491,7 @@ const postSdmLoginData = async (data: DemandCompType) => { const status = jsonData.status; const loginData = jsonData.data; + // console.log(loginData); let loginSuccess = false; if (status === 200 && loginData != null) { alert("수요처 로그인에 성공하였습니다."); @@ -683,6 +688,7 @@ export const SdmReducer = (set: any): SdmActions => { return ({ ...el, index: ++index, + dmdCoSn: res.dmdCoSn, bzmnNo: res.bzmnNo, dmdCoNm: res.dmdCoNm, dmdInfoId: res.dmdInfoId, diff --git a/apps/wr/.env.local b/apps/wr/.env.local index 96b66b536..36faf6c47 100644 --- a/apps/wr/.env.local +++ b/apps/wr/.env.local @@ -13,8 +13,8 @@ NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload NEXT_PUBLIC_GEOSERVER_URL = https://hasudo.geo.rsonesoft.com/geoserver NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver -NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 -# NEXT_PUBLIC_WR_SERVER_API_URL = http://localhost:8883/reuse-service/api/v1 +# NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 +NEXT_PUBLIC_WR_SERVER_API_URL = http://localhost:8883/reuse-service/api/v1 NEXT_PUBLIC_SEWERAGE_API_URL = http://con.mind-one.co.kr:11083/sewerage-service # 개발 서버 API # NEXT_PUBLIC_SEWERAGE_API_URL=http://localhost:8883/sewerage-service NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr diff --git a/apps/wr/app/(with-layout)/matMng/coMng/coSttn/page.tsx b/apps/wr/app/(with-layout)/matMng/coMng/coSttn/page.tsx index 894a27c71..d59d52e20 100644 --- a/apps/wr/app/(with-layout)/matMng/coMng/coSttn/page.tsx +++ b/apps/wr/app/(with-layout)/matMng/coMng/coSttn/page.tsx @@ -176,7 +176,11 @@ export default function CoSttn() { const dmdInfoId = updateData.dmdInfoId; const dmdInfoSliceId = `${dmdInfoId.slice(0, 3)}-${dmdInfoId.slice(3, 5)}-${dmdInfoId.slice(5, 10)}-${dmdInfoId.slice(10)}` const handleConfirm = async () => { - const message = await coSttnPwInit(dmdInfoId); + if (!updateData?.dmdCoSn) { + alert("수요처 식별값이 없어 비밀번호를 초기화할 수 없습니다."); + return; + } + const message = await coSttnPwInit(Number(updateData.dmdCoSn)); alert(message); }; if (await confirm(t('isInitPw', { dmdInfoId: dmdInfoSliceId }))) { @@ -198,7 +202,7 @@ export default function CoSttn() { ? objSearch.sgg : objSearch.city; - if (updateData?.dmdInfoId) { // 수정 + if (updateData?.dmdCoSn) { // 수정 const message = await updateDemandCompany(data); alert(message); coSttnInit("1", "13", String(con)); diff --git a/apps/wr/app/store/common/searchCode/index.ts b/apps/wr/app/store/common/searchCode/index.ts index f1c46820d..ac7160865 100644 --- a/apps/wr/app/store/common/searchCode/index.ts +++ b/apps/wr/app/store/common/searchCode/index.ts @@ -14,6 +14,7 @@ export const comFcltStateExport: comFcltState = { demandCompList: [], checkDemand: { index: 0, + dmdCoSn: "", dmdInfoId: "", bzmnNo: "", dmdCoNm: "", diff --git a/apps/wr/app/store/matDmd/coMatRvw/index.tsx b/apps/wr/app/store/matDmd/coMatRvw/index.tsx index afeb36fc1..9e6aaecd7 100644 --- a/apps/wr/app/store/matDmd/coMatRvw/index.tsx +++ b/apps/wr/app/store/matDmd/coMatRvw/index.tsx @@ -104,7 +104,7 @@ export const coRvwActionsExport: (set: any) => coRvwActions = (set: any) => { // 수요처 매칭 검토 저장 coMatRvwUpdate: async ( splyCd: string, // 공공기관재사용시설아이디 - 하수재이용시설id : pbnst_ruse_fclt_id - dmdCd: string, // 수요정보id dmd_info_id + dmdCd: string, // 수요처일련번호 dmd_co_sn data: DedMatUpdateType ): Promise => { try { diff --git a/apps/wr/app/store/matDmd/coMatRvw/type.tsx b/apps/wr/app/store/matDmd/coMatRvw/type.tsx index a505777ff..802e6a8f7 100644 --- a/apps/wr/app/store/matDmd/coMatRvw/type.tsx +++ b/apps/wr/app/store/matDmd/coMatRvw/type.tsx @@ -2,6 +2,7 @@ import type { ChkClass } from "@common/business_components/ui"; import { z } from "zod"; const DemandInfoSchema = z.object({ + dmdCoSn: z.number().nullish(), dmdInfoSn: z.number(), dmdInfoId: z.string().nullish(), dmndTmng: z.coerce.date().nullish(), @@ -23,6 +24,7 @@ const DemandInfoSchema = z.object({ }); const DemandMatchingSchema = z.object({ + dmdCoSn: z.number().nullish(), dmdInfoId: z.string().nullish(), pbnstRuseFcltId: z.string().nullish(), splyFcltFlg: z.string().nullish(), @@ -39,6 +41,7 @@ const DemandMatchingSchema = z.object({ }); const DemandCoSchema = z.object({ + dmdCoSn: z.number().nullish(), dmdInfoId: z.string().nullish(), dmdCoNm: z.string().nullish(), ctpvCd: z.string().nullish(), @@ -68,6 +71,7 @@ const DemandCstSchema = z.object({ }); export const DmdCoReqSchema = z.object({ + dmdCoSn: z.number().nullish(), // 수요처일련번호 cityText: z.string().nullish(), // 시도 sggText: z.string().nullish(), // 시군구 dmdInfoSn: z.number().default(0), // 수요정보 일련번호 @@ -115,6 +119,7 @@ export type DmdMatTableType = { }; export type DedMatUpdateType = { + dmdCoSn?: number; dmdInfoSn: number; usgPrcsProcs: string; cstrnCst: number; diff --git a/apps/wr/app/store/matDmd/coSttn/index.tsx b/apps/wr/app/store/matDmd/coSttn/index.tsx index ca610c381..aa34f13a3 100644 --- a/apps/wr/app/store/matDmd/coSttn/index.tsx +++ b/apps/wr/app/store/matDmd/coSttn/index.tsx @@ -24,7 +24,7 @@ type coSttnActions = { coSttnInit: (pageNo: string, pageCnt: string, con: string) => void; coSttnSearch: (pageNo: string, pageCnt: string, con: string) => void; coSttnTxtSearch: (pageNo: string, pageCnt: string, con: string, txt: string) => void; - coSttnPwInit: (dmdInfoId: string) => Promise; + coSttnPwInit: (dmdCoSn: number) => Promise; }; export type coSttnType = coSttnState & coSttnActions; @@ -122,9 +122,9 @@ export const coSttnActionsExport: (set: any) => coSttnActions = ( console.log("error", erTxt); } }, - coSttnPwInit: async (dmdInfoId: string) => { + coSttnPwInit: async (dmdCoSn: number) => { try { - const response = await putFectch(`/reuseFc/request/chgPassword/${dmdInfoId}`,[]) + const response = await putFectch(`/reuseFc/request/chgPassword/${dmdCoSn}`,[]) if (response.status === 200) { return "비밀번호가 초기화되었습니다."; } else { diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/type.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/type.tsx index 3b00754c7..e35e5df67 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/type.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/type.tsx @@ -44,6 +44,7 @@ export type ruseFcltType = { }; export type PbnstDemandComType = { + dmdCoSn?: number | ""; dmdInfoId: string | ""; bzmnNo: string | ""; dmdCoNm: string | ""; @@ -143,6 +144,7 @@ const fcOrgwtSchema = z.object({ }); const dmdColist = z.object({ + dmdCoSn: zNumNullish, dmdInfoId: z.string().default(""), dmdCoNm: z.string().default(""), daddr: z.string(), diff --git a/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx b/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx index 2bc1861ce..4fc26ef0a 100644 --- a/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx +++ b/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx @@ -1,7 +1,7 @@ -"use client"; /** - * 표준공사비 - */ + * 표준공사비 산정 +*/ +"use client"; import { useWrStore } from "@/app/store"; import { usageProcData } from "@/app/store/matDmd/coMatRvw/headList"; import type { @@ -286,6 +286,7 @@ export default function ConCostForm({ if (type === "mat") { if (value.demandCst) { const data: DedMatUpdateType = { + dmdCoSn: value.dmdCoSn ?? undefined, dmdInfoSn: value.dmdInfoSn, // 수요정보 일련번호 usgPrcsProcs: value.demandCst.usgPrcsProcs || "", // 용도에 따른 처리공정 cstrnCst: value.demandCst.cstrnCst || 0, // 공급관로 표준공사비(백만원) @@ -295,15 +296,22 @@ export default function ConCostForm({ rgtr: TEST_UUID, }; + const dmdCoSn = activeForm.getValues("dmdCoSn"); + + if (!dmdCoSn) { + alert("수요처 식별값이 없어 저장할 수 없습니다."); + return; + } + if(pk !==null){ if (checkFinish) { if (await confirm("검토완료 변경시 공사비는 변경할수 없습니다.\n진행하시겠습니까?")) { - const message = await coMatRvwUpdate( pk, activeForm.getValues("dmdInfoId"), data ); + const message = await coMatRvwUpdate( pk, String(dmdCoSn), data ); alert(message); } } else { - const message = await coMatRvwUpdate( pk, activeForm.getValues("dmdInfoId"), data ); + const message = await coMatRvwUpdate( pk, String(dmdCoSn), data ); alert(message); } }else{ diff --git a/apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx b/apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx index 411eebb95..1894a0532 100644 --- a/apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx +++ b/apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx @@ -256,6 +256,7 @@ export function PbnstBuseForm({ const plusArray = useCallback(() => { appendDemand({ + dmdCoSn: undefined, dmdCoNm: "", daddr: "", useUsgCd: "", @@ -596,6 +597,10 @@ export function PbnstBuseForm({ `items.${indexMng}.fcBizs.${indexBiz}.fcDemands.${index}.splyCpct`, valList.splyCpct ); + form.setValue( + `items.${indexMng}.fcBizs.${indexBiz}.fcDemands.${index}.dmdCoSn`, + valList.dmdCoSn + ); form.setValue( `items.${indexMng}.fcBizs.${indexBiz}.fcDemands.${index}.dmdInfoId`, valList.dmdInfoId?.toString() diff --git a/apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx b/apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx index fe69e0b4c..e4ea79afe 100644 --- a/apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx +++ b/apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx @@ -57,7 +57,6 @@ export function PbnstSttnModal({ data, isMap }: { data: customPbnst; isMap?: boo mode: "onChange", }); - useEffect(() => { let tableData; if (fcltTab === "fclt") { // 하수처리수 재이용시설 현황 diff --git a/apps/wr/docs/작업계획/DM 수요처 비밀번호 확인 dmdCoSn 전환_20260706.md b/apps/wr/docs/작업계획/DM 수요처 비밀번호 확인 dmdCoSn 전환_20260706.md new file mode 100644 index 000000000..1fffde156 --- /dev/null +++ b/apps/wr/docs/작업계획/DM 수요처 비밀번호 확인 dmdCoSn 전환_20260706.md @@ -0,0 +1,46 @@ +# DM 수요처 비밀번호 확인 `dmdCoSn` 전환 + +## 1. 배경 + +- DM 수요처 정보수정 화면 `apps/dm/app/(with-layout)/apply/dmdco/page.tsx` 의 기존 비밀번호 확인은 `dmdInfoId` 기준으로 `POST /reuseFc/request/checkPassword` 를 호출한다. +- 사업자등록번호(`dmdInfoId`)가 수정된 이후에는, 수정 전 기존 비밀번호를 다시 확인해야 하는 시점에 `dmdInfoId`가 바뀌어 있어 기존 row 식별이 불안정해진다. +- 현재 수요처 내부 PK는 `dmdCoSn` 이므로, 비밀번호 확인도 동일 키 기준으로 보는 것이 안전하다. + +## 2. 영향도 점검 + +확인 결과 `checkPassword` 호출 위치는 아래 1곳이다. + +- `frontend/apps/dm/app/(with-layout)/apply/dmdco/page.tsx` + +따라서 프런트 영향 범위는 DM 수요처 정보수정 화면으로 한정된다. + +백엔드 영향 범위는 아래 mapper query 1건이다. + +- `wr_back_out/src/main/resources/postgres/mapper/wr/demand_mapper.xml` + +`DemandController.checkDemandPassword()` 와 `DemandService.checkDemandPassword()` 는 입력 `DemandCo` 를 그대로 mapper 로 넘기므로, API 계약을 깨지 않고 mapper 조건만 보강하면 된다. + +## 3. 적용 방향 + +### 3.1 프런트 + +- `checkPassword` 호출 payload 에 `dmdCoSn` 포함 +- 수정 모드에서는 세션의 `dmdCoSn` 를 우선 전달 +- `dmdInfoId` 도 같이 유지하여 구세션/예외 상황 fallback 가능하게 둔다 + +### 3.2 백엔드 + +- `checkDemandPassword` SQL 을 `dmdCoSn` 우선 조회로 변경 +- `dmdCoSn` 가 없을 때만 `dmdInfoId` fallback +- 비밀번호 해시 비교 로직은 유지 + +## 4. 기대 효과 + +- 사업자등록번호 수정 전후와 무관하게 기존 비밀번호 확인 가능 +- 로그인, 단건조회, 비밀번호 초기화 등 다른 `dmdInfoId` 기반 화면은 영향 없음 +- `checkPassword` 전용 경로만 내부 PK 기준으로 안정화 + +## 5. 수정 파일 + +- `frontend/apps/dm/app/(with-layout)/apply/dmdco/page.tsx` +- `wr_back_out/src/main/resources/postgres/mapper/wr/demand_mapper.xml` diff --git a/apps/wr/docs/작업계획/dmd_info_id 제거 대응 계획_20260706.md b/apps/wr/docs/작업계획/dmd_info_id 제거 대응 계획_20260706.md new file mode 100644 index 000000000..ceff3f48d --- /dev/null +++ b/apps/wr/docs/작업계획/dmd_info_id 제거 대응 계획_20260706.md @@ -0,0 +1,211 @@ +# `dmd_info_id` 제거 대응 계획 + +## 1. 목적 + +아래 2개 테이블에서 `dmd_info_id`(사업자등록번호) 컬럼을 삭제하더라도 frontend/backend 오류가 발생하지 않도록 구조를 정리한다. + +- `sm_bs_wr.tb_wr_dmd_info_reg` +- `sm_bs_wr.tb_wr_sply_cndt_spt` + +추가 원칙: + +- 사업자등록번호는 더 이상 위 두 테이블의 직접 컬럼에 의존하지 않는다. +- 사업자등록번호 표시는 항상 수요처 마스터 `sm_bs_wr.tb_wr_ruse_fclt_dmd_co`를 join 하여 조회한다. +- 내부 연관관계 키는 `dmdCoSn`를 사용한다. + +## 2. 배경 + +현재 시스템은 `dmdCoSn` 중심 구조로 전환 중이지만, 일부 SQL/API/화면에서는 아직 `tb_wr_dmd_info_reg.dmd_info_id` 또는 `tb_wr_sply_cndt_spt.dmd_info_id`를 직접 참조하고 있다. + +이 상태에서 DB 컬럼을 먼저 삭제하면 아래 문제가 발생할 수 있다. + +- mapper SQL 실행 오류 +- resultMap 컬럼 매핑 실패 +- frontend 응답 타입 불일치 +- 화면에서 사업자등록번호 미표시 또는 수정/조회 실패 + +따라서 삭제 전 단계에서 모든 조회/저장 경로를 `dmdCoSn` 기반으로 정리하고, 사업자등록번호는 `tb_wr_ruse_fclt_dmd_co`에서만 읽도록 강제해야 한다. + +## 3. 목표 상태 + +### 3.1 식별 키 + +- 수요처 마스터 PK: `tb_wr_ruse_fclt_dmd_co.dmd_co_sn` +- 수요정보 등록 연관키: `tb_wr_dmd_info_reg.dmd_co_sn` +- 공급후보지점 연관키: `tb_wr_sply_cndt_spt.dmd_co_sn` + +### 3.2 사업자등록번호 조회 원칙 + +- 사업자등록번호는 `tb_wr_ruse_fclt_dmd_co.dmd_info_id`만 사용 +- `tb_wr_dmd_info_reg`, `tb_wr_sply_cndt_spt`에서는 더 이상 `dmd_info_id`를 select / where / join key / update 대상으로 사용하지 않음 + +### 3.3 화면/응답 원칙 + +- 응답 body에는 화면 표시용으로 `dmdInfoId`를 계속 내려줄 수 있음 +- 단, 그 값은 항상 `tb_wr_ruse_fclt_dmd_co` join 결과여야 함 +- 저장/수정/검증/매칭 내부키는 `dmdCoSn` 기준 + +## 4. 영향 범위 + +## 4.1 Backend + +주요 점검 대상: + +- `wr_back_out/src/main/resources/postgres/mapper/wr/demand_mapper.xml` +- `wr_back_out/src/main/resources/postgres/mapper/wr/public_fc_select.xml` +- `wr_back_out/src/main/java/kr/co/mindone/wrout/service/demand/DemandService.java` +- `wr_back_out/src/main/java/kr/co/mindone/wrout/service/public_fc/PublicFcService.java` +- `wr_back_out/src/main/java/kr/co/mindone/wrout/controller/DemandController.java` +- `wr_back_out/src/main/java/kr/co/mindone/wrout/object/demand/*.java` + +주의 포인트: + +- resultMap에서 `dmd_info_id` source column이 어느 테이블 기준인지 명확히 해야 함 +- `tb_wr_dmd_info_reg` / `tb_wr_sply_cndt_spt`를 직접 읽는 SQL은 사업자등록번호가 필요할 때 반드시 `tb_wr_ruse_fclt_dmd_co`를 추가 join 해야 함 +- insert/update 구문에서 삭제 예정 컬럼 `dmd_info_id`를 더 이상 쓰지 않도록 정리 필요 + +## 4.2 Frontend + +주요 점검 대상: + +- `frontend/apps/dm/**` +- `frontend/apps/wr/**` + +특히 아래 유형 점검: + +- 수요처 단건 조회 +- 수요처 로그인/세션 +- 수요처 정보수정 +- 수요정보 신청현황 +- 매칭검토/공사비/지도팝업 +- 하수재이용시설 등록/상세의 수요처 표시 + +주의 포인트: + +- frontend는 `dmdInfoId`를 표시값으로 계속 사용할 수 있음 +- 단, 응답이 어디에서 왔는지와 무관하게 내부 연산/요청 식별은 `dmdCoSn` 우선이어야 함 +- `dmd_info_id`가 `tb_wr_dmd_info_reg` 또는 `tb_wr_sply_cndt_spt`에서 내려온다고 가정하는 코드가 있으면 제거 + +## 5. 작업 단계 + +### 5.1 1단계: SQL 참조 제거 + +목표: + +- mapper SQL에서 `tb_wr_dmd_info_reg.dmd_info_id` +- mapper SQL에서 `tb_wr_sply_cndt_spt.dmd_info_id` + +직접 참조를 모두 제거한다. + +수정 원칙: + +- 사업자등록번호가 필요하면 `tb_wr_ruse_fclt_dmd_co twrfdc`를 join +- 표시 컬럼은 `twrfdc.dmd_info_id as ...` +- 내부 검색/연결 조건은 `dmd_co_sn` + +### 5.2 2단계: 저장/수정 API 정리 + +목표: + +- 수요정보 등록/수정 +- 공급후보지점 등록/수정 +- 비밀번호 확인/초기화 +- 매칭/공사비 관련 처리 + +위 경로에서 `dmd_info_id`를 내부 식별키로 더 이상 사용하지 않게 한다. + +수정 원칙: + +- path/body에 `dmdCoSn` 우선 사용 +- `dmdInfoId`는 신규 등록 중복체크나 표시용으로만 유지 +- `tb_wr_dmd_info_reg`, `tb_wr_sply_cndt_spt` insert/update 문에서 `dmd_info_id` 컬럼 제거 준비 + +### 5.3 3단계: 응답 계약 정리 + +목표: + +- frontend가 받는 응답에서는 계속 `dmdInfoId`를 사용할 수 있도록 유지 +- 단, source는 항상 `tb_wr_ruse_fclt_dmd_co` + +수정 원칙: + +- resultMap / alias에서 `master_dmd_info_id`, `display_dmd_info_id` 등 의미가 드러나게 정리 +- 중복 컬럼명 충돌 방지를 위해 alias 사용 + +### 5.4 4단계: Frontend 의존 제거 + +목표: + +- frontend가 `tb_wr_dmd_info_reg.dmd_info_id` 또는 `tb_wr_sply_cndt_spt.dmd_info_id` 존재를 전제하지 않도록 정리 + +수정 원칙: + +- 단건 식별, 수정, 비밀번호 확인, 매칭 선택값은 `dmdCoSn` +- 사업자등록번호 렌더링은 응답 `dmdInfoId` +- map popup / 상세조회 / 현황 / 등록 화면도 동일 원칙 적용 + +### 5.5 5단계: DB 컬럼 제거 + +선행 조건: + +- backend mapper / service / controller 정리 완료 +- frontend 주요 화면 검증 완료 + +실행 항목: + +- `tb_wr_dmd_info_reg.dmd_info_id` 삭제 +- `tb_wr_sply_cndt_spt.dmd_info_id` 삭제 + +주의: + +- DB 삭제는 반드시 마지막 단계 +- 삭제 전 운영/개발 DB에서 남은 참조 SQL 재검색 필요 + +## 6. 점검 체크리스트 + +### 6.1 Backend 체크 + +- `rg "twdir\\.dmd_info_id|twscs\\.dmd_info_id|dmd_info_id"` 재검색 +- 삭제 예정 2개 테이블의 `dmd_info_id`를 읽는 SQL이 남아 있지 않은지 확인 +- 단, `twrfdc.dmd_info_id` 참조는 정상 + +### 6.2 Frontend 체크 + +- DM 로그인 성공 후 세션에 `dmdCoSn` 존재 +- DM 수요처 정보수정 시 기존 비밀번호 확인 가능 +- DM 신청현황/지도팝업 정상 +- WR 수요처 관리/비밀번호 초기화/수정 정상 +- WR 매칭검토/공사비 저장 정상 +- WR 시설등록/상세의 수요처 목록 정상 + +### 6.3 API 체크 + +- 수요처 로그인 응답에 `dmdCoSn`, `dmdInfoId` 포함 +- 단건 조회 응답에 `dmdCoSn`, `dmdInfoId` 포함 +- 수요정보/매칭/시설상세 응답에서 사업자등록번호가 `twrfdc` 기준으로 내려오는지 확인 + +## 7. 위험 요소 + +- 구세션/구응답에 `dmdCoSn`가 없으면 DM 수정 흐름이 실패할 수 있음 +- 지도 highlight/feature id가 여전히 `dmdInfoId` 기반이면 일부 화면은 유지 전략이 필요 +- `SELECT *` 기반 SQL은 컬럼 충돌로 source 테이블 식별이 어려우므로 정리 필요 +- `tb_wr_sply_cndt_spt` 과거 데이터에 `dmd_co_sn` 누락이 있으면 조인 결과가 비정상일 수 있음 + +## 8. 권장 순서 + +1. backend mapper에서 삭제 예정 컬럼 참조 제거 +2. service/controller 입력 키를 `dmdCoSn` 기준으로 정리 +3. frontend 주요 화면을 `dmdCoSn + dmdInfoId` 병행 구조로 점검 +4. 응답에서 표시용 `dmdInfoId` source를 `twrfdc`로 통일 +5. 통합 테스트 +6. DB 컬럼 삭제 + +## 9. 완료 기준 + +아래 조건을 모두 만족하면 완료로 본다. + +- `tb_wr_dmd_info_reg.dmd_info_id` 삭제 후 backend 에러 없음 +- `tb_wr_sply_cndt_spt.dmd_info_id` 삭제 후 backend 에러 없음 +- frontend 주요 화면에서 사업자등록번호 표시 정상 +- 수요처 조회/수정/매칭/시설상세/지도팝업 정상 +- 내부 식별은 모두 `dmdCoSn` 기준으로 동작 diff --git a/apps/wr/docs/작업계획/수요처 사업자번호 수정 대응 계획_20260703.md b/apps/wr/docs/작업계획/수요처 사업자번호 수정 대응 계획_20260703.md index 1f64bba6c..4bd15e6e3 100644 --- a/apps/wr/docs/작업계획/수요처 사업자번호 수정 대응 계획_20260703.md +++ b/apps/wr/docs/작업계획/수요처 사업자번호 수정 대응 계획_20260703.md @@ -64,15 +64,15 @@ - `public_fc_select.xml` - 수요처 관련 화면/지도/팝업 API -### 3.3 현재 SQL의 불일치 +### 3.3 현재 SQL -`demand_mapper.xml`을 보면 수요처 조인이 일관되지 않다. +`demand_mapper.xml`을 보면 수요처 조인은 dmd_info_id 로 조인하는 것이다. - `getDemand`, `getDemandById`는 `twrfdc.dmd_info_id = twdir.dmd_info_id` - `tb_wr_sply_cndt_spt`도 `twrfdc.dmd_info_id = twscs.dmd_info_id` -- 반면 `getDemandInfo`, `getDemandInfoListCnt`는 일부 구간에서 `twscs.dmd_info_sn = twdir.dmd_info_sn` +- 반면 `getDemandInfo`, `getDemandInfoListCnt`는 일부 구간에서 `twscs.dmd_info_sn = twdir.dmd_info_sn`는 수요정보의 조인이다. -즉 현재도 `dmd_info_id`와 `dmd_info_sn`이 혼용되고 있으며, 이번 변경에서 `dmd_co_sn` 기준을 명확히 세우지 않으면 추가 혼선이 생긴다. +즉 현재 `dmd_info_id`와 `dmd_info_sn`이 혼용은 아니고, dmd_info_id는 사업자등록번호이며 `tb_wr_ruse_fclt_dmd_co` 재사용시설수요회사 의 pk이고, `dmd_info_sn`은 수요정보의 pk이다. ### 3.4 API 구조 @@ -158,63 +158,11 @@ DM은 로그인 후 세션의 `dmdInfoId`를 그대로 조회키로 쓰는 흐 - 기존: `twdir.dmd_info_id`, `twscs.dmd_info_id` 직접 사용 - 변경: `twdir.dmd_co_sn = twrfdc.dmd_co_sn` 또는 `twscs.dmd_co_sn = twrfdc.dmd_co_sn` 후 `twrfdc.dmd_info_id` 선택 -## 5. DB 작업계획 +## 5. DB 작업 -### 5.1 `tb_wr_ruse_fclt_dmd_co` +G:\내 드라이브\07 유역하수도\208 공단 배포\sql\alter_tables_wr.sql -`alter_tables_wr.sql` 기준으로 아래는 확정 방향이다. - -- `dmd_co_sn` 추가 -- sequence 생성 -- 기존 데이터 채번 -- `dmd_co_sn` PK 전환 -- `dmd_info_id` unique 유지 - -추가 검토: - -- `dmd_info_id` nullable 허용 여부는 업무상 불필요하면 현행 유지 -- 사업자번호 변경 이력 관리가 필요하면 별도 이력 테이블은 후속 검토 - -### 5.2 `tb_wr_dmd_info_reg` - -추가 필요 컬럼: - -- `dmd_co_sn integer not null` - -필요 작업: - -1. 기존 데이터 backfill -- `update ... set dmd_co_sn = twrfdc.dmd_co_sn from tb_wr_ruse_fclt_dmd_co twrfdc where twdir.dmd_info_id = twrfdc.dmd_info_id` - -2. FK 추가 -- `fk_tb_wr_dmd_info_reg__dmd_co_sn -> tb_wr_ruse_fclt_dmd_co(dmd_co_sn)` - -3. 신규 insert/update 시 `dmd_info_id` 대신 `dmd_co_sn`을 기준값으로 사용하도록 mapper 수정 - -주의: - -- `tb_wr_dmd_info_reg` 자체 PK인 `dmd_info_sn`은 그대로 유지 -- 수요정보 이력 식별과 수요처 마스터 식별은 분리 - -### 5.3 `tb_wr_sply_cndt_spt` - -추가 필요 컬럼: - -- `dmd_co_sn integer not null` - -필요 작업: - -1. 기존 데이터 backfill -- `dmd_info_id` 또는 `dmd_info_sn`을 통해 `tb_wr_ruse_fclt_dmd_co.dmd_co_sn` 채움 - -2. FK 추가 -- `fk_tb_wr_sply_cndt_spt__dmd_co_sn -> tb_wr_ruse_fclt_dmd_co(dmd_co_sn)` - -3. unique / conflict key 재검토 -- 현재 `ON CONFLICT (dmd_info_id, pbnst_ruse_fclt_id, sply_fclt_flg, pbnst_ruse_fclt_biz_sn)` -- 변경 후 후보: `(dmd_co_sn, pbnst_ruse_fclt_id, sply_fclt_flg, pbnst_ruse_fclt_biz_sn)` - -### 5.4 기타 테이블 +### 5.1 기타 테이블 `tb_wr_cstrn_cst`는 현재 `dmd_info_sn` 기준이므로 직접적인 `dmd_co_sn` 추가 대상은 아니다. @@ -259,7 +207,7 @@ DM은 로그인 후 세션의 `dmdInfoId`를 그대로 조회키로 쓰는 흐 3. `insertDemandInfo`, `updateDemandInfo` - `dmd_co_sn` 저장 -- 화면에서 `dmdInfoId`만 보내는 경우 backend에서 먼저 `dmd_co_sn` 조회 후 저장 +- 수요처 상세 조회 화면에서 `dmd_co_sn` 까지 조회되도록 하고 `dmdCoSn`을 수정시 보내도록 한다. 4. `insertMatching`, `updateMatching`, `getDemandMatching` - 조건식과 upsert key를 `dmd_co_sn` 중심으로 전환 @@ -286,6 +234,7 @@ DM은 로그인 후 세션의 `dmdInfoId`를 그대로 조회키로 쓰는 흐 2. 단건 조회 - `GET /reuseFc/request/{demandCd}`는 기존 호환을 위해 사업자번호 기반 조회 유지 가능 +- {demandCd} 변수명을 컬럼명(dmdInfoId)으로 수정한다. - 응답에는 반드시 `dmdCoSn` 포함 3. 수정 API @@ -294,11 +243,13 @@ DM은 로그인 후 세션의 `dmdInfoId`를 그대로 조회키로 쓰는 흐 - 다른 연관 테이블은 수정하지 않음 4. 비밀번호 초기화 -- 현재 `PUT /reuseFc/request/chgPassword/{dmdCd}`는 사업자번호 path 기반 -- 화면 호환을 위해 우선 유지하되, 내부에서는 `dmd_info_id`로 마스터 조회 후 처리 +- 현재 `PUT /reuseFc/request/chgPassword/{dmdCd}`는 `dmdCoSn` 기반 +- {dmdCd} 변수명을 컬럼명(dmdCoSn)으로 수정한다. +- 화면 호환을 위해 우선 유지하되, 내부에서는 `dmd_co_sn`로 마스터 조회 후 처리 5. 공사비/매칭 - `PUT /reuseFc/request/cst/{splyCd}/{dmdCd}`는 후속적으로 `dmdCoSn` 기반 API로 정리 검토 +- {dmdCd} 변수명을 컬럼명(dmdCoSn)으로 수정한다. - 1차는 내부 변환으로 대응 가능 ## 7. WR 프런트 작업계획 @@ -404,30 +355,22 @@ DM은 로그인 후 세션의 `dmdInfoId`를 그대로 조회키로 쓰는 흐 ## 9. 구현 순서 -1. DB DDL 작성 -- `tb_wr_ruse_fclt_dmd_co` -- `tb_wr_dmd_info_reg` -- `tb_wr_sply_cndt_spt` -- FK / unique / index 정리 +1. reuse-service DTO / mapper / service 수정 -2. 백필 SQL 작성 및 검증 -- 기존 데이터의 `dmd_co_sn` 채우기 +2. Swagger 기준 API 응답에 `dmdCoSn` 포함 확인 -3. reuse-service DTO / mapper / service 수정 - -4. Swagger 기준 API 응답에 `dmdCoSn` 포함 확인 - -5. WR 프런트 수정 -- 수요처 관리 +3. WR 프런트 수정 +- 수요처 관리 : 수요처 등록/수정 - 지도/팝업 - 공사비/매칭 +- 하수재이용시설 등록 우측 차수별정보 하단 수요처 추가 -6. DM 프런트 수정 +4. DM 프런트 수정 - 로그인/세션 - 등록/수정 - 신청현황/상세 -7. 수동 검증 +5. 수동 검증 ## 10. 검증 항목 diff --git a/packages/common/business_components/src/ui/sdmInsertForm/form.tsx b/packages/common/business_components/src/ui/sdmInsertForm/form.tsx index 9da949bee..b68dd7b3f 100644 --- a/packages/common/business_components/src/ui/sdmInsertForm/form.tsx +++ b/packages/common/business_components/src/ui/sdmInsertForm/form.tsx @@ -10,6 +10,7 @@ import { SearchAddressModal } from "../search_address/SearchAddressModal"; // 수요처 정보 export type DemandCompType = { index: number | ""; + dmdCoSn?: number | ""; dmdInfoId: string | ""; bzmnNo: string | ""; dmdCoNm: string | ""; @@ -348,10 +349,10 @@ export function SdmRegForm({ bznNoChange(e, "dmdInfoId", field)} value={formatBusinessNumber(field.value ?? "")} - readOnly={!!updateList?.dmdInfoId} + readOnly={!!readOnly} /> ), }, @@ -367,12 +368,12 @@ export function SdmRegForm({ bznNoChange(e, "bznmTotNo", field)} // {...field} value={field.value ?? ""} - readOnly={!!updateList?.dmdInfoId} + readOnly={!!readOnly} /> ), diff --git a/packages/common/business_components/src/ui/sdmInsertForm/index.tsx b/packages/common/business_components/src/ui/sdmInsertForm/index.tsx index 372cc553e..b39e7585e 100644 --- a/packages/common/business_components/src/ui/sdmInsertForm/index.tsx +++ b/packages/common/business_components/src/ui/sdmInsertForm/index.tsx @@ -140,6 +140,7 @@ export default function SdmInsertForm({selectSidoData, selectSigunData, dataList const dmdInfoId = data.dmdInfoId + (data.bznmTotNo || '0000'); const formData = { ...data, + dmdCoSn: updateList?.dmdCoSn ?? "", dmdInfoId, bzmnNo: data.dmdInfoId, daddr: data.daddr?.trim() || "",