feat: 1200%룰 이연 분급 도래월 지급(PayInstallmentStep) + 코드베이스 신입교육 주석
PayInstallmentStep(Step 4.5): 1200%룰로 이연된 분급(installment_plan SCHEDULED)을 도래월(settle_month=정산월)에 recruit_ledger로 편입해 실제 지급. 이전엔 이연만 되고 도래월 지급 절차가 없어 차액이 영구 미지급되던 갭을 메움. - BatchConfig job flow에 step4b(calcRecruit→payInstallment→calcMaintain) 등록 - InstallmentPlanMapper.resetPaidToScheduledByMonth(+XML)로 재실행 멱등 (진입 시 PAID→SCHEDULED 역산, Step4 deleteBySettleMonth가 원장 정리) - RecruitLedgerMapper insert에 reconcile_status 컬럼, SettlementContext.installmentPaidCount - 단위테스트 3건(정상지급/멱등/계약미존재) GREEN 신입교육 주석: 컨트롤러/서비스/공통/프론트 전반에 도메인·코드 설명 주석 추가 (동작 변경 없음 — 빌드/테스트/타입체크 전부 GREEN으로 무회귀 확인). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,14 @@
|
||||
/**
|
||||
* [환수감면신청] 화면
|
||||
* - 업무: 설계사에게 부과된 "환수액"의 일부/전부를 감면해 달라는 신청을 등록하고, 관리자가 승인/반려하는 워크플로 화면.
|
||||
* - 호출 API: commissionApi.chargebackWaiverList(조회), chargebackWaiverCreate(신청),
|
||||
* chargebackWaiverApprove(승인), chargebackWaiverReject(반려).
|
||||
* - 주요 상태: searchParams(검색조건), createOpen(신청 모달), rejectTarget/rejectReason(반려 모달).
|
||||
* - 도메인 용어:
|
||||
* - 환수: 계약 해지 등으로 이미 지급한 수수료를 회수하는 것.
|
||||
* - 감면(waiver): 환수액 중 일부를 사정 참작해 면제해 주는 것.
|
||||
* - 승인상태: 신청(REQUESTED) -> 승인(APPROVED) 또는 반려(REJECTED).
|
||||
*/
|
||||
import { useState } from 'react';
|
||||
import { Alert, Form, Input, InputNumber, Modal, Select, Space, message } from 'antd';
|
||||
import { ProCard } from '@ant-design/pro-components';
|
||||
@@ -24,6 +35,7 @@ const STATUS_OPTIONS = [
|
||||
{ value: 'REJECTED', label: '반려' },
|
||||
];
|
||||
|
||||
// 그리드 컬럼: 감면 신청 목록(원환수액 대비 감면액, 사유, 승인상태)
|
||||
const COLS: ColDef<ChargebackWaiverRow>[] = [
|
||||
{ field: 'settleMonth', headerName: '정산월', width: 110, pinned: 'left' },
|
||||
{ field: 'agentName', headerName: '설계사', width: 130 },
|
||||
@@ -45,6 +57,7 @@ export default function ChargebackWaiver() {
|
||||
const [rejectReason, setRejectReason] = useState('');
|
||||
const [form] = Form.useForm<ChargebackWaiverSaveReq>();
|
||||
|
||||
// 감면 신청 목록 조회(읽기). 검색조건 변경 시 자동 재조회
|
||||
const { data: listData, isLoading, isError } = useQuery({
|
||||
queryKey: ['chargebackWaiver', 'list', searchParams],
|
||||
queryFn: () => commissionApi.chargebackWaiverList(searchParams),
|
||||
@@ -53,6 +66,7 @@ export default function ChargebackWaiver() {
|
||||
|
||||
const rows = listData?.list ?? [];
|
||||
|
||||
// 감면 신청 등록 mutation: 성공 시 목록 새로고침 + 모달 닫기
|
||||
const createMutation = useMutation({
|
||||
mutationFn: (body: ChargebackWaiverSaveReq) => commissionApi.chargebackWaiverCreate(body),
|
||||
onSuccess: () => {
|
||||
@@ -63,6 +77,7 @@ export default function ChargebackWaiver() {
|
||||
},
|
||||
});
|
||||
|
||||
// 신청 승인 mutation
|
||||
const approveMutation = useMutation({
|
||||
mutationFn: (requestId: number) => commissionApi.chargebackWaiverApprove(requestId),
|
||||
onSuccess: () => {
|
||||
@@ -71,6 +86,7 @@ export default function ChargebackWaiver() {
|
||||
},
|
||||
});
|
||||
|
||||
// 신청 반려 mutation(사유 필요)
|
||||
const rejectMutation = useMutation({
|
||||
mutationFn: ({ requestId, reason }: { requestId: number; reason: string }) =>
|
||||
commissionApi.chargebackWaiverReject(requestId, reason),
|
||||
@@ -82,22 +98,26 @@ export default function ChargebackWaiver() {
|
||||
},
|
||||
});
|
||||
|
||||
// 신청 모달 "신청" 버튼: 폼 검증 후 등록
|
||||
const handleCreate = async () => {
|
||||
const values = await form.validateFields();
|
||||
createMutation.mutate(values);
|
||||
};
|
||||
|
||||
// 승인 버튼: 확인창 후 승인 처리
|
||||
const handleApprove = (row: ChargebackWaiverRow) => Modal.confirm({
|
||||
title: '승인',
|
||||
content: `${row.agentName} 감면 신청(감면액: ${fmt(row.waiverAmount)})을 승인하시겠습니까?`,
|
||||
onOk: () => approveMutation.mutateAsync(row.requestId),
|
||||
});
|
||||
|
||||
// 반려 모달 열기(대상 행 지정)
|
||||
const openReject = (row: ChargebackWaiverRow) => {
|
||||
setRejectTarget(row);
|
||||
setRejectReason('');
|
||||
};
|
||||
|
||||
// 반려 확정: 사유 필수 검증 후 반려 처리
|
||||
const handleReject = () => {
|
||||
if (!rejectReason.trim()) { message.warning('반려 사유를 입력하세요'); return; }
|
||||
rejectMutation.mutate({ requestId: rejectTarget!.requestId, reason: rejectReason });
|
||||
|
||||
Reference in New Issue
Block a user