diff --git a/ga-batch/src/main/java/com/ga/batch/settlement/step/AccountingEntryGenerateStep.java b/ga-batch/src/main/java/com/ga/batch/settlement/step/AccountingEntryGenerateStep.java index f0922fd..e698aea 100644 --- a/ga-batch/src/main/java/com/ga/batch/settlement/step/AccountingEntryGenerateStep.java +++ b/ga-batch/src/main/java/com/ga/batch/settlement/step/AccountingEntryGenerateStep.java @@ -22,7 +22,7 @@ import java.util.List; * 정산월 기준 recruit_ledger + maintain_ledger → contract_accounting_entry INSERT. * - 차변(8350 지급수수료) / 대변(2530 미지급금) 한 쌍을 단일 행으로 저장. * - journal_no는 NULL(미전기). 마감 UI에서 채번. - * - 이미 분개된 payment는 existsByPaymentId로 중복 방어. + * - 멱등성: 재실행 시 해당 정산월의 미전기 분개를 먼저 삭제 후 재생성(마감 채번분은 보존). */ @Slf4j @Component @@ -40,6 +40,12 @@ public class AccountingEntryGenerateStep implements Tasklet { String settleMonth = ctx.getSettleMonth(); log.info("[AccountingEntry] settleMonth={}", settleMonth); + // 멱등성: 이번 정산월의 미전기 분개 제거 후 재생성 (마감 채번된 분개는 보존) + int deleted = accountingMapper.deleteUnpostedBySettleMonth(settleMonth); + if (deleted > 0) { + log.info("[AccountingEntry] deleted {} unposted entries before regenerate", deleted); + } + List source = accountingMapper.selectBySettleMonth(settleMonth); if (source.isEmpty()) { log.info("[AccountingEntry] no ledger records for month={}", settleMonth); diff --git a/ga-common/src/main/resources/db/migration/V87__회계분개_정산월_멱등성.sql b/ga-common/src/main/resources/db/migration/V87__회계분개_정산월_멱등성.sql new file mode 100644 index 0000000..8dd03cc --- /dev/null +++ b/ga-common/src/main/resources/db/migration/V87__회계분개_정산월_멱등성.sql @@ -0,0 +1,19 @@ +-- V87: 회계분개(contract_accounting_entry) 정산월 멱등성 확보 +-- AccountingEntryGenerateStep(Step8b)이 동일 정산월 재실행 시 분개를 중복 INSERT 하던 버그 수정. +-- payment_id 가 항상 NULL(원장 기반)이라 기존 주석의 existsByPaymentId 중복방어는 불가능했음. +-- recruit/maintain ledger 와 동일하게 settle_month 키로 "미전기분 삭제 후 재생성" 패턴 적용. +-- (journal_no 채번된 마감 분개는 보존 — 미전기분만 재생성 대상) + +ALTER TABLE contract_accounting_entry ADD COLUMN IF NOT EXISTS settle_month CHAR(6); +COMMENT ON COLUMN contract_accounting_entry.settle_month IS '정산월 (YYYYMM). 배치 재생성 시 미전기분 멱등 삭제 키'; + +-- 기존 데이터 백필: description 의 6자리 정산월(예: "모집수수료 202604") 추출 +UPDATE contract_accounting_entry + SET settle_month = substring(description from '(\d{6})') + WHERE settle_month IS NULL + AND description ~ '\d{6}'; + +-- 미전기분 재생성 조회/삭제용 부분 인덱스 +CREATE INDEX IF NOT EXISTS idx_cae_settle_unposted + ON contract_accounting_entry(settle_month) + WHERE journal_no IS NULL; diff --git a/ga-core/src/main/java/com/ga/core/mapper/accounting/ContractAccountingEntryMapper.java b/ga-core/src/main/java/com/ga/core/mapper/accounting/ContractAccountingEntryMapper.java index 142ca34..e196c97 100644 --- a/ga-core/src/main/java/com/ga/core/mapper/accounting/ContractAccountingEntryMapper.java +++ b/ga-core/src/main/java/com/ga/core/mapper/accounting/ContractAccountingEntryMapper.java @@ -20,6 +20,12 @@ public interface ContractAccountingEntryMapper { /** 배치 step용: 정산월 기준 분개 대상 원장 조회 (recruit+maintain ledger UNION) */ List selectBySettleMonth(@Param("settleMonth") String settleMonth); + /** + * 배치 step용: 정산월의 미전기(journal_no IS NULL) 분개 삭제 — 재생성 멱등성 확보. + * 마감 채번된 분개는 보존. + */ + int deleteUnpostedBySettleMonth(@Param("settleMonth") String settleMonth); + /** * 분개 엔트리 일괄 INSERT. * 회계 자동분개 배치 step에서 호출. diff --git a/ga-core/src/main/java/com/ga/core/vo/accounting/ContractAccountingEntryVO.java b/ga-core/src/main/java/com/ga/core/vo/accounting/ContractAccountingEntryVO.java index 8025350..b0e4817 100644 --- a/ga-core/src/main/java/com/ga/core/vo/accounting/ContractAccountingEntryVO.java +++ b/ga-core/src/main/java/com/ga/core/vo/accounting/ContractAccountingEntryVO.java @@ -22,6 +22,8 @@ public class ContractAccountingEntryVO { private String creditAccount; private BigDecimal amount; private String description; + /** 정산월 (YYYYMM) — 배치 재생성 시 미전기분 멱등 삭제 키 */ + private String settleMonth; /** 마감 시 채번. NULL=미전기 */ private String journalNo; private LocalDateTime createdAt; diff --git a/ga-core/src/main/resources/mapper/accounting/ContractAccountingEntryMapper.xml b/ga-core/src/main/resources/mapper/accounting/ContractAccountingEntryMapper.xml index c5ced9e..9eb4190 100644 --- a/ga-core/src/main/resources/mapper/accounting/ContractAccountingEntryMapper.xml +++ b/ga-core/src/main/resources/mapper/accounting/ContractAccountingEntryMapper.xml @@ -66,14 +66,21 @@ INSERT INTO contract_accounting_entry (contract_id, payment_id, entry_date, - debit_account, credit_account, amount, description) + debit_account, credit_account, amount, description, settle_month) VALUES (#{e.contractId}, #{e.paymentId}, #{e.entryDate}, - #{e.debitAccount}, #{e.creditAccount}, #{e.amount}, #{e.description}) + #{e.debitAccount}, #{e.creditAccount}, #{e.amount}, #{e.description}, #{e.settleMonth}) + + + DELETE FROM contract_accounting_entry + WHERE settle_month = #{settleMonth} + AND journal_no IS NULL + + UPDATE contract_accounting_entry @@ -98,6 +105,7 @@ '2530' AS credit_account, l.agent_amount AS amount, CONCAT('모집수수료 ', l.settle_month) AS description, + l.settle_month AS settle_month, NULL AS journal_no FROM recruit_ledger l WHERE l.settle_month = #{settleMonth} @@ -110,6 +118,7 @@ '2530' AS credit_account, l.agent_amount AS amount, CONCAT('유지수수료 ', l.settle_month) AS description, + l.settle_month AS settle_month, NULL AS journal_no FROM maintain_ledger l WHERE l.settle_month = #{settleMonth}