src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipController.java
@@ -1,12 +1,12 @@ package net.hostsharing.hsadminng.hs.office.membership; import com.vladmihalcea.hibernate.type.range.Range; import net.hostsharing.hsadminng.mapper.Mapper; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeMembershipsApi; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipInsertResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipResource; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; @@ -15,13 +15,13 @@ import javax.persistence.EntityManager; import javax.validation.Valid; import java.time.LocalDate; import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.function.BiConsumer; import static net.hostsharing.hsadminng.mapper.Mapper.map; import static net.hostsharing.hsadminng.mapper.Mapper.mapList; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange; @RestController @@ -48,7 +48,7 @@ final var entities = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(partnerUuid, memberNumber); final var resources = Mapper.mapList(entities, HsOfficeMembershipResource.class, final var resources = mapList(entities, HsOfficeMembershipResource.class, SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER); return ResponseEntity.ok(resources); } @@ -62,7 +62,7 @@ context.define(currentUser, assumedRoles); final var entityToSave = map(body, HsOfficeMembershipEntity.class, SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER); final var entityToSave = mapX(body, HsOfficeMembershipEntity.class); entityToSave.setUuid(UUID.randomUUID()); final var saved = membershipRepo.save(entityToSave); @@ -122,23 +122,11 @@ final var current = membershipRepo.findByUuid(membershipUuid).orElseThrow(); current.setValidity(toPostgresDateRange(current.getValidity().lower(), body.getValidTo())); // current.setReasonForTermination(HsOfficeReasonForTermination.valueOf(body.getReasonForTermination().name())); current.setReasonForTermination( Optional.ofNullable(body.getReasonForTermination()).map(Enum::name).map(HsOfficeReasonForTermination::valueOf).orElse(current.getReasonForTermination()) ); new HsOfficeMembershipEntityPatcher(em, current).apply(body); final var saved = membershipRepo.save(current); final var mapped = map(saved, HsOfficeMembershipResource.class, SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER); return ResponseEntity.ok(mapped); } private static Range<LocalDate> toPostgresDateRange( final LocalDate validFrom, final LocalDate validTo) { return validTo != null ? Range.closedOpen(validFrom, validTo.plusDays(1)) : Range.closedInfinite(validFrom); } final BiConsumer<HsOfficeMembershipEntity, HsOfficeMembershipResource> SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> { @@ -148,7 +136,15 @@ } }; final BiConsumer<HsOfficeMembershipInsertResource, HsOfficeMembershipEntity> SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { private HsOfficeMembershipEntity mapX( final HsOfficeMembershipInsertResource resource, final Class<HsOfficeMembershipEntity> entityClass) { final var entity = new HsOfficeMembershipEntity(); entity.setPartner(em.getReference(HsOfficePartnerEntity.class, resource.getPartnerUuid())); entity.setMainDebitor(em.getReference(HsOfficeDebitorEntity.class, resource.getMainDebitorUuid())); entity.setMemberNumber(resource.getMemberNumber()); entity.setValidity(toPostgresDateRange(resource.getValidFrom(), resource.getValidTo())); }; entity.setReasonForTermination(map(resource.getReasonForTermination(), HsOfficeReasonForTermination.class)); return entity; } } src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java
@@ -18,6 +18,7 @@ import java.time.LocalDate; import java.util.UUID; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange; import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @Entity @@ -69,6 +70,10 @@ @Type(type = "pgsql_enum") private HsOfficeReasonForTermination reasonForTermination; public void setValidTo(final LocalDate validTo) { validity = toPostgresDateRange(getValidity().lower(), validTo); } @Override public String toString() { return stringify.apply(this); src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntityPatcher.java
New file @@ -0,0 +1,45 @@ package net.hostsharing.hsadminng.hs.office.membership; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource; import net.hostsharing.hsadminng.mapper.EntityPatcher; import net.hostsharing.hsadminng.mapper.OptionalFromJson; import javax.persistence.EntityManager; import java.util.Optional; import java.util.UUID; import static net.hostsharing.hsadminng.mapper.Mapper.map; public class HsOfficeMembershipEntityPatcher implements EntityPatcher<HsOfficeMembershipPatchResource> { private final EntityManager em; private final HsOfficeMembershipEntity entity; public HsOfficeMembershipEntityPatcher( final EntityManager em, final HsOfficeMembershipEntity entity) { this.em = em; this.entity = entity; } @Override public void apply(final HsOfficeMembershipPatchResource resource) { OptionalFromJson.of(resource.getMainDebitorUuid()) .ifPresent(newValue -> { verifyNotNull(newValue, "debitor"); entity.setMainDebitor(em.getReference(HsOfficeDebitorEntity.class, newValue)); }); OptionalFromJson.of(resource.getValidTo()).ifPresent( entity::setValidTo); Optional.ofNullable(resource.getReasonForTermination()) .map(v -> map(v, HsOfficeReasonForTermination.class)) .ifPresent(entity::setReasonForTermination); } private void verifyNotNull(final UUID newValue, final String propertyName) { if (newValue == null) { throw new IllegalArgumentException("property '" + propertyName + "' must not be null"); } } } src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntityPatcher.java
@@ -8,13 +8,11 @@ class HsOfficePartnerDetailsEntityPatcher implements EntityPatcher<HsOfficePartnerDetailsPatchResource> { private final EntityManager em; private final HsOfficePartnerDetailsEntity entity; HsOfficePartnerDetailsEntityPatcher( final EntityManager em, final HsOfficePartnerDetailsEntity entity) { this.em = em; this.entity = entity; } src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateController.java
@@ -21,6 +21,7 @@ import java.util.function.BiConsumer; import static net.hostsharing.hsadminng.mapper.Mapper.map; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange; @RestController @@ -124,14 +125,6 @@ final var saved = SepaMandateRepo.save(current); final var mapped = map(saved, HsOfficeSepaMandateResource.class, SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER); return ResponseEntity.ok(mapped); } private static Range<LocalDate> toPostgresDateRange( final LocalDate validFrom, final LocalDate validTo) { return validTo != null ? Range.closedOpen(validFrom, validTo.plusDays(1)) : Range.closedInfinite(validFrom); } final BiConsumer<HsOfficeSepaMandateEntity, HsOfficeSepaMandateResource> SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> { src/main/java/net/hostsharing/hsadminng/mapper/PostgresDateRange.java
New file @@ -0,0 +1,18 @@ package net.hostsharing.hsadminng.mapper; import com.vladmihalcea.hibernate.type.range.Range; import lombok.experimental.UtilityClass; import java.time.LocalDate; @UtilityClass public class PostgresDateRange { public static Range<LocalDate> toPostgresDateRange( final LocalDate validFrom, final LocalDate validTo) { return validTo != null ? Range.closedOpen(validFrom, validTo.plusDays(1)) : Range.closedInfinite(validFrom); } } src/main/resources/api-definition/hs-office/hs-office-membership-schemas.yaml
@@ -37,10 +37,16 @@ HsOfficeMembershipPatch: type: object properties: mainDebitorUuid: type: string format: uuid nullable: true validTo: type: string format: date nullable: true reasonForTermination: nullable: true $ref: '#/components/schemas/HsOfficeReasonForTermination' additionalProperties: false src/test/java/net/hostsharing/hsadminng/hs/office/bankaccount/TestHsOfficeBankAccount.java
@@ -4,7 +4,7 @@ public class TestHsOfficeBankAccount { public static final HsOfficeBankAccountEntity someBankAccount = public static final HsOfficeBankAccountEntity TEST_BANK_ACCOUNT = hsOfficeBankAccount("some bankaccount", "DE67500105173931168623", "INGDDEFFXXX"); static public HsOfficeBankAccountEntity hsOfficeBankAccount(final String holder, final String iban, final String bic) { src/test/java/net/hostsharing/hsadminng/hs/office/contact/TestHsOfficeContact.java
@@ -4,7 +4,7 @@ public class TestHsOfficeContact { public static final HsOfficeContactEntity someContact = hsOfficeContact("some contact", "some-contact@example.com"); public static final HsOfficeContactEntity TEST_CONTACT = hsOfficeContact("some contact", "some-contact@example.com"); static public HsOfficeContactEntity hsOfficeContact(final String label, final String emailAddr) { return HsOfficeContactEntity.builder() src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionEntityTest.java
@@ -5,13 +5,13 @@ import java.math.BigDecimal; import java.time.LocalDate; import static net.hostsharing.hsadminng.hs.office.membership.TestHsMembership.testMembership; import static net.hostsharing.hsadminng.hs.office.membership.TestHsMembership.TEST_MEMBERSHIP; import static org.assertj.core.api.Assertions.assertThat; class HsOfficeCoopAssetsTransactionEntityTest { final HsOfficeCoopAssetsTransactionEntity givenCoopAssetTransaction = HsOfficeCoopAssetsTransactionEntity.builder() .membership(testMembership) .membership(TEST_MEMBERSHIP) .reference("some-ref") .valueDate(LocalDate.parse("2020-01-01")) .transactionType(HsOfficeCoopAssetsTransactionType.DEPOSIT) src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntityTest.java
@@ -4,13 +4,13 @@ import java.time.LocalDate; import static net.hostsharing.hsadminng.hs.office.membership.TestHsMembership.testMembership; import static net.hostsharing.hsadminng.hs.office.membership.TestHsMembership.TEST_MEMBERSHIP; import static org.assertj.core.api.Assertions.assertThat; class HsOfficeCoopSharesTransactionEntityTest { final HsOfficeCoopSharesTransactionEntity givenCoopSharesTransaction = HsOfficeCoopSharesTransactionEntity.builder() .membership(testMembership) .membership(TEST_MEMBERSHIP) .reference("some-ref") .valueDate(LocalDate.parse("2020-01-01")) .transactionType(HsOfficeCoopSharesTransactionType.SUBSCRIPTION) src/test/java/net/hostsharing/hsadminng/hs/office/debitor/TestHsOfficeDebitor.java
@@ -4,16 +4,16 @@ import java.util.UUID; import static net.hostsharing.hsadminng.hs.office.contact.TestHsOfficeContact.someContact; import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.testPartner; import static net.hostsharing.hsadminng.hs.office.contact.TestHsOfficeContact.TEST_CONTACT; import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER; @UtilityClass public class TestHsOfficeDebitor { public static final HsOfficeDebitorEntity testDebitor = HsOfficeDebitorEntity.builder() public static final HsOfficeDebitorEntity TEST_DEBITOR = HsOfficeDebitorEntity.builder() .uuid(UUID.randomUUID()) .debitorNumber(10001) .partner(testPartner) .billingContact(someContact) .partner(TEST_PARTNER) .billingContact(TEST_CONTACT) .build(); } src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java
@@ -3,11 +3,11 @@ import com.vladmihalcea.hibernate.type.range.Range; import io.restassured.RestAssured; import io.restassured.http.ContentType; import net.hostsharing.test.Accepts; import net.hostsharing.hsadminng.HsadminNgApplication; import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository; import net.hostsharing.test.Accepts; import net.hostsharing.test.JpaAttempt; import org.json.JSONException; import org.junit.jupiter.api.AfterEach; @@ -300,7 +300,7 @@ final var givenMembership = givenSomeTemporaryMembershipBessler(); final var givenNewMainDebitor = debitorRepo.findDebitorByDebitorNumber(10003).get(0); final var location = RestAssured // @formatter:off RestAssured // @formatter:off .given() .header("current-user", "superuser-alex@hostsharing.net") .contentType(ContentType.JSON) @@ -317,8 +317,7 @@ .contentType(ContentType.JSON) .body("uuid", isUuidValid()) .body("partner.person.tradeName", is(givenMembership.getPartner().getPerson().getTradeName())) // TODO.impl: implement patching the mainDebitor // .body("mainDebitor.debitorNumber", is(10003)) .body("mainDebitor.debitorNumber", is(10003)) .body("memberNumber", is(givenMembership.getMemberNumber())) .body("validFrom", is("2022-11-01")) .body("validTo", nullValue()) @@ -329,8 +328,7 @@ assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent().get() .matches(mandate -> { assertThat(mandate.getPartner().toShortString()).isEqualTo("First GmbH"); // TODO.impl: implement patching the mainDebitor // assertThat(mandate.getMainDebitor().toString()).isEqualTo(givenMembership.getMainDebitor().toString()); assertThat(mandate.getMainDebitor().toString()).isEqualTo(givenMembership.getMainDebitor().toString()); assertThat(mandate.getMemberNumber()).isEqualTo(givenMembership.getMemberNumber()); assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,)"); assertThat(mandate.getReasonForTermination()).isEqualTo(NONE); src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntityPatcherUnitTest.java
New file @@ -0,0 +1,106 @@ package net.hostsharing.hsadminng.hs.office.membership; import com.vladmihalcea.hibernate.type.range.Range; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeReasonForTerminationResource; import net.hostsharing.test.PatchUnitTestBase; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import javax.persistence.EntityManager; import java.time.LocalDate; import java.util.UUID; import java.util.stream.Stream; import static net.hostsharing.hsadminng.hs.office.debitor.TestHsOfficeDebitor.TEST_DEBITOR; import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER; import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.lenient; @TestInstance(PER_CLASS) @ExtendWith(MockitoExtension.class) class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase< HsOfficeMembershipPatchResource, HsOfficeMembershipEntity > { private static final UUID INITIAL_MEMBERSHIP_UUID = UUID.randomUUID(); private static final UUID INITIAL_MAIN_DEBITOR_UUID = UUID.randomUUID(); private static final UUID PATCHED_MAIN_DEBITOR_UUID = UUID.randomUUID(); private static final LocalDate GIVEN_VALID_FROM = LocalDate.parse("2020-04-15"); private static final LocalDate PATCHED_VALID_TO = LocalDate.parse("2022-12-31"); @Mock private EntityManager em; @BeforeEach void initMocks() { lenient().when(em.getReference(eq(HsOfficeDebitorEntity.class), any())).thenAnswer(invocation -> HsOfficeDebitorEntity.builder().uuid(invocation.getArgument(1)).build()); lenient().when(em.getReference(eq(HsOfficeMembershipEntity.class), any())).thenAnswer(invocation -> HsOfficeMembershipEntity.builder().uuid(invocation.getArgument(1)).build()); } @Override protected HsOfficeMembershipEntity newInitialEntity() { final var entity = new HsOfficeMembershipEntity(); entity.setUuid(INITIAL_MEMBERSHIP_UUID); entity.setMainDebitor(TEST_DEBITOR); entity.setPartner(TEST_PARTNER); entity.setValidity(Range.closedInfinite(GIVEN_VALID_FROM)); return entity; } @Override protected HsOfficeMembershipPatchResource newPatchResource() { return new HsOfficeMembershipPatchResource(); } @Override protected HsOfficeMembershipEntityPatcher createPatcher(final HsOfficeMembershipEntity Membership) { return new HsOfficeMembershipEntityPatcher(em, Membership); } @Override protected Stream<Property> propertyTestDescriptors() { return Stream.of( new JsonNullableProperty<>( "debitor", HsOfficeMembershipPatchResource::setMainDebitorUuid, PATCHED_MAIN_DEBITOR_UUID, HsOfficeMembershipEntity::setMainDebitor, newDebitor(PATCHED_MAIN_DEBITOR_UUID)) .notNullable(), new JsonNullableProperty<>( "valid", HsOfficeMembershipPatchResource::setValidTo, PATCHED_VALID_TO, HsOfficeMembershipEntity::setValidTo), new SimpleProperty<>( "reasonForTermination", HsOfficeMembershipPatchResource::setReasonForTermination, HsOfficeReasonForTerminationResource.CANCELLATION, HsOfficeMembershipEntity::setReasonForTermination, HsOfficeReasonForTermination.CANCELLATION) .notNullable() ); } private static HsOfficeDebitorEntity newDebitor(final UUID uuid) { final var newDebitor = new HsOfficeDebitorEntity(); newDebitor.setUuid(uuid); return newDebitor; } private HsOfficeMembershipEntity newMembership(final UUID uuid) { final var newMembership = new HsOfficeMembershipEntity(); newMembership.setUuid(uuid); return newMembership; } } src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntityUnitTest.java
@@ -8,17 +8,18 @@ import java.time.LocalDate; import java.util.Arrays; import static net.hostsharing.hsadminng.hs.office.debitor.TestHsOfficeDebitor.testDebitor; import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.testPartner; import static net.hostsharing.hsadminng.hs.office.debitor.TestHsOfficeDebitor.TEST_DEBITOR; import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER; import static org.assertj.core.api.Assertions.assertThat; class HsOfficeMembershipEntityUnitTest { public static final LocalDate GIVEN_VALID_FROM = LocalDate.parse("2020-01-01"); final HsOfficeMembershipEntity givenMembership = HsOfficeMembershipEntity.builder() .memberNumber(10001) .partner(testPartner) .mainDebitor(testDebitor) .validity(Range.closedInfinite(LocalDate.parse("2020-01-01"))) .partner(TEST_PARTNER) .mainDebitor(TEST_DEBITOR) .validity(Range.closedInfinite(GIVEN_VALID_FROM)) .build(); @Test @@ -52,6 +53,12 @@ assertThat(givenMembership.getReasonForTermination()).isEqualTo(HsOfficeReasonForTermination.CANCELLATION); } @Test void settingValidToKeepsValidFrom() { givenMembership.setValidTo(LocalDate.parse("2024-12-31")); assertThat(givenMembership.getValidity().lower()).isEqualTo(GIVEN_VALID_FROM); } private static void invokePrePersist(final HsOfficeMembershipEntity membershipEntity) throws IllegalAccessException, InvocationTargetException { final var prePersistMethod = Arrays.stream(HsOfficeMembershipEntity.class.getDeclaredMethods()) src/test/java/net/hostsharing/hsadminng/hs/office/membership/TestHsMembership.java
@@ -5,14 +5,14 @@ import java.time.LocalDate; import java.util.UUID; import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.testPartner; import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TEST_PARTNER; public class TestHsMembership { public static final HsOfficeMembershipEntity testMembership = public static final HsOfficeMembershipEntity TEST_MEMBERSHIP = HsOfficeMembershipEntity.builder() .uuid(UUID.randomUUID()) .partner(testPartner) .partner(TEST_PARTNER) .memberNumber(300001) .validity(Range.closedInfinite(LocalDate.parse("2020-01-01"))) .build(); src/test/java/net/hostsharing/hsadminng/hs/office/partner/TestHsOfficePartner.java
@@ -9,7 +9,7 @@ public class TestHsOfficePartner { public static final HsOfficePartnerEntity testPartner = HsOfficePartnerWithLegalPerson("Test Ltd."); public static final HsOfficePartnerEntity TEST_PARTNER = HsOfficePartnerWithLegalPerson("Test Ltd."); static public HsOfficePartnerEntity HsOfficePartnerWithLegalPerson(final String tradeName) { return HsOfficePartnerEntity.builder() src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateEntityTest.java
@@ -6,13 +6,13 @@ import java.time.LocalDate; import static net.hostsharing.hsadminng.hs.office.debitor.TestHsOfficeDebitor.testDebitor; import static net.hostsharing.hsadminng.hs.office.debitor.TestHsOfficeDebitor.TEST_DEBITOR; import static org.assertj.core.api.Assertions.assertThat; class HsOfficeSepaMandateEntityTest { final HsOfficeSepaMandateEntity givenSepaMandate = HsOfficeSepaMandateEntity.builder() .debitor(testDebitor) .debitor(TEST_DEBITOR) .reference("some-ref") .validity(Range.closedOpen(LocalDate.parse("2020-01-01"), LocalDate.parse("2031-01-01"))) .bankAccount(HsOfficeBankAccountEntity.builder().iban("some label").build())