Compare commits

..

No commits in common. "9a9e3e2e7375fc70e09ceab1cbe595e5479f5b77" and "07b5e4686bc954e1d4cb9050f6ac93c939475add" have entirely different histories.

15 changed files with 17 additions and 219 deletions

View File

@ -1,28 +0,0 @@
package net.hostsharing.hsadminng.config;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;
public class FormattedBigDecimalDeserializer extends JsonDeserializer<BigDecimal> {
@Override
public BigDecimal deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
final var numberStr = p.getText();
try {
final var format = NumberFormat.getInstance(Locale.GERMANY);
if (format instanceof DecimalFormat) {
((DecimalFormat) format).setParseBigDecimal(true);
}
return (BigDecimal) format.parse(numberStr);
} catch (final ParseException e) {
throw new IOException("Failed to parse BigDecimal from string: " + numberStr, e);
}
}
}

View File

@ -2,7 +2,6 @@ package net.hostsharing.hsadminng.config;
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.openapitools.jackson.nullable.JsonNullableModule; import org.openapitools.jackson.nullable.JsonNullableModule;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -10,25 +9,15 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import java.math.BigDecimal;
@Configuration @Configuration
public class JsonObjectMapperConfiguration { public class JsonObjectMapperConfiguration {
@Bean @Bean
@Primary @Primary
public Jackson2ObjectMapperBuilder customObjectMapper() { public Jackson2ObjectMapperBuilder customObjectMapper() {
// HOWTO: add JSON converters and specify other JSON mapping configurations
return new Jackson2ObjectMapperBuilder() return new Jackson2ObjectMapperBuilder()
.modulesToInstall(formattedBigDecimalModule())
.modules(new JsonNullableModule(), new JavaTimeModule()) .modules(new JsonNullableModule(), new JavaTimeModule())
.featuresToEnable(JsonParser.Feature.ALLOW_COMMENTS, JsonParser.Feature.ALLOW_COMMENTS) .featuresToEnable(JsonParser.Feature.ALLOW_COMMENTS, JsonParser.Feature.ALLOW_COMMENTS)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
} }
private SimpleModule formattedBigDecimalModule() {
final var module = new SimpleModule();
module.addDeserializer(BigDecimal.class, new FormattedBigDecimalDeserializer());
return module;
}
} }

View File

@ -4,7 +4,6 @@ import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter; import jakarta.persistence.Converter;
import java.util.stream.Stream; import java.util.stream.Stream;
// HOWTO: convert data types for exchange between PostgreSQL and Java/Hibernate/JPA-Entities
@Converter(autoApply = true) @Converter(autoApply = true)
public class HsOfficePersonTypeConverter implements AttributeConverter<HsOfficePersonType, String> { public class HsOfficePersonTypeConverter implements AttributeConverter<HsOfficePersonType, String> {

View File

@ -22,7 +22,7 @@ create table if not exists hs_office.coopassettx
membershipUuid uuid not null references hs_office.membership(uuid), membershipUuid uuid not null references hs_office.membership(uuid),
transactionType hs_office.CoopAssetsTransactionType not null, transactionType hs_office.CoopAssetsTransactionType not null,
valueDate date not null, valueDate date not null,
assetValue numeric(12,2) not null, -- TODO.impl: use money type, but we had problems with Hibernate conversion assetValue money not null,
reference varchar(48) not null, reference varchar(48) not null,
revertedAssetTxUuid uuid unique REFERENCES hs_office.coopassettx(uuid) DEFERRABLE INITIALLY DEFERRED, revertedAssetTxUuid uuid unique REFERENCES hs_office.coopassettx(uuid) DEFERRABLE INITIALLY DEFERRED,
comment varchar(512) comment varchar(512)
@ -44,12 +44,12 @@ alter table hs_office.coopassettx
--changeset michael.hoennig:hs-office-coopassets-ASSET-VALUE-CONSTRAINT endDelimiter:--// --changeset michael.hoennig:hs-office-coopassets-ASSET-VALUE-CONSTRAINT endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
create or replace function hs_office.coopassetstx_check_positive_total(forMembershipUuid UUID, newAssetValue numeric(12, 5)) create or replace function hs_office.coopassetstx_check_positive_total(forMembershipUuid UUID, newAssetValue money)
returns boolean returns boolean
language plpgsql as $$ language plpgsql as $$
declare declare
currentAssetValue numeric(12,2); currentAssetValue money;
totalAssetValue numeric(12,2); totalAssetValue money;
begin begin
select sum(cat.assetValue) select sum(cat.assetValue)
from hs_office.coopassettx cat from hs_office.coopassettx cat

View File

@ -69,7 +69,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
final var newCoopAssetsTransaction = HsOfficeCoopAssetsTransactionEntity.builder() final var newCoopAssetsTransaction = HsOfficeCoopAssetsTransactionEntity.builder()
.membership(givenMembership) .membership(givenMembership)
.transactionType(HsOfficeCoopAssetsTransactionType.DEPOSIT) .transactionType(HsOfficeCoopAssetsTransactionType.DEPOSIT)
.assetValue(new BigDecimal("6,400.00")) .assetValue(new BigDecimal("128.00"))
.valueDate(LocalDate.parse("2022-10-18")) .valueDate(LocalDate.parse("2022-10-18"))
.reference("temp ref A") .reference("temp ref A")
.build(); .build();
@ -98,7 +98,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
final var newCoopAssetsTransaction = HsOfficeCoopAssetsTransactionEntity.builder() final var newCoopAssetsTransaction = HsOfficeCoopAssetsTransactionEntity.builder()
.membership(givenMembership) .membership(givenMembership)
.transactionType(HsOfficeCoopAssetsTransactionType.DEPOSIT) .transactionType(HsOfficeCoopAssetsTransactionType.DEPOSIT)
.assetValue(new BigDecimal("6400.00")) .assetValue(new BigDecimal("128.00"))
.valueDate(LocalDate.parse("2022-10-18")) .valueDate(LocalDate.parse("2022-10-18"))
.reference("temp ref B") .reference("temp ref B")
.build(); .build();

View File

@ -12,13 +12,10 @@ import net.hostsharing.hsadminng.hs.office.scenarios.debitor.FinallyDeleteSepaMa
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.DontDeleteDefaultDebitor; import net.hostsharing.hsadminng.hs.office.scenarios.debitor.DontDeleteDefaultDebitor;
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.InvalidateSepaMandateForDebitor; import net.hostsharing.hsadminng.hs.office.scenarios.debitor.InvalidateSepaMandateForDebitor;
import net.hostsharing.hsadminng.hs.office.scenarios.membership.CancelMembership; import net.hostsharing.hsadminng.hs.office.scenarios.membership.CancelMembership;
import net.hostsharing.hsadminng.hs.office.scenarios.membership.CreateCoopSharesCancellationTransaction;
import net.hostsharing.hsadminng.hs.office.scenarios.membership.CreateCoopSharesRevertTransaction;
import net.hostsharing.hsadminng.hs.office.scenarios.membership.CreateCoopSharesSubscriptionTransaction;
import net.hostsharing.hsadminng.hs.office.scenarios.membership.CreateMembership; import net.hostsharing.hsadminng.hs.office.scenarios.membership.CreateMembership;
import net.hostsharing.hsadminng.hs.office.scenarios.membership.coopassets.CreateCoopAssetsDepositTransaction;
import net.hostsharing.hsadminng.hs.office.scenarios.membership.coopassets.CreateCoopAssetsDisbursalTransaction;
import net.hostsharing.hsadminng.hs.office.scenarios.membership.coopassets.CreateCoopAssetsRevertTransaction;
import net.hostsharing.hsadminng.hs.office.scenarios.membership.coopshares.CreateCoopSharesCancellationTransaction;
import net.hostsharing.hsadminng.hs.office.scenarios.membership.coopshares.CreateCoopSharesRevertTransaction;
import net.hostsharing.hsadminng.hs.office.scenarios.membership.coopshares.CreateCoopSharesSubscriptionTransaction;
import net.hostsharing.hsadminng.hs.office.scenarios.partner.AddOperationsContactToPartner; import net.hostsharing.hsadminng.hs.office.scenarios.partner.AddOperationsContactToPartner;
import net.hostsharing.hsadminng.hs.office.scenarios.partner.CreatePartner; import net.hostsharing.hsadminng.hs.office.scenarios.partner.CreatePartner;
import net.hostsharing.hsadminng.hs.office.scenarios.debitor.DeleteDebitor; import net.hostsharing.hsadminng.hs.office.scenarios.debitor.DeleteDebitor;
@ -351,7 +348,7 @@ class HsOfficeScenarioTests extends ScenarioTest {
void shouldRevertCoopSharesSubscription() { void shouldRevertCoopSharesSubscription() {
new CreateCoopSharesRevertTransaction(this) new CreateCoopSharesRevertTransaction(this)
.given("memberNumber", "3101000") .given("memberNumber", "3101000")
.given("comment", "reverting some incorrect transaction") .given("comment", "reverting some incorrect subscription")
.given("dateOfIncorrectTransaction", "2024-02-15") .given("dateOfIncorrectTransaction", "2024-02-15")
.doRun(); .doRun();
} }
@ -370,45 +367,6 @@ class HsOfficeScenarioTests extends ScenarioTest {
.doRun(); .doRun();
} }
@Test
@Order(4301)
@Requires("Membership: M-3101000 - Test AG")
@Produces("Coop-Assets DEPOSIT Transaction")
void shouldSubscribeCoopAssets() {
new CreateCoopAssetsDepositTransaction(this)
.given("memberNumber", "3101000")
.given("reference", "sign 2024-01-15")
.given("assetValue", 100*64)
.given("comment", "disposal for initial shares")
.given("transactionDate", "2024-01-15")
.doRun();
}
@Test
@Order(4302)
@Requires("Membership: M-3101000 - Test AG")
void shouldRevertCoopAssetsSubscription() {
new CreateCoopAssetsRevertTransaction(this)
.given("memberNumber", "3101000")
.given("comment", "reverting some incorrect transaction")
.given("dateOfIncorrectTransaction", "2024-02-15")
.doRun();
}
@Test
@Order(4302)
@Requires("Coop-Assets DEPOSIT Transaction")
@Produces("Coop-Assets DISBURSAL Transaction")
void shouldDisburseCoopAssets() {
new CreateCoopAssetsDisbursalTransaction(this)
.given("memberNumber", "3101000")
.given("reference", "cancel 2024-01-15")
.given("valueToDisburse", 8*64)
.given("comment", "disbursal according to shares cancellation")
.given("transactionDate", "2024-02-15")
.doRun();
}
@Test @Test
@Order(4900) @Order(4900)
@Requires("Membership: M-3101000 - Test AG") @Requires("Membership: M-3101000 - Test AG")

View File

@ -19,7 +19,7 @@ public class PathAssertion {
public Consumer<UseCase.HttpResponse> contains(final String resolvableValue) { public Consumer<UseCase.HttpResponse> contains(final String resolvableValue) {
return response -> { return response -> {
try { try {
response.path(path).map(this::asString).contains(ScenarioTest.resolve(resolvableValue, DROP_COMMENTS)); response.path(path).map(Object::toString).contains(ScenarioTest.resolve(resolvableValue, DROP_COMMENTS));
} catch (final AssertionError e) { } catch (final AssertionError e) {
// without this, the error message is often lacking important context // without this, the error message is often lacking important context
fail(e.getMessage() + " in `path(\"" + path + "\").contains(\"" + resolvableValue + "\")`" ); fail(e.getMessage() + " in `path(\"" + path + "\").contains(\"" + resolvableValue + "\")`" );
@ -37,15 +37,4 @@ public class PathAssertion {
} }
}; };
} }
private String asString(final Object value) {
if (value instanceof Double doubleValue) {
if (doubleValue % 1 == 0) {
return String.valueOf(doubleValue.intValue()); // avoid trailing ".0"
} else {
return doubleValue.toString();
}
}
return value.toString();
}
} }

View File

@ -1,4 +1,4 @@
package net.hostsharing.hsadminng.hs.office.scenarios.membership.coopshares; package net.hostsharing.hsadminng.hs.office.scenarios.membership;
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest; import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;

View File

@ -1,4 +1,4 @@
package net.hostsharing.hsadminng.hs.office.scenarios.membership.coopshares; package net.hostsharing.hsadminng.hs.office.scenarios.membership;
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest; import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
@ -12,7 +12,7 @@ public class CreateCoopSharesRevertTransaction extends CreateCoopSharesTransacti
.given("memberNumber", "3101000") .given("memberNumber", "3101000")
.given("reference", "sign %{dateOfIncorrectTransaction}") // same as revertedShareTx .given("reference", "sign %{dateOfIncorrectTransaction}") // same as revertedShareTx
.given("shareCount", 100) .given("shareCount", 100)
.given("comment", "coop-shares subscription transaction with wrong share count") .given("comment", "reverting subscription transaction with wrong share count")
.given("transactionDate", "%{dateOfIncorrectTransaction}") .given("transactionDate", "%{dateOfIncorrectTransaction}")
); );
} }

View File

@ -1,4 +1,4 @@
package net.hostsharing.hsadminng.hs.office.scenarios.membership.coopshares; package net.hostsharing.hsadminng.hs.office.scenarios.membership;
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest; import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;

View File

@ -1,4 +1,4 @@
package net.hostsharing.hsadminng.hs.office.scenarios.membership.coopshares; package net.hostsharing.hsadminng.hs.office.scenarios.membership;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase; import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
@ -23,7 +23,7 @@ public abstract class CreateCoopSharesTransaction extends UseCase<CreateCoopShar
response -> response.getFromBody("$[0].uuid") response -> response.getFromBody("$[0].uuid")
); );
return withTitle("Create the Coop-Shares-%{transactionType} Transaction", () -> return withTitle("Create the CoopShares-%{transactionType} Transaction", () ->
httpPost("/api/hs/office/coopsharestransactions", usingJsonBody(""" httpPost("/api/hs/office/coopsharestransactions", usingJsonBody("""
{ {
"membership.uuid": ${membershipUuid}, "membership.uuid": ${membershipUuid},

View File

@ -1,12 +0,0 @@
package net.hostsharing.hsadminng.hs.office.scenarios.membership.coopassets;
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
public class CreateCoopAssetsDepositTransaction extends CreateCoopAssetsTransaction {
public CreateCoopAssetsDepositTransaction(final ScenarioTest testSuite) {
super(testSuite);
given("transactionType", "DEPOSIT");
}
}

View File

@ -1,17 +0,0 @@
package net.hostsharing.hsadminng.hs.office.scenarios.membership.coopassets;
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
public class CreateCoopAssetsDisbursalTransaction extends CreateCoopAssetsTransaction {
public CreateCoopAssetsDisbursalTransaction(final ScenarioTest testSuite) {
super(testSuite);
}
@Override
protected HttpResponse run() {
given("transactionType", "DISBURSAL");
given("assetValue", "-%{valueToDisburse}");
return super.run();
}
}

View File

@ -1,27 +0,0 @@
package net.hostsharing.hsadminng.hs.office.scenarios.membership.coopassets;
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
public class CreateCoopAssetsRevertTransaction extends CreateCoopAssetsTransaction {
public CreateCoopAssetsRevertTransaction(final ScenarioTest testSuite) {
super(testSuite);
requires("CoopAssets-Transaction with incorrect assetValue", alias ->
new CreateCoopAssetsDepositTransaction(testSuite)
.given("memberNumber", "3101000")
.given("reference", "sign %{dateOfIncorrectTransaction}") // same as revertedAssetTx
.given("assetValue", 10)
.given("comment", "coop-assets deposit transaction with wrong asset value")
.given("transactionDate", "%{dateOfIncorrectTransaction}")
);
}
@Override
protected HttpResponse run() {
given("transactionType", "REVERSAL");
given("assetValue", -100);
given("revertedAssetTx", uuid("CoopAssets-Transaction with incorrect assetValue"));
return super.run();
}
}

View File

@ -1,53 +0,0 @@
package net.hostsharing.hsadminng.hs.office.scenarios.membership.coopassets;
import io.restassured.http.ContentType;
import net.hostsharing.hsadminng.hs.office.scenarios.ScenarioTest;
import net.hostsharing.hsadminng.hs.office.scenarios.UseCase;
import org.springframework.http.HttpStatus;
import static io.restassured.http.ContentType.JSON;
import static org.springframework.http.HttpStatus.OK;
public abstract class CreateCoopAssetsTransaction extends UseCase<CreateCoopAssetsTransaction> {
public CreateCoopAssetsTransaction(final ScenarioTest testSuite) {
super(testSuite);
}
@Override
protected HttpResponse run() {
obtain("#{Find }membershipUuid", () ->
httpGet("/api/hs/office/memberships?memberNumber=&{memberNumber}")
.expecting(OK).expecting(JSON).expectArrayElements(1),
response -> response.getFromBody("$[0].uuid")
);
return withTitle("Create the Coop-Assets-%{transactionType} Transaction", () ->
httpPost("/api/hs/office/coopassetstransactions", usingJsonBody("""
{
"membership.uuid": ${membershipUuid},
"transactionType": ${transactionType},
"reference": ${reference},
"assetValue": ${assetValue},
"comment": ${comment},
"valueDate": ${transactionDate},
"revertedAssetTx.uuid": ${revertedAssetTx???}
}
"""))
.expecting(HttpStatus.CREATED).expecting(ContentType.JSON)
);
}
@Override
protected void verify(final HttpResponse response) {
verify("Verify Coop-Assets %{transactionType}-Transaction",
() -> httpGet("/api/hs/office/coopassetstransactions/" + response.getLocationUuid())
.expecting(HttpStatus.OK).expecting(ContentType.JSON),
path("transactionType").contains("%{transactionType}"),
path("assetValue").contains("%{assetValue}"),
path("comment").contains("%{comment}"),
path("valueDate").contains("%{transactionDate}")
);
}
}