feat: P5-W3 본론 완료 — 회계결산/이상치예측/연말명세서 (V74~V78)
P5-W2 원천세·부가세 분기신고 미커밋분 + P5-W3 메뉴/권한 보강(V74) + P5-W3 본론(V75~V78)을 일괄 정리. - V75 accounting_close / account_balance_snapshot (회계 결산) - V76 retention_anomaly_alert / forecast_kpi_monthly (이상치·예측 KPI) - V77 year_end_statement (연말 지급명세서) - V78 P5-W3 본론 메뉴 4 + 권한 + 공통코드 7그룹 + 테스트데이터 - api: AccountingClose/YearEndStatement/ForecastKpi/RetentionAnomaly Service+Controller - batch: BatchConfig Step 11/12 (원천세·부가세 분기) 연결 - frontend: 4화면(AccountingClose/YearEndStatement/RetentionAnomaly/ForecastKpi) + API 모듈 4 + 라우트 - frontend: usePermission/PermissionButton EXECUTE 권한 누락 보강 - fix: YearEndStatementMapper.xml 미존재 컬럼 a.agent_code 참조 제거 통합검증: Flyway V74~V78 운영DB 적용(v78), 4모듈 compileJava + 프론트 build PASS, 스모크 GET 8/8 + 쓰기 4/4 PASS, verify_v74.py 20/20 PASS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -40,18 +40,22 @@ public class BatchConfig {
|
||||
CalcOverrideStep step7,
|
||||
AggregateStep step8,
|
||||
AccountingEntryGenerateStep step9,
|
||||
YearEndIncomeAggregateStep step10) {
|
||||
YearEndIncomeAggregateStep step10,
|
||||
WithholdingTaxQuarterlyStep step11,
|
||||
VatReportQuarterlyStep step12) {
|
||||
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))
|
||||
.next(taskletStep("accountingEntryGenerate", step9, jobRepository, tx))
|
||||
.next(taskletStep("yearEndIncomeAggregate", step10, 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))
|
||||
.next(taskletStep("withholdingTaxQuarterly", step11, jobRepository, tx))
|
||||
.next(taskletStep("vatReportQuarterly", step12, jobRepository, tx))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.ga.batch.settlement.step;
|
||||
|
||||
import com.ga.batch.settlement.SettlementContext;
|
||||
import com.ga.core.mapper.tax.VatReportMapper;
|
||||
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 12 (conditional) — vatReportQuarterly: 분기 부가세 집계
|
||||
*
|
||||
* 분기 마감월(3/6/9/12월)에만 실행 (settleMonth ends with "03"/"06"/"09"/"12").
|
||||
* tax_invoice 테이블 기준 해당 분기 SUPPLY/PURCHASE SUM → vat_report UPSERT (분기 1건).
|
||||
* SQL은 VatReportMapper.upsertAggregate에 위임.
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@JobScope
|
||||
@RequiredArgsConstructor
|
||||
public class VatReportQuarterlyStep implements Tasklet {
|
||||
|
||||
private final SettlementContext ctx;
|
||||
private final VatReportMapper vatMapper;
|
||||
|
||||
@Override
|
||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
|
||||
String settleMonth = ctx.getSettleMonth();
|
||||
|
||||
if (!isQuarterClose(settleMonth)) {
|
||||
log.info("[VatReportQuarterly] skipped — not a quarter-close month: {}", settleMonth);
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
|
||||
String quarter = toQuarter(settleMonth);
|
||||
log.info("[VatReportQuarterly] aggregating VAT for quarter={}", quarter);
|
||||
|
||||
int rows = vatMapper.upsertAggregate(quarter);
|
||||
log.info("[VatReportQuarterly] vat_report upserted: {} rows", rows);
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
|
||||
/** 정산월이 분기 마감월(03/06/09/12)인지 확인 */
|
||||
private boolean isQuarterClose(String settleMonth) {
|
||||
if (settleMonth == null) return false;
|
||||
// 형식: yyyyMM (예: 202503) 또는 yyyy-MM
|
||||
String mm = settleMonth.replaceAll("[^0-9]", "").substring(4);
|
||||
return mm.equals("03") || mm.equals("06") || mm.equals("09") || mm.equals("12");
|
||||
}
|
||||
|
||||
/**
|
||||
* yyyyMM → 분기 문자열 (예: 202503 → 2025-Q1)
|
||||
* 03→Q1, 06→Q2, 09→Q3, 12→Q4
|
||||
*/
|
||||
private String toQuarter(String settleMonth) {
|
||||
String digits = settleMonth.replaceAll("[^0-9]", "");
|
||||
String year = digits.substring(0, 4);
|
||||
String mm = digits.substring(4);
|
||||
String q;
|
||||
switch (mm) {
|
||||
case "03": q = "Q1"; break;
|
||||
case "06": q = "Q2"; break;
|
||||
case "09": q = "Q3"; break;
|
||||
default: q = "Q4"; break;
|
||||
}
|
||||
return year + "-" + q;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.ga.batch.settlement.step;
|
||||
|
||||
import com.ga.batch.settlement.SettlementContext;
|
||||
import com.ga.core.mapper.tax.WithholdingTaxReportMapper;
|
||||
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 11 (conditional) — withholdingTaxQuarterly: 분기 원천세 집계
|
||||
*
|
||||
* 분기 마감월(3/6/9/12월)에만 실행 (settleMonth ends with "03"/"06"/"09"/"12").
|
||||
* payment 테이블 기준 해당 분기 설계사별 SUM → withholding_tax_report UPSERT.
|
||||
* SQL은 WithholdingTaxReportMapper.upsertQuarterlyAggregate에 위임.
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@JobScope
|
||||
@RequiredArgsConstructor
|
||||
public class WithholdingTaxQuarterlyStep implements Tasklet {
|
||||
|
||||
private final SettlementContext ctx;
|
||||
private final WithholdingTaxReportMapper withholdingMapper;
|
||||
|
||||
@Override
|
||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
|
||||
String settleMonth = ctx.getSettleMonth();
|
||||
|
||||
if (!isQuarterClose(settleMonth)) {
|
||||
log.info("[WithholdingTaxQuarterly] skipped — not a quarter-close month: {}", settleMonth);
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
|
||||
String quarter = toQuarter(settleMonth);
|
||||
log.info("[WithholdingTaxQuarterly] aggregating withholding tax for quarter={}", quarter);
|
||||
|
||||
int rows = withholdingMapper.upsertQuarterlyAggregate(quarter);
|
||||
log.info("[WithholdingTaxQuarterly] withholding_tax_report upserted: {} rows", rows);
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
|
||||
/** 정산월이 분기 마감월(03/06/09/12)인지 확인 */
|
||||
private boolean isQuarterClose(String settleMonth) {
|
||||
if (settleMonth == null) return false;
|
||||
// 형식: yyyyMM (예: 202503) 또는 yyyy-MM
|
||||
String mm = settleMonth.replaceAll("[^0-9]", "").substring(4);
|
||||
return mm.equals("03") || mm.equals("06") || mm.equals("09") || mm.equals("12");
|
||||
}
|
||||
|
||||
/**
|
||||
* yyyyMM → 분기 문자열 (예: 202503 → 2025-Q1)
|
||||
* 03→Q1, 06→Q2, 09→Q3, 12→Q4
|
||||
*/
|
||||
private String toQuarter(String settleMonth) {
|
||||
String digits = settleMonth.replaceAll("[^0-9]", "");
|
||||
String year = digits.substring(0, 4);
|
||||
String mm = digits.substring(4);
|
||||
String q;
|
||||
switch (mm) {
|
||||
case "03": q = "Q1"; break;
|
||||
case "06": q = "Q2"; break;
|
||||
case "09": q = "Q3"; break;
|
||||
default: q = "Q4"; break;
|
||||
}
|
||||
return year + "-" + q;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user