Compare commits
3 Commits
master
...
TP-2025022
Author | SHA1 | Date | |
---|---|---|---|
|
bfbbaed5c3 | ||
|
73509990e1 | ||
|
fb216c4769 |
@ -2,13 +2,12 @@ package net.hostsharing.hsadminng.hs.office.sepamandate;
|
|||||||
|
|
||||||
import io.micrometer.core.annotation.Timed;
|
import io.micrometer.core.annotation.Timed;
|
||||||
import net.hostsharing.hsadminng.context.Context;
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
|
import net.hostsharing.hsadminng.errors.Validate;
|
||||||
|
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
|
||||||
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountRepository;
|
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountRepository;
|
||||||
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
|
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeSepaMandatesApi;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeSepaMandatesApi;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorResource;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandateInsertResource;
|
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandatePatchResource;
|
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeSepaMandateResource;
|
|
||||||
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
import net.hostsharing.hsadminng.mapper.StrictMapper;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@ -20,6 +19,7 @@ import jakarta.persistence.EntityManager;
|
|||||||
import jakarta.persistence.PersistenceContext;
|
import jakarta.persistence.PersistenceContext;
|
||||||
import jakarta.validation.ValidationException;
|
import jakarta.validation.ValidationException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
@ -75,6 +75,17 @@ public class HsOfficeSepaMandateController implements HsOfficeSepaMandatesApi {
|
|||||||
|
|
||||||
final var entityToSave = mapper.map(body, HsOfficeSepaMandateEntity.class, SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER);
|
final var entityToSave = mapper.map(body, HsOfficeSepaMandateEntity.class, SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER);
|
||||||
|
|
||||||
|
Validate.validate("bankAccount, bankAccount.uuid").exactlyOne(body.getBankAccount(), body.getBankAccountUuid());
|
||||||
|
if ( body.getBankAccountUuid() != null) {
|
||||||
|
entityToSave.setBankAccount(bankAccountRepo.findByUuid(body.getBankAccountUuid()).orElseThrow(
|
||||||
|
() -> new NoSuchElementException("cannot find BankAccount by bankAccountUuid: " + body.getBankAccountUuid())
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
entityToSave.setBankAccount(bankAccountRepo.save(
|
||||||
|
mapper.map(body.getBankAccount(), HsOfficeBankAccountEntity.class)
|
||||||
|
) );
|
||||||
|
}
|
||||||
|
|
||||||
final var saved = sepaMandateRepo.save(entityToSave);
|
final var saved = sepaMandateRepo.save(entityToSave);
|
||||||
|
|
||||||
final var uri =
|
final var uri =
|
||||||
|
@ -38,8 +38,6 @@ create table if not exists hs_office.membership
|
|||||||
|
|
||||||
CREATE OR REPLACE FUNCTION hs_office.validate_membership_validity()
|
CREATE OR REPLACE FUNCTION hs_office.validate_membership_validity()
|
||||||
RETURNS trigger AS $$
|
RETURNS trigger AS $$
|
||||||
DECLARE
|
|
||||||
partnerNumber int;
|
|
||||||
BEGIN
|
BEGIN
|
||||||
IF EXISTS (
|
IF EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
@ -48,10 +46,7 @@ BEGIN
|
|||||||
AND uuid <> NEW.uuid
|
AND uuid <> NEW.uuid
|
||||||
AND NEW.validity && validity
|
AND NEW.validity && validity
|
||||||
) THEN
|
) THEN
|
||||||
SELECT p.partnerNumber INTO partnerNumber
|
RAISE EXCEPTION 'Membership validity ranges overlap for partnerUuid %', NEW.partnerUuid;
|
||||||
FROM hs_office.partner AS p
|
|
||||||
WHERE p.uuid = NEW.partnerUuid;
|
|
||||||
RAISE EXCEPTION 'Membership validity ranges overlap for partnerUuid %, partnerNumber %', NEW.partnerUuid, partnerNumber;
|
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
RETURN NEW;
|
RETURN NEW;
|
||||||
|
@ -4,10 +4,10 @@ import io.hypersistence.utils.hibernate.type.range.Range;
|
|||||||
import net.hostsharing.hsadminng.context.Context;
|
import net.hostsharing.hsadminng.context.Context;
|
||||||
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
|
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
|
||||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealRepository;
|
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRealRepository;
|
||||||
import net.hostsharing.hsadminng.mapper.Array;
|
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
|
||||||
import net.hostsharing.hsadminng.rbac.grant.RawRbacGrantRepository;
|
import net.hostsharing.hsadminng.rbac.grant.RawRbacGrantRepository;
|
||||||
import net.hostsharing.hsadminng.rbac.role.RawRbacRoleRepository;
|
import net.hostsharing.hsadminng.rbac.role.RawRbacRoleRepository;
|
||||||
import net.hostsharing.hsadminng.rbac.test.ContextBasedTestWithCleanup;
|
import net.hostsharing.hsadminng.mapper.Array;
|
||||||
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Tag;
|
import org.junit.jupiter.api.Tag;
|
||||||
@ -15,9 +15,9 @@ import org.junit.jupiter.api.Test;
|
|||||||
import org.postgresql.util.PSQLException;
|
import org.postgresql.util.PSQLException;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
|
||||||
|
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.orm.jpa.JpaSystemException;
|
import org.springframework.orm.jpa.JpaSystemException;
|
||||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
|
||||||
|
|
||||||
import jakarta.persistence.EntityManager;
|
import jakarta.persistence.EntityManager;
|
||||||
import jakarta.persistence.PersistenceContext;
|
import jakarta.persistence.PersistenceContext;
|
||||||
@ -32,7 +32,7 @@ import static net.hostsharing.hsadminng.rbac.test.JpaAttempt.attempt;
|
|||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@DataJpaTest
|
@DataJpaTest
|
||||||
@Import({ Context.class, JpaAttempt.class })
|
@Import( { Context.class, JpaAttempt.class })
|
||||||
@Tag("officeIntegrationTest")
|
@Tag("officeIntegrationTest")
|
||||||
class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCleanup {
|
class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCleanup {
|
||||||
|
|
||||||
@ -71,16 +71,15 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
|
|||||||
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0);
|
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = attempt(
|
final var result = attempt(em, () -> {
|
||||||
em, () -> {
|
final var newMembership = HsOfficeMembershipEntity.builder()
|
||||||
final var newMembership = HsOfficeMembershipEntity.builder()
|
.memberNumberSuffix("11")
|
||||||
.memberNumberSuffix("11")
|
.partner(givenPartner)
|
||||||
.partner(givenPartner)
|
.validity(Range.closedInfinite(LocalDate.parse("2025-01-01")))
|
||||||
.validity(Range.closedInfinite(LocalDate.parse("2025-01-01")))
|
.membershipFeeBillable(true)
|
||||||
.membershipFeeBillable(true)
|
.build();
|
||||||
.build();
|
return toCleanup(membershipRepo.save(newMembership).load());
|
||||||
return toCleanup(membershipRepo.save(newMembership).load());
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
result.assertSuccessful();
|
result.assertSuccessful();
|
||||||
@ -93,25 +92,22 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
|
|||||||
public void creatingMembershipForSamePartnerIsDisallowedIfAnotherOneIsStillActive() {
|
public void creatingMembershipForSamePartnerIsDisallowedIfAnotherOneIsStillActive() {
|
||||||
// given
|
// given
|
||||||
context("superuser-alex@hostsharing.net");
|
context("superuser-alex@hostsharing.net");
|
||||||
|
final var count = membershipRepo.count();
|
||||||
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").getFirst();
|
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").getFirst();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = attempt(
|
final var result = attempt(em, () -> {
|
||||||
em, () -> {
|
final var newMembership = HsOfficeMembershipEntity.builder()
|
||||||
final var newMembership = HsOfficeMembershipEntity.builder()
|
.memberNumberSuffix("11")
|
||||||
.memberNumberSuffix("11")
|
.partner(givenPartner)
|
||||||
.partner(givenPartner)
|
.validity(Range.closedInfinite(LocalDate.parse("2024-01-01")))
|
||||||
.validity(Range.closedInfinite(LocalDate.parse("2024-01-01")))
|
.membershipFeeBillable(true)
|
||||||
.membershipFeeBillable(true)
|
.build();
|
||||||
.build();
|
return toCleanup(membershipRepo.save(newMembership).load());
|
||||||
return toCleanup(membershipRepo.save(newMembership).load());
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
result.assertExceptionWithRootCauseMessage(
|
result.assertExceptionWithRootCauseMessage(PSQLException.class, "Membership validity ranges overlap for partnerUuid " + givenPartner.getUuid());
|
||||||
PSQLException.class,
|
|
||||||
"Membership validity ranges overlap for partnerUuid " + givenPartner.getUuid() +
|
|
||||||
", partnerNumber " + givenPartner.getPartnerNumber());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -124,17 +120,16 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
|
|||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
attempt(
|
attempt(em, () -> {
|
||||||
em, () -> {
|
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0);
|
||||||
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0);
|
final var newMembership = HsOfficeMembershipEntity.builder()
|
||||||
final var newMembership = HsOfficeMembershipEntity.builder()
|
.memberNumberSuffix("17")
|
||||||
.memberNumberSuffix("17")
|
.partner(givenPartner)
|
||||||
.partner(givenPartner)
|
.validity(Range.closedInfinite(LocalDate.parse("2025-01-01")))
|
||||||
.validity(Range.closedInfinite(LocalDate.parse("2025-01-01")))
|
.membershipFeeBillable(true)
|
||||||
.membershipFeeBillable(true)
|
.build();
|
||||||
.build();
|
return toCleanup(membershipRepo.save(newMembership));
|
||||||
return toCleanup(membershipRepo.save(newMembership));
|
}).assertSuccessful();
|
||||||
}).assertSuccessful();
|
|
||||||
|
|
||||||
// then
|
// then
|
||||||
final var all = rawRoleRepo.findAll();
|
final var all = rawRoleRepo.findAll();
|
||||||
@ -173,7 +168,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
|
|||||||
|
|
||||||
private void assertThatMembershipIsPersisted(final HsOfficeMembershipEntity saved) {
|
private void assertThatMembershipIsPersisted(final HsOfficeMembershipEntity saved) {
|
||||||
final var found = membershipRepo.findByUuid(saved.getUuid());
|
final var found = membershipRepo.findByUuid(saved.getUuid());
|
||||||
assertThat(found).isNotEmpty().get().extracting(Object::toString).isEqualTo(saved.toString());
|
assertThat(found).isNotEmpty().get().extracting(Object::toString).isEqualTo(saved.toString()) ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,8 +201,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
|
|||||||
final var result = membershipRepo.findMembershipsByPartnerUuid(givenPartner.getUuid());
|
final var result = membershipRepo.findMembershipsByPartnerUuid(givenPartner.getUuid());
|
||||||
|
|
||||||
// then
|
// then
|
||||||
exactlyTheseMembershipsAreReturned(
|
exactlyTheseMembershipsAreReturned(result,
|
||||||
result,
|
|
||||||
"Membership(M-1000101, P-10001, [2022-10-01,2024-12-31), ACTIVE)");
|
"Membership(M-1000101, P-10001, [2022-10-01,2024-12-31), ACTIVE)");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,8 +244,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
|
|||||||
final var result = membershipRepo.findMembershipsByPartnerNumber(10002);
|
final var result = membershipRepo.findMembershipsByPartnerNumber(10002);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
exactlyTheseMembershipsAreReturned(
|
exactlyTheseMembershipsAreReturned(result,
|
||||||
result,
|
|
||||||
"Membership(M-1000202, P-10002, [2022-10-01,2026-01-01), ACTIVE)");
|
"Membership(M-1000202, P-10002, [2022-10-01,2026-01-01), ACTIVE)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -263,7 +256,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
|
|||||||
public void globalAdmin_canUpdateValidityOfArbitraryMembership() {
|
public void globalAdmin_canUpdateValidityOfArbitraryMembership() {
|
||||||
// given
|
// given
|
||||||
context("superuser-alex@hostsharing.net");
|
context("superuser-alex@hostsharing.net");
|
||||||
final var givenMembership = givenSomeTemporaryMembership("First", "11");
|
final var givenMembership = givenSomeTemporaryMembership("First", "11");
|
||||||
assertThatMembershipExistsAndIsAccessibleToCurrentContext(givenMembership);
|
assertThatMembershipExistsAndIsAccessibleToCurrentContext(givenMembership);
|
||||||
final var newValidityEnd = LocalDate.now();
|
final var newValidityEnd = LocalDate.now();
|
||||||
|
|
||||||
@ -303,8 +296,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
|
|||||||
});
|
});
|
||||||
|
|
||||||
// then
|
// then
|
||||||
result.assertExceptionWithRootCauseMessage(
|
result.assertExceptionWithRootCauseMessage(JpaSystemException.class,
|
||||||
JpaSystemException.class,
|
|
||||||
"[403] Subject ", " is not allowed to update hs_office.membership uuid");
|
"[403] Subject ", " is not allowed to update hs_office.membership uuid");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,9 +404,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl
|
|||||||
"[creating Membership test-data, hs_office.membership, INSERT, 03]");
|
"[creating Membership test-data, hs_office.membership, INSERT, 03]");
|
||||||
}
|
}
|
||||||
|
|
||||||
private HsOfficeMembershipEntity givenSomeTemporaryMembership(
|
private HsOfficeMembershipEntity givenSomeTemporaryMembership(final String partnerTradeName, final String memberNumberSuffix) {
|
||||||
final String partnerTradeName,
|
|
||||||
final String memberNumberSuffix) {
|
|
||||||
return jpaAttempt.transacted(() -> {
|
return jpaAttempt.transacted(() -> {
|
||||||
context("superuser-alex@hostsharing.net");
|
context("superuser-alex@hostsharing.net");
|
||||||
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike(partnerTradeName).get(0);
|
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike(partnerTradeName).get(0);
|
||||||
|
@ -137,7 +137,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl
|
|||||||
class PostNewSepaMandate {
|
class PostNewSepaMandate {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void globalAdmin_canPostNewSepaMandate() {
|
void globalAdmin_canPostNewSepaMandateWithBankAccountUuid() {
|
||||||
|
|
||||||
context.define("superuser-alex@hostsharing.net");
|
context.define("superuser-alex@hostsharing.net");
|
||||||
final var givenDebitor = debitorRepo.findDebitorsByOptionalNameLike("Third").get(0);
|
final var givenDebitor = debitorRepo.findDebitorsByOptionalNameLike("Third").get(0);
|
||||||
@ -177,6 +177,51 @@ class HsOfficeSepaMandateControllerAcceptanceTest extends ContextBasedTestWithCl
|
|||||||
assertThat(newSubjectUuid).isNotNull();
|
assertThat(newSubjectUuid).isNotNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void globalAdmin_canPostNewSepaMandateWithBankAccountData() {
|
||||||
|
|
||||||
|
context.define("superuser-alex@hostsharing.net");
|
||||||
|
final var givenDebitor = debitorRepo.findDebitorsByOptionalNameLike("Third").get(0);
|
||||||
|
|
||||||
|
final var location = RestAssured // @formatter:off
|
||||||
|
.given()
|
||||||
|
.header("current-subject", "superuser-alex@hostsharing.net")
|
||||||
|
.contentType(ContentType.JSON)
|
||||||
|
.body("""
|
||||||
|
{
|
||||||
|
"debitor.uuid": "%s",
|
||||||
|
"bankAccount": {
|
||||||
|
"holder": "Fourth eG B",
|
||||||
|
"iban": "DE02200505501015871393"
|
||||||
|
"bic": "HASPDEHH"
|
||||||
|
},
|
||||||
|
"reference": "temp ref CAT A",
|
||||||
|
"agreement": "2020-01-02",
|
||||||
|
"validFrom": "2022-10-13"
|
||||||
|
}
|
||||||
|
""".formatted(givenDebitor.getUuid(), givenBankAccount.getUuid()))
|
||||||
|
.port(port)
|
||||||
|
.when()
|
||||||
|
.post("http://localhost/api/hs/office/sepamandates")
|
||||||
|
.then().log().all().assertThat()
|
||||||
|
.statusCode(201)
|
||||||
|
.contentType(ContentType.JSON)
|
||||||
|
.body("uuid", isUuidValid())
|
||||||
|
.body("debitor.partner.partnerNumber", is("P-10003"))
|
||||||
|
.body("bankAccount.holder", is("Fourth eG B"))
|
||||||
|
.body("bankAccount.iban", is("DE02200505501015871393"))
|
||||||
|
.body("reference", is("temp ref CAT A"))
|
||||||
|
.body("validFrom", is("2022-10-13"))
|
||||||
|
.body("validTo", equalTo(null))
|
||||||
|
.header("Location", startsWith("http://localhost"))
|
||||||
|
.extract().header("Location"); // @formatter:on
|
||||||
|
|
||||||
|
// finally, the new sepaMandate can be accessed under the generated UUID
|
||||||
|
final var newSubjectUuid = UUID.fromString(
|
||||||
|
location.substring(location.lastIndexOf('/') + 1));
|
||||||
|
assertThat(newSubjectUuid).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
// TODO.test: move validation tests to a ...WebMvcTest
|
// TODO.test: move validation tests to a ...WebMvcTest
|
||||||
@Test
|
@Test
|
||||||
void globalAdmin_canNotPostNewSepaMandateWhenDebitorUuidIsMissing() {
|
void globalAdmin_canNotPostNewSepaMandateWhenDebitorUuidIsMissing() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user