diff --git a/src/main/java/com/yau/digitalrmb/issuance/domain/model/IssuanceBankInventory.java b/src/main/java/com/yau/digitalrmb/issuance/domain/model/IssuanceBankInventory.java new file mode 100644 index 0000000..74a5a56 --- /dev/null +++ b/src/main/java/com/yau/digitalrmb/issuance/domain/model/IssuanceBankInventory.java @@ -0,0 +1,36 @@ +package com.yau.digitalrmb.issuance.domain.model; + +import java.math.BigDecimal; +import java.util.Objects; + +public final class IssuanceBankInventory { + private final String bankCode; + private final BigDecimal currentBalance; + private final BigDecimal warningThreshold; + private final BigDecimal suggestedSupplementAmount; + + public IssuanceBankInventory(String bankCode, BigDecimal currentBalance, BigDecimal warningThreshold, + BigDecimal suggestedSupplementAmount) { + this.bankCode = Objects.requireNonNull(bankCode, "bankCode must not be null"); + this.currentBalance = Objects.requireNonNull(currentBalance, "currentBalance must not be null"); + this.warningThreshold = Objects.requireNonNull(warningThreshold, "warningThreshold must not be null"); + this.suggestedSupplementAmount = Objects.requireNonNull(suggestedSupplementAmount, + "suggestedSupplementAmount must not be null"); + } + + public String getBankCode() { + return bankCode; + } + + public BigDecimal getCurrentBalance() { + return currentBalance; + } + + public BigDecimal getWarningThreshold() { + return warningThreshold; + } + + public BigDecimal getSuggestedSupplementAmount() { + return suggestedSupplementAmount; + } +} diff --git a/src/main/java/com/yau/digitalrmb/issuance/domain/model/IssuanceRequest.java b/src/main/java/com/yau/digitalrmb/issuance/domain/model/IssuanceRequest.java index b786650..2c722d9 100644 --- a/src/main/java/com/yau/digitalrmb/issuance/domain/model/IssuanceRequest.java +++ b/src/main/java/com/yau/digitalrmb/issuance/domain/model/IssuanceRequest.java @@ -5,12 +5,16 @@ import java.math.RoundingMode; import java.time.Instant; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Objects; +import java.util.Set; public class IssuanceRequest { private final IssuanceApplicationId id; + private final String requestNo; private final String bankCode; private final String organizationId; private BigDecimal totalAmount; @@ -26,9 +30,10 @@ public class IssuanceRequest { private CentralBankReceiveStatus centralBankReceiveStatus; private Instant centralBankReceivedAt; - private IssuanceRequest(IssuanceApplicationId id, String bankCode, String organizationId, + private IssuanceRequest(IssuanceApplicationId id, String requestNo, String bankCode, String organizationId, BigDecimal totalAmount, String currency, List denominations) { this.id = Objects.requireNonNull(id, "id must not be null"); + this.requestNo = Objects.requireNonNull(requestNo, "requestNo must not be null"); this.bankCode = Objects.requireNonNull(bankCode, "bankCode must not be null"); this.organizationId = Objects.requireNonNull(organizationId, "organizationId must not be null"); applyDraftValues(totalAmount, currency, denominations); @@ -39,7 +44,29 @@ public class IssuanceRequest { public static IssuanceRequest create(IssuanceApplicationId id, String bankCode, String organizationId, BigDecimal totalAmount, String currency, List denominations) { - return new IssuanceRequest(id, bankCode, organizationId, totalAmount, currency, denominations); + return new IssuanceRequest(id, requestNoFor(id), bankCode, organizationId, totalAmount, currency, denominations); + } + + public static IssuanceRequest rehydrate(IssuanceApplicationId id, String requestNo, String bankCode, String organizationId, + BigDecimal totalAmount, String currency, + List denominations, String requestTimestamp, + String messageText, String digest, String signature, String signingKeyRef, + String payloadJson, IssuanceRequestStatus status, + CentralBankReceiveStatus centralBankReceiveStatus, + Instant centralBankReceivedAt) { + IssuanceRequest request = new IssuanceRequest(id, requestNo, bankCode, organizationId, totalAmount, currency, + denominations); + request.requestTimestamp = requestTimestamp; + request.messageText = messageText; + request.digest = digest; + request.signature = signature; + request.signingKeyRef = signingKeyRef; + request.payloadJson = payloadJson; + request.status = Objects.requireNonNull(status, "status must not be null"); + request.centralBankReceiveStatus = Objects.requireNonNull(centralBankReceiveStatus, + "centralBankReceiveStatus must not be null"); + request.centralBankReceivedAt = centralBankReceivedAt; + return request; } public void updateDraft(BigDecimal totalAmount, String currency, List denominations) { @@ -94,6 +121,10 @@ public class IssuanceRequest { return id; } + public String getRequestNo() { + return requestNo; + } + public String getBankCode() { return bankCode; } @@ -163,6 +194,10 @@ public class IssuanceRequest { this.denominations = Collections.unmodifiableList(copiedDenominations); } + private static String requestNoFor(IssuanceApplicationId id) { + return "ISSUE_REQ_" + id.value().toString().replace("-", "").toUpperCase(Locale.ROOT); + } + private BigDecimal normalizeAmount(BigDecimal totalAmount) { BigDecimal amount = Objects.requireNonNull(totalAmount, "totalAmount must not be null") .setScale(2, RoundingMode.UNNECESSARY); @@ -178,8 +213,12 @@ public class IssuanceRequest { if (copiedDenominations.isEmpty()) { throw new IllegalArgumentException("denominations must not be empty"); } + Set faceValues = new HashSet(); for (DenominationItem denomination : copiedDenominations) { Objects.requireNonNull(denomination, "denomination item must not be null"); + if (!faceValues.add(denomination.getDenomination())) { + throw new IllegalArgumentException("denomination values must be unique"); + } } return copiedDenominations; } diff --git a/src/main/java/com/yau/digitalrmb/issuance/domain/repository/IssuanceRequestRepository.java b/src/main/java/com/yau/digitalrmb/issuance/domain/repository/IssuanceRequestRepository.java new file mode 100644 index 0000000..dca2d31 --- /dev/null +++ b/src/main/java/com/yau/digitalrmb/issuance/domain/repository/IssuanceRequestRepository.java @@ -0,0 +1,16 @@ +package com.yau.digitalrmb.issuance.domain.repository; + +import com.yau.digitalrmb.issuance.domain.model.IssuanceApplicationId; +import com.yau.digitalrmb.issuance.domain.model.IssuanceBankInventory; +import com.yau.digitalrmb.issuance.domain.model.IssuanceRequest; + +import java.math.BigDecimal; +import java.util.Optional; + +public interface IssuanceRequestRepository { + void save(IssuanceRequest request); + + Optional findById(IssuanceApplicationId id); + + Optional findInventoryByBankCode(String bankCode); +} diff --git a/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/entity/IssuanceBankInventoryEntity.java b/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/entity/IssuanceBankInventoryEntity.java new file mode 100644 index 0000000..5461a20 --- /dev/null +++ b/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/entity/IssuanceBankInventoryEntity.java @@ -0,0 +1,24 @@ +package com.yau.digitalrmb.issuance.infrastructure.persistence.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Getter; +import lombok.Setter; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Getter +@Setter +@TableName("issuance_bank_inventory") +public class IssuanceBankInventoryEntity { + @TableId(value = "bank_code", type = IdType.INPUT) + private String bankCode; + + private BigDecimal currentBalance; + + private BigDecimal warningThreshold; + + private LocalDateTime updatedAt; +} diff --git a/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/entity/IssuanceRequestDenominationEntity.java b/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/entity/IssuanceRequestDenominationEntity.java new file mode 100644 index 0000000..c9d3eb0 --- /dev/null +++ b/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/entity/IssuanceRequestDenominationEntity.java @@ -0,0 +1,20 @@ +package com.yau.digitalrmb.issuance.infrastructure.persistence.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@TableName("issuance_request_denomination") +public class IssuanceRequestDenominationEntity { + @TableId(value = "request_id", type = IdType.INPUT) + private String requestId; + + private Integer denomination; + + private Integer quantity; +} diff --git a/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/entity/IssuanceRequestEntity.java b/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/entity/IssuanceRequestEntity.java new file mode 100644 index 0000000..f4a47b2 --- /dev/null +++ b/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/entity/IssuanceRequestEntity.java @@ -0,0 +1,51 @@ +package com.yau.digitalrmb.issuance.infrastructure.persistence.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Getter; +import lombok.Setter; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Getter +@Setter +@TableName("issuance_request") +public class IssuanceRequestEntity { + @TableId(value = "id", type = IdType.INPUT) + private String id; + + private String requestNo; + private String bankCode; + private String organizationId; + private BigDecimal totalAmount; + private String currency; + private String requestTimestamp; + private String messageText; + private String digest; + private String signature; + private String signingKeyRef; + private String payloadJson; + private String status; + private String centralReceiveStatus; + private LocalDateTime centralReceivedAt; + + @TableField(value = "created_at", fill = FieldFill.INSERT) + private LocalDateTime createdAt; + + @TableField(value = "updated_at", fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updatedAt; + + @TableField(value = "created_by", fill = FieldFill.INSERT) + private String createdBy; + + @TableField(value = "updated_by", fill = FieldFill.INSERT_UPDATE) + private String updatedBy; + + @TableLogic + private Boolean deleted; +} diff --git a/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/mapper/IssuanceBankInventoryMapper.java b/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/mapper/IssuanceBankInventoryMapper.java new file mode 100644 index 0000000..4f53805 --- /dev/null +++ b/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/mapper/IssuanceBankInventoryMapper.java @@ -0,0 +1,9 @@ +package com.yau.digitalrmb.issuance.infrastructure.persistence.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yau.digitalrmb.issuance.infrastructure.persistence.entity.IssuanceBankInventoryEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface IssuanceBankInventoryMapper extends BaseMapper { +} diff --git a/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/mapper/IssuanceRequestDenominationMapper.java b/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/mapper/IssuanceRequestDenominationMapper.java new file mode 100644 index 0000000..cc1ca7b --- /dev/null +++ b/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/mapper/IssuanceRequestDenominationMapper.java @@ -0,0 +1,9 @@ +package com.yau.digitalrmb.issuance.infrastructure.persistence.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yau.digitalrmb.issuance.infrastructure.persistence.entity.IssuanceRequestDenominationEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface IssuanceRequestDenominationMapper extends BaseMapper { +} diff --git a/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/mapper/IssuanceRequestMapper.java b/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/mapper/IssuanceRequestMapper.java new file mode 100644 index 0000000..3a2a58b --- /dev/null +++ b/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/mapper/IssuanceRequestMapper.java @@ -0,0 +1,9 @@ +package com.yau.digitalrmb.issuance.infrastructure.persistence.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yau.digitalrmb.issuance.infrastructure.persistence.entity.IssuanceRequestEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface IssuanceRequestMapper extends BaseMapper { +} diff --git a/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/repository/MybatisIssuanceRequestRepository.java b/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/repository/MybatisIssuanceRequestRepository.java new file mode 100644 index 0000000..a608ff0 --- /dev/null +++ b/src/main/java/com/yau/digitalrmb/issuance/infrastructure/persistence/repository/MybatisIssuanceRequestRepository.java @@ -0,0 +1,130 @@ +package com.yau.digitalrmb.issuance.infrastructure.persistence.repository; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.yau.digitalrmb.issuance.domain.model.CentralBankReceiveStatus; +import com.yau.digitalrmb.issuance.domain.model.DenominationItem; +import com.yau.digitalrmb.issuance.domain.model.IssuanceApplicationId; +import com.yau.digitalrmb.issuance.domain.model.IssuanceBankInventory; +import com.yau.digitalrmb.issuance.domain.model.IssuanceRequest; +import com.yau.digitalrmb.issuance.domain.model.IssuanceRequestStatus; +import com.yau.digitalrmb.issuance.domain.repository.IssuanceRequestRepository; +import com.yau.digitalrmb.issuance.infrastructure.persistence.entity.IssuanceBankInventoryEntity; +import com.yau.digitalrmb.issuance.infrastructure.persistence.entity.IssuanceRequestDenominationEntity; +import com.yau.digitalrmb.issuance.infrastructure.persistence.entity.IssuanceRequestEntity; +import com.yau.digitalrmb.issuance.infrastructure.persistence.mapper.IssuanceBankInventoryMapper; +import com.yau.digitalrmb.issuance.infrastructure.persistence.mapper.IssuanceRequestDenominationMapper; +import com.yau.digitalrmb.issuance.infrastructure.persistence.mapper.IssuanceRequestMapper; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Repository +public class MybatisIssuanceRequestRepository implements IssuanceRequestRepository { + private final IssuanceRequestMapper requestMapper; + private final IssuanceRequestDenominationMapper denominationMapper; + private final IssuanceBankInventoryMapper inventoryMapper; + + public MybatisIssuanceRequestRepository(IssuanceRequestMapper requestMapper, + IssuanceRequestDenominationMapper denominationMapper, + IssuanceBankInventoryMapper inventoryMapper) { + this.requestMapper = requestMapper; + this.denominationMapper = denominationMapper; + this.inventoryMapper = inventoryMapper; + } + + @Override + @Transactional + public void save(IssuanceRequest request) { + String requestId = request.getId().value().toString(); + IssuanceRequestEntity entity = toEntity(request); + if (requestMapper.selectById(requestId) == null) { + requestMapper.insert(entity); + } else { + requestMapper.updateById(entity); + } + + denominationMapper.delete(new LambdaQueryWrapper() + .eq(IssuanceRequestDenominationEntity::getRequestId, requestId)); + for (DenominationItem denomination : request.getDenominations()) { + denominationMapper.insert(toDenominationEntity(requestId, denomination)); + } + } + + @Override + public Optional findById(IssuanceApplicationId id) { + String requestId = id.value().toString(); + IssuanceRequestEntity requestEntity = requestMapper.selectById(requestId); + if (requestEntity == null) { + return Optional.empty(); + } + List denominationEntities = denominationMapper.selectList( + new LambdaQueryWrapper() + .eq(IssuanceRequestDenominationEntity::getRequestId, requestId) + .orderByDesc(IssuanceRequestDenominationEntity::getDenomination)); + return Optional.of(toAggregate(requestEntity, denominationEntities)); + } + + @Override + public Optional findInventoryByBankCode(String bankCode) { + IssuanceBankInventoryEntity inventory = inventoryMapper.selectById(bankCode); + if (inventory == null) { + return Optional.empty(); + } + BigDecimal suggested = inventory.getWarningThreshold().subtract(inventory.getCurrentBalance()); + return Optional.of(new IssuanceBankInventory(inventory.getBankCode(), inventory.getCurrentBalance(), + inventory.getWarningThreshold(), suggested.max(BigDecimal.ZERO).setScale(2))); + } + + private IssuanceRequestEntity toEntity(IssuanceRequest request) { + IssuanceRequestEntity entity = new IssuanceRequestEntity(); + entity.setId(request.getId().value().toString()); + entity.setRequestNo(request.getRequestNo()); + entity.setBankCode(request.getBankCode()); + entity.setOrganizationId(request.getOrganizationId()); + entity.setTotalAmount(request.getTotalAmount()); + entity.setCurrency(request.getCurrency()); + entity.setRequestTimestamp(request.getRequestTimestamp()); + entity.setMessageText(request.getMessageText()); + entity.setDigest(request.getDigest()); + entity.setSignature(request.getSignature()); + entity.setSigningKeyRef(request.getSigningKeyRef()); + entity.setPayloadJson(request.getPayloadJson()); + entity.setStatus(request.getStatus().name()); + entity.setCentralReceiveStatus(request.getCentralBankReceiveStatus().name()); + if (request.getCentralBankReceivedAt() != null) { + entity.setCentralReceivedAt(request.getCentralBankReceivedAt().atOffset(ZoneOffset.UTC).toLocalDateTime()); + } + return entity; + } + + private IssuanceRequestDenominationEntity toDenominationEntity(String requestId, DenominationItem denomination) { + IssuanceRequestDenominationEntity entity = new IssuanceRequestDenominationEntity(); + entity.setRequestId(requestId); + entity.setDenomination(denomination.getDenomination()); + entity.setQuantity(denomination.getQuantity()); + return entity; + } + + private IssuanceRequest toAggregate(IssuanceRequestEntity requestEntity, + List denominationEntities) { + List denominations = new ArrayList(); + for (IssuanceRequestDenominationEntity denomination : denominationEntities) { + denominations.add(new DenominationItem(denomination.getDenomination(), denomination.getQuantity())); + } + return IssuanceRequest.rehydrate(new IssuanceApplicationId(UUID.fromString(requestEntity.getId())), + requestEntity.getRequestNo(), requestEntity.getBankCode(), requestEntity.getOrganizationId(), + requestEntity.getTotalAmount(), requestEntity.getCurrency(), denominations, + requestEntity.getRequestTimestamp(), requestEntity.getMessageText(), requestEntity.getDigest(), + requestEntity.getSignature(), requestEntity.getSigningKeyRef(), requestEntity.getPayloadJson(), + IssuanceRequestStatus.valueOf(requestEntity.getStatus()), + CentralBankReceiveStatus.valueOf(requestEntity.getCentralReceiveStatus()), + requestEntity.getCentralReceivedAt() == null ? null + : requestEntity.getCentralReceivedAt().toInstant(ZoneOffset.UTC)); + } +} diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index b749a73..93642a7 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -67,3 +67,46 @@ ON DUPLICATE KEY UPDATE account = VALUES(account), display_name = VALUES(display source_updated_at = CURRENT_TIMESTAMP, synced_at = CURRENT_TIMESTAMP; INSERT IGNORE INTO sys_user_role (user_id, role_id) VALUES (487, 1002); + +CREATE TABLE IF NOT EXISTS issuance_bank_inventory ( + bank_code VARCHAR(32) PRIMARY KEY, + current_balance DECIMAL(20, 2) NOT NULL, + warning_threshold DECIMAL(20, 2) NOT NULL, + updated_at TIMESTAMP NOT NULL +); + +CREATE TABLE IF NOT EXISTS issuance_request ( + id CHAR(36) PRIMARY KEY, + request_no VARCHAR(64) NOT NULL UNIQUE, + bank_code VARCHAR(32) NOT NULL, + organization_id VARCHAR(64) NOT NULL, + total_amount DECIMAL(20, 2) NOT NULL, + currency VARCHAR(16) NOT NULL, + request_timestamp VARCHAR(32), + message_text TEXT, + digest CHAR(64), + signature TEXT, + signing_key_ref VARCHAR(64), + payload_json TEXT, + status VARCHAR(32) NOT NULL, + central_receive_status VARCHAR(32) NOT NULL, + central_received_at TIMESTAMP NULL, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, + created_by VARCHAR(64) NOT NULL, + updated_by VARCHAR(64) NOT NULL, + deleted BOOLEAN NOT NULL DEFAULT FALSE +); + +CREATE TABLE IF NOT EXISTS issuance_request_denomination ( + request_id CHAR(36) NOT NULL, + denomination INT NOT NULL, + quantity INT NOT NULL, + PRIMARY KEY (request_id, denomination) +); + +INSERT INTO issuance_bank_inventory (bank_code, current_balance, warning_threshold, updated_at) +VALUES ('BKCHCNBJ00001', 49950000.00, 50000000.00, CURRENT_TIMESTAMP) +ON DUPLICATE KEY UPDATE current_balance = VALUES(current_balance), + warning_threshold = VALUES(warning_threshold), + updated_at = CURRENT_TIMESTAMP; diff --git a/src/test/java/com/yau/digitalrmb/issuance/domain/model/IssuanceRequestTest.java b/src/test/java/com/yau/digitalrmb/issuance/domain/model/IssuanceRequestTest.java index 822ceff..fa4c223 100644 --- a/src/test/java/com/yau/digitalrmb/issuance/domain/model/IssuanceRequestTest.java +++ b/src/test/java/com/yau/digitalrmb/issuance/domain/model/IssuanceRequestTest.java @@ -37,6 +37,18 @@ class IssuanceRequestTest { .isInstanceOf(IllegalArgumentException.class); } + @Test + void rejectsDuplicateDenominationsThatCannotBeStoredAsDistinctRows() { + assertThatThrownBy(() -> IssuanceRequest.create( + REQUEST_ID, + "BKCHCNBJ00001", + "ORG_3A4B5C6D7E8F", + new BigDecimal("500.00"), + "DC", + Arrays.asList(new DenominationItem(100, 2), new DenominationItem(100, 3)))) + .isInstanceOf(IllegalArgumentException.class); + } + @Test void sendsOnlyPackagedRequestAndMarksCentralBankAsReceived() { IssuanceRequest request = preparedSignedAndPackagedRequest(); diff --git a/src/test/java/com/yau/digitalrmb/issuance/infrastructure/persistence/MybatisIssuanceRequestRepositoryTest.java b/src/test/java/com/yau/digitalrmb/issuance/infrastructure/persistence/MybatisIssuanceRequestRepositoryTest.java new file mode 100644 index 0000000..51e683c --- /dev/null +++ b/src/test/java/com/yau/digitalrmb/issuance/infrastructure/persistence/MybatisIssuanceRequestRepositoryTest.java @@ -0,0 +1,92 @@ +package com.yau.digitalrmb.issuance.infrastructure.persistence; + +import com.yau.digitalrmb.issuance.domain.model.CentralBankReceiveStatus; +import com.yau.digitalrmb.issuance.domain.model.DenominationItem; +import com.yau.digitalrmb.issuance.domain.model.IssuanceBankInventory; +import com.yau.digitalrmb.issuance.domain.model.IssuanceApplicationId; +import com.yau.digitalrmb.issuance.domain.model.IssuanceRequest; +import com.yau.digitalrmb.issuance.domain.model.IssuanceRequestStatus; +import com.yau.digitalrmb.issuance.domain.repository.IssuanceRequestRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +import java.math.BigDecimal; +import java.time.Instant; +import java.util.Arrays; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@ActiveProfiles("test") +class MybatisIssuanceRequestRepositoryTest { + + @Autowired + private IssuanceRequestRepository repository; + + @Test + void savesAndRestoresTheRequestWithItsDenominationsAndCentralBankStatus() { + IssuanceRequest sentRequest = sentRequest(); + + repository.save(sentRequest); + + IssuanceRequest restored = repository.findById(sentRequest.getId()) + .orElseThrow(() -> new AssertionError("saved request was not restored")); + + assertThat(restored.getRequestNo()).isEqualTo(sentRequest.getRequestNo()); + assertThat(restored.getBankCode()).isEqualTo("BKCHCNBJ00001"); + assertThat(restored.getOrganizationId()).isEqualTo("ORG_3A4B5C6D7E8F"); + assertThat(restored.getTotalAmount()).isEqualByComparingTo("50000.00"); + assertThat(restored.getCurrency()).isEqualTo("DC"); + assertThat(restored.getDenominations()) + .containsExactly(new DenominationItem(100, 400), new DenominationItem(50, 200)); + assertThat(restored.getRequestTimestamp()).isEqualTo("20260801103218"); + assertThat(restored.getMessageText()).isEqualTo("ISSUE|BKCHCNBJ00001|ORG_3A4B5C6D7E8F|50000.00"); + assertThat(restored.getDigest()).isEqualTo("0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"); + assertThat(restored.getSignature()).isEqualTo("base64-signature"); + assertThat(restored.getSigningKeyRef()).isEqualTo("sm2-key-02"); + assertThat(restored.getPayloadJson()).isEqualTo("{\"requestId\":\"example\"}"); + assertThat(restored.getStatus()).isEqualTo(IssuanceRequestStatus.SENT); + assertThat(restored.getCentralBankReceiveStatus()).isEqualTo(CentralBankReceiveStatus.RECEIVED); + assertThat(restored.getCentralBankReceivedAt()).isEqualTo(Instant.parse("2026-08-03T12:00:00Z")); + } + + @Test + void readsSuggestedAmountAsThresholdMinusCurrentBalance() { + IssuanceBankInventory inventory = repository.findInventoryByBankCode("BKCHCNBJ00001") + .orElseThrow(() -> new AssertionError("seeded bank inventory was not found")); + + assertThat(inventory.getCurrentBalance()).isEqualByComparingTo("49950000.00"); + assertThat(inventory.getWarningThreshold()).isEqualByComparingTo("50000000.00"); + assertThat(inventory.getSuggestedSupplementAmount()).isEqualByComparingTo("50000.00"); + } + + @Test + void savingChangedDraftReplacesRatherThanAccumulatesDenominations() { + IssuanceRequest request = IssuanceRequest.create(new IssuanceApplicationId(UUID.randomUUID()), + "BKCHCNBJ00001", "ORG_3A4B5C6D7E8F", new BigDecimal("50000.00"), "DC", + Arrays.asList(new DenominationItem(100, 400), new DenominationItem(50, 200))); + repository.save(request); + + request.updateDraft(new BigDecimal("50000.00"), "DC", Arrays.asList(new DenominationItem(100, 500))); + repository.save(request); + + IssuanceRequest restored = repository.findById(request.getId()) + .orElseThrow(() -> new AssertionError("updated request was not restored")); + assertThat(restored.getDenominations()).containsExactly(new DenominationItem(100, 500)); + } + + private IssuanceRequest sentRequest() { + IssuanceRequest request = IssuanceRequest.create(new IssuanceApplicationId(UUID.randomUUID()), + "BKCHCNBJ00001", "ORG_3A4B5C6D7E8F", new BigDecimal("50000.00"), "DC", + Arrays.asList(new DenominationItem(100, 400), new DenominationItem(50, 200))); + request.prepareMessage("20260801103218", "ISSUE|BKCHCNBJ00001|ORG_3A4B5C6D7E8F|50000.00"); + request.recordDigest("0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"); + request.recordSignature("sm2-key-02", "base64-signature"); + request.packagePayload("{\"requestId\":\"example\"}"); + request.sendToCentralBank(Instant.parse("2026-08-03T12:00:00Z")); + return request; + } +}