fix: 회계분개 멱등성 — 동일 정산월 재실행 시 분개 중복 INSERT 방지 (V87)
AccountingEntryGenerateStep(Step8b)이 selectBySettleMonth 결과를 무조건 insert 하여 동일 정산월 재정산 시 분개가 누적 중복되던 버그. payment_id 가 항상 NULL(원장 기반)이라 기존 주석의 existsByPaymentId 중복방어는 불가능했음. - V87: contract_accounting_entry.settle_month 컬럼 추가 + 기존데이터 백필(description 6자리) + 미전기 부분 인덱스 - core: VO settleMonth 필드, Mapper deleteUnpostedBySettleMonth, insertBatch/selectBySettleMonth 에 settle_month 반영 - batch: 재생성 전 deleteUnpostedBySettleMonth 호출(마감 채번분 journal_no IS NOT NULL 보존) 검증: ga-core/ga-batch/ga-api compileJava SUCCESS, Flyway v87 적용(백필 433건 NULL 0), 202605 정산 2회 실행 → 193건 유지(중복 0, 수정 전이라면 386). 멱등성 PASS. 테스트데이터 롤백 완료. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+7
-1
@@ -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<ContractAccountingEntryVO> source = accountingMapper.selectBySettleMonth(settleMonth);
|
||||
if (source.isEmpty()) {
|
||||
log.info("[AccountingEntry] no ledger records for month={}", settleMonth);
|
||||
|
||||
@@ -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;
|
||||
@@ -20,6 +20,12 @@ public interface ContractAccountingEntryMapper {
|
||||
/** 배치 step용: 정산월 기준 분개 대상 원장 조회 (recruit+maintain ledger UNION) */
|
||||
List<ContractAccountingEntryVO> selectBySettleMonth(@Param("settleMonth") String settleMonth);
|
||||
|
||||
/**
|
||||
* 배치 step용: 정산월의 미전기(journal_no IS NULL) 분개 삭제 — 재생성 멱등성 확보.
|
||||
* 마감 채번된 분개는 보존.
|
||||
*/
|
||||
int deleteUnpostedBySettleMonth(@Param("settleMonth") String settleMonth);
|
||||
|
||||
/**
|
||||
* 분개 엔트리 일괄 INSERT.
|
||||
* 회계 자동분개 배치 step에서 호출.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -66,14 +66,21 @@
|
||||
<insert id="insertBatch" useGeneratedKeys="true" keyProperty="entryId">
|
||||
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
|
||||
<foreach collection="list" item="e" separator=",">
|
||||
(#{e.contractId}, #{e.paymentId}, #{e.entryDate},
|
||||
#{e.debitAccount}, #{e.creditAccount}, #{e.amount}, #{e.description})
|
||||
#{e.debitAccount}, #{e.creditAccount}, #{e.amount}, #{e.description}, #{e.settleMonth})
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<!-- ===== 미전기 분개 정산월 단위 삭제 (재생성 멱등성) ===== -->
|
||||
<delete id="deleteUnpostedBySettleMonth">
|
||||
DELETE FROM contract_accounting_entry
|
||||
WHERE settle_month = #{settleMonth}
|
||||
AND journal_no IS NULL
|
||||
</delete>
|
||||
|
||||
<!-- ===== 마감 처리 (journalNo 채번) ===== -->
|
||||
<update id="closeJournal">
|
||||
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}
|
||||
|
||||
Reference in New Issue
Block a user