Merge remote-tracking branch 'origin/iipbs/wr/dev'
This commit is contained in:
+3
-4
@@ -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://localhost:8883 # local
|
||||
CONTENTS_INTRO = 31
|
||||
CONTENTS_EXAMPLE = 32
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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<string, unknown> = {
|
||||
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() {
|
||||
</p>
|
||||
|
||||
<div className="border border-neutral-200 bg-white">
|
||||
<FormRow label="시도" required>
|
||||
<SelectBox
|
||||
value={sido}
|
||||
onChange={(v) => {
|
||||
setSido(v);
|
||||
setSigungu("");
|
||||
}}
|
||||
options={sidoOptions}
|
||||
/>
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
|
||||
<FormRow label="시군구" required>
|
||||
<select
|
||||
value={sigungu}
|
||||
onChange={(e) => setSigungu(e.target.value)}
|
||||
className="h-11 w-[220px] rounded border border-neutral-300 bg-white px-3 text-sm text-neutral-800 focus:border-neutral-400 focus:outline-none"
|
||||
>
|
||||
{sigunguOptions.map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
|
||||
|
||||
<FormRow label="회사명" required>
|
||||
<div className="max-w-[420px]">
|
||||
@@ -633,7 +608,34 @@ export default function DemandPlaceRegisterPage() {
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
<FormRow label="시도" >
|
||||
<SelectBox
|
||||
value={sido}
|
||||
onChange={(v) => {
|
||||
setSido(v);
|
||||
setSigungu("");
|
||||
}}
|
||||
options={sidoOptions}
|
||||
/>
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
|
||||
<FormRow label="시군구" >
|
||||
<select
|
||||
value={sigungu}
|
||||
onChange={(e) => setSigungu(e.target.value)}
|
||||
className="h-11 w-[220px] rounded border border-neutral-300 bg-white px-3 text-sm text-neutral-800 focus:border-neutral-400 focus:outline-none"
|
||||
>
|
||||
{sigunguOptions.map((o) => (
|
||||
<option key={o.value} value={o.value}>
|
||||
{o.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</FormRow>
|
||||
|
||||
<Divider />
|
||||
<FormRow label="전화번호" required>
|
||||
<div className="max-w-[420px]">
|
||||
<TextInput
|
||||
|
||||
@@ -286,6 +286,7 @@ export default function DemandRegisterPage() {
|
||||
}));
|
||||
|
||||
const payload: SdmInfoFormData = {
|
||||
dmdCoSn: user.dmdCoSn,
|
||||
dmndTmng: new Date(reqDate),
|
||||
dmdInfoId: user.dmdInfoId,
|
||||
dmndQty: Number(reqQty),
|
||||
@@ -317,11 +318,13 @@ export default function DemandRegisterPage() {
|
||||
const searchCandidate = (e: React.MouseEvent) => {
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<typeof sdmInfoFormSchema>;
|
||||
// 수요처 정보
|
||||
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,
|
||||
|
||||
@@ -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));
|
||||
@@ -338,7 +342,16 @@ export default function CoSttn() {
|
||||
? objSearch.sgg
|
||||
: objSearch.city;
|
||||
setPageNation(page);
|
||||
coSttnSearch(String(page), "13", String(con));
|
||||
if (seartchTxt && seartchTxt.trim().length > 0) {
|
||||
coSttnTxtSearch(
|
||||
String(page),
|
||||
"13",
|
||||
String(con),
|
||||
seartchTxt
|
||||
);
|
||||
} else {
|
||||
coSttnSearch(String(page), "13", String(con));
|
||||
}
|
||||
}}
|
||||
resetSelection={isResetSelection}
|
||||
widthFixed={true}
|
||||
|
||||
@@ -80,7 +80,11 @@ export default function Home() {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getHtdflBdstData(pageNation, laeSeCd);
|
||||
if (searchTxt && searchTxt.trim().length > 0) {
|
||||
htdflNmSearch(pageNation, laeSeCd, searchTxt);
|
||||
} else {
|
||||
getHtdflBdstData(pageNation, laeSeCd);
|
||||
}
|
||||
setResetPageNo(false);
|
||||
}, [pageNation]);
|
||||
|
||||
@@ -113,7 +117,7 @@ export default function Home() {
|
||||
className={btnClass}
|
||||
onClick={() => {
|
||||
if (searchTxt && searchTxt.length > 0) {
|
||||
htdflNmSearch(String(0), searchTxt);
|
||||
htdflNmSearch(1, String(0), searchTxt);
|
||||
} else {
|
||||
initHtdflReg(String(0));
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ export default function Home() {
|
||||
archArea: formatArchArea(item?.archArea, 1),
|
||||
bdstChgYn: item.chgBuilding[0]?.bdstChgNm != null ? "Y" : "N",
|
||||
frstArchPrmsnDt: item?.frstArchPrmsnDt || "확인불가",
|
||||
lawTrgtYn: item.reuseBuilding[0]?.lawTrgtYn ?? (item as any).lawTrgtYn,
|
||||
cityText: item.ctpvCd === 0 ? "세종특별자치시" : (item as any).cityText,
|
||||
sggText: (item as any).sggText === "없음" ? "-" : (item as any).sggText,
|
||||
};
|
||||
@@ -97,7 +98,11 @@ export default function Home() {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getRainData(pageNation, laeSeCd);
|
||||
if (searchTxt && searchTxt.trim().length > 0) {
|
||||
rainSearch(searchTxt, pageNation, laeSeCd);
|
||||
} else {
|
||||
getRainData(pageNation, laeSeCd);
|
||||
}
|
||||
setResetPageNo(false);
|
||||
}, [pageNation]);
|
||||
|
||||
|
||||
@@ -73,8 +73,9 @@ export default function Home() {
|
||||
return {
|
||||
...item,
|
||||
archArea: formatArchArea(item?.archArea, 1),
|
||||
bdstChgYn: item.chgBuilding[0]?.bdstChgNm != null ? "Y" : "N",
|
||||
chBuildingYn: item.chgBuilding[0]?.bdstChgNm != null ? "Y" : "N",
|
||||
bdstUsgCd: item.reuseBuilding[0]?.bdstUsgCd,
|
||||
instlCmptnDt: item.reuseBuilding[0]?.instlCmptnDt,
|
||||
cityText: item.ctpvCd === 0 ? "세종특별자치시" : (item as any).cityText,
|
||||
sggText: (item as any).sggText === "없음" ? "-" : (item as any).sggText,
|
||||
};
|
||||
@@ -107,7 +108,11 @@ export default function Home() {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getRswtBdstData(pageNation,laeSeCd);
|
||||
if (searchTxt && searchTxt.trim().length > 0) {
|
||||
rswtBdstSearch(searchTxt, pageNation, laeSeCd);
|
||||
} else {
|
||||
getRswtBdstData(pageNation,laeSeCd);
|
||||
}
|
||||
setResetPageNo(false);
|
||||
}, [pageNation]);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ export const comFcltStateExport: comFcltState = {
|
||||
demandCompList: [],
|
||||
checkDemand: {
|
||||
index: 0,
|
||||
dmdCoSn: "",
|
||||
dmdInfoId: "",
|
||||
bzmnNo: "",
|
||||
dmdCoNm: "",
|
||||
|
||||
@@ -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<string> => {
|
||||
try {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<string>;
|
||||
coSttnPwInit: (dmdCoSn: number) => Promise<string>;
|
||||
};
|
||||
|
||||
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 {
|
||||
|
||||
@@ -99,7 +99,7 @@ export type htdflFacState = {
|
||||
};
|
||||
type htdflFacSActions = {
|
||||
initHtdflReg: (code: string) => void;
|
||||
htdflNmSearch: (code: string | number, fcltNm:string) => void;
|
||||
htdflNmSearch: (pageNation: number, code: string | number, fcltNm:string) => void;
|
||||
htdflBdstUpdate: (data: customHtdflBdst, code: string | number,myUUID:string) => Promise<string>;
|
||||
htdflBdstReg: (data: customHtdflBdst, code: string | number,myUUID:string) => Promise<string>;
|
||||
htdflBdstDelete: (data: customHtdflBdst, code: string | number) => Promise<string>;
|
||||
@@ -250,9 +250,9 @@ export const htdflFacActionsExport: (set: any) => htdflFacSActions = (
|
||||
} finally {
|
||||
}
|
||||
},
|
||||
htdflNmSearch :async(code:string | number, fcltNm:string) =>{
|
||||
htdflNmSearch :async(pageNation: number, code:string | number, fcltNm:string) =>{
|
||||
try {
|
||||
const htdflRes = await getFetchWithCount("wr", `/drain/facilityList/1/${DEFAULT_LIST_SIZE}/${code}/${fcltNm}`);
|
||||
const htdflRes = await getFetchWithCount("wr", `/drain/facilityList/${pageNation}/${DEFAULT_LIST_SIZE}/${code}/${fcltNm}`);
|
||||
set((state: htdflFacState) => {
|
||||
return {
|
||||
...state,
|
||||
|
||||
@@ -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(),
|
||||
@@ -259,9 +261,9 @@ const PrfSchema = z.object({
|
||||
farmUswtr: z.number(),
|
||||
ugwtChr: z.number(),
|
||||
indstUswtr: z.number(),
|
||||
regDt: z.date().default(new Date()).nullish(),
|
||||
regDt: zDateNullish,
|
||||
rgtr: z.string().nullish(),
|
||||
mdfcnDt: z.date().default(new Date()).nullish(),
|
||||
mdfcnDt: zDateNullish,
|
||||
mdfr: z.string().nullish(),
|
||||
});
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ export const upHeadList: TableUpperProps[] = [
|
||||
{ id: "sggText", title: "시군구", widthPercent: 8 },
|
||||
{ id: "bdstNm", title: "시설명", widthPercent: 15 },
|
||||
{ id: "daddr", title: "위치", widthPercent: 22 },
|
||||
{ id: "bdsgUsgText", title: "건축물용도", widthPercent: 12 },
|
||||
{ id: "bdstUsgText", title: "건축물용도", widthPercent: 12 },
|
||||
{ id: "frstArchPrmsnDt", title: "최초\n건축허가일", widthPercent: 9 },
|
||||
{ id: "archArea", title: "건축면적(㎡)", widthPercent: 9 },
|
||||
{ id: "lawTrgtYn", title: "법적의무대상여부", widthPercent: 8 },
|
||||
|
||||
@@ -16,7 +16,7 @@ export const rainSttHeadListExport: z.infer<typeof sttnHeadListType> = [
|
||||
{ id: "sggText", title: "시군구", visible: true, widthPercent: 4 },
|
||||
{ id: "bdstNm", title: "건축물명", visible: true, widthPercent: 6 },
|
||||
{ id: "daddr", title: "주소", visible: true, widthPercent: 8 },
|
||||
{ id: "bdsgUsgText", title: "건축물용도", visible: true, widthPercent: 5 },
|
||||
{ id: "bdstUsgText", title: "건축물용도", visible: true, widthPercent: 5 },
|
||||
{ id: "frstArchPrmsnDt", title: "최초건축허가일시", visible: true, widthPercent: 6 },
|
||||
{ id: "archArea", title: "건축면적", visible: true, widthPercent: 4 },
|
||||
{ id: "chBuildingYn", title: "증축・개축・재축여부", visible: true, widthPercent: 4 },
|
||||
@@ -57,7 +57,7 @@ export const rainSttNodeListExport: CommonTableProps[] = [
|
||||
// 1번째 줄 건축물정보 아래의 행으로 만들 헤더
|
||||
{ id: "bdstNm", title: "건축물명", visible: true, upName: "bdnstInfo", row: 2, rowSpan: 2, colSpan: 1, },
|
||||
{ id: "daddr", title: "주소", visible: true, upName: "bdnstInfo", row: 2, rowSpan: 2, colSpan: 1, },
|
||||
{ id: "bdsgUsgText", title: "건축물용도", upName: "bdnstInfo", visible: true, row: 2, rowSpan: 2, colSpan: 1, },
|
||||
{ id: "bdstUsgText", title: "건축물용도", upName: "bdnstInfo", visible: true, row: 2, rowSpan: 2, colSpan: 1, },
|
||||
{ id: "frstArchPrmsnDt", title: "최초건축허가일시", upName: "bdnstInfo", visible: true, row: 2, rowSpan: 2, colSpan: 1, },
|
||||
{ id: "archArea", title: "건축면적(m²)", upName: "bdnstInfo", visible: true, row: 2, rowSpan: 2, colSpan: 1, },
|
||||
{ id: "chBuildingYn", title: "증축・개축・재축여부", upName: "bdnstInfo", visible: true, row: 2, rowSpan: 2, colSpan: 1, },
|
||||
|
||||
@@ -28,7 +28,7 @@ export const rswtSttHeadListExport: z.infer<typeof sttnHeadListType> = [
|
||||
widthPercent: 8,
|
||||
},
|
||||
{
|
||||
id: "bdsgUsgText",
|
||||
id: "bdstUsgText",
|
||||
title: "건축물용도",
|
||||
visible: true,
|
||||
widthPercent: 5,
|
||||
@@ -223,7 +223,7 @@ export const rswtSttNodeListExport: CommonTableProps[] = [
|
||||
colSpan: 1,
|
||||
},
|
||||
{
|
||||
id: "bdsgUsgText",
|
||||
id: "bdstUsgText",
|
||||
title: "건축물용도",
|
||||
upName: "bdnstInfo",
|
||||
visible: true,
|
||||
|
||||
@@ -52,7 +52,7 @@ export const tableData = (data: any, code: comCodeState) => {
|
||||
(cd) => cd.val === String(item.ctpvCd).trim()
|
||||
)?.text;
|
||||
if (item?.reuseBuilding?.length > 0) {
|
||||
const bdsgUsgText = chCode?.bdstTotalUsgType?.find(
|
||||
const bdstUsgText = chCode?.bdstTotalUsgType?.find(
|
||||
(cd) => cd.val === item?.reuseBuilding[0]?.bdstUsgCd
|
||||
)?.text;
|
||||
|
||||
@@ -66,7 +66,7 @@ export const tableData = (data: any, code: comCodeState) => {
|
||||
...item,
|
||||
sggText: sggText ?? "없음",
|
||||
cityText: cityText ?? "없음",
|
||||
bdsgUsgText: bdsgUsgText, // 건물용도명
|
||||
bdstUsgText: bdstUsgText, // 건물용도명
|
||||
lawTrgtYn: lawTrgtYn, // 법정대상 여부
|
||||
grntchBdstCertGrdText: grntchBdstCertGrdText, // 녹색건축인증등급명
|
||||
};
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -57,7 +57,6 @@ export function PbnstSttnModal({ data, isMap }: { data: customPbnst; isMap?: boo
|
||||
mode: "onChange",
|
||||
});
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
let tableData;
|
||||
if (fcltTab === "fclt") { // 하수처리수 재이용시설 현황
|
||||
|
||||
@@ -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`
|
||||
@@ -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` 기준으로 동작
|
||||
@@ -0,0 +1,428 @@
|
||||
# 수요처 사업자번호 수정 대응 계획
|
||||
|
||||
작성일: 2026-07-03
|
||||
|
||||
## 1. 배경
|
||||
|
||||
현재 WR 수요처 구조는 `tb_wr_ruse_fclt_dmd_co.dmd_info_id`를 수요처의 표시용 사업자등록번호이면서 동시에 연관관계 키처럼 사용하고 있다.
|
||||
|
||||
이 구조에서는 수요처의 사업자등록번호를 수정하려면 아래 연관 테이블의 `dmd_info_id`까지 함께 수정해야 한다.
|
||||
|
||||
- `tb_wr_ruse_fclt_dmd_co` 재사용시설수요회사
|
||||
- `tb_wr_dmd_info_reg` 수요정보등록
|
||||
- `tb_wr_sply_cndt_spt` 공급후보지점
|
||||
- 이를 참조하는 조회 SQL 및 프런트 화면
|
||||
|
||||
요청사항은 `tb_wr_ruse_fclt_dmd_co`에 `dmd_co_sn`(수요처일련번호)을 추가하고, 연관 테이블도 `dmd_co_sn`로 연결되도록 바꿔서 사업자등록번호 변경 시 다른 테이블 수정이 필요하지 않게 만드는 것이다.
|
||||
|
||||
기준 SQL 초안은 아래 파일이다.
|
||||
|
||||
- `G:\내 드라이브\07 유역하수도\208 공단 배포\sql\alter_tables_wr.sql`
|
||||
|
||||
## 2. 목표
|
||||
|
||||
이번 작업의 목표는 아래 3가지다.
|
||||
|
||||
1. 수요처의 내부 식별키를 `dmd_info_id`에서 `dmd_co_sn`으로 전환
|
||||
2. 사업자등록번호(`dmd_info_id`)는 수정 가능한 업무값으로만 유지
|
||||
3. 사업자등록번호를 화면/API에 보여줄 때는 항상 `dmd_co_sn` 기준으로 원본 수요처 테이블을 join해서 조회
|
||||
|
||||
정리하면:
|
||||
|
||||
- 내부 연관키: `dmd_co_sn`
|
||||
- 표시/로그인/입력값: `dmd_info_id`
|
||||
|
||||
## 3. 현재 구조 분석
|
||||
|
||||
### 3.1 기준 테이블
|
||||
|
||||
현재 수요처 마스터 역할은 `tb_wr_ruse_fclt_dmd_co`가 맡고 있다.
|
||||
|
||||
주요 컬럼:
|
||||
|
||||
- `dmd_info_id`: 사업자등록번호 + 종사업자번호 조합값
|
||||
- `dmd_co_nm`: 수요처명
|
||||
- `ctpv_cd`, `sgg_cd`, `daddr`, `telno` 등 기본정보
|
||||
|
||||
현재 `alter_tables_wr.sql`은 이 테이블에 `dmd_co_sn`을 추가하고 PK를 `dmd_co_sn`으로 전환하는 1차 작업까지 포함하고 있다.
|
||||
|
||||
### 3.2 연관 테이블
|
||||
|
||||
`reuse-service` 분석 결과, 사업자번호 변경 영향이 직접 가는 주요 연관 테이블은 아래와 같다.
|
||||
|
||||
1. `tb_wr_dmd_info_reg`
|
||||
- 수요정보 이력/상세
|
||||
- 현재 `dmd_info_id`를 보유하고 있고, 조회 시 `tb_wr_ruse_fclt_dmd_co`와 `dmd_info_id`로 join
|
||||
|
||||
2. `tb_wr_sply_cndt_spt`
|
||||
- 공급후보지점/매칭 정보
|
||||
- 현재 `dmd_info_id`, `dmd_info_sn`을 함께 사용
|
||||
- 일부 조회는 `dmd_info_id`, 일부 조회는 `dmd_info_sn` 기반으로 섞여 있음
|
||||
|
||||
3. 조회 전용 join 경로
|
||||
- `demand_mapper.xml`
|
||||
- `public_fc_select.xml`
|
||||
- 수요처 관련 화면/지도/팝업 API
|
||||
|
||||
### 3.3 현재 SQL
|
||||
|
||||
`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`는 수요정보의 조인이다.
|
||||
|
||||
즉 현재 `dmd_info_id`와 `dmd_info_sn`이 혼용은 아니고, dmd_info_id는 사업자등록번호이며 `tb_wr_ruse_fclt_dmd_co` 재사용시설수요회사 의 pk이고, `dmd_info_sn`은 수요정보의 pk이다.
|
||||
|
||||
### 3.4 API 구조
|
||||
|
||||
주요 수요처 API는 `DemandController` 아래에 있다.
|
||||
|
||||
- `POST /reuseFc/request`
|
||||
- `PUT /reuseFc/request`
|
||||
- `POST /reuseFc/request/login`
|
||||
- `GET /reuseFc/request/{demandCd}`
|
||||
- `GET /reuseFc/request/{pageNo}/{pageCnt}/{sggCd}`
|
||||
- `GET /reuseFc/request/infoList/...`
|
||||
- `PUT /reuseFc/request/chgPassword/{dmdCd}`
|
||||
- `PUT /reuseFc/request/cst/{splyCd}/{dmdCd}`
|
||||
|
||||
현재 path variable 이름은 `dmdCd`, `demandCd` 등이지만 실제 의미는 대부분 `dmd_info_id`다.
|
||||
|
||||
### 3.5 WR 프런트 영향 범위
|
||||
|
||||
WR에서 `dmdInfoId`를 직접 키로 쓰는 주요 영역은 아래와 같다.
|
||||
|
||||
1. 수요처 현황/관리
|
||||
- `apps/wr/app/store/matDmd/coSttn/index.tsx`
|
||||
- `apps/wr/app/(with-layout)/matMng/coMng/coSttn/page.tsx`
|
||||
|
||||
2. 지도/팝업
|
||||
- `apps/wr/app/store/map/coSttnMap/index.tsx`
|
||||
- `apps/wr/components/lib/func/mapUtil.ts`
|
||||
- `apps/wr/app/(popUp-layout)/splyPopup/page.tsx`
|
||||
- `apps/wr/app/(popUp-layout)/mapFeaturePopup/page.tsx`
|
||||
|
||||
3. 공공하수처리시설 등록 폼 내 수요처 연결
|
||||
- `apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx`
|
||||
|
||||
4. 공사비 검토/매칭
|
||||
- `apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx`
|
||||
- `apps/wr/app/store/matDmd/coMatRvw/index.tsx`
|
||||
|
||||
### 3.6 DM 프런트 영향 범위
|
||||
|
||||
DM에서도 `dmdInfoId`를 내부키처럼 재사용하고 있다.
|
||||
|
||||
1. 로그인/회원식별
|
||||
- `apps/dm/components/LoginModal.tsx`
|
||||
- `apps/dm/components/ui/sdmLoginForm/component.tsx`
|
||||
|
||||
2. 수요처 등록/수정 공통 컴포넌트
|
||||
- `frontend/packages/common/business_components/src/ui/sdmInsertForm/*`
|
||||
|
||||
3. 신청현황/수요정보 조회
|
||||
- `apps/dm/app/store/sdmMng/index.tsx`
|
||||
- `apps/dm/app/(with-layout)/apply/status/page.tsx`
|
||||
- `apps/dm/app/(with-layout)/apply/dmdinfo/page.tsx`
|
||||
- `apps/dm/app/(with-layout)/apply/dmdco/page.tsx`
|
||||
|
||||
4. 지도 팝업
|
||||
- `apps/dm/app/(popup-layout)/map/popup/page.tsx`
|
||||
|
||||
DM은 로그인 후 세션의 `dmdInfoId`를 그대로 조회키로 쓰는 흐름이 있으므로, 응답에 `dmdCoSn`을 함께 내려주고 세션에도 저장하도록 수정해야 한다.
|
||||
|
||||
## 4. 목표 구조
|
||||
|
||||
### 4.1 데이터 모델 원칙
|
||||
|
||||
- `tb_wr_ruse_fclt_dmd_co.dmd_co_sn`: 수요처 마스터 PK
|
||||
- `tb_wr_ruse_fclt_dmd_co.dmd_info_id`: 수정 가능한 사업자등록번호
|
||||
- `tb_wr_dmd_info_reg.dmd_co_sn`: 수요처 FK
|
||||
- `tb_wr_sply_cndt_spt.dmd_co_sn`: 수요처 FK
|
||||
|
||||
기존 `dmd_info_id` 컬럼은 즉시 제거하지 않는다.
|
||||
|
||||
역할은 아래처럼 축소한다.
|
||||
|
||||
- 입력/로그인 시 사업자번호 매칭
|
||||
- 화면 표시용 값
|
||||
- 과거 API 호환용 보조값
|
||||
|
||||
### 4.2 조회 원칙
|
||||
|
||||
사업자등록번호를 응답에 포함할 때는 `tb_wr_ruse_fclt_dmd_co`에서 가져온다.
|
||||
|
||||
즉 아래 형태로 바꾸는 것이 원칙이다.
|
||||
|
||||
- 기존: `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 작업
|
||||
|
||||
G:\내 드라이브\07 유역하수도\208 공단 배포\sql\alter_tables_wr.sql
|
||||
|
||||
### 5.1 기타 테이블
|
||||
|
||||
`tb_wr_cstrn_cst`는 현재 `dmd_info_sn` 기준이므로 직접적인 `dmd_co_sn` 추가 대상은 아니다.
|
||||
|
||||
다만 공사비 화면에서 사업자번호를 표시할 때는:
|
||||
|
||||
- `tb_wr_cstrn_cst -> tb_wr_dmd_info_reg(dmd_info_sn) -> tb_wr_ruse_fclt_dmd_co(dmd_co_sn)`
|
||||
|
||||
경로를 사용해야 한다.
|
||||
|
||||
## 6. 백엔드 작업계획
|
||||
|
||||
### 6.1 도메인/DTO 수정
|
||||
|
||||
대상:
|
||||
|
||||
- `DemandCo`
|
||||
- `DemandInfo`
|
||||
- `DemandMatching`
|
||||
- `DemandMatchingInfo`
|
||||
|
||||
추가/정리 방향:
|
||||
|
||||
- `dmdCoSn` 필드 추가
|
||||
- `dmdInfoId`는 표시값으로 유지
|
||||
- path/body에서 내부 처리에 필요한 경우 `dmdCoSn` 우선 사용
|
||||
|
||||
### 6.2 Mapper SQL 수정
|
||||
|
||||
대상 파일:
|
||||
|
||||
- `D:\iipbs\wr_back_out\src\main\resources\postgres\mapper\wr\demand_mapper.xml`
|
||||
- `D:\iipbs\wr_back_out\src\main\resources\postgres\mapper\wr\public_fc_select.xml`
|
||||
|
||||
핵심 수정:
|
||||
|
||||
1. `getDemand`, `getDemandById`
|
||||
- `tb_wr_dmd_info_reg`, `tb_wr_sply_cndt_spt` join을 `dmd_co_sn` 기준으로 변경
|
||||
- 응답의 사업자번호는 `twrfdc.dmd_info_id` 사용
|
||||
|
||||
2. `insertDemand`
|
||||
- 신규 등록 후 `dmd_co_sn`까지 반환되도록 수정 검토
|
||||
|
||||
3. `insertDemandInfo`, `updateDemandInfo`
|
||||
- `dmd_co_sn` 저장
|
||||
- 수요처 상세 조회 화면에서 `dmd_co_sn` 까지 조회되도록 하고 `dmdCoSn`을 수정시 보내도록 한다.
|
||||
|
||||
4. `insertMatching`, `updateMatching`, `getDemandMatching`
|
||||
- 조건식과 upsert key를 `dmd_co_sn` 중심으로 전환
|
||||
|
||||
5. `getDemandInfo`, `getDemandInfoListCnt`
|
||||
- 사업자번호 표시 시 `twrfdc.dmd_info_id`
|
||||
- 수요처 연계는 `dmd_co_sn`
|
||||
|
||||
6. `public_fc_select.xml`
|
||||
- 공공하수처리시설 상세의 수요처 목록 조인도 `twscs.dmd_co_sn = twrfdc.dmd_co_sn`로 전환
|
||||
|
||||
### 6.3 Service / Controller 수정
|
||||
|
||||
대상:
|
||||
|
||||
- `DemandService`
|
||||
- `DemandController`
|
||||
|
||||
수정 방향:
|
||||
|
||||
1. 로그인
|
||||
- `loginDemand()`는 사업자번호(`dmdInfoId`)로 수요처 마스터를 조회하는 흐름 유지
|
||||
- 로그인 성공 응답에 `dmdCoSn` 포함
|
||||
|
||||
2. 단건 조회
|
||||
- `GET /reuseFc/request/{demandCd}`는 기존 호환을 위해 사업자번호 기반 조회 유지 가능
|
||||
- {demandCd} 변수명을 컬럼명(dmdInfoId)으로 수정한다.
|
||||
- 응답에는 반드시 `dmdCoSn` 포함
|
||||
|
||||
3. 수정 API
|
||||
- `PUT /reuseFc/request`
|
||||
- 사업자번호 변경 시 `tb_wr_ruse_fclt_dmd_co`만 수정하도록 처리
|
||||
- 다른 연관 테이블은 수정하지 않음
|
||||
|
||||
4. 비밀번호 초기화
|
||||
- 현재 `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 프런트 작업계획
|
||||
|
||||
### 7.1 수요처 관리 화면
|
||||
|
||||
대상:
|
||||
|
||||
- `apps/wr/app/store/matDmd/coSttn/index.tsx`
|
||||
- `apps/wr/app/(with-layout)/matMng/coMng/coSttn/page.tsx`
|
||||
- `apps/wr/app/store/matDmd/coSttn/headList.tsx`
|
||||
|
||||
수정 방향:
|
||||
|
||||
- 목록/상세 응답 타입에 `dmdCoSn` 추가
|
||||
- 수정 시 내부 식별은 `dmdCoSn`, 화면 표시는 `dmdInfoId`
|
||||
- 사업자번호 중복 체크 로직은 신규 등록에서만 `dmdInfoId` 기준 사용
|
||||
- 수정 모드에서는 원본 `dmdCoSn` 기준으로 update 호출
|
||||
|
||||
### 7.2 지도/팝업
|
||||
|
||||
대상:
|
||||
|
||||
- `apps/wr/app/store/map/coSttnMap/index.tsx`
|
||||
- `apps/wr/components/lib/func/mapUtil.ts`
|
||||
- `apps/wr/app/(popUp-layout)/splyPopup/page.tsx`
|
||||
|
||||
수정 방향:
|
||||
|
||||
- 지도 feature id가 현재 `dmdInfoId` 기반이면 유지 여부 검토 필요
|
||||
- 내부 상세조회 키는 `dmdCoSn`를 우선 사용
|
||||
- 화면/툴팁의 사업자번호 표시는 응답의 `dmdInfoId`
|
||||
|
||||
주의:
|
||||
|
||||
- 지도 레이어 원본 데이터 키가 당장 `dmdInfoId`라면, 1차는 지도 highlight 키는 유지하고 상세 API만 `dmdCoSn` 병행 처리하는 방식이 안전하다.
|
||||
|
||||
### 7.3 공사비/매칭 화면
|
||||
|
||||
대상:
|
||||
|
||||
- `apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx`
|
||||
- `apps/wr/app/store/matDmd/coMatRvw/index.tsx`
|
||||
|
||||
수정 방향:
|
||||
|
||||
- 공사비 저장 시 `dmdInfoId` 대신 `dmdCoSn`를 함께 전달하도록 타입 보강
|
||||
- popup open, 조회, 저장 시 내부키와 표시값을 분리
|
||||
|
||||
### 7.4 공공하수처리시설 등록 화면 내 수요처 선택
|
||||
|
||||
대상:
|
||||
|
||||
- `apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx`
|
||||
|
||||
수정 방향:
|
||||
|
||||
- 검색 결과 항목은 `dmdInfoId`를 표시하되 선택값에는 `dmdCoSn` 포함
|
||||
- 재저장 시 연결 테이블에는 `dmd_co_sn` 기준으로 저장
|
||||
|
||||
## 8. DM 프런트 작업계획
|
||||
|
||||
### 8.1 로그인/세션
|
||||
|
||||
대상:
|
||||
|
||||
- `apps/dm/components/LoginModal.tsx`
|
||||
- `apps/dm/components/ui/sdmLoginForm/component.tsx`
|
||||
- `apps/dm/app/store/sdmMng/index.tsx`
|
||||
|
||||
수정 방향:
|
||||
|
||||
- 로그인 입력은 기존처럼 사업자번호(`dmdInfoId`) 사용
|
||||
- 로그인 성공 응답에 `dmdCoSn`를 포함하여 세션 저장
|
||||
- 이후 조회/수정 API는 가능한 범위에서 `dmdCoSn`를 함께 사용
|
||||
|
||||
### 8.2 수요처 등록/수정 공통 폼
|
||||
|
||||
대상:
|
||||
|
||||
- `frontend/packages/common/business_components/src/ui/sdmInsertForm/*`
|
||||
|
||||
수정 방향:
|
||||
|
||||
- updateList / return type에 `dmdCoSn` 추가
|
||||
- 수정 화면에서는 사업자번호 필드를 수정 가능하게 바꾸되, 내부 원본키는 `dmdCoSn` 유지
|
||||
- 신규 등록과 수정의 검증 로직을 분리
|
||||
|
||||
### 8.3 신청현황/상세/지도팝업
|
||||
|
||||
대상:
|
||||
|
||||
- `apps/dm/app/(with-layout)/apply/status/page.tsx`
|
||||
- `apps/dm/app/(with-layout)/apply/dmdinfo/page.tsx`
|
||||
- `apps/dm/app/(with-layout)/apply/dmdco/page.tsx`
|
||||
- `apps/dm/app/(popup-layout)/map/popup/page.tsx`
|
||||
|
||||
수정 방향:
|
||||
|
||||
- 세션의 `dmdInfoId`만 참조하는 흐름을 `dmdCoSn + dmdInfoId` 병행 구조로 수정
|
||||
- 사업자번호 표시 포맷은 그대로 유지
|
||||
- 단건 조회키는 backend 변경 폭에 맞춰 `dmdCoSn` 지원 여부를 우선 검토
|
||||
|
||||
## 9. 구현 순서
|
||||
|
||||
1. reuse-service DTO / mapper / service 수정
|
||||
|
||||
2. Swagger 기준 API 응답에 `dmdCoSn` 포함 확인
|
||||
|
||||
3. WR 프런트 수정
|
||||
- 수요처 관리 : 수요처 등록/수정
|
||||
- 지도/팝업
|
||||
- 공사비/매칭
|
||||
- 하수재이용시설 등록 우측 차수별정보 하단 수요처 추가
|
||||
|
||||
4. DM 프런트 수정
|
||||
- 로그인/세션
|
||||
- 등록/수정
|
||||
- 신청현황/상세
|
||||
|
||||
5. 수동 검증
|
||||
|
||||
## 10. 검증 항목
|
||||
|
||||
### 10.1 데이터 검증
|
||||
|
||||
- 기존 수요처 데이터에 `dmd_co_sn`가 모두 채워졌는지
|
||||
- `tb_wr_dmd_info_reg`, `tb_wr_sply_cndt_spt`의 `dmd_co_sn`가 누락 없이 매핑됐는지
|
||||
- 사업자번호 변경 후 연관 테이블 update 없이 조회가 정상인지
|
||||
|
||||
### 10.2 WR 검증
|
||||
|
||||
- 수요처 현황 목록 조회 정상
|
||||
- 수요처 수정 후 목록/상세/지도 팝업에 변경 사업자번호 반영
|
||||
- 공사비/매칭 저장 정상
|
||||
- 공공하수처리시설 상세의 연결 수요처 표시 정상
|
||||
|
||||
### 10.3 DM 검증
|
||||
|
||||
- 기존 사업자번호로 로그인 정상
|
||||
- 로그인 후 세션 유지 정상
|
||||
- 수요정보 등록/수정 정상
|
||||
- 신청현황/지도팝업에서 사업자번호 표시 정상
|
||||
|
||||
## 11. 리스크 및 확인 필요사항
|
||||
|
||||
### 11.1 API path의 식별자 의미
|
||||
|
||||
현재 `/reuseFc/request/{demandCd}`는 사실상 사업자번호 기반이다.
|
||||
|
||||
이번 작업에서 바로 `dmdCoSn` 기반으로 path 계약을 바꾸면 WR/DM 프런트 영향이 커진다.
|
||||
|
||||
권장 1차안:
|
||||
|
||||
- 외부 API 계약은 최대한 유지
|
||||
- 응답 body에 `dmdCoSn` 추가
|
||||
- 내부 저장/join만 `dmdCoSn`로 전환
|
||||
|
||||
### 11.2 지도 feature key
|
||||
|
||||
지도 highlight나 feature id가 `dmdInfoId`에 묶여 있을 가능성이 있다.
|
||||
|
||||
따라서 지도 레이어 원천 데이터까지 같이 바꾸는지, 아니면 상세조회 키만 분리할지 먼저 판단이 필요하다.
|
||||
|
||||
### 11.3 공통 컴포넌트 영향
|
||||
|
||||
`frontend/packages/common/business_components/src/ui/sdmInsertForm/*`는 WR/DM 양쪽이 함께 쓰므로, 이 부분 변경 시 두 앱을 같이 검증해야 한다.
|
||||
|
||||
## 12. 결론
|
||||
|
||||
이번 작업은 단순 컬럼 1개 추가가 아니라, 수요처 식별체계를 사업자번호 기반 자연키에서 일련번호 기반 대체키로 바꾸는 작업이다.
|
||||
|
||||
핵심 원칙은 아래 2줄이다.
|
||||
|
||||
1. 연관관계는 `dmd_co_sn`로 묶는다.
|
||||
2. 사업자등록번호는 `tb_wr_ruse_fclt_dmd_co`에서만 관리하고, 화면 표시도 항상 그 값을 join해서 사용한다.
|
||||
Reference in New Issue
Block a user