fix(branch-fee): 본사(HQ) 제외 + 계산서 발행 명의(supplier_branch) 기준
Deploy momo-erp / deploy (push) Successful in 2m11s

사용자 요구 — 지사 수수료 페이지는 본사 거래처를 보여줄 필요 없음.
계산서가 김포 등 지사 명의로 발행된 발주만 표시.

- 그룹핑 기준: COALESCE(supplier_branch, user.statement_branch, 'HQ')
  · 발주 시점의 supplier_branch snapshot 우선 (= 계산서 발행 명의)
  · 옛 발주(snapshot 없음) 는 거래처 statement_branch 폴백
- WHERE branch != 'HQ' 로 본사 제외
- UI: 본사 분기 제거 (모든 행이 지사)
This commit is contained in:
chpark
2026-05-14 15:05:10 +09:00
parent 7b5951c227
commit bb21be260f
2 changed files with 53 additions and 49 deletions
+21 -28
View File
@@ -45,7 +45,7 @@ export default function BranchFeePage() {
</h1>
<p className="text-xs text-slate-500 mt-0.5">
/ + ( 20%) . <b> </b> .
<b> </b> . (HQ) . 20% .
</p>
</div>
<div className="flex items-center gap-2">
@@ -105,32 +105,25 @@ export default function BranchFeePage() {
<tbody className="tabular-nums">
{rows.length === 0 ? (
<tr><td colSpan={7} className="text-center py-12 text-slate-400">{loading ? "조회 중..." : "데이터가 없습니다."}</td></tr>
) : rows.map((r) => {
const isHQ = r.BRANCH === "HQ";
return (
<tr key={r.BRANCH} className="border-t border-slate-100">
<td className="px-4 py-3">
<div className="inline-flex items-center gap-1.5">
{isHQ
? <Building2 size={14} className="text-emerald-700" />
: <MapPin size={14} className="text-sky-700" />}
<span className="font-semibold">{r.BRANCH_NAME}</span>
<span className="text-xs text-slate-400">({r.BRANCH})</span>
</div>
</td>
<td className="px-4 py-3 text-right">{fmt(r.ORDER_CNT)}</td>
<td className="px-4 py-3 text-right">{fmt(r.REVENUE)}</td>
<td className="px-4 py-3 text-right text-slate-500">{fmt(r.COST)}</td>
<td className="px-4 py-3 text-right font-semibold text-emerald-700">{fmt(r.MARGIN)}</td>
<td className="px-4 py-3 text-right bg-amber-50/60 font-bold text-amber-700">
{isHQ
? <span className="text-slate-300"></span>
: <>{fmt(r.HQ_FEE_AMOUNT)}</>}
</td>
<td className="px-4 py-3 text-right font-bold">{fmt(r.NET_TO_BRANCH)}</td>
</tr>
);
})}
) : rows.map((r) => (
<tr key={r.BRANCH} className="border-t border-slate-100">
<td className="px-4 py-3">
<div className="inline-flex items-center gap-1.5">
<MapPin size={14} className="text-sky-700" />
<span className="font-semibold">{r.BRANCH_NAME}</span>
<span className="text-xs text-slate-400">({r.BRANCH})</span>
</div>
</td>
<td className="px-4 py-3 text-right">{fmt(r.ORDER_CNT)}</td>
<td className="px-4 py-3 text-right">{fmt(r.REVENUE)}</td>
<td className="px-4 py-3 text-right text-slate-500">{fmt(r.COST)}</td>
<td className="px-4 py-3 text-right font-semibold text-emerald-700">{fmt(r.MARGIN)}</td>
<td className="px-4 py-3 text-right bg-amber-50/60 font-bold text-amber-700">
{fmt(r.HQ_FEE_AMOUNT)}
</td>
<td className="px-4 py-3 text-right font-bold">{fmt(r.NET_TO_BRANCH)}</td>
</tr>
))}
</tbody>
</table>
</div>
@@ -138,7 +131,7 @@ export default function BranchFeePage() {
<div className="text-[11px] text-slate-500 flex items-start gap-1.5">
<TrendingUp size={12} className="mt-0.5 text-slate-400" />
<div>
= (HQ ) × 20%. (HQ) 0.
<b>(HQ) </b> . = × 20%.
/ (APPROVED) (/ ) .
</div>
</div>
+32 -21
View File
@@ -14,23 +14,34 @@ export async function POST(req: NextRequest) {
const y = Number(body.year) || new Date().getFullYear();
const m = Number(body.month) || new Date().getMonth() + 1;
// 계산서가 김포 등 지사 명의로 발행된 발주만 (supplier_branch snapshot 우선,
// 없으면 거래처의 user_info.statement_branch 폴백). 본사(HQ) 는 제외.
const rows = await queryRows<{ BRANCH: string; REVENUE: string; COST: string; MARGIN: string; CNT: string }>(
`SELECT
COALESCE(U.statement_branch, 'HQ') AS "BRANCH",
COALESCE(SUM(OI.supply_amount), 0) AS "REVENUE",
COALESCE(SUM(OI.qty * COALESCE(I.cost_price, 0)), 0) AS "COST",
COALESCE(SUM(OI.supply_amount) - SUM(OI.qty * COALESCE(I.cost_price, 0)), 0) AS "MARGIN",
COUNT(DISTINCT O.objid) AS "CNT"
FROM momo_orders O
JOIN momo_order_items OI ON O.objid = OI.order_objid
LEFT JOIN momo_items I ON OI.item_objid = I.objid
LEFT JOIN user_info U ON U.user_id = O.customer_objid
WHERE EXTRACT(YEAR FROM O.order_date) = $1
AND EXTRACT(MONTH FROM O.order_date) = $2
AND O.status IN ('APPROVED','PAID','INVOICED')
AND COALESCE(O.is_del,'N') != 'Y'
AND COALESCE(OI.kind, 'ITEM') = 'ITEM'
GROUP BY COALESCE(U.statement_branch, 'HQ')
`WITH base AS (
SELECT
COALESCE(O.supplier_branch, U.statement_branch, 'HQ') AS branch,
OI.supply_amount,
OI.qty * COALESCE(I.cost_price, 0) AS line_cost,
O.objid AS order_objid
FROM momo_orders O
JOIN momo_order_items OI ON O.objid = OI.order_objid
LEFT JOIN momo_items I ON OI.item_objid = I.objid
LEFT JOIN user_info U ON U.user_id = O.customer_objid
WHERE EXTRACT(YEAR FROM O.order_date) = $1
AND EXTRACT(MONTH FROM O.order_date) = $2
AND O.status IN ('APPROVED','PAID','INVOICED')
AND COALESCE(O.is_del,'N') != 'Y'
AND COALESCE(OI.kind, 'ITEM') = 'ITEM'
)
SELECT
branch AS "BRANCH",
COALESCE(SUM(supply_amount), 0) AS "REVENUE",
COALESCE(SUM(line_cost), 0) AS "COST",
COALESCE(SUM(supply_amount) - SUM(line_cost), 0) AS "MARGIN",
COUNT(DISTINCT order_objid) AS "CNT"
FROM base
WHERE branch != 'HQ'
GROUP BY branch
ORDER BY "BRANCH"`,
[y, m]
);
@@ -45,20 +56,20 @@ export async function POST(req: NextRequest) {
return m;
})();
// 본사(HQ) 는 SQL 에서 이미 제외 — 모든 row 가 지사
const result = rows.map((r) => {
const margin = Number(r.MARGIN);
const isHQ = r.BRANCH === "HQ";
const hqFee = isHQ ? 0 : Math.round(margin * HQ_FEE_RATE);
const hqFee = Math.round(margin * HQ_FEE_RATE);
return {
BRANCH: r.BRANCH,
BRANCH_NAME: branchNames[r.BRANCH] ?? (r.BRANCH === "HQ" ? "본사" : r.BRANCH),
BRANCH_NAME: branchNames[r.BRANCH] ?? r.BRANCH,
REVENUE: Number(r.REVENUE),
COST: Number(r.COST),
MARGIN: margin,
ORDER_CNT: Number(r.CNT),
HQ_FEE_RATE: isHQ ? 0 : HQ_FEE_RATE,
HQ_FEE_RATE: HQ_FEE_RATE,
HQ_FEE_AMOUNT: hqFee,
NET_TO_BRANCH: margin - hqFee, // 지사가 가져가는 금액 (수수료 제외)
NET_TO_BRANCH: margin - hqFee,
};
});