fix: show allocation formula only for shared allocation group
This commit is contained in:
@@ -1921,6 +1921,7 @@
|
||||
const [companyAccountDetailModalLoading, setCompanyAccountDetailModalLoading] = useState(false);
|
||||
const [lifecycleBreakdownModal, setLifecycleBreakdownModal] = useState(null);
|
||||
const [lifecycleProjectTotalModal, setLifecycleProjectTotalModal] = useState(null);
|
||||
const [lifecycleProjectTotalGroup, setLifecycleProjectTotalGroup] = useState("공통배분분");
|
||||
const [lifecycleAccountDetailModal, setLifecycleAccountDetailModal] = useState(null);
|
||||
const [lifecycleAccountDetailModalLoading, setLifecycleAccountDetailModalLoading] = useState(false);
|
||||
const [lifecycleAllocationModal, setLifecycleAllocationModal] = useState(null);
|
||||
@@ -5397,16 +5398,48 @@
|
||||
className="button-muted"
|
||||
style={{ height: 30, padding: "0 10px", borderRadius: 9, fontSize: 12 }}
|
||||
onClick={() => {
|
||||
const allocationAccount = (lifecycleBreakdownModal.accounts || []).find(
|
||||
(acc) => Array.isArray(acc.allocation_details) && acc.allocation_details.length
|
||||
) || (lifecycleBreakdownModal.accounts || [])[0] || {};
|
||||
const accounts = Array.isArray(lifecycleBreakdownModal.accounts) ? lifecycleBreakdownModal.accounts : [];
|
||||
const allocationMode =
|
||||
(accounts.find((acc) => (acc.allocation_mode || "").trim()) || {}).allocation_mode || "";
|
||||
const aggregatedByYearMonth = {};
|
||||
accounts.forEach((acc) => {
|
||||
const rows = Array.isArray(acc.allocation_details) ? acc.allocation_details : [];
|
||||
rows.forEach((row) => {
|
||||
const key = String(row.year_month || "");
|
||||
if (!aggregatedByYearMonth[key]) {
|
||||
aggregatedByYearMonth[key] = {
|
||||
year_month: key,
|
||||
source_amount: 0,
|
||||
project_basis_amount: Number(row.project_basis_amount || 0),
|
||||
total_basis_amount: Number(row.total_basis_amount || 0),
|
||||
display_project_basis_amount: Number(row.display_project_basis_amount ?? row.project_basis_amount ?? 0),
|
||||
display_total_basis_amount: Number(row.display_total_basis_amount ?? row.total_basis_amount ?? 0),
|
||||
allocated_amount: 0,
|
||||
};
|
||||
}
|
||||
aggregatedByYearMonth[key].source_amount += Number(row.source_amount || 0);
|
||||
aggregatedByYearMonth[key].allocated_amount += Number(row.allocated_amount || 0);
|
||||
});
|
||||
});
|
||||
const aggregatedAllocationDetails = Object.values(aggregatedByYearMonth).sort((a, b) =>
|
||||
String(a.year_month || "").localeCompare(String(b.year_month || ""))
|
||||
);
|
||||
const sharedProjects = (Array.isArray(lifecycleBreakdownModal.projects) ? lifecycleBreakdownModal.projects : [])
|
||||
.map((project) => ({ ...project }))
|
||||
.filter((project) => Number(project.shared_expense_supply || 0) > 0);
|
||||
const directTotal = sharedProjects.reduce((sum, project) => sum + Number(project.direct_expense_supply || 0), 0);
|
||||
const sharedTotal = sharedProjects.reduce((sum, project) => sum + Number(project.shared_expense_supply || 0), 0);
|
||||
setLifecycleProjectTotalModal({
|
||||
label: lifecycleBreakdownModal.label || "",
|
||||
total_amount: Number(lifecycleBreakdownModal.expense_supply || 0),
|
||||
projects: Array.isArray(lifecycleBreakdownModal.projects) ? lifecycleBreakdownModal.projects.map((project) => ({ ...project })) : [],
|
||||
allocation_mode: allocationAccount.allocation_mode || "",
|
||||
allocation_details: Array.isArray(allocationAccount.allocation_details) ? allocationAccount.allocation_details.map((row) => ({ ...row })) : [],
|
||||
total_amount: directTotal + sharedTotal,
|
||||
direct_total: directTotal,
|
||||
shared_total: sharedTotal,
|
||||
projects: sharedProjects,
|
||||
accounts: Array.isArray(lifecycleBreakdownModal.accounts) ? lifecycleBreakdownModal.accounts.map((account) => ({ ...account })) : [],
|
||||
allocation_mode: allocationMode,
|
||||
allocation_details: aggregatedAllocationDetails,
|
||||
});
|
||||
setLifecycleProjectTotalGroup("공통배분분");
|
||||
}}
|
||||
>
|
||||
상세보기
|
||||
@@ -5415,31 +5448,66 @@
|
||||
<div className="subtle" style={{ marginTop: 6 }}>연결된 프로젝트들 중 이 항목에 반영된 지출 금액입니다.</div>
|
||||
<div style={{ display: "grid", gap: 10, marginTop: 12 }}>
|
||||
{(lifecycleBreakdownModal.projects || []).length ? (
|
||||
(lifecycleBreakdownModal.projects || []).map((item) => (
|
||||
<div
|
||||
key={`lifecycle-inline-project-${item.project_code}`}
|
||||
(() => {
|
||||
const projects = Array.isArray(lifecycleBreakdownModal.projects) ? lifecycleBreakdownModal.projects : [];
|
||||
const roleAmount = {
|
||||
영업: 0,
|
||||
설계: 0,
|
||||
시공: 0,
|
||||
};
|
||||
const roleCodes = {
|
||||
영업: new Set(),
|
||||
설계: new Set(),
|
||||
시공: new Set(),
|
||||
};
|
||||
let sharedAmount = 0;
|
||||
projects.forEach((project) => {
|
||||
const role = (project.project_type || "").trim();
|
||||
const direct = Number(project.direct_expense_supply || 0);
|
||||
const shared = Number(project.shared_expense_supply || 0);
|
||||
const projectCode = (project.project_code || "").trim();
|
||||
if (roleAmount[role] !== undefined) {
|
||||
roleAmount[role] += direct;
|
||||
if (projectCode) roleCodes[role].add(projectCode);
|
||||
}
|
||||
sharedAmount += shared;
|
||||
});
|
||||
const groupedRows = [
|
||||
{ key: "영업", label: "영업", amount: roleAmount.영업, showWhenZero: false, codes: Array.from(roleCodes.영업) },
|
||||
{ key: "설계", label: "설계", amount: roleAmount.설계, showWhenZero: false, codes: Array.from(roleCodes.설계) },
|
||||
{ key: "시공", label: "시공", amount: roleAmount.시공, showWhenZero: true, codes: Array.from(roleCodes.시공) },
|
||||
{ key: "공통배분분", label: "공통배분분", amount: sharedAmount, showWhenZero: true, codes: [] },
|
||||
].filter((row) => row.showWhenZero || row.amount > 0);
|
||||
return groupedRows.map((item) => (
|
||||
<button
|
||||
key={`lifecycle-inline-project-group-${item.key}`}
|
||||
type="button"
|
||||
onClick={() => setLifecycleProjectTotalGroup(item.key)}
|
||||
style={{
|
||||
textAlign: "left",
|
||||
border: "1px solid var(--line)",
|
||||
borderRadius: 14,
|
||||
background: "white",
|
||||
background: lifecycleProjectTotalGroup === item.key ? "#f3f8ff" : "white",
|
||||
padding: "12px 14px",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "minmax(220px, 1fr) minmax(140px, 0.4fr)",
|
||||
gap: 12,
|
||||
alignItems: "center",
|
||||
cursor: "default",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div style={{ fontWeight: 700 }}>{item.project_name || "(이름없음)"}</div>
|
||||
<div style={{ fontWeight: 700 }}>{item.label}</div>
|
||||
<div className="subtle" style={{ marginTop: 4 }}>
|
||||
{item.project_code} · {item.project_type || "미지정"} · {item.construction_family || "종류미지정"} · {item.construction_method || "공법미지정"}
|
||||
{(item.codes || []).length ? item.codes.join(", ") : "-"}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ textAlign: "right", fontWeight: 700 }}>{fmt(item.expense_supply || 0)}원</div>
|
||||
<div style={{ textAlign: "right", fontWeight: 700 }}>
|
||||
{fmt(item.amount || 0)}원
|
||||
</div>
|
||||
))
|
||||
</button>
|
||||
));
|
||||
})()
|
||||
) : (
|
||||
<div className="subtle">표시할 프로젝트가 없습니다.</div>
|
||||
)}
|
||||
@@ -5448,10 +5516,28 @@
|
||||
|
||||
<section className="panel" style={{ padding: 16 }}>
|
||||
<div style={{ fontSize: 16, fontWeight: 700 }}>계정별 금액</div>
|
||||
<div className="subtle" style={{ marginTop: 6 }}>이 항목에 포함된 계정 기준 합계입니다.</div>
|
||||
<div className="subtle" style={{ marginTop: 6 }}>
|
||||
{lifecycleProjectTotalGroup === "설계" || lifecycleProjectTotalGroup === "시공"
|
||||
? `${lifecycleProjectTotalGroup} 직접분 계정 기준 합계입니다.`
|
||||
: lifecycleProjectTotalGroup === "공통배분분"
|
||||
? "공통배분분 계정 기준 합계입니다."
|
||||
: "이 항목에 포함된 계정 기준 합계입니다."}
|
||||
</div>
|
||||
<div style={{ display: "grid", gap: 10, marginTop: 12 }}>
|
||||
{(lifecycleBreakdownModal.accounts || []).length ? (
|
||||
(lifecycleBreakdownModal.accounts || []).map((item) => (
|
||||
(() => {
|
||||
const roleField =
|
||||
lifecycleProjectTotalGroup === "설계" ? "direct_design_expense_supply"
|
||||
: lifecycleProjectTotalGroup === "시공" ? "direct_construction_expense_supply"
|
||||
: lifecycleProjectTotalGroup === "영업" ? "direct_sales_expense_supply"
|
||||
: "shared_expense_supply";
|
||||
const filtered = (lifecycleBreakdownModal.accounts || [])
|
||||
.map((item) => ({ ...item, __role_amount: Number(item?.[roleField] || 0) }))
|
||||
.filter((item) => item.__role_amount > 0);
|
||||
if (!filtered.length) {
|
||||
return <div className="subtle">표시할 계정이 없습니다.</div>;
|
||||
}
|
||||
return filtered.map((item) => (
|
||||
<button
|
||||
key={`lifecycle-inline-account-${item.account_code}`}
|
||||
type="button"
|
||||
@@ -5467,8 +5553,10 @@
|
||||
allocation_project_basis_amount: Number(item.allocation_project_basis_amount || 0),
|
||||
allocation_total_basis_amount: Number(item.allocation_total_basis_amount || 0),
|
||||
allocation_mode: item.allocation_mode || "",
|
||||
allocation_result_amount: Number(item.expense_supply || 0),
|
||||
allocation_result_amount: Number(item.__role_amount || 0),
|
||||
allocation_details: Array.isArray(item.allocation_details) ? item.allocation_details.map((row) => ({ ...row })) : [],
|
||||
allocation_group: lifecycleProjectTotalGroup,
|
||||
show_allocation_formula: lifecycleProjectTotalGroup === "공통배분분",
|
||||
detail: null,
|
||||
});
|
||||
}}
|
||||
@@ -5521,9 +5609,10 @@
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ textAlign: "right", fontWeight: 700 }}>{fmt(item.expense_supply || 0)}원</div>
|
||||
<div style={{ textAlign: "right", fontWeight: 700 }}>{fmt(item.__role_amount || 0)}원</div>
|
||||
</button>
|
||||
))
|
||||
));
|
||||
})()
|
||||
) : (
|
||||
<div className="subtle">표시할 계정이 없습니다.</div>
|
||||
)}
|
||||
@@ -6843,20 +6932,22 @@
|
||||
<div className="empty-state">계정 상세를 불러오는 중입니다.</div>
|
||||
) : lifecycleAccountDetailModal.detail?.summary ? (
|
||||
<>
|
||||
{(() => {
|
||||
const summaryAmount = Number(lifecycleAccountDetailModal.detail?.summary?.expense_supply_sum || 0);
|
||||
const selectedAmount = Number(lifecycleAccountDetailModal.allocation_result_amount || 0);
|
||||
const effectiveAccountAmount = selectedAmount > 0 ? selectedAmount : summaryAmount;
|
||||
const effectiveTxnCount = Number(lifecycleAccountDetailModal.detail?.summary?.txn_count || 0);
|
||||
return (
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(3, minmax(0, 1fr))", gap: 12, marginTop: 16 }}>
|
||||
<div className="mini-card">
|
||||
<div className="subtle">지출 합계</div>
|
||||
<div className="summary-value" style={{ marginTop: 6 }}>
|
||||
{fmt(
|
||||
Number(lifecycleAccountDetailModal.allocation_result_amount || 0) > 0
|
||||
? Number(lifecycleAccountDetailModal.allocation_result_amount || 0)
|
||||
: Number(lifecycleAccountDetailModal.detail.summary.expense_supply_sum || 0)
|
||||
)}원
|
||||
{fmt(effectiveAccountAmount)}원
|
||||
</div>
|
||||
</div>
|
||||
<div className="mini-card">
|
||||
<div className="subtle">거래 건수</div>
|
||||
<div className="summary-value" style={{ marginTop: 6 }}>{fmt(lifecycleAccountDetailModal.detail.summary.txn_count || 0)}건</div>
|
||||
<div className="summary-value" style={{ marginTop: 6 }}>{fmt(effectiveTxnCount)}건</div>
|
||||
</div>
|
||||
<div className="mini-card">
|
||||
<div className="subtle">기간</div>
|
||||
@@ -6867,8 +6958,10 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
<div style={{ display: "grid", gap: 18, marginTop: 18 }}>
|
||||
{((lifecycleAccountDetailModal.bucket_label || "") === "인건비" || (lifecycleAccountDetailModal.bucket_label || "") === "관리비") && (
|
||||
{Boolean(lifecycleAccountDetailModal.show_allocation_formula) && (
|
||||
<section className="mini-card">
|
||||
<div style={{ fontSize: 16, fontWeight: 700 }}>배부 계산식</div>
|
||||
<div className="subtle" style={{ marginTop: 6 }}>
|
||||
@@ -7011,9 +7104,11 @@
|
||||
</div>
|
||||
<div style={{ textAlign: "right", fontWeight: 700 }}>
|
||||
{fmt(
|
||||
(lifecycleAccountDetailModal.account_code || "").startsWith("SHARED_")
|
||||
? Number(lifecycleAccountDetailModal.allocation_result_amount || 0)
|
||||
: Number(lifecycleAccountDetailModal.detail?.summary?.expense_supply_sum || 0)
|
||||
(() => {
|
||||
const summaryAmount = Number(lifecycleAccountDetailModal.detail?.summary?.expense_supply_sum || 0);
|
||||
const selectedAmount = Number(lifecycleAccountDetailModal.allocation_result_amount || 0);
|
||||
return selectedAmount > 0 ? selectedAmount : summaryAmount;
|
||||
})()
|
||||
)}원
|
||||
</div>
|
||||
</div>
|
||||
@@ -7038,13 +7133,16 @@
|
||||
<div style={{ display: "flex", justifyContent: "space-between", gap: 16, alignItems: "flex-start" }}>
|
||||
<div>
|
||||
<div style={{ fontSize: 20, fontWeight: 700 }}>{lifecycleProjectTotalModal.label || "전체"} 프로젝트별 상세</div>
|
||||
<div className="subtle" style={{ marginTop: 6 }}>연결 프로젝트 기준 전체 금액 내역입니다.</div>
|
||||
<div className="subtle" style={{ marginTop: 6 }}>연결 프로젝트 기준 공통배분분 내역입니다.</div>
|
||||
</div>
|
||||
<button className="button-muted" onClick={() => setLifecycleProjectTotalModal(null)}>닫기</button>
|
||||
<button className="button-muted" onClick={() => { setLifecycleProjectTotalModal(null); setLifecycleProjectTotalGroup("공통배분분"); }}>닫기</button>
|
||||
</div>
|
||||
<div className="mini-card" style={{ marginTop: 14 }}>
|
||||
<div className="subtle">합계 금액</div>
|
||||
<div className="summary-value" style={{ marginTop: 6 }}>{fmt(lifecycleProjectTotalModal.total_amount || 0)}원</div>
|
||||
<div className="subtle" style={{ marginTop: 6 }}>
|
||||
직접분 {fmt(lifecycleProjectTotalModal.direct_total || 0)}원 · 공통배분분 {fmt(lifecycleProjectTotalModal.shared_total || 0)}원
|
||||
</div>
|
||||
</div>
|
||||
<section className="mini-card" style={{ marginTop: 12 }}>
|
||||
<div style={{ fontSize: 16, fontWeight: 700 }}>배부 계산식</div>
|
||||
@@ -7095,11 +7193,16 @@
|
||||
{item.project_code || "-"} · {item.project_type || "미지정"} · {item.construction_family || "종류미지정"} · {item.construction_method || "공법미지정"}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ textAlign: "right", fontWeight: 700 }}>{fmt(item.expense_supply || 0)}원</div>
|
||||
<div style={{ textAlign: "right", fontWeight: 700 }}>
|
||||
<div>{fmt(item.expense_supply || 0)}원</div>
|
||||
<div className="subtle" style={{ marginTop: 4 }}>
|
||||
직접분 {fmt(item.direct_expense_supply || 0)}원 · 공통배분분 {fmt(item.shared_expense_supply || 0)}원
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="empty-state">표시할 프로젝트가 없습니다.</div>
|
||||
<div className="empty-state">표시할 공통배분 프로젝트가 없습니다.</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user