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
35
modules/ledger/pom.xml
Normal file
35
modules/ledger/pom.xml
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?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>ledger</artifactId>
|
||||
<name>ledger</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>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package com.klaro.acquiring.ledger;
|
||||
|
||||
import com.klaro.acquiring.common.error.ErrorCode;
|
||||
import com.klaro.acquiring.common.error.AcquiringException;
|
||||
import com.klaro.acquiring.common.util.AmountUtil;
|
||||
import com.klaro.acquiring.domain.entity.LedgerEntry;
|
||||
import com.klaro.acquiring.domain.entity.Purchase;
|
||||
import com.klaro.acquiring.domain.enums.DrCr;
|
||||
import com.klaro.acquiring.domain.enums.EntryType;
|
||||
import com.klaro.acquiring.persistence.repository.LedgerEntryRepository;
|
||||
import com.klaro.acquiring.persistence.repository.PurchaseRepository;
|
||||
import com.klaro.acquiring.domain.enums.TxStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 원장(lg) 반영·잔액검증 서비스.
|
||||
*
|
||||
* <p>매입 1건 → 대변(CREDIT, 매입총액) + 차변(DEBIT, 수수료) 전표 2건.
|
||||
* 가맹점·영업일 순잔액 = Σ대변 − Σ차변 = Σ순지급액(net) 이어야 한다.
|
||||
*/
|
||||
@Service
|
||||
public class LedgerService {
|
||||
|
||||
private final LedgerEntryRepository ledgerRepository;
|
||||
private final PurchaseRepository purchaseRepository;
|
||||
|
||||
public LedgerService(LedgerEntryRepository ledgerRepository,
|
||||
PurchaseRepository purchaseRepository) {
|
||||
this.ledgerRepository = ledgerRepository;
|
||||
this.purchaseRepository = purchaseRepository;
|
||||
}
|
||||
|
||||
/** 매입 1건을 원장에 반영(전표 2건 생성). */
|
||||
@Transactional
|
||||
public void postPurchase(Purchase p) {
|
||||
ledgerRepository.save(new LedgerEntry(p.getMerchId(), p.getBizDate(), EntryType.PURCHASE,
|
||||
DrCr.CREDIT, p.getAmount(), "PURCHASE", String.valueOf(p.getPurchaseId())));
|
||||
if (p.totalFee().signum() > 0) {
|
||||
ledgerRepository.save(new LedgerEntry(p.getMerchId(), p.getBizDate(), EntryType.FEE,
|
||||
DrCr.DEBIT, p.totalFee(), "PURCHASE", String.valueOf(p.getPurchaseId())));
|
||||
}
|
||||
}
|
||||
|
||||
/** 영업일의 모든 매입완료 건을 원장에 반영하고 반영 건수를 반환. */
|
||||
@Transactional
|
||||
public int reflectDaily(LocalDate bizDate) {
|
||||
List<Purchase> purchases = purchaseRepository.findByBizDateAndStatus(bizDate, TxStatus.DONE);
|
||||
for (Purchase p : purchases) {
|
||||
postPurchase(p);
|
||||
}
|
||||
return purchases.size();
|
||||
}
|
||||
|
||||
/** 가맹점·영업일 순잔액 = Σ대변 − Σ차변. */
|
||||
@Transactional(readOnly = true)
|
||||
public BigDecimal balanceOf(String merchId, LocalDate bizDate) {
|
||||
BigDecimal credit = BigDecimal.ZERO;
|
||||
BigDecimal debit = BigDecimal.ZERO;
|
||||
for (LedgerEntry e : ledgerRepository.findByMerchIdAndBizDate(merchId, bizDate)) {
|
||||
if (e.getDrCr() == DrCr.CREDIT) {
|
||||
credit = credit.add(e.getAmount());
|
||||
} else {
|
||||
debit = debit.add(e.getAmount());
|
||||
}
|
||||
}
|
||||
return credit.subtract(debit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 잔액검증: 원장 순잔액이 매입 순지급액 합계와 일치하는지 확인.
|
||||
* 불일치 시 예외.
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public BigDecimal verifyBalance(String merchId, LocalDate bizDate) {
|
||||
BigDecimal ledgerBalance = balanceOf(merchId, bizDate);
|
||||
BigDecimal expectedNet = purchaseRepository.findByMerchIdAndBizDate(merchId, bizDate).stream()
|
||||
.filter(p -> p.getStatus() == TxStatus.DONE || p.getStatus() == TxStatus.SETTLED)
|
||||
.map(Purchase::getNetAmount)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
if (!AmountUtil.withinTolerance(ledgerBalance, expectedNet, BigDecimal.ZERO)) {
|
||||
throw new AcquiringException(ErrorCode.BALANCE_MISMATCH,
|
||||
"원장 잔액 불일치 merch=" + merchId + " 원장=" + ledgerBalance + " 기대=" + expectedNet);
|
||||
}
|
||||
return ledgerBalance;
|
||||
}
|
||||
}
|
||||
Reference in a new issue