수요처매칭 계산식 진행중

This commit is contained in:
leedj312
2025-06-22 16:41:42 +09:00
parent 58fcd99941
commit 347fd44ab1
7 changed files with 271 additions and 87 deletions
@@ -94,7 +94,6 @@ export default function RuseRvwHstry() {
} else {
console.log("시도코드", sggCd);
ruseRvwInqua(pageNation, 13, String(startYear), String(endYear), sggCd);
}
};
@@ -193,7 +192,6 @@ export default function RuseRvwHstry() {
}
}, [rvwHistRes]);
useEffect(() => {}, [form.formState]);
useEffect(() => {
console.log("변경여부 ", form.formState.isDirty);
@@ -187,13 +187,13 @@ export const rateFeeHeadListExport: CommonTableProps[] = [
];
export const usageProcData = [
{ usage: "청소", process: "총인처리시설 + 염소소독" },
{ usage: "화장실용수", process: "총인처리시설 + 염소소독" },
{ usage: "세척", process: "총인처리시설 + UV소독" },
{ usage: "살수", process: "총인처리시설 + UV소독" },
{ usage: "조경", process: "총인처리시설 + UV소독" },
{ usage: "농업", process: "총인처리시설 + UV소독" },
{ usage: "하천", process: "총인처리시설 + UV소독" },
{ usage: "지하수", process: "전처리시설 + RO" },
{ usage: "공업", process: "전처리시설 + RO" },
{ usage: "청소", process: "총인처리시설+염소소독" },
{ usage: "화장실용수", process: "총인처리시설+염소소독" },
{ usage: "세척", process: "총인처리시설+UV소독" },
{ usage: "살수", process: "총인처리시설+UV소독" },
{ usage: "조경", process: "총인처리시설+UV소독" },
{ usage: "농업", process: "총인처리시설+UV소독" },
{ usage: "하천", process: "총인처리시설+UV소독" },
{ usage: "지하수", process: "전처리시설+RO" },
{ usage: "공업", process: "전처리시설+RO" },
];
@@ -93,6 +93,8 @@ import { z } from "zod";
demandMatching: DemandMatchingSchema.nullish(),
demandCst: DemandCstSchema,
ruseUtztnFcltNm:z.string().nullish(),
totalConstCost:z.number().nullish(),
});
@@ -0,0 +1,80 @@
export const findBounds = (
data: any[],
fctCd: string,
qty: number
): { lower?: any; upper?: any } => {
const filtered = data.filter((item) => item.fctCd === fctCd);
const lowers = filtered.filter((item) => Number(item.fctCpc) <= qty);
const uppers = filtered.filter((item) => Number(item.fctCpc) > qty);
const lower =
lowers.length > 0
? lowers.reduce((acc, curr) =>
Number(curr.fctCpc) > Number(acc.fctCpc) ? curr : acc
)
: undefined;
const upper =
uppers.length > 0
? uppers.reduce((acc, curr) =>
Number(curr.fctCpc) < Number(acc.fctCpc) ? curr : acc
)
: undefined;
return { lower, upper };
};
// (기울기) a = (y2 - y1) / (x2 - x1)
// (절편) b = y1 - a * x1
// ↓
// 결과값 y = a * x + b
export const getInterpolation = (
Upper: { stdCost: number; fctCpc: number },
Lower: { stdCost: number; fctCpc: number },
qtyYear: number
): number => {
const slope =
(Number(Upper?.stdCost) - Number(Lower?.stdCost)) /
(Number(Upper?.fctCpc) - Number(Lower?.fctCpc));
const intercept = Number(Lower?.stdCost) - slope * Number(Lower?.fctCpc);
return slope * qtyYear + intercept;
};
export const transformAndInterpolate = (
rawData: any,
dmndQtyYear: number
): any => {
const result: any = {};
for (const key in rawData) {
const item = rawData[key];
const range = {
Lower: {
fctCpc: item.lower?.stdCost,
stdCost: item.lower?.rt,
},
Upper: {
fctCpc: item.upper?.stdCost,
stdCost: item.upper?.rt,
},
};
console.log("range ", range);
result[key] = getInterpolation(range.Upper, range.Lower, dmndQtyYear);
}
return result;
};
export const getConstFctCode = (fctCd: string) => {
const fctMap: { [key: string]: string } = {
총인처리시설: "001",
염소소독: "002",
UV소독: "003",
전처리시설: "004",
RO: "005",
};
return fctMap[fctCd];
};
+3
View File
@@ -153,3 +153,6 @@ export const getdmdMatFlag = (flag: string|undefined) => {
}
return flagMat;
};
@@ -6,6 +6,12 @@ import type {
} from "@/app/store/matDmd/coMatRvw/type";
import { TEST_UUID } from "@/app/store/ruseFcltMng/fcltReg/rainReg";
import { formH1Class } from "@/components/lib/class/comClass";
import {
findBounds,
getConstFctCode,
getInterpolation,
transformAndInterpolate,
} from "@/components/lib/func/calculateConstCost ";
import { ConstCostFeeTable } from "@/components/ui/modal/coMat/constCostFeeTable";
import { RateFeeTableModal } from "@/components/ui/modal/coMat/rateFeeModal";
import {
@@ -17,7 +23,8 @@ import {
} from "@common/business_components/ui";
import PopoverUi from "@common/business_components/ui/popover_ui";
import { Button, Input, Label } from "@common/components/ui";
import { useEffect, useRef, useState } from "react";
import type { read } from "fs";
import { useCallback, useEffect, useRef, useState } from "react";
import { type UseFormReturn } from "react-hook-form";
export default function ConCostForm({
@@ -27,7 +34,14 @@ export default function ConCostForm({
form: UseFormReturn<DmdCoReqType>;
type: string;
}) {
const { coMatRvwUpdate, ruseRvwCalUpdate,getRateFee ,getConstCostFee} = useWrStore((state) => state);
const {
constCostRes,
rateFeeRes,
coMatRvwUpdate,
ruseRvwCalUpdate,
getRateFee,
getConstCostFee,
} = useWrStore((state) => state);
const ref = useRef<boolean>(false);
@@ -38,9 +52,8 @@ export default function ConCostForm({
getRateFee(String(currentYear));
ref.current = true;
}
}, [ref, getConstCostFee ,getRateFee ]);
}, [ref, getConstCostFee, getRateFee]);
const [isTrfMo, setIsTrfMo] = useState<{
isModal: boolean;
}>({ isModal: false });
@@ -59,6 +72,82 @@ export default function ConCostForm({
link.click();
});
};
const usgPrcsProcsWatch = form.watch("demandCst.usgPrcsProcs");
const dmndQtyWatch = form.watch("dmndQty");
const totalConstCost = form.watch("totalConstCost");
const cstrnCst = form.watch("demandCst.cstrnCst");
const calculateConstCost = useCallback(() => {
const usgPrcsProcs = usgPrcsProcsWatch;
const dmndQtyYear = dmndQtyWatch * 365;
if (usgPrcsProcs && usgPrcsProcs?.length > 0) {
const firstProc =
getConstFctCode(usgPrcsProcs?.split("+")[0] || "") || "";
const secondProc =
getConstFctCode(usgPrcsProcs?.split("+")[1] || "") || "";
const { lower: firstLower, upper: firstUpper } = findBounds(
constCostRes,
firstProc || "",
Number(dmndQtyYear)
);
const { lower: secondLower, upper: secondUpper } = findBounds(
constCostRes,
secondProc || "",
Number(dmndQtyYear)
);
const firstConstFee = getInterpolation(
firstUpper,
firstLower,
dmndQtyYear
);
const secondConstFee = getInterpolation(
secondUpper,
secondLower,
dmndQtyYear
);
const result = Math.round(firstConstFee + secondConstFee) / 100;
const groupCodes = ["BD", "CE", "SE", "WD"];
const groupedBounds: { [key: string]: { lower?: any; upper?: any } } = {};
for (const code of groupCodes) {
const groupItems = rateFeeRes.filter((item) => item.incExpCd === code);
const lowers = groupItems.filter(
(item) => Number(item.stdCost) <= result
);
const uppers = groupItems.filter(
(item) => Number(item.stdCost) > result
);
const lower =
lowers.length > 0
? lowers.reduce((acc, curr) =>
Number(curr.stdCost) > Number(acc.stdCost) ? curr : acc
)
: undefined;
const upper =
uppers.length > 0
? uppers.reduce((acc, curr) =>
Number(curr.stdCost) < Number(acc.stdCost) ? curr : acc
)
: undefined;
groupedBounds[code] = { lower, upper };
}
const resultRate = transformAndInterpolate(groupedBounds, result);
const fcltSbsdEpsTrf =result * (resultRate.BD + resultRate.CE + resultRate.SE + resultRate.WD);
const realResult =
result +fcltSbsdEpsTrf;
form.setValue("demandCst.prcsFcltCpctCstrnCst", result);
form.setValue( "demandCst.fcltSbsdEpsTrf",fcltSbsdEpsTrf);
form.setValue("totalConstCost", realResult);
}
}, [constCostRes, rateFeeRes, usgPrcsProcsWatch, dmndQtyWatch]);
const onSubmit = (value: DmdCoReqType) => {
if (type === "mat") {
@@ -77,7 +166,7 @@ export default function ConCostForm({
};
useEffect(() => {
console.log(" form ", form.formState.errors);
// console.log(" form ", form.formState.errors);
}, [form.formState]);
return (
@@ -90,7 +179,7 @@ export default function ConCostForm({
<div className="flex items-center mx-3 mb-3">
<h1 className="">처리시설 표준공사비 산정</h1>
<Button size={"sm"} onClick={(e) => {}}>
<Button size={"sm"} onClick={calculateConstCost}>
계산
</Button>
<Button size={"sm"} onClick={form.handleSubmit(onSubmit)}>
@@ -111,7 +200,7 @@ export default function ConCostForm({
placeholder=""
type="text"
onChange={(e) => {
field.onChange((e.target.value));
field.onChange(e.target.value);
}}
value={field.value ?? ""}
/>
@@ -194,9 +283,10 @@ export default function ConCostForm({
type="number"
step="0.001"
onChange={(e) => {
field.onChange(Number(e.target.value));
//field.onChange(Number(e.target.value) * 365 );
}}
value={Number(field.value) * 365 || 0}
readOnly
/>
</FormUi>
)}
@@ -286,16 +376,22 @@ export default function ConCostForm({
}
bodyClassName={"w-[22vw]"}
></PopoverUi>
<Input
placeholder=""
className="mt-2"
type="number"
step="0.01"
onChange={(e) => {}}
value={
form.getValues("demandCst.prcsFcltCpctCstrnCst") +
form.getValues("demandCst.fcltSbsdEpsTrf") || 0
}
<FormField
control={form.control}
name="totalConstCost"
render={({ field }) => (
<FormUi label="">
<Input
placeholder=""
className="mt-2"
type="number"
step="0.01"
onChange={(e) => {}}
value={field.value ?? 0}
readOnly
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3 mt-4">
@@ -323,75 +419,80 @@ export default function ConCostForm({
/>
</div>
<div className="col-span-4 mt-4">
<FormField
control={form.control}
name="demandCst.cstrnCst"
render={({ field }) => (
<FormUi label="공급관로 표준공사비(백만원)">
<FormField
control={form.control}
name="demandCst.cstrnCst"
render={({ field }) => (
<FormUi label="공급관로 표준공사비(백만원)">
<Input
placeholder=""
type="number"
step="0.01"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value ?? 0}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-4 mt-4 mb-3">
<FormField
control={form.control}
name=""
render={({ field }) => (
<Label className="text-xl">
처리시설 + 공급관로 총사업비(백만원)
<FormUi label="">
<Input
placeholder=""
type="text"
type="number"
step="0.001"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
value={field.value ?? 0}
readOnly={true}
value={(totalConstCost ?? 0) + (cstrnCst ?? 0)}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-4 mt-4 mb-3">
<FormField
control={form.control}
name=""
render={({ field }) => (
<Label className="text-xl">
처리시설 + 공급관로 총사업비(백만원)
<FormUi label="">
<Input
placeholder=""
type="number"
step="0.001"
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
readOnly={true}
value={field.value ?? 0}
/>
</FormUi>
</Label>
)}
/>
</div>
<div className=" col-span-4 mt-4 ">
<Label>관로 표준공사비 산정 프로그램</Label>
<Button
color="green"
size={"sm"}
onClick={zipDowonLoad}
className="bg-none ml-6"
>
다운로드
</Button>
</div>
</Label>
)}
/>
</div>
<div className=" col-span-4 mt-4 ">
<Label>관로 표준공사비 산정 프로그램</Label>
<Button
color="green"
size={"sm"}
onClick={zipDowonLoad}
className="bg-none ml-6"
>
다운로드
</Button>
</div>
</div>
<UiModal
body={<RateFeeTableModal setReturnData={(e) =>{}}></RateFeeTableModal>}
body={
<ConstCostFeeTable setReturnData={(e) => {}}></ConstCostFeeTable>
}
bodyClass="w-[40vw] max-w-none h-[40vh]"
open={isCmpttnMo.isModal}
onOpenChange={(open) => {
setIsCmpttnMo({ isModal: open });
}}
/>
<UiModal
body={
<RateFeeTableModal setReturnData={(e) => {}}></RateFeeTableModal>
}
bodyClass="w-[40vw] h-[40vh] "
open={isTrfMo.isModal}
onOpenChange={(open) => {
setIsTrfMo({ isModal: open });
}}
/>
<UiModal
body={<ConstCostFeeTable setReturnData={(e) =>{}}></ConstCostFeeTable>}
bodyClass="w-[40vw] max-w-none h-[40vh]"
open={isCmpttnMo.isModal}
onOpenChange={(open) => {
setIsCmpttnMo({ isModal: open });
}}
/>
</Form>
</>
);
@@ -23,12 +23,12 @@ export function ConstCostFeeTable({
const [constCostData, setConstCostData] = useState<ContCostMap[]>([]);
function getFctDescription(fctCd: string) {
const getFctDescription = (fctCd: string) => {
const fctMap: { [key: string]: string } = {
"001": "총인",
"002": "염소.소독",
"003": "uv소독",
"004": "생물학적탈질",
"001": "총인처리시설",
"002": "염소소독",
"003": "UV소독",
"004": "전처리시설",
"005": "RO",
};