feat: P9 #4 — 출금 가용한도를 환수채권 상계 후 실지급액(payable_amount)으로 전환
P9에서 산출하던 payable_amount(=환수 상계 후 실지급액)가 출금 한도에 미반영 (cosmetic)이던 것을 연결. 이제 환수채권 회수가 모바일 출금 가용액을 정확히 줄인다. - V112: 기존 settle_master.payable_amount 백필 = GREATEST(net_amount,0) (V111에서 DEFAULT 0 추가됨 → 구 데이터 보정. clawback 무활동 행만 대상=멱등). - core: SettleMasterMapper.xml cols에 clawback_recovered/carryover/payable_amount 추가, SettleMasterResp 3필드 노출. - api: MobileMeService.withdrawableBalance 한도 산정을 net→payable_amount 기준으로 (payable null이면 max(net,0) 폴백). WithdrawableBalance DTO에 payableAmount 추가, remainingAmount = payable − used. createWithdrawRequest 가드도 자동 반영. - PWA: withdraw.ts 타입 + WithdrawCreate 드롭다운/카드를 '실지급가능(payable)' 표기로. 검증: ./gradlew build(test포함) SUCCESSFUL, PWA tsc 0오류. Flyway V112 적용(schema v112). 백필 150/150 행 payable==max(net,0). 라이브 agent01 withdrawable-balance가 payableAmount 노출 + remaining=payable−used(13492−151000=-137508) 정확. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -199,13 +199,18 @@ public class MobileMeService {
|
|||||||
if (!agentId.equals(master.getAgentId())) throw new BizException(ErrorCode.FORBIDDEN);
|
if (!agentId.equals(master.getAgentId())) throw new BizException(ErrorCode.FORBIDDEN);
|
||||||
|
|
||||||
BigDecimal netAmount = master.getNetAmount() != null ? master.getNetAmount() : BigDecimal.ZERO;
|
BigDecimal netAmount = master.getNetAmount() != null ? master.getNetAmount() : BigDecimal.ZERO;
|
||||||
|
// 출금 가용한도는 환수채권 상계 후 실지급액(payable_amount) 기준.
|
||||||
|
// payable_amount 미산정(구 데이터) 시 max(net,0) 로 폴백.
|
||||||
|
BigDecimal payable = master.getPayableAmount();
|
||||||
|
if (payable == null) payable = netAmount.max(BigDecimal.ZERO);
|
||||||
BigDecimal used = withdrawMapper.sumActiveAmountByMaster(settleMasterId);
|
BigDecimal used = withdrawMapper.sumActiveAmountByMaster(settleMasterId);
|
||||||
if (used == null) used = BigDecimal.ZERO;
|
if (used == null) used = BigDecimal.ZERO;
|
||||||
return WithdrawableBalance.builder()
|
return WithdrawableBalance.builder()
|
||||||
.settleId(settleMasterId)
|
.settleId(settleMasterId)
|
||||||
.netAmount(netAmount)
|
.netAmount(netAmount)
|
||||||
|
.payableAmount(payable)
|
||||||
.usedAmount(used)
|
.usedAmount(used)
|
||||||
.remainingAmount(netAmount.subtract(used))
|
.remainingAmount(payable.subtract(used))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,9 +277,10 @@ public class MobileMeService {
|
|||||||
@Builder
|
@Builder
|
||||||
public static class WithdrawableBalance {
|
public static class WithdrawableBalance {
|
||||||
private Long settleId;
|
private Long settleId;
|
||||||
private BigDecimal netAmount;
|
private BigDecimal netAmount; // 회계 순액(정보용)
|
||||||
|
private BigDecimal payableAmount; // 환수채권 상계 후 실지급가능액 (한도 기준)
|
||||||
private BigDecimal usedAmount;
|
private BigDecimal usedAmount;
|
||||||
private BigDecimal remainingAmount;
|
private BigDecimal remainingAmount; // = payableAmount − usedAmount
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 모바일 출금 신청 — 토큰에서 agentId/requestedBy 강제. */
|
/** 모바일 출금 신청 — 토큰에서 agentId/requestedBy 강제. */
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
-- V112: settle_master.payable_amount 백필 (P9 #4 — 출금 가용한도를 payable_amount 로 전환)
|
||||||
|
-- V111 에서 payable_amount 가 DEFAULT 0 으로 추가되어 기존 정산 행은 전부 0.
|
||||||
|
-- P9 이전에는 환수채권 상계가 없었으므로 기존 행의 실지급가능액 = max(net_amount, 0).
|
||||||
|
-- 신규 정산 배치(AggregateStep)는 payable_amount 를 정확히 재계산해 덮어쓴다.
|
||||||
|
-- 멱등: clawback 활동이 없는 행(recovered=0 AND carryover=0)만 대상 — 재실행해도 동일.
|
||||||
|
|
||||||
|
UPDATE settle_master
|
||||||
|
SET payable_amount = GREATEST(net_amount, 0)
|
||||||
|
WHERE clawback_recovered = 0
|
||||||
|
AND clawback_carryover = 0;
|
||||||
@@ -22,6 +22,9 @@ public class SettleMasterResp {
|
|||||||
private BigDecimal grossAmount;
|
private BigDecimal grossAmount;
|
||||||
private BigDecimal taxAmount;
|
private BigDecimal taxAmount;
|
||||||
private BigDecimal netAmount;
|
private BigDecimal netAmount;
|
||||||
|
private BigDecimal clawbackRecovered; // V111: 당월 환수채권 상계 회수액
|
||||||
|
private BigDecimal clawbackCarryover; // V111: 당월 발생→이월된 미수액
|
||||||
|
private BigDecimal payableAmount; // V111: 실지급(이체대상) = max(0,net) − recovered
|
||||||
private String status;
|
private String status;
|
||||||
private String statusName;
|
private String statusName;
|
||||||
private LocalDateTime calcDate;
|
private LocalDateTime calcDate;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
s.settle_month, s.recruit_total, s.maintain_total,
|
s.settle_month, s.recruit_total, s.maintain_total,
|
||||||
s.exception_plus, s.exception_minus, s.override_total, s.chargeback_total,
|
s.exception_plus, s.exception_minus, s.override_total, s.chargeback_total,
|
||||||
s.gross_amount, s.tax_amount, s.net_amount,
|
s.gross_amount, s.tax_amount, s.net_amount,
|
||||||
|
s.clawback_recovered, s.clawback_carryover, s.payable_amount,
|
||||||
s.status,
|
s.status,
|
||||||
(SELECT cc.code_name FROM common_code cc WHERE cc.group_code='SETTLE_STATUS' AND cc.code=s.status) AS status_name,
|
(SELECT cc.code_name FROM common_code cc WHERE cc.group_code='SETTLE_STATUS' AND cc.code=s.status) AS status_name,
|
||||||
s.calc_date, s.confirmed_by, u.user_name AS confirmed_by_name,
|
s.calc_date, s.confirmed_by, u.user_name AS confirmed_by_name,
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export interface SettleMasterResp {
|
|||||||
settleId: number;
|
settleId: number;
|
||||||
settleMonth: string;
|
settleMonth: string;
|
||||||
netAmount?: number;
|
netAmount?: number;
|
||||||
|
payableAmount?: number;
|
||||||
grossAmount?: number;
|
grossAmount?: number;
|
||||||
status?: string;
|
status?: string;
|
||||||
statusName?: string;
|
statusName?: string;
|
||||||
@@ -44,6 +45,7 @@ export interface WithdrawCreateReq {
|
|||||||
export interface WithdrawableBalance {
|
export interface WithdrawableBalance {
|
||||||
settleId: number;
|
settleId: number;
|
||||||
netAmount: number;
|
netAmount: number;
|
||||||
|
payableAmount: number;
|
||||||
usedAmount: number;
|
usedAmount: number;
|
||||||
remainingAmount: number;
|
remainingAmount: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export default function WithdrawCreate() {
|
|||||||
const pickerColumns: PickerColumn[] = useMemo(
|
const pickerColumns: PickerColumn[] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
settleMasters.map((s: SettleMasterResp) => ({
|
settleMasters.map((s: SettleMasterResp) => ({
|
||||||
label: `${s.settleMonth} · 실수령 ${fmt(s.netAmount)}원`,
|
label: `${s.settleMonth} · 실지급가능 ${fmt(s.payableAmount ?? s.netAmount)}원`,
|
||||||
value: String(s.settleId),
|
value: String(s.settleId),
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
@@ -94,7 +94,7 @@ export default function WithdrawCreate() {
|
|||||||
{fmt(balance.remainingAmount)}원
|
{fmt(balance.remainingAmount)}원
|
||||||
</div>
|
</div>
|
||||||
<div style={{ color: '#999', fontSize: 12, marginTop: 4 }}>
|
<div style={{ color: '#999', fontSize: 12, marginTop: 4 }}>
|
||||||
실수령 {fmt(balance.netAmount)}원 · 신청중 {fmt(balance.usedAmount)}원
|
실지급가능 {fmt(balance.payableAmount)}원 · 신청중 {fmt(balance.usedAmount)}원
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user