diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java index 51df906f..67313b4f 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java @@ -19,13 +19,7 @@ import org.hibernate.annotations.JoinFormula; import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; +import jakarta.persistence.*; import jakarta.persistence.Version; import jakarta.validation.constraints.Pattern; import java.io.IOException; diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java index 6c76c5c8..d031389d 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntity.java @@ -2,7 +2,11 @@ package net.hostsharing.hsadminng.hs.office.membership; import io.hypersistence.utils.hibernate.type.range.PostgreSQLRangeType; import io.hypersistence.utils.hibernate.type.range.Range; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity; import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject; @@ -13,17 +17,32 @@ import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringifyable; import org.hibernate.annotations.Type; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.PrePersist; +import jakarta.persistence.Table; +import jakarta.persistence.Version; import jakarta.validation.constraints.Pattern; import java.io.IOException; import java.time.LocalDate; import java.util.UUID; -import static net.hostsharing.hsadminng.mapper.PostgresDateRange.*; +import static io.hypersistence.utils.hibernate.type.range.Range.emptyRange; +import static net.hostsharing.hsadminng.mapper.PostgresDateRange.lowerInclusiveFromPostgresDateRange; +import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange; +import static net.hostsharing.hsadminng.mapper.PostgresDateRange.upperInclusiveFromPostgresDateRange; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Nullable.NOT_NULL; -import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.*; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.DELETE; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.INSERT; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.SELECT; +import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Permission.UPDATE; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.RbacUserReference.UserRole.CREATOR; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.ADMIN; import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Role.AGENT; @@ -50,7 +69,7 @@ public class HsOfficeMembershipEntity implements RbacObject, Stringifyable { .withProp(e -> MEMBER_NUMBER_TAG + e.getMemberNumber()) .withProp(e -> e.getPartner().toShortString()) .withProp(e -> e.getValidity().asString()) - .withProp(HsOfficeMembershipEntity::getReasonForTermination) + .withProp(HsOfficeMembershipEntity::getStatus) .quotedValues(false); @Id @@ -75,9 +94,9 @@ public class HsOfficeMembershipEntity implements RbacObject, Stringifyable { @Column(name = "membershipfeebillable", nullable = false) private Boolean membershipFeeBillable; // not primitive to force setting the value - @Column(name = "reasonfortermination") + @Column(name = "status") @Enumerated(EnumType.STRING) - private HsOfficeReasonForTermination reasonForTermination; + private HsOfficeMembershipStatus status; public void setValidFrom(final LocalDate validFrom) { setValidity(toPostgresDateRange(validFrom, getValidTo())); @@ -97,7 +116,7 @@ public class HsOfficeMembershipEntity implements RbacObject, Stringifyable { public Range getValidity() { if (validity == null) { - validity = Range.infinite(LocalDate.class); + validity = emptyRange(LocalDate.class); } return validity; } @@ -121,8 +140,8 @@ public class HsOfficeMembershipEntity implements RbacObject, Stringifyable { @PrePersist void init() { - if (getReasonForTermination() == null) { - setReasonForTermination(HsOfficeReasonForTermination.NONE); + if (getStatus() == null) { + setStatus(HsOfficeMembershipStatus.INVALID); } } @@ -135,7 +154,7 @@ public class HsOfficeMembershipEntity implements RbacObject, Stringifyable { JOIN hs_office_partner AS p ON p.uuid = m.partnerUuid """)) .withRestrictedViewOrderBy(SQL.projection("validity")) - .withUpdatableColumns("validity", "membershipFeeBillable", "reasonForTermination") + .withUpdatableColumns("validity", "membershipFeeBillable", "status") .importEntityAlias("partnerRel", HsOfficeRelationEntity.class, dependsOnColumn("partnerUuid"), diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntityPatcher.java b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntityPatcher.java index 89933fe8..cbecb800 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntityPatcher.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntityPatcher.java @@ -23,9 +23,9 @@ public class HsOfficeMembershipEntityPatcher implements EntityPatcher mapper.map(v, HsOfficeReasonForTermination.class)) - .ifPresent(entity::setReasonForTermination); + Optional.ofNullable(resource.getStatus()) + .map(v -> mapper.map(v, HsOfficeMembershipStatus.class)) + .ifPresent(entity::setStatus); OptionalFromJson.of(resource.getMembershipFeeBillable()).ifPresent( entity::setMembershipFeeBillable); } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipStatus.java b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipStatus.java new file mode 100644 index 00000000..b44ceee3 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipStatus.java @@ -0,0 +1,5 @@ +package net.hostsharing.hsadminng.hs.office.membership; + +public enum HsOfficeMembershipStatus { + INVALID, ACTIVE, CANCELLED, TRANSFERRED, DECEASED, LIQUIDATED, EXPULSED, UNKNOWN; +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeReasonForTermination.java b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeReasonForTermination.java deleted file mode 100644 index a2a41051..00000000 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeReasonForTermination.java +++ /dev/null @@ -1,5 +0,0 @@ -package net.hostsharing.hsadminng.hs.office.membership; - -public enum HsOfficeReasonForTermination { - NONE, CANCELLATION, TRANSFER, DEATH, LIQUIDATION, EXPULSION, UNKNOWN; -} diff --git a/src/main/resources/api-definition/hs-office/hs-office-membership-schemas.yaml b/src/main/resources/api-definition/hs-office/hs-office-membership-schemas.yaml index 02fba043..ca42b367 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-membership-schemas.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-membership-schemas.yaml @@ -3,15 +3,17 @@ components: schemas: - HsOfficeReasonForTermination: + HsOfficeMembershipStatus: type: string enum: - - NONE - - CANCELLATION - - TRANSFER - - DEATH - - LIQUIDATION - - EXPULSION + - INVALID + - ACTIVE + - CANCELLED + - TRANSFERRED + - DECEASED + - LIQUIDATED + - EXPULSED + - UNKNOWN HsOfficeMembership: type: object @@ -38,8 +40,8 @@ components: validTo: type: string format: date - reasonForTermination: - $ref: '#/components/schemas/HsOfficeReasonForTermination' + status: + $ref: '#/components/schemas/HsOfficeMembershipStatus' membershipFeeBillable: type: boolean @@ -50,9 +52,8 @@ components: type: string format: date nullable: true - reasonForTermination: - nullable: true - $ref: '#/components/schemas/HsOfficeReasonForTermination' + status: + $ref: '#/components/schemas/HsOfficeMembershipStatus' membershipFeeBillable: nullable: true type: boolean @@ -79,8 +80,8 @@ components: type: string format: date nullable: true - reasonForTermination: - $ref: '#/components/schemas/HsOfficeReasonForTermination' + status: + $ref: '#/components/schemas/HsOfficeMembershipStatus' membershipFeeBillable: nullable: false type: boolean diff --git a/src/main/resources/api-definition/hs-office/hs-office.yaml b/src/main/resources/api-definition/hs-office/hs-office.yaml index 3bbc5c34..6265d98e 100644 --- a/src/main/resources/api-definition/hs-office/hs-office.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office.yaml @@ -1,4 +1,4 @@ -openapi: 3.0.1 +openapi: 3.0.3 info: title: Hostsharing hsadmin-ng API version: v0 diff --git a/src/main/resources/db/changelog/5-hs-office/510-membership/5100-hs-office-membership.sql b/src/main/resources/db/changelog/5-hs-office/510-membership/5100-hs-office-membership.sql index 7abc8699..47831f9d 100644 --- a/src/main/resources/db/changelog/5-hs-office/510-membership/5100-hs-office-membership.sql +++ b/src/main/resources/db/changelog/5-hs-office/510-membership/5100-hs-office-membership.sql @@ -4,9 +4,18 @@ --changeset hs-office-membership-MAIN-TABLE:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -CREATE TYPE HsOfficeReasonForTermination AS ENUM ('NONE', 'CANCELLATION', 'TRANSFER', 'DEATH', 'LIQUIDATION', 'EXPULSION', 'UNKNOWN'); +CREATE TYPE HsOfficeMembershipStatus AS ENUM ( + 'INVALID', + 'ACTIVE', + 'CANCELLED', + 'TRANSFERRED', + 'DECEASED', + 'LIQUIDATED', + 'EXPULSED', + 'UNKNOWN' +); -CREATE CAST (character varying as HsOfficeReasonForTermination) WITH INOUT AS IMPLICIT; +CREATE CAST (character varying as HsOfficeMembershipStatus) WITH INOUT AS IMPLICIT; create table if not exists hs_office_membership ( @@ -15,7 +24,7 @@ create table if not exists hs_office_membership partnerUuid uuid not null references hs_office_partner(uuid), memberNumberSuffix char(2) not null check (memberNumberSuffix::text ~ '^[0-9][0-9]$'), validity daterange not null, - reasonForTermination HsOfficeReasonForTermination not null default 'NONE', + status HsOfficeMembershipStatus not null default 'ACTIVE', membershipFeeBillable boolean not null default true, UNIQUE(partnerUuid, memberNumberSuffix) diff --git a/src/main/resources/db/changelog/5-hs-office/510-membership/5103-hs-office-membership-rbac.sql b/src/main/resources/db/changelog/5-hs-office/510-membership/5103-hs-office-membership-rbac.sql index 7f8de66b..139a2294 100644 --- a/src/main/resources/db/changelog/5-hs-office/510-membership/5103-hs-office-membership-rbac.sql +++ b/src/main/resources/db/changelog/5-hs-office/510-membership/5103-hs-office-membership-rbac.sql @@ -172,7 +172,7 @@ call generateRbacRestrictedView('hs_office_membership', $updates$ validity = new.validity, membershipFeeBillable = new.membershipFeeBillable, - reasonForTermination = new.reasonForTermination + status = new.status $updates$); --// diff --git a/src/main/resources/db/changelog/5-hs-office/510-membership/5108-hs-office-membership-test-data.sql b/src/main/resources/db/changelog/5-hs-office/510-membership/5108-hs-office-membership-test-data.sql index d49a5344..b8cbb45b 100644 --- a/src/main/resources/db/changelog/5-hs-office/510-membership/5108-hs-office-membership-test-data.sql +++ b/src/main/resources/db/changelog/5-hs-office/510-membership/5108-hs-office-membership-test-data.sql @@ -28,8 +28,8 @@ begin raise notice 'creating test Membership: M-% %', forPartnerNumber, newMemberNumberSuffix; raise notice '- using partner (%): %', relatedPartner.uuid, relatedPartner; insert - into hs_office_membership (uuid, partneruuid, memberNumberSuffix, validity, reasonfortermination) - values (uuid_generate_v4(), relatedPartner.uuid, newMemberNumberSuffix, daterange('20221001' , null, '[]'), 'NONE'); + into hs_office_membership (uuid, partneruuid, memberNumberSuffix, validity, status) + values (uuid_generate_v4(), relatedPartner.uuid, newMemberNumberSuffix, daterange('20221001' , null, '[]'), 'ACTIVE'); end; $$; --// diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java index 5ff5c032..083eb5e0 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerAcceptanceTest.java @@ -23,8 +23,8 @@ import jakarta.persistence.PersistenceContext; import java.time.LocalDate; import java.util.UUID; -import static net.hostsharing.hsadminng.hs.office.membership.HsOfficeReasonForTermination.CANCELLATION; -import static net.hostsharing.hsadminng.hs.office.membership.HsOfficeReasonForTermination.NONE; +import static net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipStatus.ACTIVE; +import static net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipStatus.CANCELLED; import static net.hostsharing.test.IsValidUuidMatcher.isUuidValid; import static net.hostsharing.test.JsonMatcher.lenientlyEquals; import static org.assertj.core.api.Assertions.assertThat; @@ -84,7 +84,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle "memberNumberSuffix": "01", "validFrom": "2022-10-01", "validTo": null, - "reasonForTermination": "NONE" + "status": "ACTIVE" }, { "partner": { "partnerNumber": 10002 }, @@ -92,7 +92,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle "memberNumberSuffix": "02", "validFrom": "2022-10-01", "validTo": null, - "reasonForTermination": "NONE" + "status": "ACTIVE" }, { "partner": { "partnerNumber": 10003 }, @@ -100,7 +100,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle "memberNumberSuffix": "03", "validFrom": "2022-10-01", "validTo": null, - "reasonForTermination": "NONE" + "status": "ACTIVE" } ] """)); @@ -131,7 +131,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle "memberNumberSuffix": "01", "validFrom": "2022-10-01", "validTo": null, - "reasonForTermination": "NONE" + "status": "ACTIVE" } ] """)); @@ -159,7 +159,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle "memberNumberSuffix": "02", "validFrom": "2022-10-01", "validTo": null, - "reasonForTermination": "NONE" + "status": "ACTIVE" } ] """)); @@ -239,7 +239,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle "memberNumberSuffix": "01", "validFrom": "2022-10-01", "validTo": null, - "reasonForTermination": "NONE" + "status": "ACTIVE" } """)); // @formatter:on } @@ -283,7 +283,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle "memberNumberSuffix": "03", "validFrom": "2022-10-01", "validTo": null, - "reasonForTermination": "NONE" + "status": "ACTIVE" } """)); // @formatter:on } @@ -306,7 +306,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle .body(""" { "validTo": "2023-12-31", - "reasonForTermination": "CANCELLATION" + "status": "CANCELLED" } """) .port(port) @@ -320,7 +320,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle .body("memberNumberSuffix", is(givenMembership.getMemberNumberSuffix())) .body("validFrom", is("2022-11-01")) .body("validTo", is("2023-12-31")) - .body("reasonForTermination", is("CANCELLATION")); + .body("status", is("CANCELLED")); // @formatter:on // finally, the Membership is actually updated @@ -329,7 +329,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle assertThat(mandate.getPartner().toShortString()).isEqualTo("P-10001"); assertThat(mandate.getMemberNumberSuffix()).isEqualTo(givenMembership.getMemberNumberSuffix()); assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,2024-01-01)"); - assertThat(mandate.getReasonForTermination()).isEqualTo(CANCELLATION); + assertThat(mandate.getStatus()).isEqualTo(CANCELLED); return true; }); } @@ -351,7 +351,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle .body(""" { "validTo": "2024-01-01", - "reasonForTermination": "CANCELLATION" + "status": "CANCELLED" } """) .port(port) @@ -364,7 +364,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent().get() .matches(mandate -> { assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,2024-01-02)"); - assertThat(mandate.getReasonForTermination()).isEqualTo(CANCELLATION); + assertThat(mandate.getStatus()).isEqualTo(CANCELLED); return true; }); } @@ -441,7 +441,7 @@ class HsOfficeMembershipControllerAcceptanceTest extends ContextBasedTestWithCle .partner(givenPartner) .memberNumberSuffix(TEMP_MEMBER_NUMBER_SUFFIX) .validity(Range.closedInfinite(LocalDate.parse("2022-11-01"))) - .reasonForTermination(NONE) + .status(ACTIVE) .membershipFeeBillable(true) .build(); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntityPatcherUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntityPatcherUnitTest.java index ddad360e..01bc770a 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntityPatcherUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntityPatcherUnitTest.java @@ -3,7 +3,7 @@ package net.hostsharing.hsadminng.hs.office.membership; import io.hypersistence.utils.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.hsadminng.hs.office.generated.api.v1.model.HsOfficeMembershipStatusResource; import net.hostsharing.hsadminng.mapper.Mapper; import net.hostsharing.test.PatchUnitTestBase; import org.junit.jupiter.api.BeforeEach; @@ -79,11 +79,11 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase< PATCHED_VALID_TO, HsOfficeMembershipEntity::setValidTo), new SimpleProperty<>( - "reasonForTermination", - HsOfficeMembershipPatchResource::setReasonForTermination, - HsOfficeReasonForTerminationResource.CANCELLATION, - HsOfficeMembershipEntity::setReasonForTermination, - HsOfficeReasonForTermination.CANCELLATION) + "status", + HsOfficeMembershipPatchResource::setStatus, + HsOfficeMembershipStatusResource.CANCELLED, + HsOfficeMembershipEntity::setStatus, + HsOfficeMembershipStatus.CANCELLED) .notNullable(), new JsonNullableProperty<>( "membershipFeeBillable", diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntityUnitTest.java index ef47eaa0..b2e5bb68 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntityUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipEntityUnitTest.java @@ -62,27 +62,27 @@ class HsOfficeMembershipEntityUnitTest { } @Test - void getValidtyIfNull() { + void getEmptyValidtyIfNull() { givenMembership.setValidity(null); final var result = givenMembership.getValidity(); - assertThat(result).isEqualTo(Range.infinite(LocalDate.class)); + assertThat(result.isEmpty()).isTrue(); } @Test - void initializesReasonForTerminationInPrePersistIfNull() throws Exception { + void initializesStatusInPrePersistIfNull() throws Exception { final var givenUninitializedMembership = new HsOfficeMembershipEntity(); - assertThat(givenUninitializedMembership.getReasonForTermination()).as("precondition failed").isNull(); + assertThat(givenUninitializedMembership.getStatus()).as("precondition failed").isNull(); invokePrePersist(givenUninitializedMembership); - assertThat(givenUninitializedMembership.getReasonForTermination()).isEqualTo(HsOfficeReasonForTermination.NONE); + assertThat(givenUninitializedMembership.getStatus()).isEqualTo(HsOfficeMembershipStatus.INVALID); } @Test - void doesNotOverwriteReasonForTerminationInPrePersistIfNotNull() throws Exception { - givenMembership.setReasonForTermination(HsOfficeReasonForTermination.CANCELLATION); + void doesNotOverwriteStatusInPrePersistIfNotNull() throws Exception { + givenMembership.setStatus(HsOfficeMembershipStatus.CANCELLED); invokePrePersist(givenMembership); - assertThat(givenMembership.getReasonForTermination()).isEqualTo(HsOfficeReasonForTermination.CANCELLATION); + assertThat(givenMembership.getStatus()).isEqualTo(HsOfficeMembershipStatus.CANCELLED); } @Test diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java index a6da48c9..77c2bdac 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipRepositoryIntegrationTest.java @@ -161,9 +161,9 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl // then exactlyTheseMembershipsAreReturned( result, - "Membership(M-1000101, P-10001, [2022-10-01,), NONE)", - "Membership(M-1000202, P-10002, [2022-10-01,), NONE)", - "Membership(M-1000303, P-10003, [2022-10-01,), NONE)"); + "Membership(M-1000101, P-10001, [2022-10-01,), ACTIVE)", + "Membership(M-1000202, P-10002, [2022-10-01,), ACTIVE)", + "Membership(M-1000303, P-10003, [2022-10-01,), ACTIVE)"); } @Test @@ -177,7 +177,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl // then exactlyTheseMembershipsAreReturned(result, - "Membership(M-1000101, P-10001, [2022-10-01,), NONE)"); + "Membership(M-1000101, P-10001, [2022-10-01,), ACTIVE)"); } @Test @@ -192,7 +192,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl assertThat(result) .isNotNull() .extracting(Object::toString) - .isEqualTo("Membership(M-1000202, P-10002, [2022-10-01,), NONE)"); + .isEqualTo("Membership(M-1000202, P-10002, [2022-10-01,), ACTIVE)"); } } @@ -213,7 +213,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTestWithCl context("superuser-alex@hostsharing.net"); givenMembership.setValidity(Range.closedOpen( givenMembership.getValidity().lower(), newValidityEnd)); - givenMembership.setReasonForTermination(HsOfficeReasonForTermination.CANCELLATION); + givenMembership.setStatus(HsOfficeMembershipStatus.CANCELLED); return toCleanup(membershipRepo.save(givenMembership)); }); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java b/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java index a763804a..fb51e8c8 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java @@ -13,7 +13,7 @@ import net.hostsharing.hsadminng.hs.office.coopshares.HsOfficeCoopSharesTransact import net.hostsharing.hsadminng.hs.office.coopshares.HsOfficeCoopSharesTransactionType; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity; -import net.hostsharing.hsadminng.hs.office.membership.HsOfficeReasonForTermination; +import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipStatus; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerDetailsEntity; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; @@ -54,6 +54,7 @@ import java.util.stream.Collectors; import static java.lang.Boolean.parseBoolean; import static java.util.Arrays.stream; import static java.util.Objects.requireNonNull; +import static java.util.Optional.ofNullable; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -185,6 +186,7 @@ public class ImportOfficeData extends ContextBasedTest { 17=partner(P-10017: null null, null), 20=partner(P-10020: null null, null), 22=partner(P-11022: null null, null), + 90=partner(P-19090: null null, null), 99=partner(P-19999: null null, null) } """); @@ -194,14 +196,15 @@ public class ImportOfficeData extends ContextBasedTest { 17=debitor(D-1001700: rel(anchor='null null, null', type='DEBITOR'), mih), 20=debitor(D-1002000: rel(anchor='null null, null', type='DEBITOR'), xyz), 22=debitor(D-1102200: rel(anchor='null null, null', type='DEBITOR'), xxx), + 90=debitor(D-1909000: rel(anchor='null null, null', type='DEBITOR'), yyy), 99=debitor(D-1999900: rel(anchor='null null, null', type='DEBITOR'), zzz) } """); assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace(""" { - 17=Membership(M-1001700, P-10017, [2000-12-06,), NONE), + 17=Membership(M-1001700, P-10017, [2000-12-06,), ACTIVE), 20=Membership(M-1002000, P-10020, [2000-12-06,2016-01-01), UNKNOWN), - 22=Membership(M-1102200, P-11022, [2021-04-01,), NONE) + 22=Membership(M-1102200, P-11022, [2021-04-01,), ACTIVE) } """); } @@ -228,6 +231,7 @@ public class ImportOfficeData extends ContextBasedTest { 17=partner(P-10017: NP Mellies, Michael, Herr Michael Mellies ), 20=partner(P-10020: LP JM GmbH, Herr Philip Meyer-Contract , JM GmbH), 22=partner(P-11022: ?? Test PS, Petra Schmidt , Test PS), + 90=partner(P-19090: NP Camus, Cecilia, Frau Cecilia Camus ), 99=partner(P-19999: null null, null) } """); @@ -240,7 +244,8 @@ public class ImportOfficeData extends ContextBasedTest { 1203=contact(label='Herr Philip Meyer-Contract , JM GmbH', emailAddresses='pm-partner@example.org'), 1204=contact(label='Frau Tammy Meyer-VIP , JM GmbH', emailAddresses='tm-vip@example.org'), 1301=contact(label='Petra Schmidt , Test PS', emailAddresses='ps@example.com'), - 1401=contact(label='Frau Frauke Fanninga ', emailAddresses='ff@example.org') + 1401=contact(label='Frau Frauke Fanninga ', emailAddresses='ff@example.org'), + 1501=contact(label='Frau Cecilia Camus ', emailAddresses='cc@example.org') } """); assertThat(toFormattedString(persons)).isEqualToIgnoringWhitespace(""" @@ -253,7 +258,8 @@ public class ImportOfficeData extends ContextBasedTest { 1203=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-Contract', givenName='Philip'), 1204=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-VIP', givenName='Tammy'), 1301=person(personType='??', tradeName='Test PS', familyName='Schmidt', givenName='Petra'), - 1401=person(personType='NP', tradeName='', familyName='Fanninga', givenName='Frauke') + 1401=person(personType='NP', tradeName='', familyName='Fanninga', givenName='Frauke'), + 1501=person(personType='NP', tradeName='', familyName='Camus', givenName='Cecilia') } """); assertThat(toFormattedString(debitors)).isEqualToIgnoringWhitespace(""" @@ -261,14 +267,15 @@ public class ImportOfficeData extends ContextBasedTest { 17=debitor(D-1001700: rel(anchor='NP Mellies, Michael', type='DEBITOR', holder='NP Mellies, Michael'), mih), 20=debitor(D-1002000: rel(anchor='LP JM GmbH', type='DEBITOR', holder='LP JM GmbH'), xyz), 22=debitor(D-1102200: rel(anchor='?? Test PS', type='DEBITOR', holder='?? Test PS'), xxx), + 90=debitor(D-1909000: rel(anchor='NP Camus, Cecilia', type='DEBITOR', holder='NP Camus, Cecilia'), yyy), 99=debitor(D-1999900: rel(anchor='null null, null', type='DEBITOR'), zzz) } """); assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace(""" { - 17=Membership(M-1001700, P-10017, [2000-12-06,), NONE), + 17=Membership(M-1001700, P-10017, [2000-12-06,), ACTIVE), 20=Membership(M-1002000, P-10020, [2000-12-06,2016-01-01), UNKNOWN), - 22=Membership(M-1102200, P-11022, [2021-04-01,), NONE) + 22=Membership(M-1102200, P-11022, [2021-04-01,), ACTIVE) } """); assertThat(toFormattedString(relations)).isEqualToIgnoringWhitespace(""" @@ -279,21 +286,25 @@ public class ImportOfficeData extends ContextBasedTest { 2000003=rel(anchor='LP JM GmbH', type='DEBITOR', holder='LP JM GmbH', contact='Frau Dr. Jenny Meyer-Billing , JM GmbH'), 2000004=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='?? Test PS', contact='Petra Schmidt , Test PS'), 2000005=rel(anchor='?? Test PS', type='DEBITOR', holder='?? Test PS', contact='Petra Schmidt , Test PS'), - 2000006=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='null null, null'), - 2000007=rel(anchor='null null, null', type='DEBITOR'), - 2000008=rel(anchor='NP Mellies, Michael', type='OPERATIONS', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), - 2000009=rel(anchor='NP Mellies, Michael', type='REPRESENTATIVE', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), - 2000010=rel(anchor='LP JM GmbH', type='EX_PARTNER', holder='LP JM e.K.', contact='JM e.K.'), - 2000011=rel(anchor='LP JM GmbH', type='OPERATIONS', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), - 2000012=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), - 2000013=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='operations-announce', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), - 2000014=rel(anchor='LP JM GmbH', type='REPRESENTATIVE', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), - 2000015=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='members-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), - 2000016=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='customers-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), - 2000017=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Frau Tammy Meyer-VIP , JM GmbH'), - 2000018=rel(anchor='?? Test PS', type='OPERATIONS', holder='?? Test PS', contact='Petra Schmidt , Test PS'), - 2000019=rel(anchor='?? Test PS', type='REPRESENTATIVE', holder='?? Test PS', contact='Petra Schmidt , Test PS'), - 2000020=rel(anchor='NP Mellies, Michael', type='SUBSCRIBER', mark='operations-announce', holder='NP Fanninga, Frauke', contact='Frau Frauke Fanninga ') + 2000006=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus '), + 2000007=rel(anchor='NP Camus, Cecilia', type='DEBITOR', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus '), + 2000008=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='null null, null'), + 2000009=rel(anchor='null null, null', type='DEBITOR'), + 2000010=rel(anchor='NP Mellies, Michael', type='OPERATIONS', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), + 2000011=rel(anchor='NP Mellies, Michael', type='REPRESENTATIVE', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), + 2000012=rel(anchor='LP JM GmbH', type='EX_PARTNER', holder='LP JM e.K.', contact='JM e.K.'), + 2000013=rel(anchor='LP JM GmbH', type='OPERATIONS', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), + 2000014=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), + 2000015=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='operations-announce', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), + 2000016=rel(anchor='LP JM GmbH', type='REPRESENTATIVE', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), + 2000017=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='members-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), + 2000018=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='customers-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), + 2000019=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Frau Tammy Meyer-VIP , JM GmbH'), + 2000020=rel(anchor='?? Test PS', type='OPERATIONS', holder='?? Test PS', contact='Petra Schmidt , Test PS'), + 2000021=rel(anchor='?? Test PS', type='REPRESENTATIVE', holder='?? Test PS', contact='Petra Schmidt , Test PS'), + 2000022=rel(anchor='NP Mellies, Michael', type='SUBSCRIBER', mark='operations-announce', holder='NP Fanninga, Frauke', contact='Frau Frauke Fanninga '), + 2000023=rel(anchor='NP Camus, Cecilia', type='OPERATIONS', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus '), + 2000024=rel(anchor='NP Camus, Cecilia', type='REPRESENTATIVE', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus ') } """); } @@ -383,7 +394,23 @@ public class ImportOfficeData extends ContextBasedTest { 33002=CoopAssetsTransaction(M-1002000: 2005-01-10, ADOPTION, 512.00, for transfer from 7), 34001=CoopAssetsTransaction(M-1002000: 2016-12-31, CLEARING, -8.00, for cancellation D), 34002=CoopAssetsTransaction(M-1002000: 2016-12-31, DISBURSAL, -100.00, for cancellation D), - 34003=CoopAssetsTransaction(M-1002000: 2016-12-31, LOSS, -20.00, for cancellation D) + 34003=CoopAssetsTransaction(M-1002000: 2016-12-31, LOSS, -20.00, for cancellation D), + 35001=CoopAssetsTransaction(M-1909000: 2024-01-15, DEPOSIT, 128.00, for subscription E), + 35002=CoopAssetsTransaction(M-1909000: 2024-01-20, ADJUSTMENT, -128.00, chargeback for subscription E) + } + """); + } + + @Test + @Order(1099) + void verifyMemberships() { + assumeThatWeAreImportingControlledTestData(); + assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace(""" + { + 17=Membership(M-1001700, P-10017, [2000-12-06,), ACTIVE), + 20=Membership(M-1002000, P-10020, [2000-12-06,2016-01-01), UNKNOWN), + 22=Membership(M-1102200, P-11022, [2021-04-01,), ACTIVE), + 90=Membership(M-1909000, P-19090, empty, INVALID) } """); } @@ -742,10 +769,10 @@ public class ImportOfficeData extends ContextBasedTest { rec.getLocalDate("member_since"), rec.getLocalDate("member_until"))) .membershipFeeBillable(rec.isEmpty("member_role")) - .reasonForTermination( + .status( isBlank(rec.getString("member_until")) - ? HsOfficeReasonForTermination.NONE - : HsOfficeReasonForTermination.UNKNOWN) + ? HsOfficeMembershipStatus.ACTIVE + : HsOfficeMembershipStatus.UNKNOWN) .build(); memberships.put(rec.getInteger("bp_id"), membership); } @@ -760,7 +787,9 @@ public class ImportOfficeData extends ContextBasedTest { .map(this::trimAll) .map(row -> new Record(columns, row)) .forEach(rec -> { - final var member = memberships.get(rec.getInteger("bp_id")); + final var bpId = rec.getInteger("bp_id"); + final var member = ofNullable(memberships.get(bpId)) + .orElseGet(() -> createOnDemandMembership(bpId)); final var shareTransaction = HsOfficeCoopSharesTransactionEntity.builder() .membership(member) @@ -788,11 +817,14 @@ public class ImportOfficeData extends ContextBasedTest { .map(this::trimAll) .map(row -> new Record(columns, row)) .forEach(rec -> { - final var member = memberships.get(rec.getInteger("bp_id")); + final var bpId = rec.getInteger("bp_id"); + final var member = ofNullable(memberships.get(bpId)) + .orElseGet(() -> createOnDemandMembership(bpId)); final var assetTypeMapping = new HashMap() { { + put("ADJUSTMENT", HsOfficeCoopAssetsTransactionType.ADJUSTMENT); put("HANDOVER", HsOfficeCoopAssetsTransactionType.TRANSFER); put("ADOPTION", HsOfficeCoopAssetsTransactionType.ADOPTION); put("LOSS", HsOfficeCoopAssetsTransactionType.LOSS); @@ -823,6 +855,17 @@ public class ImportOfficeData extends ContextBasedTest { }); } + private static HsOfficeMembershipEntity createOnDemandMembership(final Integer bpId) { + final var onDemandMembership = HsOfficeMembershipEntity.builder() + .memberNumberSuffix("00") + .membershipFeeBillable(false) + .partner(partners.get(bpId)) + .status(HsOfficeMembershipStatus.INVALID) + .build(); + memberships.put(bpId, onDemandMembership); + return onDemandMembership; + } + private void importSepaMandates(final String[] header, final List records) { final var columns = new Columns(header); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntityUnitTest.java index 19aa3988..199e7f23 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntityUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntityUnitTest.java @@ -86,7 +86,7 @@ class HsOfficePersonEntityUnitTest { final var actualDisplay = givenPersonEntity.toShortString(); - assertThat(actualDisplay).isEqualTo("NP Frau some family name, some given name"); + assertThat(actualDisplay).isEqualTo("NP some family name, some given name"); } @Test diff --git a/src/test/resources/migration/asset-transactions.csv b/src/test/resources/migration/asset-transactions.csv index 12c2c39c..8c47e68e 100644 --- a/src/test/resources/migration/asset-transactions.csv +++ b/src/test/resources/migration/asset-transactions.csv @@ -1,9 +1,11 @@ member_asset_id; bp_id; date; action; amount; comment -30000; 17; 2000-12-06; PAYMENT; 1280.00; for subscription A -31000; 20; 2000-12-06; PAYMENT; 128.00; for subscription B -32000; 17; 2005-01-10; PAYMENT; 2560.00; for subscription C -33001; 17; 2005-01-10; HANDOVER; -512.00; for transfer to 10 -33002; 20; 2005-01-10; ADOPTION; 512.00; for transfer from 7 -34001; 20; 2016-12-31; CLEARING; -8.00; for cancellation D -34002; 20; 2016-12-31; PAYBACK; -100.00; for cancellation D -34003; 20; 2016-12-31; LOSS; -20.00; for cancellation D +30000; 17; 2000-12-06; PAYMENT; 1280.00; for subscription A +31000; 20; 2000-12-06; PAYMENT; 128.00; for subscription B +32000; 17; 2005-01-10; PAYMENT; 2560.00; for subscription C +33001; 17; 2005-01-10; HANDOVER; -512.00; for transfer to 10 +33002; 20; 2005-01-10; ADOPTION; 512.00; for transfer from 7 +34001; 20; 2016-12-31; CLEARING; -8.00; for cancellation D +34002; 20; 2016-12-31; PAYBACK; -100.00; for cancellation D +34003; 20; 2016-12-31; LOSS; -20.00; for cancellation D +35001; 90; 2024-01-15; PAYMENT; 128.00; for subscription E +35002; 90; 2024-01-20; ADJUSTMENT;-128.00; chargeback for subscription E diff --git a/src/test/resources/migration/business-partners.csv b/src/test/resources/migration/business-partners.csv index 3d49d950..a28ead25 100644 --- a/src/test/resources/migration/business-partners.csv +++ b/src/test/resources/migration/business-partners.csv @@ -2,4 +2,5 @@ bp_id;member_id;member_code;member_since;member_until;member_role;author_contrac 17;10017;hsh00-mih;2000-12-06;;Aufsichtsrat;2006-10-15;2001-10-15;false;false;NET;DE-VAT-007 20;10020;hsh00-xyz;2000-12-06;2015-12-31;;;;false;false;GROSS; 22;11022;hsh00-xxx;2021-04-01;;;;;true;true;GROSS; +90;19090;hsh00-yyy;;;;;;true;true;GROSS; 99;19999;hsh00-zzz;;;;;;false;false;GROSS; diff --git a/src/test/resources/migration/contacts.csv b/src/test/resources/migration/contacts.csv index 3aa1aa04..afcefdf9 100644 --- a/src/test/resources/migration/contacts.csv +++ b/src/test/resources/migration/contacts.csv @@ -15,3 +15,6 @@ contact_id; bp_id; salut; first_name; last_name; title; firma; co; street; zip # eine natürliche Person, die nur Subscriber ist 1401; 17; Frau; Frauke; Fanninga; ; ; ; Am Walde 1; 29456; Hitzacker; DE; ; ; ;; ff@example.org; subscriber:operations-announce + +# eine natürliche Person als Partner +1501; 90; Frau; Cecilia; Camus; ; ; ; Rue d'Avignion 60; 45000; Orléans; FR; ; ; ;; cc@example.org; partner,contractual,billing,operation