fix(branch-fee): 본사(HQ) 제외 + 계산서 발행 명의(supplier_branch) 기준
Deploy momo-erp / deploy (push) Successful in 2m11s
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:
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user