# P12 — 지급대상건 구성·수정 (#2)

작성: PL, 2026-06-04. 후속 갭 3종 중 마지막.
근거: incar `지급대상건구성`/`지급대상건수정`/`지급대상건조회`. GA는 settle_master를 건별
confirm/hold/release만 가능, **정산월 단위 지급대상 배치 구성(일괄 포함/제외 후 확정)** 레이어 부재.

범위: 풀스택. 기존 settle_master 위의 **구성/스냅샷 레이어**(settle_master 상태는 본 범위에서 변경 안 함 — 결합 최소화). Flyway: **V114 최신 → V115**. 금액 NUMERIC(15,2).

## 1. 개념
- 정산월의 지급대상(settle_master, payable_amount>0)을 모아 **배치(payment_batch)** 로 구성.
- 각 건(payment_batch_item)을 **포함/제외**(사유) 편집 → 합계 재계산.
- **확정(finalize)**: 배치 잠금(FINALIZED). 확정된 포함건 목록 = 지급대상 확정본(스냅샷).
- AccountingClose(V75 DRAFT/마감/재오픈) 패턴 준용. **settle_master.status는 본 범위에서 미변경**(향후 finalize→지급실행 연계는 별도).

## 2. 스키마 (V115)
### payment_batch (지급대상 배치)
| 컬럼 | 타입 | 설명 |
|---|---|---|
| batch_id | BIGSERIAL PK | |
| settle_month | CHAR(6) NN | |
| batch_name | VARCHAR(100) NN | |
| status | VARCHAR(20) NN DEFAULT 'DRAFT' | DRAFT/FINALIZED/CANCELLED |
| total_count | INT NN DEFAULT 0 | 포함건수 |
| total_payable | NUMERIC(15,2) NN DEFAULT 0 | 포함건 payable 합 |
| created_at/created_by/finalized_at/finalized_by/updated_at | | |
- idx(settle_month, status). CHECK status IN('DRAFT','FINALIZED','CANCELLED').

### payment_batch_item (지급대상건)
| 컬럼 | 타입 | 설명 |
|---|---|---|
| item_id | BIGSERIAL PK | |
| batch_id | BIGINT NN FK payment_batch | |
| settle_id | BIGINT NN FK settle_master | |
| agent_id | BIGINT NN FK agent | |
| payable_amount | NUMERIC(15,2) NN | settle_master.payable_amount 스냅샷 |
| included | BOOLEAN NN DEFAULT true | 포함여부 |
| exclude_reason | VARCHAR(300) | 제외사유 |
| created_at | | |
- UNIQUE(batch_id, settle_id). idx(batch_id).

### 메뉴/권한/코드
- 메뉴 `PAYMENT_TARGET`(지급대상관리), GRP_SETTLE, `/settle/payment-target`, `settle/PaymentTarget`.
- 권한 READ/CREATE/UPDATE/EXECUTE(확정) + 역할(SUPER_ADMIN·ADMIN 전체, MANAGER READ).
- 공통코드 PAYMENT_BATCH_STATUS(DRAFT=작성중/FINALIZED=확정/CANCELLED=취소).

## 3. API (PaymentBatchService, `/api/payment-batches`)
- `POST` create(settleMonth, batchName) @Transactional: payment_batch(DRAFT) INSERT → settle_master 조회(settle_month=, payable_amount>0, status != 'PAID') → 각 행 payment_batch_item(included=true, payable 스냅샷) INSERT → total_count/total_payable 집계 UPDATE. 동일월 DRAFT 중복 생성 허용(이름으로 구분).
- `GET` list(settleMonth/status 필터, PageResponse) / `GET /{id}` detail(배치 + items).
- `PUT /{id}/items/{itemId}` updateItem(included, excludeReason) @Transactional: DRAFT 배치만(FINALIZED/CANCELLED 시 E411). 항목 갱신 후 배치 total 재집계.
- `PUT /{id}/finalize` @Transactional, @RequirePermission EXECUTE: DRAFT→FINALIZED, finalized_at/by. 포함건 0이면 E411.
- `PUT /{id}/cancel`: DRAFT/FINALIZED→CANCELLED.
- `GET /{id}/export` 엑셀(@Transactional).
- @RequirePermission(PAYMENT_TARGET, *) + @DataChangeLog(쓰기).

총계 재집계: `total_count = COUNT(included=true)`, `total_payable = SUM(payable WHERE included)` — Mapper에 recalcTotals(batchId) 제공.

## 4. Frontend (PaymentTarget.tsx)
- 배치 목록(정산월/상태 필터) + "구성" 버튼(정산월+배치명 입력 → create).
- 배치 행 클릭 → 상세: 배치 헤더(상태/포함건수/합계) + items AG Grid(설계사/payable/포함체크/제외사유). DRAFT일 때 포함토글·사유 편집(PermissionButton UPDATE) → updateItem. 합계행.
- "확정" 버튼(PermissionButton EXECUTE, DRAFT만) → finalize. "취소" 버튼. 엑셀.
- React Query, 변경 후 invalidate.

## 5. 검증(PL)
build+test, Flyway V115, 라이브: create(특정월) → items 자동생성 확인 → updateItem 제외 → total 재집계 확인 → finalize → 잠금(이후 updateItem E411) → cancel. 테스트 데이터 정리.
</content>
