Skip to content

Commit db2f9ad

Browse files
SIP2-300: Always use root locale for currency values (#262)
1 parent 4cfba5a commit db2f9ad

25 files changed

Lines changed: 531 additions & 295 deletions

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ FOLIO results for all supported (TBD) SIP2 commands.
6363
| Env Property | System Property | Required | Default Value | Description |
6464
|-----------------------------------------|-------------------------------------|----------|----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
6565
| `HEALTH_CHECK_PORT_ENV_VAR` | `healthCheckPort` | - | 8081 | Health check port |
66-
| `SIP2_TEMPLATE_LOCALE` | `templateLocale` | - | root | Locale being used for freemarker template processing. The root locale is the locale whose language, country, and variant are empty ("") strings. This is regarded as the base locale of all locales, and is used as the language/country neutral locale for the locale sensitive operations. |
6766
| `SIP2_TENANT_RESOLVERS` | `sip2TenantResolvers` | - | PORT,IP_SUBNET | Approach for tenant resolution during connect/login operations |
6867
| `SIP2_TENANT_USERNAME_PREFIX_DELIMITER` | `sip2TenantUsernamePrefixDelimiter` | - | _ | Delimiter used for tenant resolution based on username prefix. |
6968

@@ -121,7 +120,8 @@ command line arguments to point it to the right path (e.g. `-conf /path/to/confi
121120
| `optional` | boolean | If a failure is caught while loading the tenant configuration from an optional store, the failure is logged, but the processing does not fail. Instead, the tenant configuration will be empty. |
122121
| `netServerOptions` | JSON object | Configuration options for the server. These are Vertx options and are numerous. See: [NetServerOptions](https://vertx.io/docs/apidocs/io/vertx/core/net/NetServerOptions.html). |
123122
| `token_cache_capacity` | int | Max token cache size. Default size is 100. |
124-
Note: edge-sip2 now requires two config files: the main bootstrap sip2.conf and tenant configuration: sip2-tenants.conf. The additional config file is required to support multi-tenants and runtime reloading of tenant configuration without restarting the edge-sip2 module.
123+
124+
> **Note:** `edge-sip2` now requires two config files: the main bootstrap sip2.conf and tenant configuration: sip2-tenants.conf. The additional config file is required to support multi-tenants and runtime reloading of tenant configuration without restarting the edge-sip2 module.
125125
126126
Here is a sample sip2-tenants.conf file:
127127
```json

pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,8 @@
428428
</configuration>
429429
<executions>
430430
<execution>
431+
<id>verify-style</id>
432+
<phase>process-classes</phase>
431433
<goals>
432434
<goal>check</goal>
433435
</goals>

src/main/java/org/folio/edge/sip2/domain/messages/PatronAccountInfo.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
public class PatronAccountInfo {
66
private String id;
77
private String feeFineType;
8-
private Double feeFineAmount;
9-
private Double feeFineRemaining;
10-
private Double feeFinePaid;
8+
private String feeFineAmount;
9+
private String feeFineRemaining;
10+
private String feeFinePaid;
1111
private String itemBarcode;
1212
private OffsetDateTime feeCreationDate;
1313
private String feeFineId;
@@ -46,19 +46,19 @@ public void setFeeFineType(String feeFineType) {
4646
this.feeFineType = feeFineType;
4747
}
4848

49-
public Double getFeeFineAmount() {
49+
public String getFeeFineAmount() {
5050
return feeFineAmount;
5151
}
5252

53-
public Double getFeeFineRemaining() {
53+
public String getFeeFineRemaining() {
5454
return feeFineRemaining;
5555
}
5656

57-
public void setFeeFineAmount(Double feeFineAmount) {
57+
public void setFeeFineAmount(String feeFineAmount) {
5858
this.feeFineAmount = feeFineAmount;
5959
}
6060

61-
public void setFeeFineRemaining(Double feeFineRemaining) {
61+
public void setFeeFineRemaining(String feeFineRemaining) {
6262
this.feeFineRemaining = feeFineRemaining;
6363
}
6464

@@ -86,11 +86,11 @@ public void setFeeDescription(String feeDescription) {
8686
this.feeDescription = feeDescription;
8787
}
8888

89-
public Double getFeeFinePaid() {
89+
public String getFeeFinePaid() {
9090
return feeFinePaid;
9191
}
9292

93-
public void setFeeFinePaid(Double feeFinePaid) {
93+
public void setFeeFinePaid(String feeFinePaid) {
9494
this.feeFinePaid = feeFinePaid;
9595
}
9696
}

src/main/java/org/folio/edge/sip2/handlers/freemarker/FreemarkerRepository.java

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,20 @@
1414
import static org.folio.edge.sip2.parser.Command.RENEW_RESPONSE;
1515
import static org.folio.edge.sip2.parser.Command.REQUEST_SC_RESEND;
1616
import static org.folio.edge.sip2.parser.Command.SC_STATUS;
17-
import static org.folio.edge.sip2.utils.Utils.getEnvOrDefault;
1817

1918
import freemarker.template.Configuration;
2019
import freemarker.template.Template;
2120
import freemarker.template.TemplateExceptionHandler;
2221
import java.io.IOException;
2322
import java.lang.invoke.MethodHandles;
2423
import java.util.EnumMap;
25-
import java.util.IllformedLocaleException;
2624
import java.util.Locale;
2725
import org.apache.logging.log4j.LogManager;
2826
import org.apache.logging.log4j.Logger;
2927
import org.folio.edge.sip2.parser.Command;
3028

3129
public class FreemarkerRepository {
3230

33-
private static final String SIP2_LOCALE_PROPERTY = "sip2TemplateLocale";
34-
private static final String SIP2_LOCALE_ENV_VAR = "SIP2_TEMPLATE_LOCALE";
35-
3631
private EnumMap<Command, Template> templates;
3732
private final Logger log;
3833

@@ -66,9 +61,6 @@ private void initializeTemplates() {
6661
configuration.setLogTemplateExceptions(false);
6762
configuration.setWrapUncheckedExceptions(true);
6863

69-
configuration.setLocale(getEnvOrDefault(
70-
SIP2_LOCALE_PROPERTY, SIP2_LOCALE_ENV_VAR, ROOT, this::parseLocale));
71-
7264
addTemplate(CHECKOUT_RESPONSE, "CheckoutResponse.ftl", configuration);
7365
addTemplate(CHECKIN_RESPONSE, "CheckinResponse.ftl", configuration);
7466
addTemplate(ACS_STATUS, "acs-status.ftl", configuration);
@@ -82,22 +74,9 @@ private void initializeTemplates() {
8274
addTemplate(RENEW_ALL_RESPONSE, "RenewAllResponse.ftl", configuration);
8375
addTemplate(FEE_PAID_RESPONSE, "FeePaidResponse.ftl", configuration);
8476
addTemplate(SC_STATUS, "acs-status.ftl", configuration);
85-
86-
}
87-
88-
private Locale parseLocale(String tag) {
89-
try {
90-
var locale = new Locale.Builder().setLanguageTag(tag).build();
91-
log.info("parseLocale:: Freemarker locale: {}", locale);
92-
return locale;
93-
} catch (IllformedLocaleException e) {
94-
log.warn("Invalid locale tag '{}', falling back to locale: root", tag);
95-
return ROOT;
96-
}
9777
}
9878

9979
private void addTemplate(Command command, String templateName, Configuration configuration) {
100-
10180
Template template;
10281

10382
try {

src/main/java/org/folio/edge/sip2/repositories/FeeFinesRepository.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import static java.lang.Boolean.FALSE;
44
import static java.lang.Boolean.TRUE;
5+
import static org.folio.edge.sip2.utils.Utils.getCurrencyValue;
56

67
import io.vertx.core.Future;
78
import io.vertx.core.json.JsonArray;
@@ -48,7 +49,6 @@ public class FeeFinesRepository {
4849
private final UsersRepository usersRepository;
4950
private Clock clock;
5051

51-
5252
@Inject
5353
FeeFinesRepository(IResourceProvider<IRequestData> resourceProvider,
5454
UsersRepository usersRepository,
@@ -92,9 +92,6 @@ public Future<JsonObject> getManualBlocksByUserId(
9292
.map(IResource::getResource);
9393
}
9494

95-
96-
97-
9895
/**
9996
* Get a patron's total fee amount.
10097
*
@@ -121,7 +118,6 @@ public Future<JsonObject> getFeeAmountByUserId(
121118
.map(IResource::getResource);
122119
}
123120

124-
125121
/**
126122
* Get a patron's account.
127123
*
@@ -509,7 +505,7 @@ public Future<FeePaidResponse> performFeePaidCommand(FeePaid feePaid, SessionDat
509505
patronAccountInfo.setId(accountJson.getString("id"));
510506
patronAccountInfo.setItemBarcode(accountJson.getString("barcode"));
511507
patronAccountInfo.setFeeFineId(accountJson.getString("feeFineId"));
512-
patronAccountInfo.setFeeFineAmount(accountJson.getDouble("amount"));
508+
patronAccountInfo.setFeeFineAmount(getCurrencyValue(accountJson, "amount"));
513509
String accountDate = getDateFromAccountJson(accountJson);
514510
patronAccountInfo.setFeeCreationDate(accountDate != null
515511
? OffsetDateTime.parse(accountDate) : null);
@@ -621,8 +617,8 @@ private void updatePatronAccountInfoList(List<PatronAccountInfo> patronAccountIn
621617
String accountId = actionJson.getString("accountId");
622618
for (PatronAccountInfo patronAccountInfo : patronAccountInfoList) {
623619
if (patronAccountInfo.getId().equals(accountId)) {
624-
patronAccountInfo.setFeeFineRemaining(actionJson.getDouble("balance"));
625-
patronAccountInfo.setFeeFinePaid(actionJson.getDouble("amountAction"));
620+
patronAccountInfo.setFeeFineRemaining(getCurrencyValue(actionJson, "balance"));
621+
patronAccountInfo.setFeeFinePaid(getCurrencyValue(actionJson, "amountAction"));
626622
break;
627623
}
628624
}

src/main/java/org/folio/edge/sip2/repositories/PatronRepository.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,22 @@
2222
import static org.folio.edge.sip2.domain.messages.enumerations.Summary.UNAVAILABLE_HOLDS;
2323
import static org.folio.edge.sip2.utils.JsonUtils.getChildString;
2424
import static org.folio.edge.sip2.utils.Utils.DEFAULT_USER_LOANS_LIMIT;
25+
import static org.folio.edge.sip2.utils.Utils.getCurrencyValue;
2526

2627
import io.vertx.core.Future;
2728
import io.vertx.core.json.JsonArray;
2829
import io.vertx.core.json.JsonObject;
2930
import jakarta.inject.Inject;
3031
import java.math.BigDecimal;
31-
import java.math.MathContext;
32+
import java.math.RoundingMode;
3233
import java.time.Clock;
3334
import java.time.OffsetDateTime;
3435
import java.util.ArrayList;
3536
import java.util.Collections;
3637
import java.util.Comparator;
3738
import java.util.EnumSet;
3839
import java.util.List;
40+
import java.util.Locale;
3941
import java.util.Objects;
4042
import java.util.Optional;
4143
import java.util.function.Predicate;
@@ -432,12 +434,11 @@ private PatronInformationResponseBuilder populateFinesCount(JsonObject accounts,
432434
private PatronInformationResponseBuilder totalAmount(
433435
SessionData sessionData, JsonObject jo,
434436
PatronInformationResponseBuilder builder) {
435-
Float total;
436437
if (jo != null) {
437438
final JsonArray arr = jo.getJsonArray(FIELD_ACCOUNTS);
438-
total = getTotalRemaining(arr);
439+
String total = getTotalRemaining(arr);
439440
log.debug(sessionData, "Total is {}", total);
440-
return builder.feeAmount(total.toString());
441+
return builder.feeAmount(total);
441442
}
442443
return null;
443444
}
@@ -447,19 +448,19 @@ private PatronStatusResponseBuilder totalAmount(
447448
PatronStatusResponseBuilder builder) {
448449

449450
final JsonArray arr = jo.getJsonArray(FIELD_ACCOUNTS);
450-
Float total = getTotalRemaining(arr);
451+
String total = getTotalRemaining(arr);
451452
log.debug(sessionData, "Total is {}", total);
452-
return builder.feeAmount(total.toString());
453+
return builder.feeAmount(total);
453454
}
454455

455-
protected static Float getTotalRemaining(JsonArray accounts) {
456+
protected static String getTotalRemaining(JsonArray accounts) {
456457
BigDecimal total = BigDecimal.ZERO;
457458
for (int i = 0; i < accounts.size(); i++) {
458459
BigDecimal bdValue = BigDecimal.valueOf(accounts.getJsonObject(i)
459460
.getFloat(FIELD_REMAINING));
460461
total = total.add(bdValue);
461462
}
462-
return total.round(MathContext.DECIMAL32).floatValue();
463+
return total.setScale(2, RoundingMode.HALF_UP).toPlainString();
463464
}
464465

465466

@@ -861,10 +862,9 @@ private List<PatronAccountInfo> getPatronAccountList(
861862
if (!"Open".equalsIgnoreCase(status) || (feeFineRemaining != null && feeFineRemaining <= 0)) {
862863
continue;
863864
}
864-
Double feeFineAmount = jo.getNumber("amount") != null
865-
? jo.getNumber("amount").doubleValue() : null;
866-
patronAccount.setFeeFineAmount(feeFineAmount);
867-
patronAccount.setFeeFineRemaining(feeFineRemaining);
865+
patronAccount.setFeeFineAmount(getCurrencyValue(jo, "amount"));
866+
patronAccount.setFeeFineRemaining(feeFineRemaining != null
867+
? String.format(Locale.ROOT, "%.2f", feeFineRemaining) : null);
868868
patronAccount.setItemBarcode(jo.getString(FIELD_BARCODE));
869869
patronAccount.setId(jo.getString("id"));
870870
patronAccount.setFeeFineId(jo.getString("feeFineId"));

src/main/java/org/folio/edge/sip2/session/SessionData.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.security.SecureRandom;
66
import java.util.ArrayList;
77
import java.util.List;
8+
import java.util.Locale;
89
import lombok.Data;
910
import lombok.EqualsAndHashCode;
1011
import lombok.RequiredArgsConstructor;
@@ -136,6 +137,7 @@ private static String generateRequestId() {
136137
return format("%06d%s", random.nextInt(1000000), "/sip2");
137138
}
138139

140+
139141
/**
140142
* Sets the tenant for the session and logs the change.
141143
*

src/main/java/org/folio/edge/sip2/utils/Utils.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.folio.edge.sip2.utils;
22

3+
import static java.util.Locale.ROOT;
4+
35
import io.micrometer.common.util.StringUtils;
46
import io.vertx.core.json.JsonObject;
57
import java.time.OffsetDateTime;
@@ -84,6 +86,11 @@ public static OffsetDateTime convertDateTime(OffsetDateTime instance, String tim
8486
return OffsetDateTime.ofInstant(instance.toInstant(), ZoneId.of(timeZone));
8587
}
8688

89+
public static String getCurrencyValue(JsonObject actionJson, String field) {
90+
var fieldValue = actionJson.getDouble(field);
91+
return fieldValue != null ? String.format(ROOT, "%.2f", fieldValue) : null;
92+
}
93+
8794
/**
8895
* Constructs a query string given a list of KV pairs parameters
8996
* a delimeter, and an "equal" delimeter.

src/main/resources/templates/lib.ftl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -683,7 +683,7 @@
683683
</#macro>
684684

685685
<#macro feeRemaining value>
686-
<@variableLengthCurrencyField id="FA" value=value/>
686+
<@variableLengthField id="FA" value=value/>
687687
</#macro>
688688

689689
<#macro accountItemIdentifier value>
@@ -707,7 +707,7 @@
707707
</#macro>
708708

709709
<#macro feePaid value>
710-
<@variableLengthCurrencyField id="FG" value=value/>
710+
<@variableLengthField id="FG" value=value/>
711711
</#macro>
712712

713713
<#macro pickupServicePoint value>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package org.folio.edge.sip2.api;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.folio.edge.sip2.support.Sip2TestCommand.sip2Exchange;
5+
6+
import java.util.UUID;
7+
import org.folio.edge.sip2.api.support.AbstractErrorDetectionEnabledTest;
8+
import org.folio.edge.sip2.domain.messages.enumerations.CurrencyType;
9+
import org.folio.edge.sip2.domain.messages.enumerations.FeeType;
10+
import org.folio.edge.sip2.domain.messages.enumerations.PaymentType;
11+
import org.folio.edge.sip2.support.model.FeePaidCommand;
12+
import org.folio.edge.sip2.support.model.Sip2Command;
13+
import org.folio.edge.sip2.support.response.FeePaidResponseParser;
14+
import org.folio.edge.sip2.support.tags.IntegrationTest;
15+
import org.folio.edge.sip2.support.wiremock.WiremockStubs;
16+
import org.junit.jupiter.api.Test;
17+
18+
@IntegrationTest
19+
class FeePaidIT extends AbstractErrorDetectionEnabledTest {
20+
21+
private static final String TIMEZONE = "Europe/Paris";
22+
23+
@Test
24+
@WiremockStubs({
25+
"/wiremock/stubs/mod-settings/200-get-locale.json",
26+
"/wiremock/stubs/mod-settings/200-get-settings.json",
27+
"/wiremock/stubs/mod-login/201-post-acs-login.json",
28+
"/wiremock/stubs/mod-users/200-get-user-by-patron-identifier.json",
29+
"/wiremock/stubs/mod-users-bl/200-get-user-by-id.json",
30+
"/wiremock/stubs/mod-fee-fines/200-get-accounts-open-status.json",
31+
"/wiremock/stubs/mod-fee-fines/201-post(accounts-bulk-pay).json",
32+
})
33+
void payFee_positive() throws Throwable {
34+
executeInSession(
35+
successLoginExchange(),
36+
sip2Exchange(
37+
feePaidCommand(),
38+
sip2Result -> {
39+
assertThat(sip2Result).isNotNull();
40+
assertSuccessfulExchange(sip2Result);
41+
42+
var respMsg = sip2Result.getResponseMessage();
43+
var response = new FeePaidResponseParser(delimiter, TIMEZONE).parse(respMsg);
44+
assertThat(response.getPaymentAccepted()).isTrue();
45+
assertThat(respMsg).contains("FG7.50");
46+
assertThat(respMsg).contains("FA17.50");
47+
}
48+
));
49+
}
50+
51+
private static Sip2Command feePaidCommand() {
52+
return FeePaidCommand.builder()
53+
.paymentType(PaymentType.VISA)
54+
.feeType(FeeType.HOLD_FEE)
55+
.currencyType(CurrencyType.USD)
56+
.feeAmount(7.5f)
57+
.institutionId(TENANT_ID)
58+
.patronIdentifier(PATRON_BARCODE)
59+
.transactionId(UUID.randomUUID().toString())
60+
.build();
61+
}
62+
}

0 commit comments

Comments
 (0)