From bda520215c29490cd5e6ca5fa066da8df25b1631 Mon Sep 17 00:00:00 2001 From: chenyuan Date: Tue, 4 Aug 2026 13:05:29 +0800 Subject: [PATCH] feat: add denomination plan APIs --- .../query/DenominationPlanItemView.java | 19 ++++ .../query/DenominationPlanView.java | 25 +++++ ...mercialBankIssuanceApplicationService.java | 6 +- .../service/DenominationPlanService.java | 103 ++++++++++++++++++ .../domain/model/DenominationItem.java | 20 +++- .../domain/model/IssuanceRequest.java | 6 +- .../service/IssuanceMessageComposer.java | 4 +- .../IssuanceRequestDenominationEntity.java | 4 +- .../dto/DenominationItemRequest.java | 8 +- .../dto/DenominationPlanRequest.java | 14 +++ .../CommercialBankIssuanceController.java | 28 ++++- src/main/resources/schema.sql | 2 +- .../rest/IssuanceControllerTest.java | 39 +++++++ 13 files changed, 256 insertions(+), 22 deletions(-) create mode 100644 src/main/java/com/yau/digitalrmb/issuance/application/query/DenominationPlanItemView.java create mode 100644 src/main/java/com/yau/digitalrmb/issuance/application/query/DenominationPlanView.java create mode 100644 src/main/java/com/yau/digitalrmb/issuance/application/service/DenominationPlanService.java create mode 100644 src/main/java/com/yau/digitalrmb/issuance/interfaces/dto/DenominationPlanRequest.java diff --git a/src/main/java/com/yau/digitalrmb/issuance/application/query/DenominationPlanItemView.java b/src/main/java/com/yau/digitalrmb/issuance/application/query/DenominationPlanItemView.java new file mode 100644 index 0000000..e82affd --- /dev/null +++ b/src/main/java/com/yau/digitalrmb/issuance/application/query/DenominationPlanItemView.java @@ -0,0 +1,19 @@ +package com.yau.digitalrmb.issuance.application.query; + +import java.math.BigDecimal; + +public final class DenominationPlanItemView { + private final BigDecimal denomination; + private final int quantity; + private final BigDecimal subtotal; + + public DenominationPlanItemView(BigDecimal denomination, int quantity, BigDecimal subtotal) { + this.denomination = denomination; + this.quantity = quantity; + this.subtotal = subtotal; + } + + public BigDecimal getDenomination() { return denomination; } + public int getQuantity() { return quantity; } + public BigDecimal getSubtotal() { return subtotal; } +} diff --git a/src/main/java/com/yau/digitalrmb/issuance/application/query/DenominationPlanView.java b/src/main/java/com/yau/digitalrmb/issuance/application/query/DenominationPlanView.java new file mode 100644 index 0000000..6f9a11c --- /dev/null +++ b/src/main/java/com/yau/digitalrmb/issuance/application/query/DenominationPlanView.java @@ -0,0 +1,25 @@ +package com.yau.digitalrmb.issuance.application.query; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.List; + +public final class DenominationPlanView { + private final List items; + private final int totalQuantity; + private final BigDecimal totalAmount; + private final boolean valid; + + public DenominationPlanView(List items, int totalQuantity, BigDecimal totalAmount, + boolean valid) { + this.items = Collections.unmodifiableList(items); + this.totalQuantity = totalQuantity; + this.totalAmount = totalAmount; + this.valid = valid; + } + + public List getItems() { return items; } + public int getTotalQuantity() { return totalQuantity; } + public BigDecimal getTotalAmount() { return totalAmount; } + public boolean isValid() { return valid; } +} diff --git a/src/main/java/com/yau/digitalrmb/issuance/application/service/CommercialBankIssuanceApplicationService.java b/src/main/java/com/yau/digitalrmb/issuance/application/service/CommercialBankIssuanceApplicationService.java index 2f10402..cc4056d 100644 --- a/src/main/java/com/yau/digitalrmb/issuance/application/service/CommercialBankIssuanceApplicationService.java +++ b/src/main/java/com/yau/digitalrmb/issuance/application/service/CommercialBankIssuanceApplicationService.java @@ -189,10 +189,10 @@ public class CommercialBankIssuanceApplicationService { } } - private List> denominationPayload(List denominations) { - List> payload = new ArrayList>(); + private List> denominationPayload(List denominations) { + List> payload = new ArrayList>(); for (DenominationItem denomination : denominations) { - Map item = new LinkedHashMap(); + Map item = new LinkedHashMap(); item.put("denomination", denomination.getDenomination()); item.put("quantity", denomination.getQuantity()); payload.add(item); diff --git a/src/main/java/com/yau/digitalrmb/issuance/application/service/DenominationPlanService.java b/src/main/java/com/yau/digitalrmb/issuance/application/service/DenominationPlanService.java new file mode 100644 index 0000000..5584be3 --- /dev/null +++ b/src/main/java/com/yau/digitalrmb/issuance/application/service/DenominationPlanService.java @@ -0,0 +1,103 @@ +package com.yau.digitalrmb.issuance.application.service; + +import com.yau.digitalrmb.issuance.application.query.DenominationPlanItemView; +import com.yau.digitalrmb.issuance.application.query.DenominationPlanView; +import com.yau.digitalrmb.issuance.interfaces.dto.DenominationItemRequest; +import com.yau.digitalrmb.shared.api.ErrorCode; +import com.yau.digitalrmb.shared.exception.BusinessException; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +public class DenominationPlanService { + private static final List STANDARD_DENOMINATIONS = Arrays.asList( + new BigDecimal("100.00"), new BigDecimal("50.00"), new BigDecimal("20.00"), + new BigDecimal("10.00"), new BigDecimal("5.00"), new BigDecimal("1.00"), + new BigDecimal("0.50"), new BigDecimal("0.20"), new BigDecimal("0.10"), + new BigDecimal("0.05"), new BigDecimal("0.01")); + private static final int[] DEFAULT_QUANTITIES = {400, 100, 200, 50, 80, 100, 0, 0, 0, 0, 0}; + private static final BigDecimal DEFAULT_TOTAL_AMOUNT = new BigDecimal("50000.00"); + + public DenominationPlanView defaultPlan() { + List items = new ArrayList(); + for (int index = 0; index < STANDARD_DENOMINATIONS.size(); index++) { + DenominationItemRequest item = new DenominationItemRequest(); + item.setDenomination(STANDARD_DENOMINATIONS.get(index)); + item.setQuantity(DEFAULT_QUANTITIES[index]); + items.add(item); + } + return validate(DEFAULT_TOTAL_AMOUNT, items); + } + + public DenominationPlanView validate(BigDecimal totalAmount, List items) { + BigDecimal requestedTotal = normalizeAmount(totalAmount); + Map quantities = quantitiesByDenomination(items); + List planItems = new ArrayList(); + BigDecimal calculatedTotal = BigDecimal.ZERO.setScale(2); + int totalQuantity = 0; + for (BigDecimal denomination : STANDARD_DENOMINATIONS) { + int quantity = quantities.get(denomination); + BigDecimal subtotal = denomination.multiply(BigDecimal.valueOf(quantity)).setScale(2); + planItems.add(new DenominationPlanItemView(denomination, quantity, subtotal)); + calculatedTotal = calculatedTotal.add(subtotal); + totalQuantity = Math.addExact(totalQuantity, quantity); + } + return new DenominationPlanView(planItems, totalQuantity, calculatedTotal, + calculatedTotal.compareTo(requestedTotal) == 0); + } + + private Map quantitiesByDenomination(List items) { + if (items == null || items.size() != STANDARD_DENOMINATIONS.size()) { + throw validationError("items must contain every standard denomination"); + } + Map quantities = new HashMap(); + for (DenominationItemRequest item : items) { + if (item == null || item.getDenomination() == null || item.getQuantity() == null) { + throw validationError("denomination item must include denomination and quantity"); + } + BigDecimal denomination = normalizeDenomination(item.getDenomination()); + if (!STANDARD_DENOMINATIONS.contains(denomination) || item.getQuantity() < 0 + || quantities.put(denomination, item.getQuantity()) != null) { + throw validationError("denomination plan contains an invalid item"); + } + } + if (quantities.size() != STANDARD_DENOMINATIONS.size()) { + throw validationError("denomination plan must not omit a standard denomination"); + } + return quantities; + } + + private BigDecimal normalizeAmount(BigDecimal totalAmount) { + if (totalAmount == null) { + throw validationError("totalAmount must not be null"); + } + try { + BigDecimal amount = totalAmount.setScale(2, RoundingMode.UNNECESSARY); + if (amount.signum() <= 0) { + throw validationError("totalAmount must be positive"); + } + return amount; + } catch (ArithmeticException exception) { + throw validationError("totalAmount must have at most two decimal places"); + } + } + + private BigDecimal normalizeDenomination(BigDecimal denomination) { + try { + return denomination.setScale(2, RoundingMode.UNNECESSARY); + } catch (ArithmeticException exception) { + throw validationError("denomination must have at most two decimal places"); + } + } + + private BusinessException validationError(String message) { + return new BusinessException(ErrorCode.VALIDATION_ERROR, message); + } +} diff --git a/src/main/java/com/yau/digitalrmb/issuance/domain/model/DenominationItem.java b/src/main/java/com/yau/digitalrmb/issuance/domain/model/DenominationItem.java index 1e18436..52a922d 100644 --- a/src/main/java/com/yau/digitalrmb/issuance/domain/model/DenominationItem.java +++ b/src/main/java/com/yau/digitalrmb/issuance/domain/model/DenominationItem.java @@ -1,24 +1,32 @@ package com.yau.digitalrmb.issuance.domain.model; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.Objects; public final class DenominationItem { - private final int denomination; + private final BigDecimal denomination; private final int quantity; public DenominationItem(int denomination, int quantity) { - if (denomination <= 0) { + this(BigDecimal.valueOf(denomination), quantity); + } + + public DenominationItem(BigDecimal denomination, int quantity) { + BigDecimal normalizedDenomination = Objects.requireNonNull(denomination, "denomination must not be null") + .setScale(2, RoundingMode.UNNECESSARY); + if (normalizedDenomination.signum() <= 0) { throw new IllegalArgumentException("denomination must be positive"); } if (quantity <= 0) { throw new IllegalArgumentException("quantity must be positive"); } - this.denomination = denomination; + this.denomination = normalizedDenomination; this.quantity = quantity; } - public int getDenomination() { + public BigDecimal getDenomination() { return denomination; } @@ -35,11 +43,11 @@ public final class DenominationItem { return false; } DenominationItem that = (DenominationItem) other; - return denomination == that.denomination && quantity == that.quantity; + return denomination.compareTo(that.denomination) == 0 && quantity == that.quantity; } @Override public int hashCode() { - return Objects.hash(denomination, quantity); + return Objects.hash(denomination.stripTrailingZeros(), quantity); } } 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 7b1d5d9..4f72f5a 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 @@ -208,10 +208,10 @@ public class IssuanceRequest { if (copiedDenominations.isEmpty()) { throw new IllegalArgumentException("denominations must not be empty"); } - Set faceValues = new HashSet(); + Set faceValues = new HashSet(); for (DenominationItem denomination : copiedDenominations) { Objects.requireNonNull(denomination, "denomination item must not be null"); - if (!faceValues.add(denomination.getDenomination())) { + if (!faceValues.add(denomination.getDenomination().stripTrailingZeros())) { throw new IllegalArgumentException("denomination values must be unique"); } } @@ -221,7 +221,7 @@ public class IssuanceRequest { private BigDecimal calculateDenominationTotal(List denominations) { BigDecimal total = BigDecimal.ZERO; for (DenominationItem denomination : denominations) { - total = total.add(BigDecimal.valueOf(denomination.getDenomination()) + total = total.add(denomination.getDenomination() .multiply(BigDecimal.valueOf(denomination.getQuantity()))); } return total.setScale(2); diff --git a/src/main/java/com/yau/digitalrmb/issuance/domain/service/IssuanceMessageComposer.java b/src/main/java/com/yau/digitalrmb/issuance/domain/service/IssuanceMessageComposer.java index e05ba66..295a47e 100644 --- a/src/main/java/com/yau/digitalrmb/issuance/domain/service/IssuanceMessageComposer.java +++ b/src/main/java/com/yau/digitalrmb/issuance/domain/service/IssuanceMessageComposer.java @@ -28,7 +28,7 @@ public final class IssuanceMessageComposer { Collections.sort(orderedDenominations, new Comparator() { @Override public int compare(DenominationItem left, DenominationItem right) { - return Integer.compare(right.getDenomination(), left.getDenomination()); + return right.getDenomination().compareTo(left.getDenomination()); } }); @@ -37,7 +37,7 @@ public final class IssuanceMessageComposer { if (denominationText.length() > 0) { denominationText.append(','); } - denominationText.append(denomination.getDenomination()) + denominationText.append(denomination.getDenomination().stripTrailingZeros().toPlainString()) .append(':') .append(denomination.getQuantity()); } 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 index c9d3eb0..3e3c4c2 100644 --- 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 @@ -7,6 +7,8 @@ import com.baomidou.mybatisplus.annotation.TableName; import lombok.Getter; import lombok.Setter; +import java.math.BigDecimal; + @Getter @Setter @TableName("issuance_request_denomination") @@ -14,7 +16,7 @@ public class IssuanceRequestDenominationEntity { @TableId(value = "request_id", type = IdType.INPUT) private String requestId; - private Integer denomination; + private BigDecimal denomination; private Integer quantity; } diff --git a/src/main/java/com/yau/digitalrmb/issuance/interfaces/dto/DenominationItemRequest.java b/src/main/java/com/yau/digitalrmb/issuance/interfaces/dto/DenominationItemRequest.java index 14b3a0d..8a8e226 100644 --- a/src/main/java/com/yau/digitalrmb/issuance/interfaces/dto/DenominationItemRequest.java +++ b/src/main/java/com/yau/digitalrmb/issuance/interfaces/dto/DenominationItemRequest.java @@ -2,14 +2,16 @@ package com.yau.digitalrmb.issuance.interfaces.dto; import io.swagger.v3.oas.annotations.media.Schema; +import java.math.BigDecimal; + @Schema(description = "数字货币面额明细") public class DenominationItemRequest { @Schema(description = "面额", example = "100") - private Integer denomination; + private BigDecimal denomination; @Schema(description = "数量", example = "400") private Integer quantity; - public Integer getDenomination() { return denomination; } - public void setDenomination(Integer denomination) { this.denomination = denomination; } + public BigDecimal getDenomination() { return denomination; } + public void setDenomination(BigDecimal denomination) { this.denomination = denomination; } public Integer getQuantity() { return quantity; } public void setQuantity(Integer quantity) { this.quantity = quantity; } } diff --git a/src/main/java/com/yau/digitalrmb/issuance/interfaces/dto/DenominationPlanRequest.java b/src/main/java/com/yau/digitalrmb/issuance/interfaces/dto/DenominationPlanRequest.java new file mode 100644 index 0000000..349e011 --- /dev/null +++ b/src/main/java/com/yau/digitalrmb/issuance/interfaces/dto/DenominationPlanRequest.java @@ -0,0 +1,14 @@ +package com.yau.digitalrmb.issuance.interfaces.dto; + +import java.math.BigDecimal; +import java.util.List; + +public class DenominationPlanRequest { + private BigDecimal totalAmount; + private List items; + + public BigDecimal getTotalAmount() { return totalAmount; } + public void setTotalAmount(BigDecimal totalAmount) { this.totalAmount = totalAmount; } + public List getItems() { return items; } + public void setItems(List items) { this.items = items; } +} diff --git a/src/main/java/com/yau/digitalrmb/issuance/interfaces/rest/CommercialBankIssuanceController.java b/src/main/java/com/yau/digitalrmb/issuance/interfaces/rest/CommercialBankIssuanceController.java index 6991ad0..637a0c0 100644 --- a/src/main/java/com/yau/digitalrmb/issuance/interfaces/rest/CommercialBankIssuanceController.java +++ b/src/main/java/com/yau/digitalrmb/issuance/interfaces/rest/CommercialBankIssuanceController.java @@ -3,11 +3,14 @@ package com.yau.digitalrmb.issuance.interfaces.rest; import com.yau.digitalrmb.issuance.application.command.CreateIssuanceRequestCommand; import com.yau.digitalrmb.issuance.application.command.UpdateIssuanceRequestCommand; import com.yau.digitalrmb.issuance.application.query.CommercialBankInventoryView; +import com.yau.digitalrmb.issuance.application.query.DenominationPlanView; import com.yau.digitalrmb.issuance.application.query.IssuanceRequestView; import com.yau.digitalrmb.issuance.application.service.CommercialBankIssuanceApplicationService; +import com.yau.digitalrmb.issuance.application.service.DenominationPlanService; import com.yau.digitalrmb.issuance.domain.model.DenominationItem; import com.yau.digitalrmb.issuance.interfaces.dto.CreateIssuanceRequest; import com.yau.digitalrmb.issuance.interfaces.dto.DenominationItemRequest; +import com.yau.digitalrmb.issuance.interfaces.dto.DenominationPlanRequest; import com.yau.digitalrmb.issuance.interfaces.dto.UpdateIssuanceRequest; import com.yau.digitalrmb.shared.api.ApiResponse; import com.yau.digitalrmb.shared.web.TraceIdFilter; @@ -19,16 +22,26 @@ import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import java.util.*; -import java.util.stream.Collectors; @RestController @RequestMapping("/api/v1/commercial-banks/issuance") @Tag(name="数字货币发行模块 - 商业银行端", description="商业银行发起和处理数字货币发行申请") public class CommercialBankIssuanceController { private final CommercialBankIssuanceApplicationService service; - public CommercialBankIssuanceController(CommercialBankIssuanceApplicationService service){this.service=service;} + private final DenominationPlanService denominationPlanService; + public CommercialBankIssuanceController(CommercialBankIssuanceApplicationService service, + DenominationPlanService denominationPlanService){ + this.service=service; + this.denominationPlanService=denominationPlanService; + } @GetMapping("/inventory") @Operation(summary="查询货币需求信息", description="商业银行端查询库存余额、预警阈值和建议补充金额") public ApiResponse inventory(){return ok(service.getInventory());} + @GetMapping("/denomination-plan/default") @Operation(summary="获取默认面额结构", description="返回原型预置的11种标准面额、拆解数量和自动计算结果") + public ApiResponse defaultDenominationPlan(){return ok(denominationPlanService.defaultPlan());} + @PostMapping("/denomination-plan/validate") @Operation(summary="校验定制面额结构", description="自动计算小计、总张数和总金额,并返回是否等于申请总额") + public ApiResponse validateDenominationPlan(@RequestBody DenominationPlanRequest request){ + return ok(denominationPlanService.validate(request.getTotalAmount(), request.getItems())); + } @PostMapping("/requests") @Operation(summary="创建发行申请", description="商业银行端创建数字货币发行申请草稿") public ApiResponse create(@Valid @RequestBody CreateIssuanceRequest request,@AuthenticationPrincipal Jwt jwt){return ok(service.create(new CreateIssuanceRequestCommand(request.getBankCode(),request.getOrganizationId(),request.getTotalAmount(),request.getCurrency(),items(request.getDenominations())),actor(jwt)));} @PutMapping("/requests/{id}") @Operation(summary="更新发行申请", description="商业银行端仅可更新草稿状态的发行申请") @@ -39,7 +52,16 @@ public class CommercialBankIssuanceController { @PostMapping("/requests/{id}/sign") @Operation(summary="生成SM2签名",description="使用实验密钥sm2-key-02生成SM2签名") public ApiResponse sign(@PathVariable UUID id,@AuthenticationPrincipal Jwt jwt){return ok(service.sign(id,actor(jwt)));} @PostMapping("/requests/{id}/package") @Operation(summary="封装发行请求报文",description="封装包含摘要与签名的JSON发行请求报文") public ApiResponse pack(@PathVariable UUID id,@AuthenticationPrincipal Jwt jwt){return ok(service.packagePayload(id,actor(jwt)));} @PostMapping("/requests/{id}/send") @Operation(summary="发送发行请求",description="发送至中央银行端并更新中央银行接收状态") public ApiResponse send(@PathVariable UUID id,@AuthenticationPrincipal Jwt jwt){return ok(service.send(id,actor(jwt)));} - private List items(List input){return input.stream().map(i->new DenominationItem(i.getDenomination(),i.getQuantity())).collect(Collectors.toList());} + private List items(List input){ + List result = new ArrayList(); + for (DenominationItemRequest item : input) { + if (item.getQuantity() != null && item.getQuantity() == 0) { + continue; + } + result.add(new DenominationItem(item.getDenomination(), item.getQuantity())); + } + return result; + } private String actor(Jwt jwt){String account=jwt.getClaimAsString("preferred_username"); return account==null||account.trim().isEmpty()?jwt.getSubject():account;} private ApiResponse ok(T data){return ApiResponse.success(data,MDC.get(TraceIdFilter.MDC_KEY));} } diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql index 29d7aa9..ae2c631 100644 --- a/src/main/resources/schema.sql +++ b/src/main/resources/schema.sql @@ -110,7 +110,7 @@ CREATE TABLE IF NOT EXISTS issuance_request ( CREATE TABLE IF NOT EXISTS issuance_request_denomination ( request_id CHAR(36) NOT NULL, - denomination INT NOT NULL, + denomination DECIMAL(10, 2) NOT NULL, quantity INT NOT NULL, PRIMARY KEY (request_id, denomination) ); diff --git a/src/test/java/com/yau/digitalrmb/issuance/interfaces/rest/IssuanceControllerTest.java b/src/test/java/com/yau/digitalrmb/issuance/interfaces/rest/IssuanceControllerTest.java index 1a9045c..51d4107 100644 --- a/src/test/java/com/yau/digitalrmb/issuance/interfaces/rest/IssuanceControllerTest.java +++ b/src/test/java/com/yau/digitalrmb/issuance/interfaces/rest/IssuanceControllerTest.java @@ -62,6 +62,45 @@ class IssuanceControllerTest { .andExpect(jsonPath("$.data.suggestedSupplementAmount").value(50000.00)); } + @Test + void returnsThePrototypeDefaultDenominationPlan() throws Exception { + mockMvc.perform(get("/api/v1/commercial-banks/issuance/denomination-plan/default") + .with(jwt().jwt(jwt -> jwt.subject("487").claim("preferred_username", "tzs001")))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.items.length()").value(11)) + .andExpect(jsonPath("$.data.items[0].denomination").value(100)) + .andExpect(jsonPath("$.data.items[0].quantity").value(400)) + .andExpect(jsonPath("$.data.items[0].subtotal").value(40000.00)) + .andExpect(jsonPath("$.data.totalQuantity").value(930)) + .andExpect(jsonPath("$.data.totalAmount").value(50000.00)) + .andExpect(jsonPath("$.data.valid").value(true)); + } + + @Test + void validatesDenominationPlanTotalsForTheConfirmationAction() throws Exception { + String requestBody = "{\"totalAmount\":50000.00,\"items\":[{\"denomination\":100,\"quantity\":400},{\"denomination\":50,\"quantity\":100},{\"denomination\":20,\"quantity\":200},{\"denomination\":10,\"quantity\":50},{\"denomination\":5,\"quantity\":80},{\"denomination\":1,\"quantity\":100},{\"denomination\":0.5,\"quantity\":0},{\"denomination\":0.2,\"quantity\":0},{\"denomination\":0.1,\"quantity\":0},{\"denomination\":0.05,\"quantity\":0},{\"denomination\":0.01,\"quantity\":0}]}"; + + mockMvc.perform(post("/api/v1/commercial-banks/issuance/denomination-plan/validate") + .with(jwt().jwt(jwt -> jwt.subject("487").claim("preferred_username", "tzs001"))) + .contentType(MediaType.APPLICATION_JSON).content(requestBody)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.totalQuantity").value(930)) + .andExpect(jsonPath("$.data.totalAmount").value(50000.00)) + .andExpect(jsonPath("$.data.valid").value(true)); + } + + @Test + void createsAnIssuanceRequestWhenThePlanIncludesZeroQuantityRows() throws Exception { + String requestBody = "{\"bankCode\":\"BKCHCNBJ00001\",\"organizationId\":\"ORG_3A4B5C6D7E8F\",\"totalAmount\":50000.00,\"currency\":\"DC\",\"denominations\":[{\"denomination\":100,\"quantity\":400},{\"denomination\":50,\"quantity\":100},{\"denomination\":20,\"quantity\":200},{\"denomination\":10,\"quantity\":50},{\"denomination\":5,\"quantity\":80},{\"denomination\":1,\"quantity\":100},{\"denomination\":0.5,\"quantity\":0},{\"denomination\":0.2,\"quantity\":0},{\"denomination\":0.1,\"quantity\":0},{\"denomination\":0.05,\"quantity\":0},{\"denomination\":0.01,\"quantity\":0}]}"; + + mockMvc.perform(post("/api/v1/commercial-banks/issuance/requests") + .with(jwt().jwt(jwt -> jwt.subject("487").claim("preferred_username", "tzs001"))) + .contentType(MediaType.APPLICATION_JSON).content(requestBody)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.status").value("DRAFT")) + .andExpect(jsonPath("$.data.denominations.length()").value(6)); + } + @Test void swaggerDocumentsBothChineseIssuanceEnds() throws Exception { String apiDocs = new String(mockMvc.perform(get("/v3/api-docs"))