동기화

This commit is contained in:
GA Pro
2026-05-15 00:25:18 +09:00
parent 88e14edffd
commit d5ed642c80
69 changed files with 2634 additions and 26 deletions
@@ -38,16 +38,20 @@ public class BatchConfig {
CalcMaintainStep step5,
ChargebackExceptionStep step6,
CalcOverrideStep step7,
AggregateStep step8) {
AggregateStep step8,
AccountingEntryGenerateStep step9,
YearEndIncomeAggregateStep step10) {
return new JobBuilder("MONTHLY_SETTLEMENT", jobRepository)
.start(taskletStep("receiveData", step1, jobRepository, tx))
.next(taskletStep("transformData", step2, jobRepository, tx))
.next(taskletStep("reconcile", step3, jobRepository, tx))
.next(taskletStep("calcRecruit", step4, jobRepository, tx))
.next(taskletStep("calcMaintain", step5, jobRepository, tx))
.next(taskletStep("chargebackException", step6, jobRepository, tx))
.next(taskletStep("calcOverride", step7, jobRepository, tx))
.next(taskletStep("aggregate", step8, jobRepository, tx))
.start(taskletStep("receiveData", step1, jobRepository, tx))
.next(taskletStep("transformData", step2, jobRepository, tx))
.next(taskletStep("reconcile", step3, jobRepository, tx))
.next(taskletStep("calcRecruit", step4, jobRepository, tx))
.next(taskletStep("calcMaintain", step5, jobRepository, tx))
.next(taskletStep("chargebackException", step6, jobRepository, tx))
.next(taskletStep("calcOverride", step7, jobRepository, tx))
.next(taskletStep("aggregate", step8, jobRepository, tx))
.next(taskletStep("accountingEntryGenerate", step9, jobRepository, tx))
.next(taskletStep("yearEndIncomeAggregate", step10, jobRepository, tx))
.build();
}
@@ -0,0 +1,73 @@
package com.ga.batch.settlement.step;
import com.ga.batch.settlement.SettlementContext;
import com.ga.core.mapper.accounting.ContractAccountingEntryMapper;
import com.ga.core.vo.accounting.ContractAccountingEntryVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.JobScope;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
/**
* Step 8b — accountingEntryGenerate: 회계 자동분개
*
* 정산월 기준 recruit_ledger + maintain_ledger → contract_accounting_entry INSERT.
* - 차변(8350 지급수수료) / 대변(2530 미지급금) 한 쌍을 단일 행으로 저장.
* - journal_no는 NULL(미전기). 마감 UI에서 채번.
* - 이미 분개된 payment는 existsByPaymentId로 중복 방어.
*/
@Slf4j
@Component
@JobScope
@RequiredArgsConstructor
public class AccountingEntryGenerateStep implements Tasklet {
private final SettlementContext ctx;
private final ContractAccountingEntryMapper accountingMapper;
private static final int BATCH_SIZE = 500;
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
String settleMonth = ctx.getSettleMonth();
log.info("[AccountingEntry] settleMonth={}", settleMonth);
List<ContractAccountingEntryVO> source = accountingMapper.selectBySettleMonth(settleMonth);
if (source.isEmpty()) {
log.info("[AccountingEntry] no ledger records for month={}", settleMonth);
return RepeatStatus.FINISHED;
}
LocalDate today = LocalDate.now();
List<ContractAccountingEntryVO> batch = new ArrayList<>(BATCH_SIZE);
int total = 0;
for (ContractAccountingEntryVO entry : source) {
if (entry.getAmount() == null || entry.getAmount().signum() == 0) continue;
entry.setEntryDate(today);
entry.setJournalNo(null);
batch.add(entry);
if (batch.size() >= BATCH_SIZE) {
accountingMapper.insertBatch(batch);
total += batch.size();
batch.clear();
}
}
if (!batch.isEmpty()) {
accountingMapper.insertBatch(batch);
total += batch.size();
}
log.info("[AccountingEntry] inserted {} entries for month={}", total, settleMonth);
return RepeatStatus.FINISHED;
}
}
@@ -0,0 +1,58 @@
package com.ga.batch.settlement.step;
import com.ga.batch.settlement.SettlementContext;
import com.ga.core.mapper.income.AgentAnnualIncomeMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.JobScope;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.stereotype.Component;
/**
* Step 9 (conditional) — yearEndIncomeAggregate: 연말 사업소득 집계
*
* 12월 마감 시에만 실행 (settleMonth ends with "-12" 또는 "12").
* payment 테이블 기준으로 해당 연도 설계사별 SUM → agent_annual_income UPSERT.
* SQL은 AgentAnnualIncomeMapper.upsertAggregate에 위임.
*/
@Slf4j
@Component
@JobScope
@RequiredArgsConstructor
public class YearEndIncomeAggregateStep implements Tasklet {
private final SettlementContext ctx;
private final AgentAnnualIncomeMapper incomeMapper;
@Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
String settleMonth = ctx.getSettleMonth();
if (!isDecemberClose(settleMonth)) {
log.info("[YearEndIncome] skipped — not a December close: {}", settleMonth);
return RepeatStatus.FINISHED;
}
int year = extractYear(settleMonth);
log.info("[YearEndIncome] aggregating income for year={}", year);
int rows = incomeMapper.upsertAggregate(year);
log.info("[YearEndIncome] agent_annual_income upserted: {} rows", rows);
return RepeatStatus.FINISHED;
}
private boolean isDecemberClose(String settleMonth) {
if (settleMonth == null) return false;
// 형식: yyyyMM (예: 202512) 또는 yyyy-MM
return settleMonth.endsWith("12");
}
private int extractYear(String settleMonth) {
// yyyyMM → 앞 4자리
String digits = settleMonth.replaceAll("[^0-9]", "");
return Integer.parseInt(digits.substring(0, 4));
}
}
@@ -1,7 +1,7 @@
# 실제 운영 PostgreSQL 연결용 프로파일 (ga-batch)
spring:
datasource:
url: jdbc:postgresql://125.241.236.203:55432/trading_ai?currentSchema=ga
url: jdbc:postgresql://192.168.0.60:55432/trading_ai?currentSchema=ga
username: ${DB_USER:kyu}
password: ${DB_PASSWORD:7895123}
driver-class-name: org.postgresql.Driver