import-cancelled-memberships-if-booking-exist (#36)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #36
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
This commit is contained in:
Michael Hoennig 2024-04-09 10:22:57 +02:00
parent 4314b647f6
commit 48f4cf8ed6
19 changed files with 191 additions and 119 deletions

View File

@ -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;

View File

@ -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<LocalDate> 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"),

View File

@ -23,9 +23,9 @@ public class HsOfficeMembershipEntityPatcher implements EntityPatcher<HsOfficeMe
public void apply(final HsOfficeMembershipPatchResource resource) {
OptionalFromJson.of(resource.getValidTo()).ifPresent(
entity::setValidTo);
Optional.ofNullable(resource.getReasonForTermination())
.map(v -> 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);
}

View File

@ -0,0 +1,5 @@
package net.hostsharing.hsadminng.hs.office.membership;
public enum HsOfficeMembershipStatus {
INVALID, ACTIVE, CANCELLED, TRANSFERRED, DECEASED, LIQUIDATED, EXPULSED, UNKNOWN;
}

View File

@ -1,5 +0,0 @@
package net.hostsharing.hsadminng.hs.office.membership;
public enum HsOfficeReasonForTermination {
NONE, CANCELLATION, TRANSFER, DEATH, LIQUIDATION, EXPULSION, UNKNOWN;
}

View File

@ -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

View File

@ -1,4 +1,4 @@
openapi: 3.0.1
openapi: 3.0.3
info:
title: Hostsharing hsadmin-ng API
version: v0

View File

@ -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)

View File

@ -172,7 +172,7 @@ call generateRbacRestrictedView('hs_office_membership',
$updates$
validity = new.validity,
membershipFeeBillable = new.membershipFeeBillable,
reasonForTermination = new.reasonForTermination
status = new.status
$updates$);
--//

View File

@ -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; $$;
--//

View File

@ -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();

View File

@ -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",

View File

@ -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

View File

@ -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));
});

View File

@ -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<String, HsOfficeCoopAssetsTransactionType>() {
{
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<String[]> records) {
final var columns = new Columns(header);

View File

@ -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

View File

@ -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

1 member_asset_id bp_id date action amount comment
2 30000 17 2000-12-06 PAYMENT 1280.00 for subscription A
3 31000 20 2000-12-06 PAYMENT 128.00 for subscription B
4 32000 17 2005-01-10 PAYMENT 2560.00 for subscription C
5 33001 17 2005-01-10 HANDOVER -512.00 for transfer to 10
6 33002 20 2005-01-10 ADOPTION 512.00 for transfer from 7
7 34001 20 2016-12-31 CLEARING -8.00 for cancellation D
8 34002 20 2016-12-31 PAYBACK -100.00 for cancellation D
9 34003 20 2016-12-31 LOSS -20.00 for cancellation D
10 35001 90 2024-01-15 PAYMENT 128.00 for subscription E
11 35002 90 2024-01-20 ADJUSTMENT -128.00 chargeback for subscription E

View File

@ -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;

1 bp_id member_id member_code member_since member_until member_role author_contract nondisc_contract free exempt_vat indicator_vat uid_vat
2 17 10017 hsh00-mih 2000-12-06 Aufsichtsrat 2006-10-15 2001-10-15 false false NET DE-VAT-007
3 20 10020 hsh00-xyz 2000-12-06 2015-12-31 false false GROSS
4 22 11022 hsh00-xxx 2021-04-01 true true GROSS
5 90 19090 hsh00-yyy true true GROSS
6 99 19999 hsh00-zzz false false GROSS

View File

@ -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

1 contact_id; bp_id; salut; first_name; last_name; title; firma; co; street; zipcode;city; country; phone_private; phone_office; phone_mobile; fax; email; roles
15 1501; 90; Frau; Cecilia; Camus; ; ; ; Rue d'Avignion 60; 45000; Orléans; FR; ; ; ;; cc@example.org; partner,contractual,billing,operation
16
17
18
19
20