diff --git a/ga-api/src/main/java/com/ga/api/service/mobile/MobileMeService.java b/ga-api/src/main/java/com/ga/api/service/mobile/MobileMeService.java index 1e7fb46..db0d464 100644 --- a/ga-api/src/main/java/com/ga/api/service/mobile/MobileMeService.java +++ b/ga-api/src/main/java/com/ga/api/service/mobile/MobileMeService.java @@ -199,13 +199,18 @@ public class MobileMeService { if (!agentId.equals(master.getAgentId())) throw new BizException(ErrorCode.FORBIDDEN); 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); if (used == null) used = BigDecimal.ZERO; return WithdrawableBalance.builder() .settleId(settleMasterId) .netAmount(netAmount) + .payableAmount(payable) .usedAmount(used) - .remainingAmount(netAmount.subtract(used)) + .remainingAmount(payable.subtract(used)) .build(); } @@ -272,9 +277,10 @@ public class MobileMeService { @Builder public static class WithdrawableBalance { private Long settleId; - private BigDecimal netAmount; + private BigDecimal netAmount; // 회계 순액(정보용) + private BigDecimal payableAmount; // 환수채권 상계 후 실지급가능액 (한도 기준) private BigDecimal usedAmount; - private BigDecimal remainingAmount; + private BigDecimal remainingAmount; // = payableAmount − usedAmount } /** 모바일 출금 신청 — 토큰에서 agentId/requestedBy 강제. */ diff --git a/ga-common/src/main/resources/db/migration/V112__settle_payable_백필.sql b/ga-common/src/main/resources/db/migration/V112__settle_payable_백필.sql new file mode 100644 index 0000000..efda1a1 --- /dev/null +++ b/ga-common/src/main/resources/db/migration/V112__settle_payable_백필.sql @@ -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; diff --git a/ga-core/src/main/java/com/ga/core/vo/settle/SettleMasterResp.java b/ga-core/src/main/java/com/ga/core/vo/settle/SettleMasterResp.java index ff0298d..9f514b7 100644 --- a/ga-core/src/main/java/com/ga/core/vo/settle/SettleMasterResp.java +++ b/ga-core/src/main/java/com/ga/core/vo/settle/SettleMasterResp.java @@ -22,6 +22,9 @@ public class SettleMasterResp { private BigDecimal grossAmount; private BigDecimal taxAmount; private BigDecimal netAmount; + private BigDecimal clawbackRecovered; // V111: 당월 환수채권 상계 회수액 + private BigDecimal clawbackCarryover; // V111: 당월 발생→이월된 미수액 + private BigDecimal payableAmount; // V111: 실지급(이체대상) = max(0,net) − recovered private String status; private String statusName; private LocalDateTime calcDate; diff --git a/ga-core/src/main/resources/mapper/settle/SettleMasterMapper.xml b/ga-core/src/main/resources/mapper/settle/SettleMasterMapper.xml index b6a4d23..3f81a5f 100644 --- a/ga-core/src/main/resources/mapper/settle/SettleMasterMapper.xml +++ b/ga-core/src/main/resources/mapper/settle/SettleMasterMapper.xml @@ -11,6 +11,7 @@ s.settle_month, s.recruit_total, s.maintain_total, s.exception_plus, s.exception_minus, s.override_total, s.chargeback_total, s.gross_amount, s.tax_amount, s.net_amount, + s.clawback_recovered, s.clawback_carryover, s.payable_amount, s.status, (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, diff --git a/ga-mobile-pwa/src/api/withdraw.ts b/ga-mobile-pwa/src/api/withdraw.ts index 264e2f9..93df64a 100644 --- a/ga-mobile-pwa/src/api/withdraw.ts +++ b/ga-mobile-pwa/src/api/withdraw.ts @@ -29,6 +29,7 @@ export interface SettleMasterResp { settleId: number; settleMonth: string; netAmount?: number; + payableAmount?: number; grossAmount?: number; status?: string; statusName?: string; @@ -44,6 +45,7 @@ export interface WithdrawCreateReq { export interface WithdrawableBalance { settleId: number; netAmount: number; + payableAmount: number; usedAmount: number; remainingAmount: number; } diff --git a/ga-mobile-pwa/src/pages/WithdrawCreate.tsx b/ga-mobile-pwa/src/pages/WithdrawCreate.tsx index d034b2c..7d1b633 100644 --- a/ga-mobile-pwa/src/pages/WithdrawCreate.tsx +++ b/ga-mobile-pwa/src/pages/WithdrawCreate.tsx @@ -36,7 +36,7 @@ export default function WithdrawCreate() { const pickerColumns: PickerColumn[] = useMemo( () => [ settleMasters.map((s: SettleMasterResp) => ({ - label: `${s.settleMonth} · 실수령 ${fmt(s.netAmount)}원`, + label: `${s.settleMonth} · 실지급가능 ${fmt(s.payableAmount ?? s.netAmount)}원`, value: String(s.settleId), })), ], @@ -94,7 +94,7 @@ export default function WithdrawCreate() { {fmt(balance.remainingAmount)}원