feat: complete Spring Boot 카드 매입·정산 system (acquire-core migration target, mvn verify green, 24 tests)
This commit is contained in:
commit
0f6e3acfe6
92 changed files with 4320 additions and 0 deletions
46
modules/closing/pom.xml
Normal file
46
modules/closing/pom.xml
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.klaro.acquiring</groupId>
|
||||
<artifactId>card-acquiring-boot</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<artifactId>closing</artifactId>
|
||||
<name>closing</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.klaro.acquiring</groupId>
|
||||
<artifactId>common-framework</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.klaro.acquiring</groupId>
|
||||
<artifactId>domain</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.klaro.acquiring</groupId>
|
||||
<artifactId>persistence</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.klaro.acquiring</groupId>
|
||||
<artifactId>settlement</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.klaro.acquiring</groupId>
|
||||
<artifactId>ledger</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-batch</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package com.klaro.acquiring.closing;
|
||||
|
||||
import com.klaro.acquiring.domain.entity.Settlement;
|
||||
import com.klaro.acquiring.ledger.LedgerService;
|
||||
import com.klaro.acquiring.settlement.SettlementService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.YearMonth;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 마감(cl) 서비스. 일 마감: 당일 정산 집계 + 가맹점별 원장 잔액검증.
|
||||
* 월 마감: 월중 일별 정산 순지급액 합산.
|
||||
*/
|
||||
@Service
|
||||
public class ClosingService {
|
||||
|
||||
private final SettlementService settlementService;
|
||||
private final LedgerService ledgerService;
|
||||
|
||||
public ClosingService(SettlementService settlementService, LedgerService ledgerService) {
|
||||
this.settlementService = settlementService;
|
||||
this.ledgerService = ledgerService;
|
||||
}
|
||||
|
||||
/** 일 마감. 정산 집계 + 원장 잔액검증(불일치 시 예외). */
|
||||
@Transactional(readOnly = true)
|
||||
public DailyClosing closeDaily(LocalDate bizDate) {
|
||||
List<Settlement> settlements = settlementService.listByBizDate(bizDate);
|
||||
BigDecimal gross = BigDecimal.ZERO;
|
||||
BigDecimal fee = BigDecimal.ZERO;
|
||||
BigDecimal net = BigDecimal.ZERO;
|
||||
int txnCount = 0;
|
||||
for (Settlement s : settlements) {
|
||||
gross = gross.add(s.getGrossAmount());
|
||||
fee = fee.add(s.getTotalFee());
|
||||
net = net.add(s.getNetAmount());
|
||||
txnCount += s.getTxnCount();
|
||||
// 원장 반영이 있으면 잔액검증(반영 전이면 0 == 0 통과)
|
||||
ledgerService.verifyBalance(s.getMerchId(), bizDate);
|
||||
}
|
||||
return new DailyClosing(bizDate, settlements.size(), txnCount, gross, fee, net);
|
||||
}
|
||||
|
||||
/** 월 마감. 해당 월 각 일자 일마감의 순지급액 합산. */
|
||||
@Transactional(readOnly = true)
|
||||
public MonthlyClosing closeMonthly(YearMonth month) {
|
||||
BigDecimal net = BigDecimal.ZERO;
|
||||
BigDecimal gross = BigDecimal.ZERO;
|
||||
BigDecimal fee = BigDecimal.ZERO;
|
||||
int settlementCount = 0;
|
||||
LocalDate d = month.atDay(1);
|
||||
LocalDate end = month.atEndOfMonth();
|
||||
while (!d.isAfter(end)) {
|
||||
List<Settlement> settlements = settlementService.listByBizDate(d);
|
||||
for (Settlement s : settlements) {
|
||||
gross = gross.add(s.getGrossAmount());
|
||||
fee = fee.add(s.getTotalFee());
|
||||
net = net.add(s.getNetAmount());
|
||||
settlementCount++;
|
||||
}
|
||||
d = d.plusDays(1);
|
||||
}
|
||||
return new MonthlyClosing(month, settlementCount, gross, fee, net);
|
||||
}
|
||||
|
||||
public record DailyClosing(LocalDate bizDate, int settlementCount, int txnCount,
|
||||
BigDecimal grossAmount, BigDecimal totalFee, BigDecimal netAmount) {
|
||||
}
|
||||
|
||||
public record MonthlyClosing(YearMonth month, int settlementCount,
|
||||
BigDecimal grossAmount, BigDecimal totalFee, BigDecimal netAmount) {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package com.klaro.acquiring.closing.batch;
|
||||
|
||||
import com.klaro.acquiring.closing.ClosingService;
|
||||
import com.klaro.acquiring.common.util.DateUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.configuration.annotation.StepScope;
|
||||
import org.springframework.batch.core.job.builder.JobBuilder;
|
||||
import org.springframework.batch.core.repository.JobRepository;
|
||||
import org.springframework.batch.core.step.builder.StepBuilder;
|
||||
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||
import org.springframework.batch.repeat.RepeatStatus;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
/**
|
||||
* 일 마감 배치 Job (cl). Tasklet 으로 일 마감 집계·잔액검증을 수행한다.
|
||||
*/
|
||||
@Configuration
|
||||
public class ClosingJobConfig {
|
||||
|
||||
public static final String JOB_NAME = "closingJob";
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ClosingJobConfig.class);
|
||||
|
||||
private final ClosingService closingService;
|
||||
|
||||
public ClosingJobConfig(ClosingService closingService) {
|
||||
this.closingService = closingService;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@StepScope
|
||||
public Tasklet closingTasklet(@Value("#{jobParameters['bizDate']}") String bizDate) {
|
||||
return (contribution, chunkContext) -> {
|
||||
LocalDate date = DateUtil.parse(bizDate);
|
||||
ClosingService.DailyClosing c = closingService.closeDaily(date);
|
||||
log.info("[cl] 일마감 date={} 정산건수={} 거래건수={} gross={} fee={} net={}",
|
||||
date, c.settlementCount(), c.txnCount(), c.grossAmount(), c.totalFee(), c.netAmount());
|
||||
chunkContext.getStepContext().getStepExecution().getExecutionContext()
|
||||
.put("netAmount", c.netAmount().toString());
|
||||
return RepeatStatus.FINISHED;
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Step closingStep(JobRepository jobRepository,
|
||||
PlatformTransactionManager transactionManager,
|
||||
Tasklet closingTasklet) {
|
||||
return new StepBuilder("closingStep", jobRepository)
|
||||
.tasklet(closingTasklet, transactionManager)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Job closingJob(JobRepository jobRepository, Step closingStep) {
|
||||
return new JobBuilder(JOB_NAME, jobRepository)
|
||||
.start(closingStep)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
Reference in a new issue