Files
frontend/apps/as/src/widgets/pbadms-sprt/lib/fnrs-shar-form-utils.ts
T
2026-09-22 15:39:06 +09:00

254 lines
7.0 KiB
TypeScript

import type { FnrsSharItem } from "@/entities/fnrs-cnsltn";
import type { FnrsSharBizCstTreeDto, FnrsSharSeMngTreeDto } from "@/entities/fnrs-shar-se";
import type { BizCstListItem, CostItemDef } from "./business-cost-form-utils";
import { roundAmount } from "./format-utils";
export interface FnrsSharItemDef {
code: string;
label: string;
level: number;
readonly?: boolean;
syncFromBizCst?: boolean;
children?: string[];
}
export interface FnrsSharListItem {
fnrsSharSeCd: string;
exstnAmt: number;
chgRqtAmt: number;
ajmtAmt?: number | null;
rmrkCn?: string | null;
}
export type FnrsSharTreeNode = FnrsSharSeMngTreeDto | FnrsSharBizCstTreeDto;
function getParentCode(node: FnrsSharTreeNode): string | undefined {
if ("upFnrsSharSeCd" in node && node.upFnrsSharSeCd) {
return node.upFnrsSharSeCd;
}
const parts = node.treePath.split("|");
if (parts.length <= 1) return undefined;
return parts[parts.length - 2];
}
function sortTreeNodes<T extends { sortPath: string }>(nodes: T[]): T[] {
return [...nodes].sort((a, b) => a.sortPath.localeCompare(b.sortPath));
}
export function buildFnrsSharItems(tree: FnrsSharTreeNode[]): FnrsSharItemDef[] {
if (tree.length === 0) return [];
const activeNodes = sortTreeNodes(tree.filter((node) => node.useYn === "Y"));
const childrenMap = new Map<string, string[]>();
activeNodes.forEach((node) => {
const parentCode = getParentCode(node);
if (!parentCode) return;
const siblings = childrenMap.get(parentCode) ?? [];
siblings.push(node.fnrsSharSeCd);
childrenMap.set(parentCode, siblings);
});
const levelOneNodes = activeNodes.filter((node) => node.treeLevel === 1);
const syncFromBizCstCode = sortTreeNodes(levelOneNodes)[0]?.fnrsSharSeCd;
return activeNodes.map((node) => {
const children = childrenMap.get(node.fnrsSharSeCd);
return {
code: node.fnrsSharSeCd,
label: node.fnrsSharSeCdNm,
level: node.treeLevel,
readonly: Boolean(children && children.length > 0),
syncFromBizCst: node.fnrsSharSeCd === syncFromBizCstCode,
children,
};
});
}
function sumChildSharAmounts(list: FnrsSharListItem[], childCodes: string[]) {
let exstnAmt = 0;
let chgRqtAmt = 0;
childCodes.forEach((childCode) => {
const child = list.find((item) => item.fnrsSharSeCd === childCode);
if (!child) return;
exstnAmt += child.exstnAmt || 0;
chgRqtAmt += child.chgRqtAmt || 0;
});
return {
exstnAmt: roundAmount(exstnAmt),
chgRqtAmt: roundAmount(chgRqtAmt),
};
}
export function recalculateFnrsSharTotals(
list: FnrsSharListItem[],
items: FnrsSharItemDef[],
): FnrsSharListItem[] {
const newList = list.map((item) => ({ ...item }));
let hasChanges = false;
for (let i = items.length - 1; i >= 0; i--) {
const item = items[i];
if (!item.readonly || !item.children) continue;
const idx = newList.findIndex((row) => row.fnrsSharSeCd === item.code);
if (idx < 0) continue;
const { exstnAmt, chgRqtAmt } = sumChildSharAmounts(newList, item.children);
const current = newList[idx];
if ((current.exstnAmt || 0) !== exstnAmt || (current.chgRqtAmt || 0) !== chgRqtAmt) {
newList[idx] = {
...current,
exstnAmt,
chgRqtAmt,
ajmtAmt: roundAmount(exstnAmt - chgRqtAmt),
};
hasChanges = true;
}
}
return hasChanges ? newList : list;
}
function findBizCstSyncItem(costItems: CostItemDef[]): CostItemDef | undefined {
const levelOneReadonly = costItems.filter((item) => item.level === 1 && item.readonly);
return levelOneReadonly[0];
}
export interface BizCstTotalAmounts {
bgtRfltAmt: number;
tstmDesignAmt: number;
}
export interface BizCstSharRemaining {
exstnRemaining: number;
chgRqtRemaining: number;
}
export function getBizCstTotalRow(
bizCstList: BizCstListItem[],
costItems: CostItemDef[],
): BizCstTotalAmounts {
const syncCostItem = findBizCstSyncItem(costItems);
if (!syncCostItem) return { bgtRfltAmt: 0, tstmDesignAmt: 0 };
const costRow = bizCstList.find((item) => item.acntgSeCd === syncCostItem.code);
if (!costRow) return { bgtRfltAmt: 0, tstmDesignAmt: 0 };
return {
bgtRfltAmt: costRow.bgtRfltAmt || 0,
tstmDesignAmt: costRow.tstmDesignAmt || 0,
};
}
export function sumFnrsSharLeafAmounts(
sharList: FnrsSharListItem[],
sharItems: FnrsSharItemDef[],
): { exstnAmt: number; chgRqtAmt: number } {
const leafItems = sharItems.filter((item) => !item.readonly);
let exstnAmt = 0;
let chgRqtAmt = 0;
leafItems.forEach((item) => {
const row = sharList.find((entry) => entry.fnrsSharSeCd === item.code);
if (!row) return;
exstnAmt += row.exstnAmt || 0;
chgRqtAmt += row.chgRqtAmt || 0;
});
return {
exstnAmt: roundAmount(exstnAmt),
chgRqtAmt: roundAmount(chgRqtAmt),
};
}
export function calcBizCstSharRemaining(
bizCstList: BizCstListItem[],
sharList: FnrsSharListItem[],
sharItems: FnrsSharItemDef[],
costItems: CostItemDef[],
): BizCstSharRemaining {
const bizCstTotal = getBizCstTotalRow(bizCstList, costItems);
const leafSum = sumFnrsSharLeafAmounts(sharList, sharItems);
return {
exstnRemaining: roundAmount(bizCstTotal.bgtRfltAmt - leafSum.exstnAmt),
chgRqtRemaining: roundAmount(bizCstTotal.tstmDesignAmt - leafSum.chgRqtAmt),
};
}
export function validateBizCstSharBalance(
bizCstList: BizCstListItem[],
sharList: FnrsSharListItem[],
sharItems: FnrsSharItemDef[],
costItems: CostItemDef[],
): string | null {
const { exstnRemaining, chgRqtRemaining } = calcBizCstSharRemaining(
bizCstList,
sharList,
sharItems,
costItems,
);
if (exstnRemaining !== 0 || chgRqtRemaining !== 0) {
return "재원분담 총사업비를 확인해주세요.";
}
return null;
}
export function toFnrsSharSaveList(list: FnrsSharListItem[]): FnrsSharItem[] {
return list.map((row) => ({
fnrsSharSeCd: row.fnrsSharSeCd,
exstnAmt: row.exstnAmt || 0,
chgRqtAmt: row.chgRqtAmt || 0,
ajmtAmt: roundAmount((row.exstnAmt || 0) - (row.chgRqtAmt || 0)),
rmrkCn: row.rmrkCn,
}));
}
export function mergeFnrsSharTreeWithForm(
tree: FnrsSharTreeNode[],
existing: FnrsSharListItem[],
): FnrsSharListItem[] {
const items = buildFnrsSharItems(tree);
const existingMap = new Map(existing.map((row) => [row.fnrsSharSeCd, row]));
return items.map((item) => {
const saved = existingMap.get(item.code);
if (saved) return saved;
return {
fnrsSharSeCd: item.code,
exstnAmt: 0,
chgRqtAmt: 0,
ajmtAmt: 0,
rmrkCn: "",
};
});
}
export function mapFnrsSharDetToFormList(tree: FnrsSharBizCstTreeDto[]): FnrsSharListItem[] {
const items = buildFnrsSharItems(tree);
return items.map((item) => {
const node = tree.find((row) => row.fnrsSharSeCd === item.code);
const exstnAmt = Number(node?.exstnAmt) || 0;
const chgRqtAmt = Number(node?.chgRqtAmt) || 0;
return {
fnrsSharSeCd: item.code,
exstnAmt,
chgRqtAmt,
ajmtAmt: roundAmount(exstnAmt - chgRqtAmt),
rmrkCn: node?.rmrkCn ?? "",
};
});
}