diff --git a/.aliases b/.aliases index 9477474d..c0b4e22d 100644 --- a/.aliases +++ b/.aliases @@ -1,3 +1,10 @@ +# For using the alias import-office-tables, # copy these exports to .environment (ignored by git) +# and amend them according to your external DB: +export HSADMINNG_POSTGRES_JDBC_URL=jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers +export HSADMINNG_POSTGRES_ADMIN_USERNAME=admin +export HSADMINNG_POSTGRES_ADMIN_PASSWORD= +export HSADMINNG_POSTGRES_RESTRICTED_USERNAME=restricted + gradleWrapper () { if [ ! -f gradlew ]; then echo "No 'gradlew' found. Maybe you are not in the root dir of a gradle project?" @@ -36,9 +43,26 @@ postgresAutodoc () { dot -Tsvg build/postgres-autodoc.neato >build/postgres-autodoc-rbac.svg && \ echo "generated $PWD/build/postgres-autodoc-rbac.svg" } - alias postgres-autodoc=postgresAutodoc +function importOfficeData() { + export HSADMINNG_POSTGRES_JDBC=jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers + export HSADMINNG_POSTGRES_ADMIN_USERNAME=admin + export HSADMINNG_POSTGRES_ADMIN_PASSWORD=password + export HSADMINNG_POSTGRES_RESTRICTED_USERNAME=restricted + export HSADMINNG_SUPERUSER=superuser-alex@hostsharing.net + + if [ -f .environment ]; then + source .environment + fi + + echo "using environment (with ending ';' for use in IntelliJ IDEA):" + set | grep ^HSADMINNG_ | sed 's/$/;/' + + ./gradlew importOfficeData --rerun +} +alias gw-importOfficeData=importOfficeData + alias podman-start='systemctl --user enable --now podman.socket && systemctl --user status podman.socket && ls -la /run/user/$UID/podman/podman.sock' alias podman-stop='systemctl --user disable --now podman.socket && systemctl --user status podman.socket && ls -la /run/user/$UID/podman/podman.sock' alias podman-use='export DOCKER_HOST="unix:///run/user/$UID/podman/podman.sock"; export TESTCONTAINERS_RYUK_DISABLED=true' @@ -53,3 +77,6 @@ alias pg-sql-backup='docker exec -i hsadmin-ng-postgres /usr/bin/pg_dump --clean alias pg-sql-restore='gunzip --stdout | docker exec -i hsadmin-ng-postgres psql -U postgres -d postgres' alias fp='grep -r '@Accepts' src | sed -e 's/^.*@/@/g' | sort -u | wc -l' + +alias gw-spotless='./gradlew spotlessApply -x pitest -x test -x :processResources' +alias gw-test='. .aliases; ./gradlew test' diff --git a/.gitignore b/.gitignore index 9500b657..d6a2e347 100644 --- a/.gitignore +++ b/.gitignore @@ -137,3 +137,4 @@ Desktop.ini # ESLint ###################### .eslintcache +/.environment* diff --git a/build.gradle b/build.gradle index 4268446c..285fa8d9 100644 --- a/build.gradle +++ b/build.gradle @@ -100,6 +100,7 @@ dependencies { testImplementation 'io.rest-assured:spring-mock-mvc' testImplementation 'org.hamcrest:hamcrest-core:2.2' testImplementation 'org.pitest:pitest-junit5-plugin:1.2.1' + testImplementation 'org.junit.jupiter:junit-jupiter-api' } dependencyManagement { @@ -129,7 +130,7 @@ openapiProcessor { processor 'io.openapiprocessor:openapi-processor-spring:2022.5' apiPath "$projectDir/src/main/resources/api-definition.yaml" mapping "$projectDir/src/main/resources/api-mappings.yaml" - targetDir "$projectDir/build/generated/sources/openapi-javax" + targetDir "$buildDir/generated/sources/openapi-javax" showWarnings true openApiNullable true } @@ -138,7 +139,7 @@ openapiProcessor { processor 'io.openapiprocessor:openapi-processor-spring:2022.5' apiPath "$projectDir/src/main/resources/api-definition/rbac/rbac.yaml" mapping "$projectDir/src/main/resources/api-definition/rbac/api-mappings.yaml" - targetDir "$projectDir/build/generated/sources/openapi-javax" + targetDir "$buildDir/generated/sources/openapi-javax" showWarnings true openApiNullable true } @@ -147,7 +148,7 @@ openapiProcessor { processor 'io.openapiprocessor:openapi-processor-spring:2022.5' apiPath "$projectDir/src/main/resources/api-definition/test/test.yaml" mapping "$projectDir/src/main/resources/api-definition/test/api-mappings.yaml" - targetDir "$projectDir/build/generated/sources/openapi-javax" + targetDir "$buildDir/generated/sources/openapi-javax" showWarnings true openApiNullable true } @@ -156,7 +157,7 @@ openapiProcessor { processor 'io.openapiprocessor:openapi-processor-spring:2022.5' apiPath "$projectDir/src/main/resources/api-definition/hs-office/hs-office.yaml" mapping "$projectDir/src/main/resources/api-definition/hs-office/api-mappings.yaml" - targetDir "$projectDir/build/generated/sources/openapi-javax" + targetDir "$buildDir/generated/sources/openapi-javax" showWarnings true openApiNullable true } @@ -237,6 +238,9 @@ test { excludes = [ 'net.hostsharing.hsadminng.**.generated.**', ] + useJUnitPlatform { + excludeTags 'import' + } } jacocoTestReport { dependsOn test @@ -297,6 +301,16 @@ jacocoTestCoverageVerification { } } +tasks.register('importOfficeData', Test) { + useJUnitPlatform { + includeTags 'import' + } + + group 'verification' + description 'run the import jobs as tests' +} + + // pitest mutation testing pitest { targetClasses = ['net.hostsharing.hsadminng.**'] diff --git a/doc/hs-office-data-structure.md b/doc/hs-office-data-structure.md new file mode 100644 index 00000000..960e572b --- /dev/null +++ b/doc/hs-office-data-structure.md @@ -0,0 +1,186 @@ +# Beispiel: juristische Person (GmbH) + +```mermaid +classDiagram + direction TD + + namespace Hostsharing { + class person-HostsharingEG + } + + namespace Partner { + class partner-MeierGmbH + class role-MeierGmbH + class personDetails-MeierGmbH + class contactData-MeierGmbH + class person-MeierGmbH + } + + namespace Representatives { + class person-FrankMeier + class contactData-FrankMeier + class role-MeierGmbH-FrankMeier + } + + namespace Debitors { + class debitor-MeierGmbH + class contactData-MeierGmbH-Buha + class role-MeierGmbH-Buha + } + + namespace Operations { + class person-SabineMeier + class contactData-SabineMeier + class role-MeierGmbH-SabineMeier + } + + namespace Enums { + + class RoleType { + <> + UNKNOWN + REPRESENTATIVE + ACCOUNTING + OPERATIONS + } + + class PersonType { + <> + UNKNOWN: nur für Import + NATURAL_PERSON: natürliche Person + LEGAL_PERSON: z.B. GmbH, e.K., eG, e.V. + INCORORATED_FIRM: z.B. OHG, Partnerschaftsgesellschaft + UNINCORPORATED_FIRM: z.B. GbR, ARGE, Erbengemeinschaft + PUBLIC_INSTITUTION: KdöR, AöR [ohne Registergericht/Registernummer] + } + } + + class person-HostsharingEG { + +personType: LEGAL + +tradeName: Hostsahring eG + +familyName + +givenName + } + + class partner-MeierGmbH { + +Numeric partnerNumber: 12345 + +Role partnerRole + } + partner-MeierGmbH *-- role-MeierGmbH + + class person-MeierGmbH { + +personType: LEGAL + +tradeName: Meier GmbH + +familyName + +givenName + } + person-MeierGmbH *-- personDetails-MeierGmbH + + class personDetails-MeierGmbH { + +registrationOffice: AG Hamburg + +registrationNumber: ABC123434 + +birthName + +birthPlace + +dateOfDeath + } + + class contactData-MeierGmbH { + +postalAddress: Hauptstraße 5, 22345 Hamburg + +phoneNumbers: +49 40 12345-00 + +emailAddresses: office@meier-gmbh.de + } + + class role-MeierGmbH { + +RoleType RoleType PARTNER + +Person anchor + +Person holder + +Contact roleContact + } + role-MeierGmbH o-- person-HostsharingEG : anchor + role-MeierGmbH o-- person-MeierGmbH : holder + role-MeierGmbH o-- contactData-MeierGmbH + + %% --- Debitors --- + + class debitor-MeierGmbH { + +Partner partner + +Numeric[2] debitorNumberSuffix: 00 + +Role billingRole + +boolean billable: true + +String vatId: ID123456789 + +String vatCountryCode: DE + +boolean vatBusiness: true + +boolean vatReverseCharge: false + +BankAccount refundBankAccount + +String defaultPrefix: mei + } + debitor-MeierGmbH o-- partner-MeierGmbH + debitor-MeierGmbH *-- role-MeierGmbH-Buha + + class contactData-MeierGmbH-Buha { + +postalAddress: Hauptstraße 5, 22345 Hamburg + +phoneNumbers: +49 40 12345-05 + +emailAddresses: buha@meier-gmbh.de + } + + class role-MeierGmbH-Buha { + +RoleType RoleType ACCOUNTING + +Person anchor + +Person holder + +Contact roleContact + } + role-MeierGmbH-Buha o-- person-MeierGmbH : anchor + role-MeierGmbH-Buha o-- person-MeierGmbH : holder + role-MeierGmbH-Buha o-- contactData-MeierGmbH-Buha + + %% --- Representatives --- + + class person-FrankMeier { + + personType: NATURAL + + tradeName + + familyName: Meier + + givenName: Frank + } + + class contactData-FrankMeier { + +postalAddress + +phoneNumbers: +49 40 12345-22 + +emailAddresses: frank.meier@meier-gmbh.de + } + + class role-MeierGmbH-FrankMeier { + +RoleType RoleType REPRESENTATIVE + +Person anchor + +Person holder + +Contact roleContact + } + role-MeierGmbH-FrankMeier o-- person-MeierGmbH : anchor + role-MeierGmbH-FrankMeier o-- person-FrankMeier : holder + role-MeierGmbH-FrankMeier o-- contactData-FrankMeier + + %% --- Operations --- + + class person-SabineMeier { + +personType: NATURAL + +tradeName + +familyName: Meier + +givenName: Sabine + } + + class contactData-SabineMeier { + +postalAddress + +phoneNumbers: +49 40 12345-22 + +emailAddresses: sabine.meier@meier-gmbh.de + } + + class role-MeierGmbH-SabineMeier { + +RoleType RoleType OPERATIONAL + +Person anchor + +Person holder + +Contact roleContact + } + role-MeierGmbH-SabineMeier o-- person-MeierGmbH : anchor + role-MeierGmbH-SabineMeier o-- person-SabineMeier : holder + role-MeierGmbH-SabineMeier o-- contactData-SabineMeier + +``` diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountEntity.java index bbaacb1d..fd6b0c44 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/bankaccount/HsOfficeBankAccountEntity.java @@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.office.bankaccount; import lombok.*; import lombok.experimental.FieldNameConstants; import net.hostsharing.hsadminng.errors.DisplayName; +import net.hostsharing.hsadminng.hs.office.migration.HasUuid; import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringifyable; @@ -23,7 +24,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @AllArgsConstructor @FieldNameConstants @DisplayName("BankAccount") -public class HsOfficeBankAccountEntity implements Stringifyable { +public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable { private static Stringify toString = stringify(HsOfficeBankAccountEntity.class, "bankAccount") .withProp(Fields.holder, HsOfficeBankAccountEntity::getHolder) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntity.java index 7a2c84e0..c3ecb6be 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntity.java @@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.office.contact; import lombok.*; import lombok.experimental.FieldNameConstants; import net.hostsharing.hsadminng.errors.DisplayName; +import net.hostsharing.hsadminng.hs.office.migration.HasUuid; import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringifyable; import org.hibernate.annotations.GenericGenerator; @@ -21,7 +22,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @AllArgsConstructor @FieldNameConstants @DisplayName("Contact") -public class HsOfficeContactEntity implements Stringifyable { +public class HsOfficeContactEntity implements Stringifyable, HasUuid { private static Stringify toString = stringify(HsOfficeContactEntity.class, "contact") .withProp(Fields.label, HsOfficeContactEntity::getLabel) @@ -38,10 +39,10 @@ public class HsOfficeContactEntity implements Stringifyable { private String postalAddress; @Column(name = "emailaddresses", columnDefinition = "json") - private String emailAddresses; + private String emailAddresses; // TODO: check if we can really add multiple. format: ["eins@...", "zwei@..."] @Column(name = "phonenumbers", columnDefinition = "json") - private String phoneNumbers; + private String phoneNumbers; // TODO: check if we can really add multiple. format: { "office": "+49 40 12345-10", "fax": "+49 40 12345-05" } @Override public String toString() { diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionEntity.java index e699fb5c..0f1b3600 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionEntity.java @@ -1,8 +1,10 @@ + package net.hostsharing.hsadminng.hs.office.coopassets; import lombok.*; import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity; +import net.hostsharing.hsadminng.hs.office.migration.HasUuid; import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringifyable; import org.hibernate.annotations.GenericGenerator; @@ -13,6 +15,7 @@ import java.text.DecimalFormat; import java.time.LocalDate; import java.util.UUID; +import static java.util.Optional.ofNullable; import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @Entity @@ -23,14 +26,15 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @NoArgsConstructor @AllArgsConstructor @DisplayName("CoopAssetsTransaction") -public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable { +public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, HasUuid { private static Stringify stringify = stringify(HsOfficeCoopAssetsTransactionEntity.class) - .withProp(e -> e.getMembership().getMemberNumber()) + .withProp(HsOfficeCoopAssetsTransactionEntity::getMemberNumber) .withProp(HsOfficeCoopAssetsTransactionEntity::getValueDate) .withProp(HsOfficeCoopAssetsTransactionEntity::getTransactionType) .withProp(HsOfficeCoopAssetsTransactionEntity::getAssetValue) .withProp(HsOfficeCoopAssetsTransactionEntity::getReference) + .withProp(HsOfficeCoopAssetsTransactionEntity::getComment) .withSeparator(", ") .quotedValues(false); @@ -54,11 +58,16 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable { private BigDecimal assetValue; @Column(name = "reference") - private String reference; + private String reference; // TODO: what is this for? @Column(name = "comment") private String comment; + + public Integer getMemberNumber() { + return ofNullable(membership).map(HsOfficeMembershipEntity::getMemberNumber).orElse(null); + } + @Override public String toString() { return stringify.apply(this); @@ -66,6 +75,6 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable { @Override public String toShortString() { - return membership.getMemberNumber() + new DecimalFormat("+0.00").format(assetValue); + return "%s%+1.2f".formatted(getMemberNumber(), assetValue); } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionType.java b/src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionType.java index cc386966..2245f864 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionType.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionType.java @@ -1,5 +1,12 @@ package net.hostsharing.hsadminng.hs.office.coopassets; public enum HsOfficeCoopAssetsTransactionType { - ADJUSTMENT, DEPOSIT, DISBURSAL, TRANSFER, ADOPTION, CLEARING, LOSS + ADJUSTMENT, // correction of wrong bookings + DEPOSIT, // payment received from member after signing shares, >0 + DISBURSAL, // payment send to member after cancellation of shares, <0 + TRANSFER, // transferring shares to another member, <0 + ADOPTION, // receiving shares from another member, >0 + CLEARING, // settlement with members dept, <0 + LOSS, // assignment of balance sheet loss in case of cancellation of shares, <0 + LIMITATION // limitation period was reached after impossible disbursal, <0 } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntity.java index b5d4979b..6c47bbb8 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntity.java @@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.office.coopshares; import lombok.*; import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity; +import net.hostsharing.hsadminng.hs.office.migration.HasUuid; import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringifyable; @@ -10,6 +11,7 @@ import jakarta.persistence.*; import java.time.LocalDate; import java.util.UUID; +import static java.util.Optional.ofNullable; import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @Entity @@ -20,14 +22,15 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @NoArgsConstructor @AllArgsConstructor @DisplayName("CoopShareTransaction") -public class HsOfficeCoopSharesTransactionEntity implements Stringifyable { +public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, HasUuid { private static Stringify stringify = stringify(HsOfficeCoopSharesTransactionEntity.class) - .withProp(e -> e.getMembership().getMemberNumber()) + .withProp(HsOfficeCoopSharesTransactionEntity::getMemberNumber) .withProp(HsOfficeCoopSharesTransactionEntity::getValueDate) .withProp(HsOfficeCoopSharesTransactionEntity::getTransactionType) .withProp(HsOfficeCoopSharesTransactionEntity::getShareCount) .withProp(HsOfficeCoopSharesTransactionEntity::getReference) + .withProp(HsOfficeCoopSharesTransactionEntity::getComment) .withSeparator(", ") .quotedValues(false); @@ -50,7 +53,7 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable { private int shareCount; @Column(name = "reference") - private String reference; + private String reference; // TODO: what is this for? @Column(name = "comment") private String comment; @@ -60,8 +63,12 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable { return stringify.apply(this); } + public Integer getMemberNumber() { + return ofNullable(membership).map(HsOfficeMembershipEntity::getMemberNumber).orElse(null); + } + @Override public String toShortString() { - return "%s%+d".formatted(membership.getMemberNumber(), shareCount); + return "%s%+d".formatted(getMemberNumber(), shareCount); } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionType.java b/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionType.java index fedccc5c..e18451f5 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionType.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionType.java @@ -1,5 +1,7 @@ package net.hostsharing.hsadminng.hs.office.coopshares; public enum HsOfficeCoopSharesTransactionType { - ADJUSTMENT, SUBSCRIPTION, CANCELLATION; + ADJUSTMENT, // correction of wrong bookings + SUBSCRIPTION, // shares signed, e.g. with the declaration of accession, >0 + CANCELLATION; // shares terminated, e.g. when a membership is resigned, <0 } 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 4a29be90..332ea8dd 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 @@ -4,12 +4,14 @@ import lombok.*; import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; +import net.hostsharing.hsadminng.hs.office.migration.HasUuid; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringifyable; import org.hibernate.annotations.GenericGenerator; import jakarta.persistence.*; +import java.util.Optional; import java.util.UUID; import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @@ -22,12 +24,16 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @NoArgsConstructor @AllArgsConstructor @DisplayName("Debitor") -public class HsOfficeDebitorEntity implements Stringifyable { +public class HsOfficeDebitorEntity implements HasUuid, Stringifyable { + // TODO: I would rather like to generate something matching this example: + // debitor(1234500: Test AG, tes) + // maybe remove withSepararator (always use ', ') and add withBusinessIdProp (with ': ' afterwards)? private static Stringify stringify = stringify(HsOfficeDebitorEntity.class, "debitor") .withProp(HsOfficeDebitorEntity::getDebitorNumber) .withProp(HsOfficeDebitorEntity::getPartner) + .withProp(HsOfficeDebitorEntity::getDefaultPrefix) .withSeparator(": ") .quotedValues(false); @@ -40,12 +46,15 @@ public class HsOfficeDebitorEntity implements Stringifyable { @JoinColumn(name = "partneruuid") private HsOfficePartnerEntity partner; - @Column(name = "debitornumber") - private Integer debitorNumber; + @Column(name = "debitornumbersuffix", columnDefinition = "numeric(2)") + private Byte debitorNumberSuffix; // TODO maybe rather as a formatted String? @ManyToOne @JoinColumn(name = "billingcontactuuid") - private HsOfficeContactEntity billingContact; + private HsOfficeContactEntity billingContact; // TODO: migrate to billingPerson + + @Column(name = "billable", nullable = false) + private Boolean billable; // not a primitive because otherwise the default would be false @Column(name = "vatid") private String vatId; @@ -56,10 +65,34 @@ public class HsOfficeDebitorEntity implements Stringifyable { @Column(name = "vatbusiness") private boolean vatBusiness; + @Column(name = "vatreversecharge") + private boolean vatReverseCharge; + @ManyToOne @JoinColumn(name = "refundbankaccountuuid") private HsOfficeBankAccountEntity refundBankAccount; + @Column(name = "defaultprefix", columnDefinition = "char(3) not null") + private String defaultPrefix; + + public String getDebitorNumberString() { + // TODO: refactor + if (partner.getDebitorNumberPrefix() == null ) { + if (debitorNumberSuffix == null) { + return null; + } + return String.format("%02d", debitorNumberSuffix); + } + if (debitorNumberSuffix == null) { + return partner.getDebitorNumberPrefix() + "??"; + } + return partner.getDebitorNumberPrefix() + String.format("%02d", debitorNumberSuffix); + } + + public Integer getDebitorNumber() { + return Optional.ofNullable(getDebitorNumberString()).map(Integer::parseInt).orElse(null); + } + @Override public String toString() { return stringify.apply(this); @@ -67,6 +100,6 @@ public class HsOfficeDebitorEntity implements Stringifyable { @Override public String toShortString() { - return debitorNumber.toString(); + return getDebitorNumberString(); } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntityPatcher.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntityPatcher.java index b57b1ab2..914c8230 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntityPatcher.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntityPatcher.java @@ -1,11 +1,13 @@ package net.hostsharing.hsadminng.hs.office.debitor; +import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorPatchResource; import net.hostsharing.hsadminng.mapper.EntityPatcher; import net.hostsharing.hsadminng.mapper.OptionalFromJson; import jakarta.persistence.EntityManager; +import java.util.Optional; class HsOfficeDebitorEntityPatcher implements EntityPatcher { @@ -25,11 +27,18 @@ class HsOfficeDebitorEntityPatcher implements EntityPatcher { - verifyNotNull(newValue, "vatBusiness"); - entity.setVatBusiness(newValue); + Optional.ofNullable(resource.getVatBusiness()).ifPresent(entity::setVatBusiness); + Optional.ofNullable(resource.getVatReverseCharge()).ifPresent(entity::setVatReverseCharge); + OptionalFromJson.of(resource.getDefaultPrefix()).ifPresent(newValue -> { + verifyNotNull(newValue, "defaultPrefix"); + entity.setDefaultPrefix(newValue); + }); + OptionalFromJson.of(resource.getRefundBankAccountUuid()).ifPresent(newValue -> { + verifyNotNull(newValue, "refundBankAccount"); + entity.setRefundBankAccount(em.getReference(HsOfficeBankAccountEntity.class, newValue)); }); } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java index f0013ef9..5ca04719 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepository.java @@ -13,9 +13,14 @@ public interface HsOfficeDebitorRepository extends Repository findDebitorByDebitorNumber(int debitorNumber); + List findDebitorByDebitorNumber(int debitorNumberPrefix, byte debitorNumberSuffix); + + default List findDebitorByDebitorNumber(int debitorNumber) { + return findDebitorByDebitorNumber( debitorNumber/100, (byte) (debitorNumber%100)); + } @Query(""" SELECT debitor FROM HsOfficeDebitorEntity debitor 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 671ae7f7..b5846324 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 @@ -5,6 +5,7 @@ import com.vladmihalcea.hibernate.type.range.Range; import lombok.*; import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; +import net.hostsharing.hsadminng.hs.office.migration.HasUuid; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringifyable; @@ -27,7 +28,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @NoArgsConstructor @AllArgsConstructor @DisplayName("Membership") -public class HsOfficeMembershipEntity implements Stringifyable { +public class HsOfficeMembershipEntity implements HasUuid, Stringifyable { private static Stringify stringify = stringify(HsOfficeMembershipEntity.class) .withProp(HsOfficeMembershipEntity::getMemberNumber) @@ -52,12 +53,15 @@ public class HsOfficeMembershipEntity implements Stringifyable { private HsOfficeDebitorEntity mainDebitor; @Column(name = "membernumber") - private int memberNumber; + private int memberNumber; // TODO: migrate to suffix, like debitorNumberSuffix @Column(name = "validity", columnDefinition = "daterange") @Type(PostgreSQLRangeType.class) private Range validity; + @Column(name = "membershipfeebillable", nullable = false) + private Boolean membershipFeeBillable; // not primitive to force setting the value + @Column(name = "reasonfortermination") @Enumerated(EnumType.STRING) private HsOfficeReasonForTermination reasonForTermination; 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 f163434f..59fa6070 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 @@ -37,6 +37,8 @@ public class HsOfficeMembershipEntityPatcher implements EntityPatcher mapper.map(v, HsOfficeReasonForTermination.class)) .ifPresent(entity::setReasonForTermination); + OptionalFromJson.of(resource.getMembershipFeeBillable()).ifPresent( + entity::setMembershipFeeBillable); } private void verifyNotNull(final UUID newValue, final String propertyName) { 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 index c043152d..a2a41051 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeReasonForTermination.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeReasonForTermination.java @@ -1,5 +1,5 @@ package net.hostsharing.hsadminng.hs.office.membership; public enum HsOfficeReasonForTermination { - NONE, CANCELLATION, TRANSFER, DEATH, LIQUIDATION, EXPULSION; + NONE, CANCELLATION, TRANSFER, DEATH, LIQUIDATION, EXPULSION, UNKNOWN; } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/migration/HasUuid.java b/src/main/java/net/hostsharing/hsadminng/hs/office/migration/HasUuid.java new file mode 100644 index 00000000..97e3eff1 --- /dev/null +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/migration/HasUuid.java @@ -0,0 +1,7 @@ +package net.hostsharing.hsadminng.hs.office.migration; + +import java.util.UUID; + +public interface HasUuid { + UUID getUuid(); +} diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java index abfd5d8b..ea09eb44 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java @@ -2,6 +2,7 @@ package net.hostsharing.hsadminng.hs.office.partner; import lombok.*; import net.hostsharing.hsadminng.errors.DisplayName; +import net.hostsharing.hsadminng.hs.office.migration.HasUuid; import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringifyable; @@ -19,15 +20,16 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @NoArgsConstructor @AllArgsConstructor @DisplayName("PartnerDetails") -public class HsOfficePartnerDetailsEntity implements Stringifyable { +public class HsOfficePartnerDetailsEntity implements HasUuid, Stringifyable { private static Stringify stringify = stringify( HsOfficePartnerDetailsEntity.class, "partnerDetails") .withProp(HsOfficePartnerDetailsEntity::getRegistrationOffice) .withProp(HsOfficePartnerDetailsEntity::getRegistrationNumber) + .withProp(HsOfficePartnerDetailsEntity::getBirthPlace) .withProp(HsOfficePartnerDetailsEntity::getBirthday) - .withProp(HsOfficePartnerDetailsEntity::getBirthday) + .withProp(HsOfficePartnerDetailsEntity::getBirthName) .withProp(HsOfficePartnerDetailsEntity::getDateOfDeath) .withSeparator(", ") .quotedValues(false); @@ -39,6 +41,7 @@ public class HsOfficePartnerDetailsEntity implements Stringifyable { private @Column(name = "registrationoffice") String registrationOffice; private @Column(name = "registrationnumber") String registrationNumber; private @Column(name = "birthname") String birthName; + private @Column(name = "birthplace") String birthPlace; private @Column(name = "birthday") LocalDate birthday; private @Column(name = "dateofdeath") LocalDate dateOfDeath; diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntityPatcher.java b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntityPatcher.java index 5940339a..61cd49c0 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntityPatcher.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntityPatcher.java @@ -22,6 +22,7 @@ class HsOfficePartnerDetailsEntityPatcher implements EntityPatcher stringify = stringify(HsOfficePartnerEntity.class, "partner") .withProp(HsOfficePartnerEntity::getPerson) @@ -34,6 +36,9 @@ public class HsOfficePartnerEntity implements Stringifyable { @GeneratedValue private UUID uuid; + @Column(name = "debitornumberprefix", columnDefinition = "numeric(5) not null") + private Integer debitorNumberPrefix; + @ManyToOne @JoinColumn(name = "personuuid", nullable = false) private HsOfficePersonEntity person; @@ -43,7 +48,7 @@ public class HsOfficePartnerEntity implements Stringifyable { private HsOfficeContactEntity contact; @ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH }, optional = true) - @JoinColumn(name = "detailsuuid", nullable = true) + @JoinColumn(name = "detailsuuid") @NotFound(action = NotFoundAction.IGNORE) private HsOfficePartnerDetailsEntity details; @@ -54,6 +59,6 @@ public class HsOfficePartnerEntity implements Stringifyable { @Override public String toShortString() { - return person.toShortString(); + return Optional.ofNullable(person).map(HsOfficePersonEntity::toShortString).orElse(""); } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntity.java index a76d4130..94a1a74e 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonEntity.java @@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.office.person; import lombok.*; import lombok.experimental.FieldNameConstants; import net.hostsharing.hsadminng.errors.DisplayName; +import net.hostsharing.hsadminng.hs.office.migration.HasUuid; import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringifyable; import org.apache.commons.lang3.StringUtils; @@ -21,7 +22,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @AllArgsConstructor @FieldNameConstants @DisplayName("Person") -public class HsOfficePersonEntity implements Stringifyable { +public class HsOfficePersonEntity implements HasUuid, Stringifyable { private static Stringify toString = stringify(HsOfficePersonEntity.class, "person") .withProp(Fields.personType, HsOfficePersonEntity::getPersonType) @@ -53,6 +54,7 @@ public class HsOfficePersonEntity implements Stringifyable { @Override public String toShortString() { - return !StringUtils.isEmpty(tradeName) ? tradeName : (familyName + ", " + givenName); + return personType + " " + + (!StringUtils.isEmpty(tradeName) ? tradeName : (familyName + ", " + givenName)); } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonType.java b/src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonType.java index 589e6eeb..aae35eaf 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonType.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonType.java @@ -1,6 +1,7 @@ package net.hostsharing.hsadminng.hs.office.person; public enum HsOfficePersonType { + UNKNOWN, NATURAL, LEGAL, SOLE_REPRESENTATION, diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntity.java index 383c6853..b9b4f980 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntity.java @@ -3,8 +3,10 @@ package net.hostsharing.hsadminng.hs.office.relationship; import lombok.*; import lombok.experimental.FieldNameConstants; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; +import net.hostsharing.hsadminng.hs.office.migration.HasUuid; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.stringify.Stringify; +import net.hostsharing.hsadminng.stringify.Stringifyable; import jakarta.persistence.*; import java.util.UUID; @@ -19,7 +21,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @NoArgsConstructor @AllArgsConstructor @FieldNameConstants -public class HsOfficeRelationshipEntity { +public class HsOfficeRelationshipEntity implements HasUuid, Stringifyable { private static Stringify toString = stringify(HsOfficeRelationshipEntity.class, "rel") .withProp(Fields.relAnchor, HsOfficeRelationshipEntity::getRelAnchor) @@ -27,6 +29,11 @@ public class HsOfficeRelationshipEntity { .withProp(Fields.relHolder, HsOfficeRelationshipEntity::getRelHolder) .withProp(Fields.contact, HsOfficeRelationshipEntity::getContact); + private static Stringify toShortString = stringify(HsOfficeRelationshipEntity.class, "rel") + .withProp(Fields.relAnchor, HsOfficeRelationshipEntity::getRelAnchor) + .withProp(Fields.relType, HsOfficeRelationshipEntity::getRelType) + .withProp(Fields.relHolder, HsOfficeRelationshipEntity::getRelHolder); + @Id @GeneratedValue private UUID uuid; @@ -51,4 +58,9 @@ public class HsOfficeRelationshipEntity { public String toString() { return toString.apply(this); } + + @Override + public String toShortString() { + return toShortString.apply(this); + } } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipType.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipType.java index 7a5097a3..e3955c7e 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipType.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipType.java @@ -1,8 +1,10 @@ package net.hostsharing.hsadminng.hs.office.relationship; public enum HsOfficeRelationshipType { - SOLE_AGENT, - JOINT_AGENT, - ACCOUNTING_CONTACT, - TECHNICAL_CONTACT + UNKNOWN, + EX_PARTNER, + REPRESENTATIVE, + VIP_CONTACT, + ACCOUNTING, + OPERATIONS } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateEntity.java index 84264bd6..bdd0b045 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateEntity.java @@ -6,6 +6,7 @@ import lombok.*; import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; +import net.hostsharing.hsadminng.hs.office.migration.HasUuid; import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringifyable; import org.hibernate.annotations.Type; @@ -25,7 +26,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify; @NoArgsConstructor @AllArgsConstructor @DisplayName("SEPA-Mandate") -public class HsOfficeSepaMandateEntity implements Stringifyable { +public class HsOfficeSepaMandateEntity implements Stringifyable, HasUuid { private static Stringify stringify = stringify(HsOfficeSepaMandateEntity.class) .withProp(e -> e.getBankAccount().getIban()) diff --git a/src/main/resources/api-definition/hs-office/hs-office-debitor-schemas.yaml b/src/main/resources/api-definition/hs-office/hs-office-debitor-schemas.yaml index 583ae8fa..26736fac 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-debitor-schemas.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-debitor-schemas.yaml @@ -12,12 +12,19 @@ components: debitorNumber: type: integer format: int32 - minimum: 10000 - maximum: 99999 + minimum: 1000000 + maximum: 9999999 + debitorNumberSuffix: + type: integer + format: int8 + minimum: 00 + maximum: 99 partner: $ref: './hs-office-partner-schemas.yaml#/components/schemas/HsOfficePartner' billingContact: $ref: './hs-office-contact-schemas.yaml#/components/schemas/HsOfficeContact' + billable: + type: boolean vatId: type: string vatCountryCode: @@ -25,8 +32,13 @@ components: pattern: '^[A-Z][A-Z]$' vatBusiness: type: boolean + vatReverseCharge: + type: boolean refundBankAccount: $ref: './hs-office-bankaccount-schemas.yaml#/components/schemas/HsOfficeBankAccount' + defaultPrefix: + type: string + pattern: '^[a-z0-9]{3}$' HsOfficeDebitorPatch: type: object @@ -35,6 +47,9 @@ components: type: string format: uuid nullable: true + billable: + type: boolean + nullable: false vatId: type: string nullable: true @@ -44,11 +59,18 @@ components: nullable: true vatBusiness: type: boolean - nullable: true + nullable: false + vatReverseCharge: + type: boolean + nullable: false refundBankAccountUuid: type: string format: uuid nullable: true + defaultPrefix: + type: string + pattern: '^[a-z0-9]{3}$' + nullable: true HsOfficeDebitorInsert: type: object @@ -61,11 +83,13 @@ components: type: string format: uuid nullable: false - debitorNumber: + debitorNumberSuffix: type: integer - format: int32 - minimum: 10000 - maximum: 99999 + format: int8 + minimum: 00 + maximum: 99 + billable: + type: boolean vatId: type: string vatCountryCode: @@ -73,9 +97,17 @@ components: pattern: '^[A-Z][A-Z]$' vatBusiness: type: boolean + vatReverseCharge: + type: boolean refundBankAccountUuid: type: string format: uuid + defaultPrefix: + type: string + pattern: '^[a-z]{3}$' + required: - partnerUuid - billingContactUuid + - defaultPrefix + - billable 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 fd27412a..74c8143b 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 @@ -33,6 +33,8 @@ components: format: date reasonForTermination: $ref: '#/components/schemas/HsOfficeReasonForTermination' + membershipFeeBillable: + type: boolean HsOfficeMembershipPatch: type: object @@ -48,6 +50,9 @@ components: reasonForTermination: nullable: true $ref: '#/components/schemas/HsOfficeReasonForTermination' + membershipFeeBillable: + nullable: true + type: boolean additionalProperties: false HsOfficeMembershipInsert: @@ -74,8 +79,12 @@ components: nullable: true reasonForTermination: $ref: '#/components/schemas/HsOfficeReasonForTermination' + membershipFeeBillable: + nullable: false + type: boolean required: - partnerUuid - mainDebitorUuid - validFrom + - membershipFeeBillable additionalProperties: false diff --git a/src/main/resources/api-definition/hs-office/hs-office-partner-schemas.yaml b/src/main/resources/api-definition/hs-office/hs-office-partner-schemas.yaml index c75fa24a..6b044a27 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-partner-schemas.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-partner-schemas.yaml @@ -9,6 +9,11 @@ components: uuid: type: string format: uuid + debitorNumberPrefix: + type: integer + format: int8 + minimum: 10000 + maximum: 99999 person: $ref: './hs-office-person-schemas.yaml#/components/schemas/HsOfficePerson' contact: @@ -32,6 +37,9 @@ components: birthName: type: string nullable: true + birthPlace: + type: string + nullable: true birthday: type: string format: date @@ -68,6 +76,9 @@ components: birthName: type: string nullable: true + birthPlace: + type: string + nullable: true birthday: type: string format: date @@ -80,6 +91,11 @@ components: HsOfficePartnerInsert: type: object properties: + debitorNumberPrefix: + type: integer + format: int8 + minimum: 10000 + maximum: 99999 personUuid: type: string format: uuid @@ -89,6 +105,7 @@ components: details: $ref: '#/components/schemas/HsOfficePartnerDetailsInsert' required: + - debitorNumberPrefix - personUuid - contactUuid - details @@ -106,6 +123,9 @@ components: birthName: type: string nullable: true + birthPlace: + type: string + nullable: true birthday: type: string format: date diff --git a/src/main/resources/api-definition/hs-office/hs-office-relationship-schemas.yaml b/src/main/resources/api-definition/hs-office/hs-office-relationship-schemas.yaml index 23af0ff0..cb865205 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-relationship-schemas.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-relationship-schemas.yaml @@ -6,10 +6,12 @@ components: HsOfficeRelationshipType: type: string enum: - - SOLE_AGENT # e.g. CEO - - JOINT_AGENT # e.g. heir - - ACCOUNTING_CONTACT - - TECHNICAL_CONTACT + - UNKNOWN + - EX_PARTNER + - REPRESENTATIVE, + - VIP_CONTACT + - ACCOUNTING, + - OPERATIONS HsOfficeRelationship: type: object diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 120e98c3..8d928aeb 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -20,3 +20,7 @@ spring: liquibase: contexts: dev + +hsadminng: + postgres: + leakproof: diff --git a/src/main/resources/db/changelog/009-check-environment.sql b/src/main/resources/db/changelog/009-check-environment.sql new file mode 100644 index 00000000..0bad2670 --- /dev/null +++ b/src/main/resources/db/changelog/009-check-environment.sql @@ -0,0 +1,18 @@ +--liquibase formatted sql + + +-- ============================================================================ +-- NUMERIC-HASH-FUNCTIONS +--changeset hash:1 endDelimiter:--// +-- ---------------------------------------------------------------------------- + +do $$ + begin + if starts_with ('${HSADMINNG_POSTGRES_ADMIN_USERNAME}', '$') then + RAISE EXCEPTION 'environment variable HSADMINNG_POSTGRES_ADMIN_USERNAME not set'; + end if; + if starts_with ('${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}', '$') then + RAISE EXCEPTION 'environment variable HSADMINNG_POSTGRES_RESTRICTED_USERNAME not set'; + end if; + end $$ +--// diff --git a/src/main/resources/db/changelog/010-context.sql b/src/main/resources/db/changelog/010-context.sql index 9d640165..4820cf9c 100644 --- a/src/main/resources/db/changelog/010-context.sql +++ b/src/main/resources/db/changelog/010-context.sql @@ -55,7 +55,7 @@ end; $$; */ create or replace function currentTask() returns varchar(96) - stable leakproof + stable -- leakproof language plpgsql as $$ declare currentTask varchar(96); @@ -83,7 +83,7 @@ end; $$; */ create or replace function currentRequest() returns varchar(512) - stable leakproof + stable -- leakproof language plpgsql as $$ declare currentRequest varchar(512); @@ -107,7 +107,7 @@ end; $$; */ create or replace function currentUser() returns varchar(63) - stable leakproof + stable -- leakproof language plpgsql as $$ declare currentUser varchar(63); @@ -131,7 +131,7 @@ end; $$; */ create or replace function assumedRoles() returns varchar(63)[] - stable leakproof + stable -- leakproof language plpgsql as $$ declare currentSubject varchar(63); @@ -155,7 +155,7 @@ create or replace function cleanIdentifier(rawIdentifier varchar) declare cleanIdentifier varchar; begin - cleanIdentifier := regexp_replace(rawIdentifier, '[^A-Za-z0-9\-._]+', '', 'g'); + cleanIdentifier := regexp_replace(rawIdentifier, '[^A-Za-z0-9\-._:]+', '', 'g'); return cleanIdentifier; end; $$; @@ -214,7 +214,7 @@ end ; $$; create or replace function currentSubjects() returns varchar(63)[] - stable leakproof + stable -- leakproof language plpgsql as $$ declare assumedRoles varchar(63)[]; @@ -229,7 +229,7 @@ end; $$; create or replace function hasAssumedRole() returns boolean - stable leakproof + stable -- leakproof language plpgsql as $$ begin return array_length(assumedRoles(), 1) > 0; diff --git a/src/main/resources/db/changelog/050-rbac-base.sql b/src/main/resources/db/changelog/050-rbac-base.sql index 3b395d33..3254b464 100644 --- a/src/main/resources/db/changelog/050-rbac-base.sql +++ b/src/main/resources/db/changelog/050-rbac-base.sql @@ -208,7 +208,7 @@ create type RbacRoleDescriptor as create or replace function roleDescriptor(objectTable varchar(63), objectUuid uuid, roleType RbacRoleType) returns RbacRoleDescriptor returns null on null input - stable leakproof + stable -- leakproof language sql as $$ select objectTable, objectUuid, roleType::RbacRoleType; $$; @@ -432,7 +432,7 @@ $$; create or replace function findPermissionId(forObjectUuid uuid, forOp RbacOp) returns uuid returns null on null input - stable leakproof + stable -- leakproof language sql as $$ select uuid from RbacPermission p @@ -515,7 +515,7 @@ end; $$; create or replace function isPermissionGrantedToSubject(permissionId uuid, subjectId uuid) returns BOOL - stable leakproof + stable -- leakproof language sql as $$ select exists( select * @@ -537,7 +537,7 @@ $$; create or replace function hasGlobalRoleGranted(userUuid uuid) returns bool - stable leakproof + stable -- leakproof language sql as $$ select exists( select r.uuid @@ -758,13 +758,20 @@ $$; -- ============================================================================ ---changeset rbac-base-PGSQL-ROLES:1 endDelimiter:--// +--changeset rbac-base-PGSQL-ROLES:1 context:dev,tc endDelimiter:--// -- ---------------------------------------------------------------------------- -create role admin; -grant all privileges on all tables in schema public to admin; - -create role restricted; -grant all privileges on all tables in schema public to restricted; +do $$ + begin + if '${HSADMINNG_POSTGRES_ADMIN_USERNAME}'='admin' then + create role admin; + grant all privileges on all tables in schema public to admin; + end if; + if '${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}'='restricted' then + create role restricted; + end if; + -- grant all privileges on all tables in schema public to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}; + end $$ --// + diff --git a/src/main/resources/db/changelog/051-rbac-user-grant.sql b/src/main/resources/db/changelog/051-rbac-user-grant.sql index 81cadc94..23dcbdd4 100644 --- a/src/main/resources/db/changelog/051-rbac-user-grant.sql +++ b/src/main/resources/db/changelog/051-rbac-user-grant.sql @@ -6,7 +6,7 @@ create or replace function assumedRoleUuid() returns uuid - stable leakproof + stable -- leakproof language plpgsql as $$ declare currentSubjectsUuids uuid[]; diff --git a/src/main/resources/db/changelog/054-rbac-context.sql b/src/main/resources/db/changelog/054-rbac-context.sql index f1c8cfdc..ede86057 100644 --- a/src/main/resources/db/changelog/054-rbac-context.sql +++ b/src/main/resources/db/changelog/054-rbac-context.sql @@ -7,7 +7,7 @@ create or replace function determineCurrentUserUuid(currentUser varchar) returns uuid - stable leakproof + stable -- leakproof language plpgsql as $$ declare currentUserUuid uuid; @@ -25,11 +25,11 @@ end; $$; create or replace function determineCurrentSubjectsUuids(currentUserUuid uuid, assumedRoles varchar) returns uuid[] - stable leakproof + stable -- leakproof language plpgsql as $$ declare - roleName varchar(63); - roleNameParts varchar(63); + roleName text; + roleNameParts text; objectTableToAssume varchar(63); objectNameToAssume varchar(63); objectUuidToAssume uuid; @@ -116,7 +116,7 @@ end; $$; create or replace function currentUserUuid() returns uuid - stable leakproof + stable -- leakproof language plpgsql as $$ declare currentUserUuid text; @@ -150,7 +150,7 @@ end; $$; */ create or replace function currentSubjectsUuids() returns uuid[] - stable leakproof + stable -- leakproof language plpgsql as $$ declare currentSubjectsUuids text; diff --git a/src/main/resources/db/changelog/055-rbac-views.sql b/src/main/resources/db/changelog/055-rbac-views.sql index 68ea11b5..d1d1d926 100644 --- a/src/main/resources/db/changelog/055-rbac-views.sql +++ b/src/main/resources/db/changelog/055-rbac-views.sql @@ -41,7 +41,7 @@ select * ) as unordered -- @formatter:on order by objectTable || '#' || objectIdName || '.' || roleType; -grant all privileges on rbacrole_rv to restricted; +grant all privileges on rbacrole_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}; --// @@ -126,7 +126,7 @@ select o.objectTable || '#' || findIdNameByObjectUuid(o.objectTable, o.uuid) || join RbacObject as o on o.uuid = r.objectUuid order by grantedRoleIdName; -- @formatter:on -grant all privileges on rbacrole_rv to restricted; +grant all privileges on rbacrole_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}; --// @@ -240,7 +240,7 @@ create or replace view RbacUser_rv as ) as unordered -- @formatter:on order by unordered.name; -grant all privileges on RbacUser_rv to restricted; +grant all privileges on RbacUser_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}; --// -- ============================================================================ @@ -326,7 +326,7 @@ select r.uuid as roleuuid, p.uuid as permissionUuid, join rbacgrants g on g.ascendantuuid = r.uuid join rbacpermission p on p.uuid = g.descendantuuid join rbacobject o on o.uuid = p.objectuuid; -grant all privileges on RbacOwnGrantedPermissions_rv to restricted; +grant all privileges on RbacOwnGrantedPermissions_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}; -- @formatter:om -- ============================================================================ diff --git a/src/main/resources/db/changelog/058-rbac-generators.sql b/src/main/resources/db/changelog/058-rbac-generators.sql index 82685028..fa198308 100644 --- a/src/main/resources/db/changelog/058-rbac-generators.sql +++ b/src/main/resources/db/changelog/058-rbac-generators.sql @@ -104,7 +104,7 @@ begin create or replace view %1$s_iv as select target.uuid, cleanIdentifier(%2$s) as idName from %1$s as target; - grant all privileges on %1$s_iv to restricted; + grant all privileges on %1$s_iv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}; $sql$, targetTable, idNameExpression); execute sql; @@ -157,7 +157,7 @@ begin from %1$s as target where target.uuid in (select * from accessibleObjects) order by %2$s; - grant all privileges on %1$s_rv to restricted; + grant all privileges on %1$s_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}; $sql$, targetTable, orderBy); execute sql; diff --git a/src/main/resources/db/changelog/080-rbac-global.sql b/src/main/resources/db/changelog/080-rbac-global.sql index deb690c0..034400fa 100644 --- a/src/main/resources/db/changelog/080-rbac-global.sql +++ b/src/main/resources/db/changelog/080-rbac-global.sql @@ -18,7 +18,7 @@ create table Global ); create unique index Global_Singleton on Global ((0)); -grant select on global to restricted; +grant select on global to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}; --// @@ -48,7 +48,7 @@ drop view if exists global_iv; create or replace view global_iv as select target.uuid, target.name as idName from global as target; -grant all privileges on global_iv to restricted; +grant all privileges on global_iv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}; /* Returns the objectUuid for a given identifying name (in this case the idName). @@ -99,7 +99,7 @@ commit; create or replace function globalAdmin() returns RbacRoleDescriptor returns null on null input - stable leakproof + stable -- leakproof language sql as $$ select 'global', (select uuid from RbacObject where objectTable = 'global'), 'admin'::RbacRoleType; $$; diff --git a/src/main/resources/db/changelog/123-test-package-rbac.sql b/src/main/resources/db/changelog/123-test-package-rbac.sql index f4eb2edd..8a2fd857 100644 --- a/src/main/resources/db/changelog/123-test-package-rbac.sql +++ b/src/main/resources/db/changelog/123-test-package-rbac.sql @@ -93,7 +93,7 @@ call generateRbacIdentityView('test_package', 'target.name'); -- from test_package as target -- where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'test_package', currentSubjectsUuids())) -- order by target.name; --- grant all privileges on test_package_rv to restricted; +-- grant all privileges on test_package_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}; call generateRbacRestrictedView('test_package', 'target.name', $updates$ diff --git a/src/main/resources/db/changelog/133-test-domain-rbac.sql b/src/main/resources/db/changelog/133-test-domain-rbac.sql index ceeb5de3..89b63018 100644 --- a/src/main/resources/db/changelog/133-test-domain-rbac.sql +++ b/src/main/resources/db/changelog/133-test-domain-rbac.sql @@ -110,5 +110,5 @@ create or replace view test_domain_rv as select target.* from test_domain as target where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'domain', currentSubjectsUuids())); -grant all privileges on test_domain_rv to restricted; +grant all privileges on test_domain_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}; --// diff --git a/src/main/resources/db/changelog/200-hs-office-contact.sql b/src/main/resources/db/changelog/200-hs-office-contact.sql index 4bbf0bee..9b67db1b 100644 --- a/src/main/resources/db/changelog/200-hs-office-contact.sql +++ b/src/main/resources/db/changelog/200-hs-office-contact.sql @@ -7,7 +7,7 @@ create table if not exists hs_office_contact ( uuid uuid unique references RbacObject (uuid) initially deferred, - label varchar(96) not null, + label varchar(128) not null, postalAddress text, emailAddresses text, -- TODO.feat: change to json phoneNumbers text -- TODO.feat: change to json diff --git a/src/main/resources/db/changelog/203-hs-office-contact-rbac.sql b/src/main/resources/db/changelog/203-hs-office-contact-rbac.sql index 39cdbf5e..7ba7891b 100644 --- a/src/main/resources/db/changelog/203-hs-office-contact-rbac.sql +++ b/src/main/resources/db/changelog/203-hs-office-contact-rbac.sql @@ -26,9 +26,6 @@ create or replace function createRbacRolesForHsOfficeContact() returns trigger language plpgsql strict as $$ -declare - ownerRole uuid; - adminRole uuid; begin if TG_OP <> 'INSERT' then raise exception 'invalid usage of TRIGGER AFTER INSERT'; @@ -107,7 +104,7 @@ do language plpgsql $$ declare addCustomerPermissions uuid[]; globalObjectUuid uuid; - globalAdminRoleUuid uuid ; + globalAdminRoleUuid uuid; begin call defineContext('granting global new-contact permission to global admin role', null, null, null); diff --git a/src/main/resources/db/changelog/210-hs-office-person.sql b/src/main/resources/db/changelog/210-hs-office-person.sql index 946a205e..3f404b65 100644 --- a/src/main/resources/db/changelog/210-hs-office-person.sql +++ b/src/main/resources/db/changelog/210-hs-office-person.sql @@ -4,7 +4,7 @@ --changeset hs-office-person-MAIN-TABLE:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -CREATE TYPE HsOfficePersonType AS ENUM ('NATURAL', 'LEGAL', 'SOLE_REPRESENTATION', 'JOINT_REPRESENTATION'); +CREATE TYPE HsOfficePersonType AS ENUM ('UNKNOWN', 'NATURAL', 'LEGAL', 'SOLE_REPRESENTATION', 'JOINT_REPRESENTATION'); CREATE CAST (character varying as HsOfficePersonType) WITH INOUT AS IMPLICIT; diff --git a/src/main/resources/db/changelog/213-hs-office-person-rbac.sql b/src/main/resources/db/changelog/213-hs-office-person-rbac.sql index 4f217e7a..42eacf2f 100644 --- a/src/main/resources/db/changelog/213-hs-office-person-rbac.sql +++ b/src/main/resources/db/changelog/213-hs-office-person-rbac.sql @@ -37,6 +37,7 @@ begin grantedByRole => globalAdmin() ); + -- TODO: who is admin? the person itself? is it allowed for the person itself or a representative to edit the data? perform createRoleWithGrants( hsOfficePersonAdmin(NEW), permissions => array['edit'], diff --git a/src/main/resources/db/changelog/220-hs-office-partner.sql b/src/main/resources/db/changelog/220-hs-office-partner.sql index dc01228d..340c0455 100644 --- a/src/main/resources/db/changelog/220-hs-office-partner.sql +++ b/src/main/resources/db/changelog/220-hs-office-partner.sql @@ -10,6 +10,7 @@ create table hs_office_partner_details uuid uuid unique references RbacObject (uuid) initially deferred, registrationOffice varchar(96), registrationNumber varchar(96), + birthPlace varchar(96), birthName varchar(96), birthday date, dateOfDeath date @@ -31,6 +32,7 @@ call create_journal('hs_office_partner_details'); create table hs_office_partner ( uuid uuid unique references RbacObject (uuid) initially deferred, + debitorNumberPrefix varchar(5), personUuid uuid not null references hs_office_person(uuid), contactUuid uuid not null references hs_office_contact(uuid), detailsUuid uuid not null references hs_office_partner_details(uuid) on delete cascade diff --git a/src/main/resources/db/changelog/223-hs-office-partner-rbac.sql b/src/main/resources/db/changelog/223-hs-office-partner-rbac.sql index 83a99573..db5db365 100644 --- a/src/main/resources/db/changelog/223-hs-office-partner-rbac.sql +++ b/src/main/resources/db/changelog/223-hs-office-partner-rbac.sql @@ -27,7 +27,6 @@ create or replace function hsOfficePartnerRbacRolesTrigger() language plpgsql strict as $$ declare - hsOfficePartnerTenant RbacRoleDescriptor; oldPerson hs_office_person; newPerson hs_office_person; oldContact hs_office_contact; @@ -166,6 +165,8 @@ execute procedure hsOfficePartnerRbacRolesTrigger(); --changeset hs-office-partner-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- call generateRbacIdentityView('hs_office_partner', $idName$ + -- TODO: simplify by using just debitorNumberPrefix for the essential part + debitorNumberPrefix || ':' || (select idName from hs_office_person_iv p where p.uuid = target.personuuid) || '-' || (select idName from hs_office_contact_iv c where c.uuid = target.contactuuid) @@ -179,6 +180,7 @@ call generateRbacIdentityView('hs_office_partner', $idName$ call generateRbacRestrictedView('hs_office_partner', '(select idName from hs_office_person_iv p where p.uuid = target.personUuid)', $updates$ + debitorNumberPrefix = new.debitorNumberPrefix, personUuid = new.personUuid, contactUuid = new.contactUuid $updates$); diff --git a/src/main/resources/db/changelog/224-hs-office-partner-details-rbac.sql b/src/main/resources/db/changelog/224-hs-office-partner-details-rbac.sql index 97bab5e5..ab94481e 100644 --- a/src/main/resources/db/changelog/224-hs-office-partner-details-rbac.sql +++ b/src/main/resources/db/changelog/224-hs-office-partner-details-rbac.sql @@ -29,6 +29,7 @@ call generateRbacRestrictedView('hs_office_partner_details', $updates$ registrationOffice = new.registrationOffice, registrationNumber = new.registrationNumber, + birthPlace = new.birthPlace, birthName = new.birthName, birthday = new.birthday, dateOfDeath = new.dateOfDeath diff --git a/src/main/resources/db/changelog/228-hs-office-partner-test-data.sql b/src/main/resources/db/changelog/228-hs-office-partner-test-data.sql index 648a850d..759c48c9 100644 --- a/src/main/resources/db/changelog/228-hs-office-partner-test-data.sql +++ b/src/main/resources/db/changelog/228-hs-office-partner-test-data.sql @@ -8,7 +8,10 @@ /* Creates a single partner test record. */ -create or replace procedure createHsOfficePartnerTestData( personTradeOrFamilyName varchar, contactLabel varchar ) +create or replace procedure createHsOfficePartnerTestData( + debitorNumberPrefix numeric(5), + personTradeOrFamilyName varchar, + contactLabel varchar ) language plpgsql as $$ declare currentTask varchar; @@ -51,8 +54,8 @@ begin end if; insert - into hs_office_partner (uuid, personuuid, contactuuid, detailsUuid) - values (uuid_generate_v4(), relatedPerson.uuid, relatedContact.uuid, relatedDetailsUuid); + into hs_office_partner (uuid, debitorNumberPrefix, personuuid, contactuuid, detailsUuid) + values (uuid_generate_v4(), debitorNumberPrefix, relatedPerson.uuid, relatedContact.uuid, relatedDetailsUuid); end; $$; --// @@ -64,11 +67,11 @@ end; $$; do language plpgsql $$ begin - call createHsOfficePartnerTestData('First GmbH', 'first contact'); - call createHsOfficePartnerTestData('Second e.K.', 'second contact'); - call createHsOfficePartnerTestData('Third OHG', 'third contact'); - call createHsOfficePartnerTestData('Fourth e.G.', 'forth contact'); - call createHsOfficePartnerTestData('Smith', 'fifth contact'); + call createHsOfficePartnerTestData(10001, 'First GmbH', 'first contact'); + call createHsOfficePartnerTestData(10002, 'Second e.K.', 'second contact'); + call createHsOfficePartnerTestData(10003, 'Third OHG', 'third contact'); + call createHsOfficePartnerTestData(10004, 'Fourth e.G.', 'forth contact'); + call createHsOfficePartnerTestData(10010, 'Smith', 'fifth contact'); end; $$; --// diff --git a/src/main/resources/db/changelog/230-hs-office-relationship.sql b/src/main/resources/db/changelog/230-hs-office-relationship.sql index 6e9e5961..942813d9 100644 --- a/src/main/resources/db/changelog/230-hs-office-relationship.sql +++ b/src/main/resources/db/changelog/230-hs-office-relationship.sql @@ -4,7 +4,13 @@ --changeset hs-office-relationship-MAIN-TABLE:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -CREATE TYPE HsOfficeRelationshipType AS ENUM ('SOLE_AGENT', 'JOINT_AGENT', 'CO_OWNER', 'ACCOUNTING_CONTACT', 'TECHNICAL_CONTACT'); +CREATE TYPE HsOfficeRelationshipType AS ENUM ( + 'UNKNOWN', + 'EX_PARTNER', + 'REPRESENTATIVE', + 'VIP_CONTACT', + 'ACCOUNTING', + 'OPERATIONS'); CREATE CAST (character varying as HsOfficeRelationshipType) WITH INOUT AS IMPLICIT; diff --git a/src/main/resources/db/changelog/238-hs-office-relationship-test-data.sql b/src/main/resources/db/changelog/238-hs-office-relationship-test-data.sql index 1e8e40c0..a46cf7ce 100644 --- a/src/main/resources/db/changelog/238-hs-office-relationship-test-data.sql +++ b/src/main/resources/db/changelog/238-hs-office-relationship-test-data.sql @@ -58,7 +58,7 @@ begin select p.* from hs_office_person p where tradeName = intToVarChar(t, 4) into person; select c.* from hs_office_contact c where c.label = intToVarChar(t, 4) || '#' || t into contact; - call createHsOfficeRelationshipTestData(person.uuid, contact.uuid, 'SOLE_AGENT'); + call createHsOfficeRelationshipTestData(person.uuid, contact.uuid, 'REPRESENTATIVE'); commit; end loop; end; $$; @@ -71,11 +71,11 @@ end; $$; do language plpgsql $$ begin - call createHsOfficeRelationshipTestData('First GmbH', 'Smith', 'SOLE_AGENT', 'first contact'); + call createHsOfficeRelationshipTestData('First GmbH', 'Smith', 'REPRESENTATIVE', 'first contact'); - call createHsOfficeRelationshipTestData('Second e.K.', 'Smith', 'SOLE_AGENT', 'second contact'); + call createHsOfficeRelationshipTestData('Second e.K.', 'Smith', 'REPRESENTATIVE', 'second contact'); - call createHsOfficeRelationshipTestData('Third OHG', 'Smith', 'SOLE_AGENT', 'third contact'); + call createHsOfficeRelationshipTestData('Third OHG', 'Smith', 'REPRESENTATIVE', 'third contact'); end; $$; --// diff --git a/src/main/resources/db/changelog/240-hs-office-bankaccount.sql b/src/main/resources/db/changelog/240-hs-office-bankaccount.sql index bf3ed342..427b0199 100644 --- a/src/main/resources/db/changelog/240-hs-office-bankaccount.sql +++ b/src/main/resources/db/changelog/240-hs-office-bankaccount.sql @@ -6,7 +6,7 @@ create table hs_office_bankaccount ( uuid uuid unique references RbacObject (uuid) initially deferred, - holder varchar(27) not null, + holder varchar(64) not null, iban varchar(34) not null, bic varchar(11) not null ); diff --git a/src/main/resources/db/changelog/256-hs-office-sepamandate-migration.sql b/src/main/resources/db/changelog/256-hs-office-sepamandate-migration.sql index fe43706c..4b483c6b 100644 --- a/src/main/resources/db/changelog/256-hs-office-sepamandate-migration.sql +++ b/src/main/resources/db/changelog/256-hs-office-sepamandate-migration.sql @@ -10,7 +10,7 @@ CREATE TABLE hs_office_sepamandate_legacy_id ( uuid uuid NOT NULL REFERENCES hs_office_sepamandate(uuid), - sepa_mandat_id integer NOT NULL + sepa_mandate_id integer NOT NULL ); --// @@ -22,7 +22,7 @@ CREATE TABLE hs_office_sepamandate_legacy_id CREATE SEQUENCE IF NOT EXISTS hs_office_sepamandate_legacy_id_seq AS integer START 1000000000 - OWNED BY hs_office_sepamandate_legacy_id.sepa_mandat_id; + OWNED BY hs_office_sepamandate_legacy_id.sepa_mandate_id; --// @@ -31,7 +31,7 @@ CREATE SEQUENCE IF NOT EXISTS hs_office_sepamandate_legacy_id_seq -- ---------------------------------------------------------------------------- ALTER TABLE hs_office_sepamandate_legacy_id - ALTER COLUMN sepa_mandat_id + ALTER COLUMN sepa_mandate_id SET DEFAULT nextVal('hs_office_sepamandate_legacy_id_seq'); --/ @@ -42,7 +42,7 @@ ALTER TABLE hs_office_sepamandate_legacy_id -- ---------------------------------------------------------------------------- CALL defineContext('schema-migration'); -INSERT INTO hs_office_sepamandate_legacy_id(uuid, sepa_mandat_id) +INSERT INTO hs_office_sepamandate_legacy_id(uuid, sepa_mandate_id) SELECT uuid, nextVal('hs_office_sepamandate_legacy_id_seq') FROM hs_office_sepamandate; --/ diff --git a/src/main/resources/db/changelog/270-hs-office-debitor.sql b/src/main/resources/db/changelog/270-hs-office-debitor.sql index 10da431e..fae4e90c 100644 --- a/src/main/resources/db/changelog/270-hs-office-debitor.sql +++ b/src/main/resources/db/changelog/270-hs-office-debitor.sql @@ -8,12 +8,18 @@ create table hs_office_debitor ( uuid uuid unique references RbacObject (uuid) initially deferred, partnerUuid uuid not null references hs_office_partner(uuid), - debitorNumber numeric(5) not null, + billable boolean not null default true, + debitorNumberSuffix numeric(2) not null, billingContactUuid uuid not null references hs_office_contact(uuid), vatId varchar(24), -- TODO.spec: here or in person? vatCountryCode varchar(2), - vatBusiness boolean not null, -- TODO.spec: more of such? - refundBankAccountUuid uuid references hs_office_bankaccount(uuid) + vatBusiness boolean not null, + vatReverseCharge boolean not null, + refundBankAccountUuid uuid references hs_office_bankaccount(uuid), + defaultPrefix char(3) not null unique + constraint check_default_prefix check ( + defaultPrefix::text ~ '^([a-z]{3}|al0|bh1|c4s|f3k|k8i|l3d|mh1|o13|p2m|s80|t4w)$' + ) -- TODO.impl: SEPA-mandate ); --// diff --git a/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql b/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql index 01b740c7..ab70aa27 100644 --- a/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql +++ b/src/main/resources/db/changelog/273-hs-office-debitor-rbac.sql @@ -172,8 +172,10 @@ execute procedure hsOfficeDebitorRbacRolesTrigger(); --changeset hs-office-debitor-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- call generateRbacIdentityView('hs_office_debitor', $idName$ - '#' || debitorNumber || ':' || - (select idName from hs_office_partner_iv p where p.uuid = target.partnerUuid) + '#' || + (select debitornumberprefix from hs_office_partner p where p.uuid = target.partnerUuid) || + to_char(debitorNumberSuffix, 'fm00') || + ':' || (select split_part(idName, ':', 2) from hs_office_partner_iv pi where pi.uuid = target.partnerUuid) $idName$); --// @@ -181,14 +183,18 @@ call generateRbacIdentityView('hs_office_debitor', $idName$ -- ============================================================================ --changeset hs-office-debitor-rbac-RESTRICTED-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -call generateRbacRestrictedView('hs_office_debitor', 'target.debitorNumber', +call generateRbacRestrictedView('hs_office_debitor', 'target.debitorNumberSuffix', $updates$ - partnerUuid = new.partnerUuid, + partnerUuid = new.partnerUuid, -- TODO: remove? should never do anything + billable = new.billable, billingContactUuid = new.billingContactUuid, + debitorNumberSuffix = new.debitorNumberSuffix, -- TODO: Should it be allowed to updated this value? refundBankAccountUuid = new.refundBankAccountUuid, vatId = new.vatId, vatCountryCode = new.vatCountryCode, - vatBusiness = new.vatBusiness + vatBusiness = new.vatBusiness, + vatreversecharge = new.vatreversecharge, + defaultPrefix = new.defaultPrefix -- TODO: Should it be allowed to updated this value? $updates$); --// diff --git a/src/main/resources/db/changelog/278-hs-office-debitor-test-data.sql b/src/main/resources/db/changelog/278-hs-office-debitor-test-data.sql index 1435a86d..af75d074 100644 --- a/src/main/resources/db/changelog/278-hs-office-debitor-test-data.sql +++ b/src/main/resources/db/changelog/278-hs-office-debitor-test-data.sql @@ -8,7 +8,12 @@ /* Creates a single debitor test record. */ -create or replace procedure createHsOfficeDebitorTestData( partnerTradeName varchar, billingContactLabel varchar ) +create or replace procedure createHsOfficeDebitorTestData( + debitorNumberSuffix numeric(5), + partnerTradeName varchar, + billingContactLabel varchar, + defaultPrefix varchar + ) language plpgsql as $$ declare currentTask varchar; @@ -16,7 +21,6 @@ declare relatedPartner hs_office_partner; relatedContact hs_office_contact; relatedBankAccountUuid uuid; - newDebitorNumber numeric(6); begin idName := cleanIdentifier( partnerTradeName|| '-' || billingContactLabel); currentTask := 'creating debitor test-data ' || idName; @@ -28,14 +32,13 @@ begin where person.tradeName = partnerTradeName into relatedPartner; select c.* from hs_office_contact c where c.label = billingContactLabel into relatedContact; select b.uuid from hs_office_bankaccount b where b.holder = partnerTradeName into relatedBankAccountUuid; - select coalesce(max(debitorNumber)+1, 10001) from hs_office_debitor into newDebitorNumber; - raise notice 'creating test debitor: % (#%)', idName, newDebitorNumber; + raise notice 'creating test debitor: % (#%)', idName, debitorNumberSuffix; raise notice '- using partner (%): %', relatedPartner.uuid, relatedPartner; raise notice '- using billingContact (%): %', relatedContact.uuid, relatedContact; insert - into hs_office_debitor (uuid, partneruuid, debitornumber, billingcontactuuid, vatbusiness, refundbankaccountuuid) - values (uuid_generate_v4(), relatedPartner.uuid, newDebitorNumber, relatedContact.uuid, true, relatedBankAccountUuid); + into hs_office_debitor (uuid, partneruuid, debitornumbersuffix, billable, billingcontactuuid, vatbusiness, vatreversecharge, refundbankaccountuuid, defaultprefix) + values (uuid_generate_v4(), relatedPartner.uuid, debitorNumberSuffix, true, relatedContact.uuid, true, false, relatedBankAccountUuid, defaultPrefix); end; $$; --// @@ -46,9 +49,9 @@ end; $$; do language plpgsql $$ begin - call createHsOfficeDebitorTestData('First GmbH', 'first contact'); - call createHsOfficeDebitorTestData('Second e.K.', 'second contact'); - call createHsOfficeDebitorTestData('Third OHG', 'third contact'); + call createHsOfficeDebitorTestData(11, 'First GmbH', 'first contact', 'fir'); + call createHsOfficeDebitorTestData(12, 'Second e.K.', 'second contact', 'sec'); + call createHsOfficeDebitorTestData(13, 'Third OHG', 'third contact', 'thi'); end; $$; --// diff --git a/src/main/resources/db/changelog/300-hs-office-membership.sql b/src/main/resources/db/changelog/300-hs-office-membership.sql index b1b75231..12f2dd34 100644 --- a/src/main/resources/db/changelog/300-hs-office-membership.sql +++ b/src/main/resources/db/changelog/300-hs-office-membership.sql @@ -4,7 +4,7 @@ --changeset hs-office-membership-MAIN-TABLE:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -CREATE TYPE HsOfficeReasonForTermination AS ENUM ('NONE', 'CANCELLATION', 'TRANSFER', 'DEATH', 'LIQUIDATION', 'EXPULSION'); +CREATE TYPE HsOfficeReasonForTermination AS ENUM ('NONE', 'CANCELLATION', 'TRANSFER', 'DEATH', 'LIQUIDATION', 'EXPULSION', 'UNKNOWN'); CREATE CAST (character varying as HsOfficeReasonForTermination) WITH INOUT AS IMPLICIT; @@ -15,7 +15,8 @@ create table if not exists hs_office_membership mainDebitorUuid uuid not null references hs_office_debitor(uuid), memberNumber numeric(5) not null unique, validity daterange not null, - reasonForTermination HsOfficeReasonForTermination not null default 'NONE' + reasonForTermination HsOfficeReasonForTermination not null default 'NONE', + membershipFeeBillable boolean not null default true ); --// diff --git a/src/main/resources/db/changelog/303-hs-office-membership-rbac.sql b/src/main/resources/db/changelog/303-hs-office-membership-rbac.sql index 4335c32d..972021e7 100644 --- a/src/main/resources/db/changelog/303-hs-office-membership-rbac.sql +++ b/src/main/resources/db/changelog/303-hs-office-membership-rbac.sql @@ -27,7 +27,7 @@ create or replace function hsOfficeMembershipRbacRolesTrigger() language plpgsql strict as $$ declare - newHsOfficePartner hs_office_partner; + newHsOfficePartner hs_office_partner; newHsOfficeDebitor hs_office_debitor; begin @@ -92,7 +92,8 @@ execute procedure hsOfficeMembershipRbacRolesTrigger(); --changeset hs-office-membership-rbac-IDENTITY-VIEW:1 endDelimiter:--// -- ---------------------------------------------------------------------------- call generateRbacIdentityView('hs_office_membership', idNameExpression => $idName$ - target.memberNumber || (select idName from hs_office_partner_iv p where p.uuid = target.partnerUuid) + target.memberNumber || + ':' || (select split_part(idName, ':', 2) from hs_office_partner_iv p where p.uuid = target.partnerUuid) $idName$); --// @@ -104,7 +105,8 @@ call generateRbacRestrictedView('hs_office_membership', orderby => 'target.memberNumber', columnUpdates => $updates$ validity = new.validity, - reasonForTermination = new.reasonForTermination + reasonForTermination = new.reasonForTermination, + membershipFeeBillable = new.membershipFeeBillable $updates$); --// diff --git a/src/main/resources/db/changelog/308-hs-office-membership-test-data.sql b/src/main/resources/db/changelog/308-hs-office-membership-test-data.sql index 5b229466..5a4adaa6 100644 --- a/src/main/resources/db/changelog/308-hs-office-membership-test-data.sql +++ b/src/main/resources/db/changelog/308-hs-office-membership-test-data.sql @@ -11,11 +11,11 @@ create or replace procedure createHsOfficeMembershipTestData( forPartnerTradeName varchar, forMainDebitorNumber numeric ) language plpgsql as $$ declare - currentTask varchar; - idName varchar; - relatedPartner hs_office_partner; - relatedDebitor hs_office_debitor; - newMemberNumber numeric; + currentTask varchar; + idName varchar; + relatedPartner hs_office_partner; + relatedDebitor hs_office_debitor; + newMemberNumber numeric; begin idName := cleanIdentifier( forPartnerTradeName || '#' || forMainDebitorNumber); currentTask := 'creating Membership test-data ' || idName; @@ -25,7 +25,7 @@ begin select partner.* from hs_office_partner partner join hs_office_person person on person.uuid = partner.personUuid where person.tradeName = forPartnerTradeName into relatedPartner; - select d.* from hs_office_debitor d where d.debitorNumber = forMainDebitorNumber into relatedDebitor; + select d.* from hs_office_debitor d where d.debitorNumberSuffix = forMainDebitorNumber into relatedDebitor; select coalesce(max(memberNumber)+1, 10001) from hs_office_membership into newMemberNumber; raise notice 'creating test Membership: %', idName; @@ -44,9 +44,9 @@ end; $$; do language plpgsql $$ begin - call createHsOfficeMembershipTestData('First GmbH', 10001); - call createHsOfficeMembershipTestData('Second e.K.', 10002); - call createHsOfficeMembershipTestData('Third OHG', 10003); + call createHsOfficeMembershipTestData('First GmbH', 11); + call createHsOfficeMembershipTestData('Second e.K.', 12); + call createHsOfficeMembershipTestData('Third OHG', 13); end; $$; --// diff --git a/src/main/resources/db/changelog/320-hs-office-coopassets.sql b/src/main/resources/db/changelog/320-hs-office-coopassets.sql index db76d95b..9a712f3a 100644 --- a/src/main/resources/db/changelog/320-hs-office-coopassets.sql +++ b/src/main/resources/db/changelog/320-hs-office-coopassets.sql @@ -10,7 +10,8 @@ CREATE TYPE HsOfficeCoopAssetsTransactionType AS ENUM ('ADJUSTMENT', 'TRANSFER', 'ADOPTION', 'CLEARING', - 'LOSS'); + 'LOSS', + 'LIMITATION'); CREATE CAST (character varying as HsOfficeCoopAssetsTransactionType) WITH INOUT AS IMPLICIT; diff --git a/src/main/resources/db/changelog/db.changelog-master.yaml b/src/main/resources/db/changelog/db.changelog-master.yaml index 68719b66..88c70bef 100644 --- a/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/src/main/resources/db/changelog/db.changelog-master.yaml @@ -11,6 +11,8 @@ databaseChangeLog: file: db/changelog/005-uuid-ossp-extension.sql - include: file: db/changelog/006-numeric-hash-functions.sql + - include: + file: db/changelog/009-check-environment.sql - include: file: db/changelog/010-context.sql - include: diff --git a/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java b/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java index cafd3a0d..a09e00b9 100644 --- a/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java +++ b/src/test/java/net/hostsharing/hsadminng/arch/ArchitectureTest.java @@ -30,16 +30,17 @@ public class ArchitectureTest { "..test.pac", "..context", "..generated..", - "..hs.office.person", - "..hs.office.partner", "..hs.office.bankaccount", - "..hs.office.debitor", - "..hs.office.relationship", "..hs.office.contact", - "..hs.office.sepamandate", "..hs.office.coopassets", "..hs.office.coopshares", + "..hs.office.debitor", "..hs.office.membership", + "..hs.office.migration", + "..hs.office.partner", + "..hs.office.person", + "..hs.office.relationship", + "..hs.office.sepamandate", "..errors", "..mapper", "..ping", @@ -121,14 +122,19 @@ public class ArchitectureTest { public static final ArchRule hsOfficeBankAccountPackageRule = classes() .that().resideInAPackage("..hs.office.bankaccount..") .should().onlyBeAccessed().byClassesThat() - .resideInAnyPackage("..hs.office.bankaccount..", "..hs.office.sepamandate..", "..hs.office.debitor.."); + .resideInAnyPackage("..hs.office.bankaccount..", + "..hs.office.sepamandate..", + "..hs.office.debitor..", + "..hs.office.migration.."); @ArchTest @SuppressWarnings("unused") public static final ArchRule hsOfficeSepaMandatePackageRule = classes() .that().resideInAPackage("..hs.office.sepamandate..") .should().onlyBeAccessed().byClassesThat() - .resideInAnyPackage("..hs.office.sepamandate..", "..hs.office.debitor.."); + .resideInAnyPackage("..hs.office.sepamandate..", + "..hs.office.debitor..", + "..hs.office.migration.."); @ArchTest @SuppressWarnings("unused") @@ -136,7 +142,10 @@ public class ArchitectureTest { .that().resideInAPackage("..hs.office.contact..") .should().onlyBeAccessed().byClassesThat() .resideInAnyPackage("..hs.office.contact..", "..hs.office.relationship..", - "..hs.office.partner..", "..hs.office.debitor..", "..hs.office.membership.."); + "..hs.office.partner..", + "..hs.office.debitor..", + "..hs.office.membership..", + "..hs.office.migration.."); @ArchTest @SuppressWarnings("unused") @@ -144,42 +153,63 @@ public class ArchitectureTest { .that().resideInAPackage("..hs.office.person..") .should().onlyBeAccessed().byClassesThat() .resideInAnyPackage("..hs.office.person..", "..hs.office.relationship..", - "..hs.office.partner..", "..hs.office.debitor..", "..hs.office.membership.."); + "..hs.office.partner..", + "..hs.office.debitor..", + "..hs.office.membership..", + "..hs.office.migration.."); @ArchTest @SuppressWarnings("unused") public static final ArchRule hsOfficeRelationshipPackageRule = classes() .that().resideInAPackage("..hs.office.relationship..") .should().onlyBeAccessed().byClassesThat() - .resideInAnyPackage("..hs.office.relationship.."); + .resideInAnyPackage("..hs.office.relationship..", + "..hs.office.migration.."); @ArchTest @SuppressWarnings("unused") public static final ArchRule hsOfficePartnerPackageRule = classes() .that().resideInAPackage("..hs.office.partner..") .should().onlyBeAccessed().byClassesThat() - .resideInAnyPackage("..hs.office.partner..", "..hs.office.debitor..", "..hs.office.membership.."); + .resideInAnyPackage("..hs.office.partner..", + "..hs.office.debitor..", + "..hs.office.membership..", + "..hs.office.migration.."); @ArchTest @SuppressWarnings("unused") public static final ArchRule hsOfficeMembershipPackageRule = classes() .that().resideInAPackage("..hs.office.membership..") .should().onlyBeAccessed().byClassesThat() - .resideInAnyPackage("..hs.office.membership..", "..hs.office.coopassets..", "..hs.office.coopshares.."); + .resideInAnyPackage("..hs.office.membership..", + "..hs.office.coopassets..", + "..hs.office.coopshares..", + "..hs.office.migration.."); @ArchTest @SuppressWarnings("unused") public static final ArchRule hsOfficeCoopAssetsPackageRule = classes() .that().resideInAPackage("..hs.office.coopassets..") .should().onlyBeAccessed().byClassesThat() - .resideInAnyPackage("..hs.office.coopassets.."); + .resideInAnyPackage( + "..hs.office.coopassets..", + "..hs.office.migration.."); @ArchTest @SuppressWarnings("unused") public static final ArchRule hsOfficeCoopSharesPackageRule = classes() .that().resideInAPackage("..hs.office.coopshares..") .should().onlyBeAccessed().byClassesThat() - .resideInAnyPackage("..hs.office.coopshares.."); + .resideInAnyPackage( + "..hs.office.coopshares..", + "..hs.office.migration.."); + + @ArchTest + @SuppressWarnings("unused") + public static final ArchRule hsOfficeMigrationPackageRule = classes() + .that().resideInAPackage("..hs.office.migration..") + .should().onlyBeAccessed().byClassesThat() + .resideInAnyPackage("..hs.office.migration.."); @ArchTest @SuppressWarnings("unused") @@ -197,7 +227,7 @@ public class ArchitectureTest { @ArchTest @SuppressWarnings("unused") - public static final ArchRule doNotUsejakartaTransactionAnnotationAtClassLevel = noClasses() + public static final ArchRule doNotUseJakartaTransactionAnnotationAtClassLevel = noClasses() .should().beAnnotatedWith(jakarta.transaction.Transactional.class.getName()) .as("Use @%s instead of @%s.".formatted( org.springframework.transaction.annotation.Transactional.class.getName(), @@ -205,7 +235,7 @@ public class ArchitectureTest { @ArchTest @SuppressWarnings("unused") - public static final ArchRule doNotUsejakartaTransactionAnnotationAtMethodLevel = noMethods() + public static final ArchRule doNotUseJakartaTransactionAnnotationAtMethodLevel = noMethods() .should().beAnnotatedWith(jakarta.transaction.Transactional.class) .as("Use @%s instead of @%s.".formatted( org.springframework.transaction.annotation.Transactional.class.getName(), diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionEntityUnitTest.java index 7be6ec0b..0cc2988a 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionEntityUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionEntityUnitTest.java @@ -17,6 +17,7 @@ class HsOfficeCoopAssetsTransactionEntityUnitTest { .transactionType(HsOfficeCoopAssetsTransactionType.DEPOSIT) .assetValue(new BigDecimal("128.00")) .build(); + final HsOfficeCoopAssetsTransactionEntity givenEmptyCoopAssetsTransaction = HsOfficeCoopAssetsTransactionEntity.builder().build(); @Test void toStringContainsAlmostAllPropertiesAccount() { @@ -31,4 +32,18 @@ class HsOfficeCoopAssetsTransactionEntityUnitTest { assertThat(result).isEqualTo("300001+128.00"); } + + @Test + void toStringWithEmptyTransactionDoesNotThrowException() { + final var result = givenEmptyCoopAssetsTransaction.toString(); + + assertThat(result).isEqualTo("CoopAssetsTransaction()"); + } + + @Test + void toShortStringEmptyTransactionDoesNotThrowException() { + final var result = givenEmptyCoopAssetsTransaction.toShortString(); + + assertThat(result).isEqualTo("nullnu"); + } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionRepositoryIntegrationTest.java index 9728e438..54c5bbb3 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/coopassets/HsOfficeCoopAssetsTransactionRepositoryIntegrationTest.java @@ -117,7 +117,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase .map(s -> s.replace("hs_office_", "")) .containsExactlyInAnyOrder(Array.fromFormatted( initialGrantNames, - "{ grant perm view on coopassetstransaction#temprefB to role membership#10001....tenant by system and assume }", + "{ grant perm view on coopassetstransaction#temprefB to role membership#10001:....tenant by system and assume }", null)); } @@ -144,17 +144,17 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase // then allTheseCoopAssetsTransactionsAreReturned( result, - "CoopAssetsTransaction(10001, 2010-03-15, DEPOSIT, 320.00, ref 10001-1)", - "CoopAssetsTransaction(10001, 2021-09-01, DISBURSAL, -128.00, ref 10001-2)", - "CoopAssetsTransaction(10001, 2022-10-20, ADJUSTMENT, 128.00, ref 10001-3)", + "CoopAssetsTransaction(10001, 2010-03-15, DEPOSIT, 320.00, ref 10001-1, initial deposit)", + "CoopAssetsTransaction(10001, 2021-09-01, DISBURSAL, -128.00, ref 10001-2, partial disbursal)", + "CoopAssetsTransaction(10001, 2022-10-20, ADJUSTMENT, 128.00, ref 10001-3, some adjustment)", - "CoopAssetsTransaction(10002, 2010-03-15, DEPOSIT, 320.00, ref 10002-1)", - "CoopAssetsTransaction(10002, 2021-09-01, DISBURSAL, -128.00, ref 10002-2)", - "CoopAssetsTransaction(10002, 2022-10-20, ADJUSTMENT, 128.00, ref 10002-3)", + "CoopAssetsTransaction(10002, 2010-03-15, DEPOSIT, 320.00, ref 10002-1, initial deposit)", + "CoopAssetsTransaction(10002, 2021-09-01, DISBURSAL, -128.00, ref 10002-2, partial disbursal)", + "CoopAssetsTransaction(10002, 2022-10-20, ADJUSTMENT, 128.00, ref 10002-3, some adjustment)", - "CoopAssetsTransaction(10003, 2010-03-15, DEPOSIT, 320.00, ref 10003-1)", - "CoopAssetsTransaction(10003, 2021-09-01, DISBURSAL, -128.00, ref 10003-2)", - "CoopAssetsTransaction(10003, 2022-10-20, ADJUSTMENT, 128.00, ref 10003-3)"); + "CoopAssetsTransaction(10003, 2010-03-15, DEPOSIT, 320.00, ref 10003-1, initial deposit)", + "CoopAssetsTransaction(10003, 2021-09-01, DISBURSAL, -128.00, ref 10003-2, partial disbursal)", + "CoopAssetsTransaction(10003, 2022-10-20, ADJUSTMENT, 128.00, ref 10003-3, some adjustment)"); } @Test @@ -173,9 +173,9 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase // then allTheseCoopAssetsTransactionsAreReturned( result, - "CoopAssetsTransaction(10002, 2010-03-15, DEPOSIT, 320.00, ref 10002-1)", - "CoopAssetsTransaction(10002, 2021-09-01, DISBURSAL, -128.00, ref 10002-2)", - "CoopAssetsTransaction(10002, 2022-10-20, ADJUSTMENT, 128.00, ref 10002-3)"); + "CoopAssetsTransaction(10002, 2010-03-15, DEPOSIT, 320.00, ref 10002-1, initial deposit)", + "CoopAssetsTransaction(10002, 2021-09-01, DISBURSAL, -128.00, ref 10002-2, partial disbursal)", + "CoopAssetsTransaction(10002, 2022-10-20, ADJUSTMENT, 128.00, ref 10002-3, some adjustment)"); } @Test @@ -194,14 +194,13 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase // then allTheseCoopAssetsTransactionsAreReturned( result, - "CoopAssetsTransaction(10002, 2021-09-01, DISBURSAL, -128.00, ref 10002-2)"); + "CoopAssetsTransaction(10002, 2021-09-01, DISBURSAL, -128.00, ref 10002-2, partial disbursal)"); } @Test public void normalUser_canViewOnlyRelatedCoopAssetsTransactions() { // given: - context("superuser-alex@hostsharing.net", "hs_office_partner#FirstGmbH-firstcontact.admin"); - // "hs_office_person#FirstGmbH.admin", + context("superuser-alex@hostsharing.net", "hs_office_partner#10001:FirstGmbH-firstcontact.admin"); // when: final var result = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange( @@ -212,9 +211,9 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase // then: exactlyTheseCoopAssetsTransactionsAreReturned( result, - "CoopAssetsTransaction(10001, 2010-03-15, DEPOSIT, 320.00, ref 10001-1)", - "CoopAssetsTransaction(10001, 2021-09-01, DISBURSAL, -128.00, ref 10001-2)", - "CoopAssetsTransaction(10001, 2022-10-20, ADJUSTMENT, 128.00, ref 10001-3)"); + "CoopAssetsTransaction(10001, 2010-03-15, DEPOSIT, 320.00, ref 10001-1, initial deposit)", + "CoopAssetsTransaction(10001, 2021-09-01, DISBURSAL, -128.00, ref 10001-2, partial disbursal)", + "CoopAssetsTransaction(10001, 2022-10-20, ADJUSTMENT, 128.00, ref 10001-3, some adjustment)"); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntityUnitTest.java index fc0df074..8dcfd133 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntityUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionEntityUnitTest.java @@ -16,6 +16,7 @@ class HsOfficeCoopSharesTransactionEntityUnitTest { .transactionType(HsOfficeCoopSharesTransactionType.SUBSCRIPTION) .shareCount(4) .build(); + final HsOfficeCoopSharesTransactionEntity givenEmptyCoopSharesTransaction = HsOfficeCoopSharesTransactionEntity.builder().build(); @Test void toStringContainsAlmostAllPropertiesAccount() { @@ -25,9 +26,23 @@ class HsOfficeCoopSharesTransactionEntityUnitTest { } @Test - void toShortStringContainsOnlyMemberNumberAndshareCountOnly() { + void toShortStringContainsOnlyMemberNumberAndShareCountOnly() { final var result = givenCoopSharesTransaction.toShortString(); assertThat(result).isEqualTo("300001+4"); } + + @Test + void toStringEmptyTransactionDoesNotThrowException() { + final var result = givenEmptyCoopSharesTransaction.toString(); + + assertThat(result).isEqualTo("CoopShareTransaction(0)"); + } + + @Test + void toShortStringEmptyTransactionDoesNotThrowException() { + final var result = givenEmptyCoopSharesTransaction.toShortString(); + + assertThat(result).isEqualTo("null+0"); + } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionRepositoryIntegrationTest.java index 772c59c2..3e125770 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/coopshares/HsOfficeCoopSharesTransactionRepositoryIntegrationTest.java @@ -116,7 +116,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase .map(s -> s.replace("hs_office_", "")) .containsExactlyInAnyOrder(Array.fromFormatted( initialGrantNames, - "{ grant perm view on coopsharestransaction#temprefB to role membership#10001....tenant by system and assume }", + "{ grant perm view on coopsharestransaction#temprefB to role membership#10001:....tenant by system and assume }", null)); } @@ -143,17 +143,17 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase // then allTheseCoopSharesTransactionsAreReturned( result, - "CoopShareTransaction(10001, 2010-03-15, SUBSCRIPTION, 4, ref 10001-1)", - "CoopShareTransaction(10001, 2021-09-01, CANCELLATION, -2, ref 10001-2)", - "CoopShareTransaction(10001, 2022-10-20, ADJUSTMENT, 2, ref 10001-3)", + "CoopShareTransaction(10001, 2010-03-15, SUBSCRIPTION, 4, ref 10001-1, initial subscription)", + "CoopShareTransaction(10001, 2021-09-01, CANCELLATION, -2, ref 10001-2, cancelling some)", + "CoopShareTransaction(10001, 2022-10-20, ADJUSTMENT, 2, ref 10001-3, some adjustment)", - "CoopShareTransaction(10002, 2010-03-15, SUBSCRIPTION, 4, ref 10002-1)", - "CoopShareTransaction(10002, 2021-09-01, CANCELLATION, -2, ref 10002-2)", - "CoopShareTransaction(10002, 2022-10-20, ADJUSTMENT, 2, ref 10002-3)", + "CoopShareTransaction(10002, 2010-03-15, SUBSCRIPTION, 4, ref 10002-1, initial subscription)", + "CoopShareTransaction(10002, 2021-09-01, CANCELLATION, -2, ref 10002-2, cancelling some)", + "CoopShareTransaction(10002, 2022-10-20, ADJUSTMENT, 2, ref 10002-3, some adjustment)", - "CoopShareTransaction(10003, 2010-03-15, SUBSCRIPTION, 4, ref 10003-1)", - "CoopShareTransaction(10003, 2021-09-01, CANCELLATION, -2, ref 10003-2)", - "CoopShareTransaction(10003, 2022-10-20, ADJUSTMENT, 2, ref 10003-3)"); + "CoopShareTransaction(10003, 2010-03-15, SUBSCRIPTION, 4, ref 10003-1, initial subscription)", + "CoopShareTransaction(10003, 2021-09-01, CANCELLATION, -2, ref 10003-2, cancelling some)", + "CoopShareTransaction(10003, 2022-10-20, ADJUSTMENT, 2, ref 10003-3, some adjustment)"); } @Test @@ -172,9 +172,9 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase // then allTheseCoopSharesTransactionsAreReturned( result, - "CoopShareTransaction(10002, 2010-03-15, SUBSCRIPTION, 4, ref 10002-1)", - "CoopShareTransaction(10002, 2021-09-01, CANCELLATION, -2, ref 10002-2)", - "CoopShareTransaction(10002, 2022-10-20, ADJUSTMENT, 2, ref 10002-3)"); + "CoopShareTransaction(10002, 2010-03-15, SUBSCRIPTION, 4, ref 10002-1, initial subscription)", + "CoopShareTransaction(10002, 2021-09-01, CANCELLATION, -2, ref 10002-2, cancelling some)", + "CoopShareTransaction(10002, 2022-10-20, ADJUSTMENT, 2, ref 10002-3, some adjustment)"); } @Test @@ -193,14 +193,13 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase // then allTheseCoopSharesTransactionsAreReturned( result, - "CoopShareTransaction(10002, 2021-09-01, CANCELLATION, -2, ref 10002-2)"); + "CoopShareTransaction(10002, 2021-09-01, CANCELLATION, -2, ref 10002-2, cancelling some)"); } @Test public void normalUser_canViewOnlyRelatedCoopSharesTransactions() { // given: - context("superuser-alex@hostsharing.net", "hs_office_partner#FirstGmbH-firstcontact.admin"); - // "hs_office_person#FirstGmbH.admin", + context("superuser-alex@hostsharing.net", "hs_office_partner#10001:FirstGmbH-firstcontact.admin"); // when: final var result = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange( @@ -211,9 +210,9 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase // then: exactlyTheseCoopSharesTransactionsAreReturned( result, - "CoopShareTransaction(10001, 2010-03-15, SUBSCRIPTION, 4, ref 10001-1)", - "CoopShareTransaction(10001, 2021-09-01, CANCELLATION, -2, ref 10001-2)", - "CoopShareTransaction(10001, 2022-10-20, ADJUSTMENT, 2, ref 10001-3)"); + "CoopShareTransaction(10001, 2010-03-15, SUBSCRIPTION, 4, ref 10001-1, initial subscription)", + "CoopShareTransaction(10001, 2021-09-01, CANCELLATION, -2, ref 10001-2, cancelling some)", + "CoopShareTransaction(10001, 2022-10-20, ADJUSTMENT, 2, ref 10001-3, some adjustment)"); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java index 76d6758f..1de3bb06 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java @@ -35,8 +35,8 @@ import static org.hamcrest.Matchers.*; @Transactional class HsOfficeDebitorControllerAcceptanceTest { - private static final int LOWEST_TEMP_DEBITOR_NUMBER = 20000; - private static int nextDebitorNumber = LOWEST_TEMP_DEBITOR_NUMBER; + private static final int LOWEST_TEMP_DEBITOR_SUFFIX = 90; + private static byte nextDebitorSuffix = LOWEST_TEMP_DEBITOR_SUFFIX; @LocalServerPort private Integer port; @@ -81,7 +81,8 @@ class HsOfficeDebitorControllerAcceptanceTest { .body("", lenientlyEquals(""" [ { - "debitorNumber": 10001, + "debitorNumber": 1000111, + "debitorNumberSuffix": 11, "partner": { "person": { "personType": "LEGAL" } }, "billingContact": { "label": "first contact" }, "vatId": null, @@ -90,7 +91,8 @@ class HsOfficeDebitorControllerAcceptanceTest { "refundBankAccount": { "holder": "First GmbH" } }, { - "debitorNumber": 10002, + "debitorNumber": 1000212, + "debitorNumberSuffix": 12, "partner": { "person": { "tradeName": "Second e.K." } }, "billingContact": { "label": "second contact" }, "vatId": null, @@ -99,7 +101,8 @@ class HsOfficeDebitorControllerAcceptanceTest { "refundBankAccount": { "holder": "Second e.K." } }, { - "debitorNumber": 10003, + "debitorNumber": 1000313, + "debitorNumberSuffix": 13, "partner": { "person": { "tradeName": "Third OHG" } }, "billingContact": { "label": "third contact" }, "vatId": null, @@ -120,14 +123,14 @@ class HsOfficeDebitorControllerAcceptanceTest { .header("current-user", "superuser-alex@hostsharing.net") .port(port) .when() - .get("http://localhost/api/hs/office/debitors?debitorNumber=10002") + .get("http://localhost/api/hs/office/debitors?debitorNumber=1000212") .then().log().all().assertThat() .statusCode(200) .contentType("application/json") .body("", lenientlyEquals(""" [ { - "debitorNumber": 10002, + "debitorNumber": 1000212, "partner": { "person": { "tradeName": "Second e.K." } }, "billingContact": { "label": "second contact" }, "vatId": null, @@ -160,13 +163,16 @@ class HsOfficeDebitorControllerAcceptanceTest { { "partnerUuid": "%s", "billingContactUuid": "%s", - "debitorNumber": "%s", + "debitorNumberSuffix": "%s", + "billable": "true", "vatId": "VAT123456", "vatCountryCode": "DE", "vatBusiness": true, - "refundBankAccountUuid": "%s" + "vatReverseCharge": "false", + "refundBankAccountUuid": "%s", + "defaultPrefix": "for" } - """.formatted( givenPartner.getUuid(), givenContact.getUuid(), ++nextDebitorNumber, givenBankAccount.getUuid())) + """.formatted( givenPartner.getUuid(), givenContact.getUuid(), ++nextDebitorSuffix, givenBankAccount.getUuid())) .port(port) .when() .post("http://localhost/api/hs/office/debitors") @@ -175,6 +181,7 @@ class HsOfficeDebitorControllerAcceptanceTest { .contentType(ContentType.JSON) .body("uuid", isUuidValid()) .body("vatId", is("VAT123456")) + .body("defaultPrefix", is("for")) .body("billingContact.label", is(givenContact.getLabel())) .body("partner.person.tradeName", is(givenPartner.getPerson().getTradeName())) .body("refundBankAccount.holder", is(givenBankAccount.getHolder())) @@ -202,9 +209,12 @@ class HsOfficeDebitorControllerAcceptanceTest { { "partnerUuid": "%s", "billingContactUuid": "%s", - "debitorNumber": "%s" + "debitorNumberSuffix": "%s", + "defaultPrefix": "for", + "billable": "true", + "vatReverseCharge": "false" } - """.formatted( givenPartner.getUuid(), givenContact.getUuid(), ++nextDebitorNumber)) + """.formatted( givenPartner.getUuid(), givenContact.getUuid(), ++nextDebitorSuffix)) .port(port) .when() .post("http://localhost/api/hs/office/debitors") @@ -218,6 +228,7 @@ class HsOfficeDebitorControllerAcceptanceTest { .body("vatCountryCode", equalTo(null)) .body("vatBusiness", equalTo(false)) .body("refundBankAccount", equalTo(null)) + .body("defaultPrefix", equalTo("for")) .header("Location", startsWith("http://localhost")) .extract().header("Location"); // @formatter:on @@ -242,12 +253,16 @@ class HsOfficeDebitorControllerAcceptanceTest { { "partnerUuid": "%s", "billingContactUuid": "%s", - "debitorNumber": "%s", + "debitorNumberSuffix": "%s", + "billable": "true", "vatId": "VAT123456", "vatCountryCode": "DE", - "vatBusiness": true + "vatBusiness": true, + "vatReverseCharge": "false", + "defaultPrefix": "thi" } - """.formatted( givenPartner.getUuid(), givenContactUuid, ++nextDebitorNumber)) + """ + .formatted( givenPartner.getUuid(), givenContactUuid, ++nextDebitorSuffix)) .port(port) .when() .post("http://localhost/api/hs/office/debitors") @@ -272,12 +287,15 @@ class HsOfficeDebitorControllerAcceptanceTest { { "partnerUuid": "%s", "billingContactUuid": "%s", - "debitorNumber": "%s", + "debitorNumberSuffix": "%s", + "billable": "true", "vatId": "VAT123456", "vatCountryCode": "DE", - "vatBusiness": true + "vatBusiness": true, + "vatReverseCharge": "false", + "defaultPrefix": "for" } - """.formatted( givenPartnerUuid, givenContact.getUuid(), ++nextDebitorNumber)) + """.formatted( givenPartnerUuid, givenContact.getUuid(), ++nextDebitorSuffix)) .port(port) .when() .post("http://localhost/api/hs/office/debitors") @@ -375,7 +393,8 @@ class HsOfficeDebitorControllerAcceptanceTest { "contactUuid": "%s", "vatId": "VAT222222", "vatCountryCode": "AA", - "vatBusiness": true + "vatBusiness": true, + "defaultPrefix": "for" } """.formatted(givenContact.getUuid())) .port(port) @@ -388,6 +407,7 @@ class HsOfficeDebitorControllerAcceptanceTest { .body("vatId", is("VAT222222")) .body("vatCountryCode", is("AA")) .body("vatBusiness", is(true)) + .body("defaultPrefix", is("for")) .body("billingContact.label", is(givenContact.getLabel())) .body("partner.person.tradeName", is(givenDebitor.getPartner().getPerson().getTradeName())); // @formatter:on @@ -522,9 +542,12 @@ class HsOfficeDebitorControllerAcceptanceTest { final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("Fourth").get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike("forth contact").get(0); final var newDebitor = HsOfficeDebitorEntity.builder() - .debitorNumber(++nextDebitorNumber) + .debitorNumberSuffix(++nextDebitorSuffix) + .billable(true) .partner(givenPartner) .billingContact(givenContact) + .defaultPrefix("abc") + .vatReverseCharge(false) .build(); return debitorRepo.save(newDebitor); @@ -537,7 +560,7 @@ class HsOfficeDebitorControllerAcceptanceTest { jpaAttempt.transacted(() -> { context.define("superuser-alex@hostsharing.net"); final var count = em.createQuery( - "DELETE FROM HsOfficeDebitorEntity d WHERE d.debitorNumber > " + LOWEST_TEMP_DEBITOR_NUMBER) + "DELETE FROM HsOfficeDebitorEntity d WHERE d.debitorNumberSuffix >= " + LOWEST_TEMP_DEBITOR_SUFFIX) .executeUpdate(); System.out.printf("deleted %d entities%n", count); }); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntityPatcherUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntityPatcherUnitTest.java index 6edb6cd8..01ea5777 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntityPatcherUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntityPatcherUnitTest.java @@ -1,5 +1,6 @@ package net.hostsharing.hsadminng.hs.office.debitor; +import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorPatchResource; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; @@ -31,10 +32,20 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase< private static final UUID INITIAL_CONTACT_UUID = UUID.randomUUID(); private static final UUID PATCHED_CONTACT_UUID = UUID.randomUUID(); + private static final String PATCHED_DEFAULT_PREFIX = "xyz"; private static final String PATCHED_VAT_COUNTRY_CODE = "ZZ"; private static final boolean PATCHED_VAT_BUSINESS = false; + private static final boolean INITIAL_BILLABLE = false; + private static final boolean PATCHED_BILLABLE = true; + + private static final boolean INITIAL_VAT_REVERSE_CHARGE = true; + private static final boolean PATCHED_VAT_REVERSE_CHARGE = false; + + private static final UUID INITIAL_REFUND_BANK_ACCOUNT_UUID = UUID.randomUUID(); + private static final UUID PATCHED_REFUND_BANK_ACCOUNT_UUID = UUID.randomUUID(); + private final HsOfficePartnerEntity givenInitialPartner = HsOfficePartnerEntity.builder() .uuid(INITIAL_PARTNER_UUID) .build(); @@ -42,6 +53,10 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase< private final HsOfficeContactEntity givenInitialContact = HsOfficeContactEntity.builder() .uuid(INITIAL_CONTACT_UUID) .build(); + + private final HsOfficeBankAccountEntity givenInitialBankAccount = HsOfficeBankAccountEntity.builder() + .uuid(INITIAL_REFUND_BANK_ACCOUNT_UUID) + .build(); @Mock private EntityManager em; @@ -49,8 +64,8 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase< void initMocks() { lenient().when(em.getReference(eq(HsOfficeContactEntity.class), any())).thenAnswer(invocation -> HsOfficeContactEntity.builder().uuid(invocation.getArgument(1)).build()); - lenient().when(em.getReference(eq(HsOfficeContactEntity.class), any())).thenAnswer(invocation -> - HsOfficeContactEntity.builder().uuid(invocation.getArgument(1)).build()); + lenient().when(em.getReference(eq(HsOfficeBankAccountEntity.class), any())).thenAnswer(invocation -> + HsOfficeBankAccountEntity.builder().uuid(invocation.getArgument(1)).build()); } @Override @@ -59,9 +74,13 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase< entity.setUuid(INITIAL_DEBITOR_UUID); entity.setPartner(givenInitialPartner); entity.setBillingContact(givenInitialContact); + entity.setBillable(INITIAL_BILLABLE); entity.setVatId("initial VAT-ID"); entity.setVatCountryCode("AA"); entity.setVatBusiness(true); + entity.setVatReverseCharge(INITIAL_VAT_REVERSE_CHARGE); + entity.setDefaultPrefix("abc"); + entity.setRefundBankAccount(givenInitialBankAccount); return entity; } @@ -85,6 +104,12 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase< HsOfficeDebitorEntity::setBillingContact, newBillingContact(PATCHED_CONTACT_UUID)) .notNullable(), + new SimpleProperty<>( + "billable", + HsOfficeDebitorPatchResource::setBillable, + PATCHED_BILLABLE, + HsOfficeDebitorEntity::setBillable) + .notNullable(), new JsonNullableProperty<>( "vatId", HsOfficeDebitorPatchResource::setVatId, @@ -95,11 +120,30 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase< HsOfficeDebitorPatchResource::setVatCountryCode, PATCHED_VAT_COUNTRY_CODE, HsOfficeDebitorEntity::setVatCountryCode), - new JsonNullableProperty<>( + new SimpleProperty<>( "vatBusiness", HsOfficeDebitorPatchResource::setVatBusiness, PATCHED_VAT_BUSINESS, HsOfficeDebitorEntity::setVatBusiness) + .notNullable(), + new SimpleProperty<>( + "vatReverseCharge", + HsOfficeDebitorPatchResource::setVatReverseCharge, + PATCHED_BILLABLE, + HsOfficeDebitorEntity::setVatReverseCharge) + .notNullable(), + new JsonNullableProperty<>( + "defaultPrefix", + HsOfficeDebitorPatchResource::setDefaultPrefix, + PATCHED_DEFAULT_PREFIX, + HsOfficeDebitorEntity::setDefaultPrefix) + .notNullable(), + new JsonNullableProperty<>( + "refundBankAccount", + HsOfficeDebitorPatchResource::setRefundBankAccountUuid, + PATCHED_REFUND_BANK_ACCOUNT_UUID, + HsOfficeDebitorEntity::setRefundBankAccount, + newBankAccount(PATCHED_REFUND_BANK_ACCOUNT_UUID)) .notNullable() ); } @@ -109,4 +153,10 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase< newContact.setUuid(uuid); return newContact; } + + private HsOfficeBankAccountEntity newBankAccount(final UUID uuid) { + final var newBankAccount = new HsOfficeBankAccountEntity(); + newBankAccount.setUuid(uuid); + return newBankAccount; + } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntityUnitTest.java index 0f147f93..270431b9 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntityUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntityUnitTest.java @@ -4,6 +4,7 @@ import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerDetailsEntity; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; +import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -13,30 +14,52 @@ class HsOfficeDebitorEntityUnitTest { @Test void toStringContainsPartnerAndContact() { final var given = HsOfficeDebitorEntity.builder() - .debitorNumber(123456) + .debitorNumberSuffix((byte)67) .partner(HsOfficePartnerEntity.builder() .person(HsOfficePersonEntity.builder() + .personType(HsOfficePersonType.LEGAL) .tradeName("some trade name") .build()) .details(HsOfficePartnerDetailsEntity.builder().birthName("some birth name").build()) + .debitorNumberPrefix(12345) + .build()) + .billingContact(HsOfficeContactEntity.builder().label("some label").build()) + .defaultPrefix("som") + .build(); + final var result = given.toString(); + + assertThat(result).isEqualTo("debitor(1234567: LEGAL some trade name: som)"); + } + + @Test + void toStringWithoutPersonContainsDebitorNumber() { + final var given = HsOfficeDebitorEntity.builder() + .debitorNumberSuffix((byte)67) + .partner(HsOfficePartnerEntity.builder() + .person(null) + .details(HsOfficePartnerDetailsEntity.builder().birthName("some birth name").build()) + .debitorNumberPrefix(12345) .build()) .billingContact(HsOfficeContactEntity.builder().label("some label").build()) .build(); final var result = given.toString(); - assertThat(result).isEqualTo("debitor(123456: some trade name)"); + assertThat(result).isEqualTo("debitor(1234567: )"); } @Test - void toShortStringContainsPartnerAndContact() { + void toShortStringContainsDebitorNumber() { final var given = HsOfficeDebitorEntity.builder() - .debitorNumber(123456) + .partner(HsOfficePartnerEntity.builder() + .debitorNumberPrefix(12345) + .build()) + .debitorNumberSuffix((byte)67) .build(); final var result = given.toShortString(); - assertThat(result).isEqualTo("123456"); + assertThat(result).isEqualTo("1234567"); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java index b8ab0710..97eb49b1 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorRepositoryIntegrationTest.java @@ -9,8 +9,6 @@ import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; import net.hostsharing.test.Array; import net.hostsharing.test.JpaAttempt; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -20,14 +18,13 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import org.springframework.orm.jpa.JpaSystemException; +import org.springframework.transaction.annotation.Transactional; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.servlet.http.HttpServletRequest; import java.util.Arrays; -import java.util.HashSet; import java.util.List; -import java.util.Set; import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.grantDisplaysOf; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf; @@ -65,8 +62,6 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { @MockBean HttpServletRequest request; - Set tempDebitors = new HashSet<>(); - @Nested class CreateDebitor { @@ -81,9 +76,11 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { // when final var result = attempt(em, () -> { final var newDebitor = HsOfficeDebitorEntity.builder() - .debitorNumber(20001) + .debitorNumberSuffix((byte)21) .partner(givenPartner) .billingContact(givenContact) + .defaultPrefix("abc") + .billable(false) .build(); return debitorRepo.save(newDebitor); }); @@ -95,14 +92,43 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { assertThat(debitorRepo.count()).isEqualTo(count + 1); } + @ParameterizedTest + @ValueSource(strings = {"", "a", "ab", "a12", "123", "12a"}) + @Transactional + public void canNotCreateNewDebitorWithInvalidDefaultPrefix(final String givenPrefix) { + // given + context("superuser-alex@hostsharing.net"); + final var count = debitorRepo.count(); + final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("First GmbH").get(0); + final var givenContact = contactRepo.findContactByOptionalLabelLike("first contact").get(0); + + // when + final var result = attempt(em, () -> { + final var newDebitor = HsOfficeDebitorEntity.builder() + .debitorNumberSuffix((byte)21) + .partner(givenPartner) + .billingContact(givenContact) + .billable(true) + .vatReverseCharge(false) + .vatBusiness(false) + .defaultPrefix(givenPrefix) + .build(); + return debitorRepo.save(newDebitor); + }); + + // then + result.assertExceptionWithRootCauseMessage(org.hibernate.exception.ConstraintViolationException.class); + } + @Test public void createsAndGrantsRoles() { // given context("superuser-alex@hostsharing.net"); final var initialRoleNames = roleNamesOf(rawRoleRepo.findAll()); final var initialGrantNames = grantDisplaysOf(rawGrantRepo.findAll()).stream() + // some search+replace to make the output fit into the screen width .map(s -> s.replace("superuser-alex@hostsharing.net", "superuser-alex")) - .map(s -> s.replace("20002Fourthe.G.-forthcontact", "FeG")) + .map(s -> s.replace("22Fourthe.G.-forthcontact", "FeG")) .map(s -> s.replace("Fourthe.G.-forthcontact", "FeG")) .map(s -> s.replace("forthcontact", "4th")) .map(s -> s.replace("hs_office_", "")) @@ -113,9 +139,11 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("Fourth").get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike("forth contact").get(0); final var newDebitor = HsOfficeDebitorEntity.builder() - .debitorNumber(20002) + .debitorNumberSuffix((byte)22) .partner(givenPartner) .billingContact(givenContact) + .defaultPrefix("abc") + .billable(false) .build(); return debitorRepo.save(newDebitor); }).assertSuccessful(); @@ -123,42 +151,42 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { // then assertThat(roleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from( initialRoleNames, - "hs_office_debitor#20002Fourthe.G.-forthcontact.owner", - "hs_office_debitor#20002Fourthe.G.-forthcontact.admin", - "hs_office_debitor#20002Fourthe.G.-forthcontact.agent", - "hs_office_debitor#20002Fourthe.G.-forthcontact.tenant", - "hs_office_debitor#20002Fourthe.G.-forthcontact.guest")); + "hs_office_debitor#1000422:Fourthe.G.-forthcontact.owner", + "hs_office_debitor#1000422:Fourthe.G.-forthcontact.admin", + "hs_office_debitor#1000422:Fourthe.G.-forthcontact.agent", + "hs_office_debitor#1000422:Fourthe.G.-forthcontact.tenant", + "hs_office_debitor#1000422:Fourthe.G.-forthcontact.guest")); assertThat(grantDisplaysOf(rawGrantRepo.findAll())) .map(s -> s.replace("superuser-alex@hostsharing.net", "superuser-alex")) - .map(s -> s.replace("20002Fourthe.G.-forthcontact", "FeG")) + .map(s -> s.replace("22Fourthe.G.-forthcontact", "FeG")) .map(s -> s.replace("Fourthe.G.-forthcontact", "FeG")) .map(s -> s.replace("forthcontact", "4th")) .map(s -> s.replace("hs_office_", "")) .containsExactlyInAnyOrder(Array.fromFormatted( initialGrantNames, // owner - "{ grant perm * on debitor#FeG to role debitor#FeG.owner by system and assume }", - "{ grant role debitor#FeG.owner to role global#global.admin by system and assume }", - "{ grant role debitor#FeG.owner to user superuser-alex by global#global.admin and assume }", + "{ grant perm * on debitor#1000422:FeG to role debitor#1000422:FeG.owner by system and assume }", + "{ grant role debitor#1000422:FeG.owner to role global#global.admin by system and assume }", + "{ grant role debitor#1000422:FeG.owner to user superuser-alex by global#global.admin and assume }", // admin - "{ grant perm edit on debitor#FeG to role debitor#FeG.admin by system and assume }", - "{ grant role debitor#FeG.admin to role debitor#FeG.owner by system and assume }", + "{ grant perm edit on debitor#1000422:FeG to role debitor#1000422:FeG.admin by system and assume }", + "{ grant role debitor#1000422:FeG.admin to role debitor#1000422:FeG.owner by system and assume }", // agent - "{ grant role debitor#FeG.agent to role debitor#FeG.admin by system and assume }", - "{ grant role debitor#FeG.agent to role contact#4th.admin by system and assume }", - "{ grant role debitor#FeG.agent to role partner#FeG.admin by system and assume }", + "{ grant role debitor#1000422:FeG.agent to role debitor#1000422:FeG.admin by system and assume }", + "{ grant role debitor#1000422:FeG.agent to role contact#4th.admin by system and assume }", + "{ grant role debitor#1000422:FeG.agent to role partner#10004:FeG.admin by system and assume }", // tenant - "{ grant role contact#4th.guest to role debitor#FeG.tenant by system and assume }", - "{ grant role debitor#FeG.tenant to role debitor#FeG.agent by system and assume }", - "{ grant role debitor#FeG.tenant to role partner#FeG.agent by system and assume }", - "{ grant role partner#FeG.tenant to role debitor#FeG.tenant by system and assume }", + "{ grant role contact#4th.guest to role debitor#1000422:FeG.tenant by system and assume }", + "{ grant role debitor#1000422:FeG.tenant to role debitor#1000422:FeG.agent by system and assume }", + "{ grant role debitor#1000422:FeG.tenant to role partner#10004:FeG.agent by system and assume }", + "{ grant role partner#10004:FeG.tenant to role debitor#1000422:FeG.tenant by system and assume }", // guest - "{ grant perm view on debitor#FeG to role debitor#FeG.guest by system and assume }", - "{ grant role debitor#FeG.guest to role debitor#FeG.tenant by system and assume }", + "{ grant perm view on debitor#1000422:FeG to role debitor#1000422:FeG.guest by system and assume }", + "{ grant role debitor#1000422:FeG.guest to role debitor#1000422:FeG.tenant by system and assume }", null)); } @@ -183,14 +211,14 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { // then allTheseDebitorsAreReturned( result, - "debitor(10001: First GmbH)", - "debitor(10002: Second e.K.)", - "debitor(10003: Third OHG)"); + "debitor(1000111: LEGAL First GmbH: fir)", + "debitor(1000212: LEGAL Second e.K.: sec)", + "debitor(1000313: SOLE_REPRESENTATION Third OHG: thi)"); } @ParameterizedTest @ValueSource(strings = { - "hs_office_partner#FirstGmbH-firstcontact.admin", + "hs_office_partner#10001:FirstGmbH-firstcontact.admin", "hs_office_person#FirstGmbH.admin", "hs_office_contact#firstcontact.admin", }) @@ -202,7 +230,9 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { final var result = debitorRepo.findDebitorByOptionalNameLike(null); // then: - exactlyTheseDebitorsAreReturned(result, "debitor(10001: First GmbH)"); + exactlyTheseDebitorsAreReturned(result, + "debitor(1000111: LEGAL First GmbH: fir)", + "debitor(1000120: LEGAL First GmbH: fif)"); } @Test @@ -227,10 +257,10 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { context("superuser-alex@hostsharing.net"); // when - final var result = debitorRepo.findDebitorByDebitorNumber(10003); + final var result = debitorRepo.findDebitorByDebitorNumber(1000313); // then - exactlyTheseDebitorsAreReturned(result, "debitor(10003: Third OHG)"); + exactlyTheseDebitorsAreReturned(result, "debitor(1000313: SOLE_REPRESENTATION Third OHG: thi)"); } } @@ -246,7 +276,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { final var result = debitorRepo.findDebitorByOptionalNameLike("third contact"); // then - exactlyTheseDebitorsAreReturned(result, "debitor(10003: Third OHG)"); + exactlyTheseDebitorsAreReturned(result, "debitor(1000313: SOLE_REPRESENTATION Third OHG: thi)"); } } @@ -257,10 +287,10 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { public void globalAdmin_canUpdateArbitraryDebitor() { // given context("superuser-alex@hostsharing.net"); - final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", "Fourth"); + final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", "Fourth", "fif"); assertThatDebitorIsVisibleForUserWithRole( givenDebitor, - "hs_office_partner#Fourthe.G.-forthcontact.admin"); + "hs_office_partner#10004:Fourthe.G.-forthcontact.admin"); assertThatDebitorActuallyInDatabase(givenDebitor); final var givenNewPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0); final var givenNewContact = contactRepo.findContactByOptionalLabelLike("sixth contact").get(0); @@ -290,10 +320,10 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { // ... partner role was reassigned: assertThatDebitorIsNotVisibleForUserWithRole( result.returnedValue(), - "hs_office_partner#Fourthe.G.-forthcontact.agent"); + "hs_office_partner#10004:Fourthe.G.-forthcontact.agent"); assertThatDebitorIsVisibleForUserWithRole( result.returnedValue(), - "hs_office_partner#FirstGmbH-firstcontact.agent"); + "hs_office_partner#10001:FirstGmbH-firstcontact.agent"); // ... contact role was reassigned: assertThatDebitorIsNotVisibleForUserWithRole( @@ -316,10 +346,10 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { public void globalAdmin_canUpdateNullRefundBankAccountToNotNullBankAccountForArbitraryDebitor() { // given context("superuser-alex@hostsharing.net"); - final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", null); + final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", null, "fig"); assertThatDebitorIsVisibleForUserWithRole( givenDebitor, - "hs_office_partner#Fourthe.G.-forthcontact.admin"); + "hs_office_partner#10004:Fourthe.G.-forthcontact.admin"); assertThatDebitorActuallyInDatabase(givenDebitor); final var givenNewBankAccount = bankAccountRepo.findByOptionalHolderLike("first").get(0); @@ -346,10 +376,10 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { public void globalAdmin_canUpdateRefundBankAccountToNullForArbitraryDebitor() { // given context("superuser-alex@hostsharing.net"); - final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", "Fourth"); + final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", "Fourth", "fih"); assertThatDebitorIsVisibleForUserWithRole( givenDebitor, - "hs_office_partner#Fourthe.G.-forthcontact.admin"); + "hs_office_partner#10004:Fourthe.G.-forthcontact.admin"); assertThatDebitorActuallyInDatabase(givenDebitor); // when @@ -375,15 +405,15 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { public void partnerAdmin_canNotUpdateRelatedDebitor() { // given context("superuser-alex@hostsharing.net"); - final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "eighth", "Fourth"); + final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "eighth", "Fourth", "eig"); assertThatDebitorIsVisibleForUserWithRole( givenDebitor, - "hs_office_partner#Fourthe.G.-forthcontact.admin"); + "hs_office_partner#10004:Fourthe.G.-forthcontact.admin"); assertThatDebitorActuallyInDatabase(givenDebitor); // when final var result = jpaAttempt.transacted(() -> { - context("superuser-alex@hostsharing.net", "hs_office_partner#Fourthe.G.-forthcontact.admin"); + context("superuser-alex@hostsharing.net", "hs_office_partner#10004:Fourthe.G.-forthcontact.admin"); givenDebitor.setVatId("NEW-VAT-ID"); return debitorRepo.save(givenDebitor); }); @@ -397,7 +427,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { public void contactAdmin_canNotUpdateRelatedDebitor() { // given context("superuser-alex@hostsharing.net"); - final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "ninth", "Fourth"); + final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "ninth", "Fourth", "nin"); assertThatDebitorIsVisibleForUserWithRole( givenDebitor, "hs_office_contact#ninthcontact.admin"); @@ -448,7 +478,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { public void globalAdmin_canDeleteAnyDebitor() { // given context("superuser-alex@hostsharing.net", null); - final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "tenth", "Fourth"); + final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "tenth", "Fourth", "ten"); // when final var result = jpaAttempt.transacted(() -> { @@ -468,7 +498,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { public void relatedPerson_canNotDeleteTheirRelatedDebitor() { // given context("superuser-alex@hostsharing.net", null); - final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "eleventh", "Fourth"); + final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "eleventh", "Fourth", "ele"); // when final var result = jpaAttempt.transacted(() -> { @@ -494,7 +524,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { context("superuser-alex@hostsharing.net"); final var initialRoleNames = Array.from(roleNamesOf(rawRoleRepo.findAll())); final var initialGrantNames = Array.from(grantDisplaysOf(rawGrantRepo.findAll())); - final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "twelfth", "Fourth"); + final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "twelfth", "Fourth", "twe"); assertThat(rawRoleRepo.findAll().size()).as("precondition failed: unexpected number of roles created") .isEqualTo(initialRoleNames.length + 5); assertThat(rawGrantRepo.findAll().size()).as("precondition failed: unexpected number of grants created") @@ -536,7 +566,8 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { private HsOfficeDebitorEntity givenSomeTemporaryDebitor( final String partner, final String contact, - final String bankAccount) { + final String bankAccount, + final String defaultPrefix) { return jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net"); final var givenPartner = partnerRepo.findPartnerByOptionalNameLike(partner).get(0); @@ -544,23 +575,18 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest { final var givenBankAccount = bankAccount != null ? bankAccountRepo.findByOptionalHolderLike(bankAccount).get(0) : null; final var newDebitor = HsOfficeDebitorEntity.builder() - .debitorNumber(20000) + .debitorNumberSuffix((byte)20) .partner(givenPartner) .billingContact(givenContact) .refundBankAccount(givenBankAccount) + .defaultPrefix(defaultPrefix) + .billable(true) .build(); return debitorRepo.save(newDebitor); }).assertSuccessful().returnedValue(); } - @BeforeEach - @AfterEach - void cleanup() { - context("superuser-alex@hostsharing.net"); - em.createQuery("DELETE FROM HsOfficeDebitorEntity d where d.debitorNumber >= 20000").executeUpdate(); - } - void exactlyTheseDebitorsAreReturned(final List actualResult, final String... debitorNames) { assertThat(actualResult) .extracting(HsOfficeDebitorEntity::toString) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/TestHsOfficeDebitor.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/TestHsOfficeDebitor.java index d9d482ba..36b3d534 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/TestHsOfficeDebitor.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/TestHsOfficeDebitor.java @@ -9,8 +9,10 @@ import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TE @UtilityClass public class TestHsOfficeDebitor { + public byte DEFAULT_DEBITOR_SUFFIX = 0; + public static final HsOfficeDebitorEntity TEST_DEBITOR = HsOfficeDebitorEntity.builder() - .debitorNumber(10001) + .debitorNumberSuffix(DEFAULT_DEBITOR_SUFFIX) .partner(TEST_PARTNER) .billingContact(TEST_CONTACT) .build(); 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 79e621ee..6d394b53 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 @@ -83,7 +83,7 @@ class HsOfficeMembershipControllerAcceptanceTest { [ { "partner": { "person": { "tradeName": "First GmbH" } }, - "mainDebitor": { "debitorNumber": 10001 }, + "mainDebitor": { "debitorNumber": 1000111 }, "memberNumber": 10001, "validFrom": "2022-10-01", "validTo": null, @@ -91,7 +91,7 @@ class HsOfficeMembershipControllerAcceptanceTest { }, { "partner": { "person": { "tradeName": "Second e.K." } }, - "mainDebitor": { "debitorNumber": 10002 }, + "mainDebitor": { "debitorNumber": 1000212 }, "memberNumber": 10002, "validFrom": "2022-10-01", "validTo": null, @@ -99,7 +99,7 @@ class HsOfficeMembershipControllerAcceptanceTest { }, { "partner": { "person": { "tradeName": "Third OHG" } }, - "mainDebitor": { "debitorNumber": 10003 }, + "mainDebitor": { "debitorNumber": 1000313 }, "memberNumber": 10003, "validFrom": "2022-10-01", "validTo": null, @@ -131,7 +131,8 @@ class HsOfficeMembershipControllerAcceptanceTest { "partnerUuid": "%s", "mainDebitorUuid": "%s", "memberNumber": 20001, - "validFrom": "2022-10-13" + "validFrom": "2022-10-13", + "membershipFeeBillable": "true" } """.formatted(givenPartner.getUuid(), givenDebitor.getUuid())) .port(port) @@ -142,6 +143,7 @@ class HsOfficeMembershipControllerAcceptanceTest { .contentType(ContentType.JSON) .body("uuid", isUuidValid()) .body("mainDebitor.debitorNumber", is(givenDebitor.getDebitorNumber())) + .body("mainDebitor.debitorNumberSuffix", is((int) givenDebitor.getDebitorNumberSuffix())) .body("partner.person.tradeName", is("Third OHG")) .body("memberNumber", is(20001)) .body("validFrom", is("2022-10-13")) @@ -182,7 +184,7 @@ class HsOfficeMembershipControllerAcceptanceTest { .body("", lenientlyEquals(""" { "partner": { "person": { "tradeName": "First GmbH" } }, - "mainDebitor": { "debitorNumber": 10001 }, + "mainDebitor": { "debitorNumber": 1000111 }, "memberNumber": 10001, "validFrom": "2022-10-01", "validTo": null, @@ -224,7 +226,7 @@ class HsOfficeMembershipControllerAcceptanceTest { RestAssured // @formatter:off .given() .header("current-user", "superuser-alex@hostsharing.net") - .header("assumed-roles", "hs_office_debitor#10003ThirdOHG-thirdcontact.agent") + .header("assumed-roles", "hs_office_debitor#1000313:ThirdOHG-thirdcontact.agent") .port(port) .when() .get("http://localhost/api/hs/office/memberships/" + givenMembershipUuid) @@ -235,7 +237,7 @@ class HsOfficeMembershipControllerAcceptanceTest { { "partner": { "person": { "tradeName": "Third OHG" } }, "mainDebitor": { - "debitorNumber": 10003, + "debitorNumber": 1000313, "billingContact": { "label": "third contact" } }, "memberNumber": 10003, @@ -276,6 +278,7 @@ class HsOfficeMembershipControllerAcceptanceTest { .body("uuid", isUuidValid()) .body("partner.person.tradeName", is(givenMembership.getPartner().getPerson().getTradeName())) .body("mainDebitor.debitorNumber", is(givenMembership.getMainDebitor().getDebitorNumber())) + .body("mainDebitor.debitorNumberSuffix", is((int) givenMembership.getMainDebitor().getDebitorNumberSuffix())) .body("memberNumber", is(givenMembership.getMemberNumber())) .body("validFrom", is("2022-11-01")) .body("validTo", is("2023-12-31")) @@ -285,7 +288,7 @@ class HsOfficeMembershipControllerAcceptanceTest { // finally, the Membership is actually updated assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent().get() .matches(mandate -> { - assertThat(mandate.getPartner().toShortString()).isEqualTo("First GmbH"); + assertThat(mandate.getPartner().toShortString()).isEqualTo("LEGAL First GmbH"); assertThat(mandate.getMainDebitor().toString()).isEqualTo(givenMembership.getMainDebitor().toString()); assertThat(mandate.getMemberNumber()).isEqualTo(givenMembership.getMemberNumber()); assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,2024-01-01)"); @@ -299,7 +302,7 @@ class HsOfficeMembershipControllerAcceptanceTest { context.define("superuser-alex@hostsharing.net"); final var givenMembership = givenSomeTemporaryMembershipBessler(); - final var givenNewMainDebitor = debitorRepo.findDebitorByDebitorNumber(10003).get(0); + final var givenNewMainDebitor = debitorRepo.findDebitorByDebitorNumber(1000313).get(0); RestAssured // @formatter:off .given() @@ -318,7 +321,7 @@ class HsOfficeMembershipControllerAcceptanceTest { .contentType(ContentType.JSON) .body("uuid", isUuidValid()) .body("partner.person.tradeName", is(givenMembership.getPartner().getPerson().getTradeName())) - .body("mainDebitor.debitorNumber", is(10003)) + .body("mainDebitor.debitorNumber", is(1000313)) .body("memberNumber", is(givenMembership.getMemberNumber())) .body("validFrom", is("2022-11-01")) .body("validTo", nullValue()) @@ -328,7 +331,7 @@ class HsOfficeMembershipControllerAcceptanceTest { // finally, the Membership is actually updated assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent().get() .matches(mandate -> { - assertThat(mandate.getPartner().toShortString()).isEqualTo("First GmbH"); + assertThat(mandate.getPartner().toShortString()).isEqualTo("LEGAL First GmbH"); assertThat(mandate.getMainDebitor().toString()).isEqualTo(givenMembership.getMainDebitor().toString()); assertThat(mandate.getMemberNumber()).isEqualTo(givenMembership.getMemberNumber()); assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,)"); @@ -340,13 +343,13 @@ class HsOfficeMembershipControllerAcceptanceTest { @Test void partnerAgent_canViewButNotPatchValidityOfRelatedMembership() { - context.define("superuser-alex@hostsharing.net", "hs_office_partner#FirstGmbH-firstcontact.agent"); + context.define("superuser-alex@hostsharing.net", "hs_office_partner#10001:FirstGmbH-firstcontact.agent"); final var givenMembership = givenSomeTemporaryMembershipBessler(); final var location = RestAssured // @formatter:off .given() .header("current-user", "superuser-alex@hostsharing.net") - .header("assumed-roles", "hs_office_partner#FirstGmbH-firstcontact.agent") + .header("assumed-roles", "hs_office_partner#10001:FirstGmbH-firstcontact.agent") .contentType(ContentType.JSON) .body(""" { @@ -444,6 +447,7 @@ class HsOfficeMembershipControllerAcceptanceTest { .memberNumber(++tempMemberNumber) .validity(Range.closedInfinite(LocalDate.parse("2022-11-01"))) .reasonForTermination(NONE) + .membershipFeeBillable(true) .build(); return membershipRepo.save(newMembership); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java index a2aebfce..2f42cb58 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/membership/HsOfficeMembershipControllerRestTest.java @@ -72,7 +72,8 @@ public class HsOfficeMembershipControllerRestTest { "partnerUuid": null, "mainDebitorUuid": "%s", "memberNumber": 20001, - "validFrom": "2022-10-13" + "validFrom": "2022-10-13", + "membershipFeeBillable": "true" } """.formatted(UUID.randomUUID())) .accept(MediaType.APPLICATION_JSON)) @@ -97,7 +98,8 @@ public class HsOfficeMembershipControllerRestTest { "partnerUuid": "%s", "mainDebitorUuid": null, "memberNumber": 20001, - "validFrom": "2022-10-13" + "validFrom": "2022-10-13", + "membershipFeeBillable": "true" } """.formatted(UUID.randomUUID())) .accept(MediaType.APPLICATION_JSON)) @@ -128,7 +130,8 @@ public class HsOfficeMembershipControllerRestTest { "partnerUuid": "%s", "mainDebitorUuid": "%s", "memberNumber": 20001, - "validFrom": "2022-10-13" + "validFrom": "2022-10-13", + "membershipFeeBillable": "true" } """.formatted(givenPartnerUuid, givenMainDebitorUuid)) .accept(MediaType.APPLICATION_JSON)) @@ -159,7 +162,8 @@ public class HsOfficeMembershipControllerRestTest { "partnerUuid": "%s", "mainDebitorUuid": "%s", "memberNumber": 20001, - "validFrom": "2022-10-13" + "validFrom": "2022-10-13", + "membershipFeeBillable": "true" } """.formatted(givenPartnerUuid, givenMainDebitorUuid)) .accept(MediaType.APPLICATION_JSON)) 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 25f68f4b..ee4944c1 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 @@ -36,6 +36,9 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase< private static final LocalDate GIVEN_VALID_FROM = LocalDate.parse("2020-04-15"); private static final LocalDate PATCHED_VALID_TO = LocalDate.parse("2022-12-31"); + private static final Boolean GIVEN_MEMBERSHIP_FEE_BILLABLE = true; + private static final Boolean PATCHED_MEMBERSHIP_FEE_BILLABLE = false; + @Mock private EntityManager em; @@ -56,6 +59,7 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase< entity.setMainDebitor(TEST_DEBITOR); entity.setPartner(TEST_PARTNER); entity.setValidity(Range.closedInfinite(GIVEN_VALID_FROM)); + entity.setMembershipFeeBillable(GIVEN_MEMBERSHIP_FEE_BILLABLE); return entity; } @@ -90,7 +94,12 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase< HsOfficeReasonForTerminationResource.CANCELLATION, HsOfficeMembershipEntity::setReasonForTermination, HsOfficeReasonForTermination.CANCELLATION) - .notNullable() + .notNullable(), + new JsonNullableProperty<>( + "membershipFeeBillable", + HsOfficeMembershipPatchResource::setMembershipFeeBillable, + PATCHED_MEMBERSHIP_FEE_BILLABLE, + HsOfficeMembershipEntity::setMembershipFeeBillable) ); } @@ -99,10 +108,4 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase< newDebitor.setUuid(uuid); return newDebitor; } - - private HsOfficeMembershipEntity newMembership(final UUID uuid) { - final var newMembership = new HsOfficeMembershipEntity(); - newMembership.setUuid(uuid); - return newMembership; - } } 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 30ad5ac1..14959428 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 @@ -27,7 +27,7 @@ class HsOfficeMembershipEntityUnitTest { void toStringContainsAllProps() { final var result = givenMembership.toString(); - assertThat(result).isEqualTo("Membership(10001, Test Ltd., 10001, [2020-01-01,))"); + assertThat(result).isEqualTo("Membership(10001, LEGAL Test Ltd., 1000100, [2020-01-01,))"); } @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 c85a9b13..d633d8af 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 @@ -81,6 +81,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest { .partner(givenPartner) .mainDebitor(givenDebitor) .validity(Range.closedInfinite(LocalDate.parse("2020-01-01"))) + .membershipFeeBillable(true) .build()); return membershipRepo.save(newMembership); }); @@ -111,6 +112,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest { .partner(givenPartner) .mainDebitor(givenDebitor) .validity(Range.closedInfinite(LocalDate.parse("2020-01-01"))) + .membershipFeeBillable(true) .build()); return membershipRepo.save(newMembership); }); @@ -119,11 +121,11 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest { final var all = rawRoleRepo.findAll(); assertThat(roleNamesOf(all)).containsExactlyInAnyOrder(Array.from( initialRoleNames, - "hs_office_membership#20002FirstGmbH-firstcontact.admin", - "hs_office_membership#20002FirstGmbH-firstcontact.agent", - "hs_office_membership#20002FirstGmbH-firstcontact.guest", - "hs_office_membership#20002FirstGmbH-firstcontact.owner", - "hs_office_membership#20002FirstGmbH-firstcontact.tenant")); + "hs_office_membership#20002:FirstGmbH-firstcontact.admin", + "hs_office_membership#20002:FirstGmbH-firstcontact.agent", + "hs_office_membership#20002:FirstGmbH-firstcontact.guest", + "hs_office_membership#20002:FirstGmbH-firstcontact.owner", + "hs_office_membership#20002:FirstGmbH-firstcontact.tenant")); assertThat(grantDisplaysOf(rawGrantRepo.findAll())) .map(s -> s.replace("GmbH-firstcontact", "")) .map(s -> s.replace("hs_office_", "")) @@ -131,32 +133,33 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest { initialGrantNames, // owner - "{ grant perm * on membership#20002First to role membership#20002First.owner by system and assume }", - "{ grant role membership#20002First.owner to role global#global.admin by system and assume }", + "{ grant perm * on membership#20002:First to role membership#20002:First.owner by system and assume }", + "{ grant role membership#20002:First.owner to role global#global.admin by system and assume }", // admin - "{ grant perm edit on membership#20002First to role membership#20002First.admin by system and assume }", - "{ grant role membership#20002First.admin to role membership#20002First.owner by system and assume }", + "{ grant perm edit on membership#20002:First to role membership#20002:First.admin by system and assume }", + "{ grant role membership#20002:First.admin to role membership#20002:First.owner by system and assume }", // agent - "{ grant role membership#20002First.agent to role membership#20002First.admin by system and assume }", - "{ grant role partner#First.tenant to role membership#20002First.agent by system and assume }", - "{ grant role membership#20002First.agent to role debitor#10001First.admin by system and assume }", - "{ grant role membership#20002First.agent to role partner#First.admin by system and assume }", - "{ grant role debitor#10001First.tenant to role membership#20002First.agent by system and assume }", + "{ grant role membership#20002:First.agent to role membership#20002:First.admin by system and assume }", + "{ grant role partner#10001:First.tenant to role membership#20002:First.agent by system and assume }", + "{ grant role membership#20002:First.agent to role debitor#1000111:First.admin by system and assume }", + "{ grant role membership#20002:First.agent to role partner#10001:First.admin by system and assume }", + "{ grant role debitor#1000111:First.tenant to role membership#20002:First.agent by system and assume }", // tenant - "{ grant role membership#20002First.tenant to role membership#20002First.agent by system and assume }", - "{ grant role partner#First.guest to role membership#20002First.tenant by system and assume }", - "{ grant role debitor#10001First.guest to role membership#20002First.tenant by system and assume }", - "{ grant role membership#20002First.tenant to role debitor#10001First.agent by system and assume }", - "{ grant role membership#20002First.tenant to role partner#First.agent by system and assume }", + "{ grant role membership#20002:First.tenant to role membership#20002:First.agent by system and assume }", + "{ grant role partner#10001:First.guest to role membership#20002:First.tenant by system and assume }", + "{ grant role debitor#1000111:First.guest to role membership#20002:First.tenant by system and assume }", + "{ grant role membership#20002:First.tenant to role debitor#1000111:First.agent by system and assume }", + + "{ grant role membership#20002:First.tenant to role partner#10001:First.agent by system and assume }", // guest - "{ grant perm view on membership#20002First to role membership#20002First.guest by system and assume }", - "{ grant role membership#20002First.guest to role membership#20002First.tenant by system and assume }", - "{ grant role membership#20002First.guest to role partner#First.tenant by system and assume }", - "{ grant role membership#20002First.guest to role debitor#10001First.tenant by system and assume }", + "{ grant perm view on membership#20002:First to role membership#20002:First.guest by system and assume }", + "{ grant role membership#20002:First.guest to role membership#20002:First.tenant by system and assume }", + "{ grant role membership#20002:First.guest to role partner#10001:First.tenant by system and assume }", + "{ grant role membership#20002:First.guest to role debitor#1000111:First.tenant by system and assume }", null)); } @@ -181,9 +184,9 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest { // then exactlyTheseMembershipsAreReturned( result, - "Membership(10001, First GmbH, 10001, [2022-10-01,), NONE)", - "Membership(10002, Second e.K., 10002, [2022-10-01,), NONE)", - "Membership(10003, Third OHG, 10003, [2022-10-01,), NONE)"); + "Membership(10001, LEGAL First GmbH, 1000111, [2022-10-01,), NONE)", + "Membership(10002, LEGAL Second e.K., 1000212, [2022-10-01,), NONE)", + "Membership(10003, SOLE_REPRESENTATION Third OHG, 1000313, [2022-10-01,), NONE)"); } @Test @@ -198,7 +201,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest { null); // then - exactlyTheseMembershipsAreReturned(result, "Membership(10001, First GmbH, 10001, [2022-10-01,), NONE)"); + exactlyTheseMembershipsAreReturned(result, "Membership(10001, LEGAL First GmbH, 1000111, [2022-10-01,), NONE)"); } @Test @@ -210,7 +213,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest { final var result = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10002); // then - exactlyTheseMembershipsAreReturned(result, "Membership(10002, Second e.K., 10002, [2022-10-01,), NONE)"); + exactlyTheseMembershipsAreReturned(result, "Membership(10002, LEGAL Second e.K., 1000212, [2022-10-01,), NONE)"); } } @@ -224,7 +227,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest { final var givenMembership = givenSomeTemporaryMembership("First", "First"); assertThatMembershipIsVisibleForUserWithRole( givenMembership, - "hs_office_debitor#10001FirstGmbH-firstcontact.admin"); + "hs_office_debitor#1000111:FirstGmbH-firstcontact.admin"); assertThatMembershipExistsAndIsAccessibleToCurrentContext(givenMembership); final var newValidityEnd = LocalDate.now(); @@ -251,13 +254,13 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest { final var givenMembership = givenSomeTemporaryMembership("First", "First"); assertThatMembershipIsVisibleForUserWithRole( givenMembership, - "hs_office_debitor#10001FirstGmbH-firstcontact.admin"); + "hs_office_debitor#1000111:FirstGmbH-firstcontact.admin"); assertThatMembershipExistsAndIsAccessibleToCurrentContext(givenMembership); final var newValidityEnd = LocalDate.now(); // when final var result = jpaAttempt.transacted(() -> { - context("superuser-alex@hostsharing.net", "hs_office_debitor#10001FirstGmbH-firstcontact.admin"); + context("superuser-alex@hostsharing.net", "hs_office_debitor#1000111:FirstGmbH-firstcontact.admin"); givenMembership.setValidity(Range.closedOpen( givenMembership.getValidity().lower(), newValidityEnd)); return membershipRepo.save(givenMembership); @@ -325,7 +328,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest { // when final var result = jpaAttempt.transacted(() -> { - context("superuser-alex@hostsharing.net", "hs_office_debitor#10003ThirdOHG-thirdcontact.admin"); + context("superuser-alex@hostsharing.net", "hs_office_debitor#1000313:ThirdOHG-thirdcontact.admin"); assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent(); membershipRepo.deleteByUuid(givenMembership.getUuid()); @@ -382,8 +385,8 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest { // then assertThat(customerLogEntries).map(Arrays::toString).contains( - "[creating Membership test-data FirstGmbH10001, hs_office_membership, INSERT]", - "[creating Membership test-data Seconde.K.10002, hs_office_membership, INSERT]"); + "[creating Membership test-data FirstGmbH11, hs_office_membership, INSERT]", + "[creating Membership test-data Seconde.K.12, hs_office_membership, INSERT]"); } @BeforeEach @@ -412,6 +415,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest { .partner(givenPartner) .mainDebitor(givenDebitor) .validity(Range.closedInfinite(LocalDate.parse("2020-01-01"))) + .membershipFeeBillable(true) .build(); toCleanup(newMembership); 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 new file mode 100644 index 00000000..bb08f2e7 --- /dev/null +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java @@ -0,0 +1,1038 @@ +package net.hostsharing.hsadminng.hs.office.migration; + +import com.opencsv.CSVParserBuilder; +import com.opencsv.CSVReader; +import com.opencsv.CSVReaderBuilder; +import net.hostsharing.hsadminng.context.Context; +import net.hostsharing.hsadminng.context.ContextBasedTest; +import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; +import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; +import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionEntity; +import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionType; +import net.hostsharing.hsadminng.hs.office.coopshares.HsOfficeCoopSharesTransactionEntity; +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.partner.HsOfficePartnerDetailsEntity; +import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; +import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; +import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType; +import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity; +import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipType; +import net.hostsharing.hsadminng.hs.office.sepamandate.HsOfficeSepaMandateEntity; +import net.hostsharing.test.JpaAttempt; +import org.apache.commons.lang3.StringUtils; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestWatcher; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.test.annotation.Commit; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.transaction.support.TransactionTemplate; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.constraints.NotNull; +import java.io.*; +import java.math.BigDecimal; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.LocalDate; +import java.util.*; +import java.util.stream.Collectors; + +import static java.util.Arrays.stream; +import static java.util.Objects.requireNonNull; +import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; +import static org.assertj.core.api.Fail.fail; + +/* + * This 'test' includes the complete legacy 'office' data import. + * + * There is no code in 'main' because the import is not needed a normal runtime. + * There is some test data in Java resources to verify the data conversion. + * For a real import a main method will be added later + * which reads CSV files from the file system. + * + * When run on a Hostsharing database, it needs the following settings (hsh99_... just examples). + * + * In a real Hostsharing environment, these are created via (the old) hsadmin: + + CREATE USER hsh99_admin WITH PASSWORD 'password'; + CREATE DATABASE hsh99_hsadminng ENCODING 'UTF8' TEMPLATE template0; + REVOKE ALL ON DATABASE hsh99_hsadminng FROM public; -- why does hsadmin do that? + ALTER DATABASE hsh99_hsadminng OWNER TO hsh99_admin; + + CREATE USER hsh99_restricted WITH PASSWORD 'password'; + + \c hsh99_hsadminng + + GRANT ALL PRIVILEGES ON SCHEMA public to hsh99_admin; + + * Additionally, we need these settings (because the Hostsharing DB-Admin has no CREATE right): + + CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + + -- maybe something like that is needed for the 2nd user + -- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public to hsh99_restricted; + + * Then copy this to a file named .environment (excluded from git) and fill in your specific values: + + export HSADMINNG_POSTGRES_JDBC_URL=jdbc:postgresql://localhost:6432/hsh99_hsadminng + export HSADMINNG_POSTGRES_ADMIN_USERNAME=hsh99_admin + export HSADMINNG_POSTGRES_ADMIN_PASSWORD=password + export HSADMINNG_POSTGRES_RESTRICTED_USERNAME=hsh99_restricted + export HSADMINNG_SUPERUSER=some-precreated-superuser@example.org + + * To finally import the office data, run: + * + * import-office-tables # comes from .aliases file and uses .environment + */ +@Tag("import") +@DataJpaTest(properties = { + "spring.datasource.url=${HSADMINNG_POSTGRES_JDBC_URL:jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers}", + "spring.datasource.username=${HSADMINNG_POSTGRES_ADMIN_USERNAME:admin}", + "spring.datasource.password=${HSADMINNG_POSTGRES_ADMIN_PASSWORD:password}", + "hsadminng.superuser=${HSADMINNG_SUPERUSER:superuser-alex@hostsharing.net}" +}) +@DirtiesContext +@Import({ Context.class, JpaAttempt.class }) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +@ExtendWith(OrderedDependedTestsExtension.class) +public class ImportOfficeData extends ContextBasedTest { + + static int relationshipId = 2000000; + + @Value("${spring.datasource.url}") + private String jdbcUrl; + + @Value("${spring.datasource.username}") + private String postgresAdminUser; + + @Value("${hsadminng.superuser}") + private String rbacSuperuser; + + private static NavigableMap contacts = new TreeMap<>(); + private static NavigableMap persons = new TreeMap<>(); + private static NavigableMap partners = new TreeMap<>(); + private static NavigableMap debitors = new TreeMap<>(); + private static NavigableMap memberships = new TreeMap<>(); + + private static NavigableMap relationships = new TreeMap<>(); + private static NavigableMap sepaMandates = new TreeMap<>(); + private static NavigableMap bankAccounts = new TreeMap<>(); + private static NavigableMap coopShares = new TreeMap<>(); + private static NavigableMap coopAssets = new TreeMap<>(); + + @PersistenceContext + EntityManager em; + + @Autowired + TransactionTemplate txTemplate; + + @Autowired + JpaAttempt jpaAttempt; + + @MockBean + HttpServletRequest request; + + @Test + @Order(1010) + void importBusinessPartners() { + + try (Reader reader = resourceReader("migration/business-partners.csv")) { + final var lines = readAllLines(reader); + importBusinessPartners(justHeader(lines), withoutHeader(lines)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + @Order(1011) + void verifyBusinessPartners() { + assumeThat(postgresAdminUser).isEqualTo("admin"); + + // no contacts yet => mostly null values + assertThat(toFormattedString(partners)).isEqualToIgnoringWhitespace(""" + { + 17=partner(null null, null), + 20=partner(null null, null), + 22=partner(null null, null) + } + """); + assertThat(toFormattedString(contacts)).isEqualTo("{}"); + assertThat(toFormattedString(debitors)).isEqualToIgnoringWhitespace(""" + { + 17=debitor(1001700: null null, null: mih), + 20=debitor(1002000: null null, null: xyz), + 22=debitor(1102200: null null, null: xxx)} + """); + assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace(""" + { + 17=Membership(10017, null null, null, 1001700, [2000-12-06,), NONE), + 20=Membership(10020, null null, null, 1002000, [2000-12-06,2016-01-01), UNKNOWN), + 22=Membership(11022, null null, null, 1102200, [2021-04-01,), NONE) + } + """); + } + + @Test + @Order(1020) + void importContacts() { + + try (Reader reader = resourceReader("migration/contacts.csv")) { + final var lines = readAllLines(reader); + importContacts(justHeader(lines), withoutHeader(lines)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + @Order(1021) + void verifyContacts() { + assumeThat(postgresAdminUser).isEqualTo("admin"); + + assertThat(toFormattedString(partners)).isEqualToIgnoringWhitespace(""" + { + 17=partner(NATURAL Mellies, Michael: Herr Michael Mellies ), + 20=partner(LEGAL JM GmbH: Herr Philip Meyer-Contract , JM GmbH), + 22=partner(LEGAL Test PS: Petra Schmidt , Test PS) + } + """); + assertThat(toFormattedString(contacts)).isEqualToIgnoringWhitespace(""" + { + 1101=contact(label='Herr Michael Mellies ', emailAddresses='mih@example.org'), + 1200=contact(label='JM e.K.', emailAddresses='jm-ex-partner@example.org'), + 1201=contact(label='Frau Dr. Jenny Meyer-Billing , JM GmbH', emailAddresses='jm-billing@example.org'), + 1202=contact(label='Herr Andrew Meyer-Operation , JM GmbH', emailAddresses='am-operation@example.org'), + 1203=contact(label='Herr Philip Meyer-Contract , JM GmbH', emailAddresses='pm-partner@example.org'), + 1301=contact(label='Petra Schmidt , Test PS', emailAddresses='ps@example.com') + } + """); + assertThat(toFormattedString(persons)).isEqualToIgnoringWhitespace(""" + { + 1101=person(personType='NATURAL', tradeName='', familyName='Mellies', givenName='Michael'), + 1200=person(personType='LEGAL', tradeName='JM e.K.', familyName='', givenName=''), + 1201=person(personType='LEGAL', tradeName='JM GmbH', familyName='Meyer-Billing', givenName='Jenny'), + 1202=person(personType='LEGAL', tradeName='JM GmbH', familyName='Meyer-Operation', givenName='Andrew'), + 1203=person(personType='LEGAL', tradeName='JM GmbH', familyName='Meyer-Contract', givenName='Philip'), + 1301=person(personType='LEGAL', tradeName='Test PS', familyName='Schmidt', givenName='Petra') + } + """); + assertThat(toFormattedString(debitors)).isEqualToIgnoringWhitespace(""" + { + 17=debitor(1001700: NATURAL Mellies, Michael: mih), + 20=debitor(1002000: LEGAL JM GmbH: xyz), + 22=debitor(1102200: LEGAL Test PS: xxx) + } + """); + assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace(""" + { + 17=Membership(10017, NATURAL Mellies, Michael, 1001700, [2000-12-06,), NONE), + 20=Membership(10020, LEGAL JM GmbH, 1002000, [2000-12-06,2016-01-01), UNKNOWN), + 22=Membership(11022, LEGAL Test PS, 1102200, [2021-04-01,), NONE) + } + """); + assertThat(toFormattedString(relationships)).isEqualToIgnoringWhitespace(""" + { + 2000000=rel(relAnchor='NATURAL Mellies, Michael', relType='OPERATIONS', relHolder='NATURAL Mellies, Michael', contact='Herr Michael Mellies '), + 2000001=rel(relAnchor='LEGAL JM GmbH', relType='EX_PARTNER', relHolder='LEGAL JM e.K.', contact='JM e.K.'), + 2000002=rel(relAnchor='LEGAL JM GmbH', relType='OPERATIONS', relHolder='LEGAL JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), + 2000003=rel(relAnchor='LEGAL JM GmbH', relType='VIP_CONTACT', relHolder='LEGAL JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), + 2000004=rel(relAnchor='LEGAL JM GmbH', relType='REPRESENTATIVE', relHolder='LEGAL JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), + 2000005=rel(relAnchor='LEGAL Test PS', relType='OPERATIONS', relHolder='LEGAL Test PS', contact='Petra Schmidt , Test PS'), + 2000006=rel(relAnchor='LEGAL Test PS', relType='REPRESENTATIVE', relHolder='LEGAL Test PS', contact='Petra Schmidt , Test PS'), + 2000007=rel(relAnchor='NATURAL Mellies, Michael', relType='REPRESENTATIVE', relHolder='NATURAL Mellies, Michael', contact='Herr Michael Mellies ') + } + """); + } + + @Test + @Order(1030) + void importSepaMandates() { + + try (Reader reader = resourceReader("migration/sepa-mandates.csv")) { + final var lines = readAllLines(reader); + importSepaMandates(justHeader(lines), withoutHeader(lines)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + @Order(1031) + void verifySepaMandates() { + assumeThat(postgresAdminUser).isEqualTo("admin"); + + assertThat(toFormattedString(bankAccounts)).isEqualToIgnoringWhitespace(""" + { + 234234=bankAccount(holder='Michael Mellies', iban='DE37500105177419788228', bic='INGDDEFFXXX'), + 235600=bankAccount(holder='JM e.K.', iban='DE02300209000106531065', bic='CMCIDEDD'), + 235662=bankAccount(holder='JM GmbH', iban='DE49500105174516484892', bic='INGDDEFFXXX') + } + """); + assertThat(toFormattedString(sepaMandates)).isEqualToIgnoringWhitespace(""" + { + 234234=SEPA-Mandate(DE37500105177419788228, MH12345, 2004-06-12, [2004-06-15,)), + 235600=SEPA-Mandate(DE02300209000106531065, JM33344, 2004-01-15, [2004-01-20,2005-06-28)), + 235662=SEPA-Mandate(DE49500105174516484892, JM33344, 2005-06-28, [2005-07-01,)) + } + """); + } + + @Test + @Order(1040) + void importCoopShares() { + try (Reader reader = resourceReader("migration/share-transactions.csv")) { + final var lines = readAllLines(reader); + importCoopShares(justHeader(lines), withoutHeader(lines)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + @Order(1041) + void verifyCoopShares() { + assumeThat(postgresAdminUser).isEqualTo("admin"); + + assertThat(toFormattedString(coopShares)).isEqualToIgnoringWhitespace(""" + { + 33443=CoopShareTransaction(10017, 2000-12-06, SUBSCRIPTION, 20, initial share subscription), + 33451=CoopShareTransaction(10020, 2000-12-06, SUBSCRIPTION, 2, initial share subscription), + 33701=CoopShareTransaction(10017, 2005-01-10, SUBSCRIPTION, 40, increase), + 33810=CoopShareTransaction(10020, 2016-12-31, CANCELLATION, 22, membership ended) + } + """); + } + + @Test + @Order(1050) + void importCoopAssets() { + + try (Reader reader = resourceReader("migration/asset-transactions.csv")) { + final var lines = readAllLines(reader); + importCoopAssets(justHeader(lines), withoutHeader(lines)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + @Order(1051) + void verifyCoopAssets() { + assumeThat(postgresAdminUser).isEqualTo("admin"); + + assertThat(toFormattedString(coopAssets)).isEqualToIgnoringWhitespace(""" + { + 30000=CoopAssetsTransaction(10017, 2000-12-06, DEPOSIT, 1280.00, for subscription A), + 31000=CoopAssetsTransaction(10020, 2000-12-06, DEPOSIT, 128.00, for subscription B), + 32000=CoopAssetsTransaction(10017, 2005-01-10, DEPOSIT, 2560.00, for subscription C), + 33001=CoopAssetsTransaction(10017, 2005-01-10, TRANSFER, -512.00, for transfer to 10), + 33002=CoopAssetsTransaction(10020, 2005-01-10, ADOPTION, 512.00, for transfer from 7), + 34001=CoopAssetsTransaction(10020, 2016-12-31, CLEARING, -8.00, for cancellation D), + 34002=CoopAssetsTransaction(10020, 2016-12-31, DISBURSAL, -100.00, for cancellation D), + 34003=CoopAssetsTransaction(10020, 2016-12-31, LOSS, -20.00, for cancellation D) + } + """); + } + + @Test + @Order(2000) + @Commit + void persistEntities() { + + System.out.println("PERSISTING to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'"); + deleteTestDataFromHsOfficeTables(); + resetFromHsOfficeSequences(); + deleteFromTestTables(); + deleteFromRbacTables(); + + jpaAttempt.transacted(() -> { + context(rbacSuperuser); + contacts.forEach(this::persist); + updateLegacyIds(contacts, "hs_office_contact_legacy_id", "contact_id"); + }).assertSuccessful(); + + jpaAttempt.transacted(() -> { + context(rbacSuperuser); + persons.forEach(this::persist); + }).assertSuccessful(); + + jpaAttempt.transacted(() -> { + context(rbacSuperuser); + partners.forEach(this::persist); + updateLegacyIds(partners, "hs_office_partner_legacy_id", "bp_id"); + }).assertSuccessful(); + + jpaAttempt.transacted(() -> { + context(rbacSuperuser); + debitors.forEach(this::persist); + }).assertSuccessful(); + + jpaAttempt.transacted(() -> { + context(rbacSuperuser); + memberships.forEach(this::persist); + + }).assertSuccessful(); + + jpaAttempt.transacted(() -> { + context(rbacSuperuser); + relationships.forEach(this::persist); + + }).assertSuccessful(); + + jpaAttempt.transacted(() -> { + context(rbacSuperuser); + bankAccounts.forEach(this::persist); + + }).assertSuccessful(); + + jpaAttempt.transacted(() -> { + context(rbacSuperuser); + sepaMandates.forEach(this::persist); + updateLegacyIds(sepaMandates, "hs_office_sepamandate_legacy_id", "sepa_mandate_id"); + + }).assertSuccessful(); + + jpaAttempt.transacted(() -> { + context(rbacSuperuser); + coopShares.forEach(this::persist); + updateLegacyIds(coopShares, "hs_office_coopsharestransaction_legacy_id", "member_share_id"); + + }).assertSuccessful(); + + jpaAttempt.transacted(() -> { + context(rbacSuperuser); + coopAssets.forEach(this::persist); + updateLegacyIds(coopShares, "hs_office_coopassetstransaction_legacy_id", "member_asset_id"); + }).assertSuccessful(); + + } + + private void persist(final Integer id, final HasUuid entity) { + try { + System.out.println("persisting #" + entity.hashCode() + ": " + entity.toString()); + em.persist(entity); + em.flush(); + System.out.println("persisted #" + entity.hashCode() + " as " + entity.getUuid()); + } catch (Exception x) { + System.out.println("failed to persist: " + entity.toString()); + throw x; + } + + } + + private void deleteTestDataFromHsOfficeTables() { + jpaAttempt.transacted(() -> { + context(rbacSuperuser); + em.createNativeQuery("delete from hs_office_relationship where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_coopassetstransaction where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_coopassetstransaction_legacy_id where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_coopsharestransaction where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_coopsharestransaction_legacy_id where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_membership where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_sepamandate where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_sepamandate_legacy_id where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_debitor where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_bankaccount where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_partner where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_partner_details where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_contact where true").executeUpdate(); + em.createNativeQuery("delete from hs_office_person where true").executeUpdate(); + }).assertSuccessful(); + } + + private void resetFromHsOfficeSequences() { + jpaAttempt.transacted(() -> { + context(rbacSuperuser); + em.createNativeQuery("alter sequence hs_office_contact_legacy_id_seq restart with 1000000000;").executeUpdate(); + em.createNativeQuery("alter sequence hs_office_coopassetstransaction_legacy_id_seq restart with 1000000000;") + .executeUpdate(); + em.createNativeQuery("alter sequence public.hs_office_coopsharestransaction_legacy_id_seq restart with 1000000000;") + .executeUpdate(); + em.createNativeQuery("alter sequence public.hs_office_partner_legacy_id_seq restart with 1000000000;") + .executeUpdate(); + em.createNativeQuery("alter sequence public.hs_office_sepamandate_legacy_id_seq restart with 1000000000;") + .executeUpdate(); + }); + } + + private void deleteFromTestTables() { + jpaAttempt.transacted(() -> { + context(rbacSuperuser); + em.createNativeQuery("delete from test_domain where true").executeUpdate(); + em.createNativeQuery("delete from test_package where true").executeUpdate(); + em.createNativeQuery("delete from test_customer where true").executeUpdate(); + }).assertSuccessful(); + } + + private void deleteFromRbacTables() { + jpaAttempt.transacted(() -> { + context(rbacSuperuser); + em.createNativeQuery("delete from rbacuser_rv where name not like 'superuser-%'").executeUpdate(); + em.createNativeQuery("delete from tx_journal where true").executeUpdate(); + em.createNativeQuery("delete from tx_context where true").executeUpdate(); + }).assertSuccessful(); + } + + private void updateLegacyIds( + Map entities, + final String legacyIdTable, + final String legacyIdColumn) { + entities.forEach((id, entity) -> em.createNativeQuery(""" + UPDATE ${legacyIdTable} + SET ${legacyIdColumn} = :legacyId + WHERE uuid = :uuid + """ + .replace("${legacyIdTable}", legacyIdTable) + .replace("${legacyIdColumn}", legacyIdColumn)) + .setParameter("legacyId", id) + .setParameter("uuid", entity.getUuid()) + .executeUpdate() + ); + } + + public List readAllLines(Reader reader) throws Exception { + + final var parser = new CSVParserBuilder() + .withSeparator(';') + .withQuoteChar('"') + .build(); + + final var filteredReader = skippingEmptyAndCommentLines(reader); + try (CSVReader csvReader = new CSVReaderBuilder(filteredReader) + .withCSVParser(parser) + .build()) { + return csvReader.readAll(); + } + } + + public static Reader skippingEmptyAndCommentLines(Reader reader) throws IOException { + try (var bufferedReader = new BufferedReader(reader); + StringWriter writer = new StringWriter()) { + + String line; + while ((line = bufferedReader.readLine()) != null) { + if (!line.isBlank() && !line.startsWith("#")) { + writer.write(line); + writer.write("\n"); + } + } + + return new StringReader(writer.toString()); + } + } + + private void importBusinessPartners(final String[] header, final List records) { + + final var columns = new Columns(header); + + records.stream() + .map(this::trimAll) + .map(row -> new Record(columns, row)) + .forEach(rec -> { + final var person = HsOfficePersonEntity.builder().build(); + + final var partner = HsOfficePartnerEntity.builder() + .debitorNumberPrefix(rec.getInteger("member_id")) + .details(HsOfficePartnerDetailsEntity.builder().build()) + .contact(null) // is set during contacts import depending on assigned roles + .person(person) + .build(); + partners.put(rec.getInteger("bp_id"), partner); + + final var debitor = HsOfficeDebitorEntity.builder() + .partner(partner) + .debitorNumberSuffix((byte) 0) + .defaultPrefix(rec.getString("member_code").replace("hsh00-", "")) + .partner(partner) + .billable(rec.isEmpty("free")) + .vatReverseCharge(rec.getBoolean("exempt_vat")) + .vatBusiness("GROSS".equals(rec.getString("indicator_vat"))) // TODO: remove + .vatId(rec.getString("uid_vat")) + .build(); + debitors.put(rec.getInteger("bp_id"), debitor); + + partners.put(rec.getInteger("bp_id"), partner); + + if (isNotBlank(rec.getString("member_since"))) { + final var membership = HsOfficeMembershipEntity.builder() + .partner(partner) + .memberNumber(rec.getInteger("member_id")) + .validity(toPostgresDateRange( + rec.getLocalDate("member_since"), + rec.getLocalDate("member_until"))) + .membershipFeeBillable(rec.isEmpty("member_role")) + .reasonForTermination( + isBlank(rec.getString("member_until")) + ? HsOfficeReasonForTermination.NONE + : HsOfficeReasonForTermination.UNKNOWN) + .mainDebitor(debitor) + .build(); + memberships.put(rec.getInteger("bp_id"), membership); + } + }); + } + + private void importCoopShares(final String[] header, final List records) { + + final var columns = new Columns(header); + + records.stream() + .map(this::trimAll) + .map(row -> new Record(columns, row)) + .forEach(rec -> { + final var member = memberships.get(rec.getInteger("bp_id")); + + final var shareTransaction = HsOfficeCoopSharesTransactionEntity.builder() + .membership(member) + .valueDate(rec.getLocalDate("date")) + .transactionType( + "SUBSCRIPTION".equals(rec.getString("action")) + ? HsOfficeCoopSharesTransactionType.SUBSCRIPTION + : "UNSUBSCRIPTION".equals(rec.getString("action")) + ? HsOfficeCoopSharesTransactionType.CANCELLATION + : HsOfficeCoopSharesTransactionType.ADJUSTMENT + ) + .shareCount(rec.getInteger("quantity")) + .comment(rec.getString("comment")) + .build(); + + coopShares.put(rec.getInteger("member_share_id"), shareTransaction); + }); + } + + private void importCoopAssets(final String[] header, final List records) { + + final var columns = new Columns(header); + + records.stream() + .map(this::trimAll) + .map(row -> new Record(columns, row)) + .forEach(rec -> { + final var member = memberships.get(rec.getInteger("bp_id")); + + final var assetTypeMapping = new HashMap() { + + { + put("HANDOVER", HsOfficeCoopAssetsTransactionType.TRANSFER); + put("ADOPTION", HsOfficeCoopAssetsTransactionType.ADOPTION); + put("LOSS", HsOfficeCoopAssetsTransactionType.LOSS); + put("CLEARING", HsOfficeCoopAssetsTransactionType.CLEARING); + put("PRESCRIPTION", HsOfficeCoopAssetsTransactionType.LIMITATION); + put("PAYBACK", HsOfficeCoopAssetsTransactionType.DISBURSAL); + put("PAYMENT", HsOfficeCoopAssetsTransactionType.DEPOSIT); + } + + public HsOfficeCoopAssetsTransactionType get(final String key) { + final var value = super.get(key); + if (value != null) { + return value; + } + throw new IllegalStateException("no mapping value found for: " + key); + } + }; + + final var assetTransaction = HsOfficeCoopAssetsTransactionEntity.builder() + .membership(member) + .valueDate(rec.getLocalDate("date")) + .transactionType(assetTypeMapping.get(rec.getString("action"))) + .assetValue(rec.getBigDecimal("amount")) + .comment(rec.getString("comment")) + .build(); + + coopAssets.put(rec.getInteger("member_asset_id"), assetTransaction); + }); + } + + private void importSepaMandates(final String[] header, final List records) { + + final var columns = new Columns(header); + + records.stream() + .map(this::trimAll) + .map(row -> new Record(columns, row)) + .forEach(rec -> { + final var debitor = debitors.get(rec.getInteger("bp_id")); + + final var sepaMandate = HsOfficeSepaMandateEntity.builder() + .debitor(debitor) + .bankAccount(HsOfficeBankAccountEntity.builder() + .holder(rec.getString("bank_customer")) + // .bankName(rec.get("bank_name")) // not supported + .iban(rec.getString("bank_iban")) + .bic(rec.getString("bank_bic")) + .build()) + .reference(rec.getString("mandat_ref")) + .agreement(LocalDate.parse(rec.getString("mandat_signed"))) + .validity(toPostgresDateRange( + rec.getLocalDate("mandat_since"), + rec.getLocalDate("mandat_until"))) + .build(); + + sepaMandates.put(rec.getInteger("sepa_mandat_id"), sepaMandate); + bankAccounts.put(rec.getInteger("sepa_mandat_id"), sepaMandate.getBankAccount()); + }); + } + + private void importContacts(final String[] header, final List records) { + + final var columns = new Columns(header); + + records.stream() + .map(this::trimAll) + .map(row -> new Record(columns, row)) + .forEach(rec -> { + final var contactId = rec.getInteger("contact_id"); + + if (rec.getString("roles").isBlank()) { + fail("empty roles assignment not allowed for contact_id: " + contactId); + } + + final var partner = partners.get(rec.getInteger("bp_id")); + final var debitor = debitors.get(rec.getInteger("bp_id")); + + final var partnerPerson = partner.getPerson(); + if (containsRole(rec)) { + initPerson(partner.getPerson(), rec); + } + + HsOfficePersonEntity contactPerson = partnerPerson; + if (!StringUtils.equals(rec.getString("firma"), partnerPerson.getTradeName()) || + !StringUtils.equals(rec.getString("first_name"), partnerPerson.getGivenName()) || + !StringUtils.equals(rec.getString("last_name"), partnerPerson.getFamilyName())) { + contactPerson = initPerson(HsOfficePersonEntity.builder().build(), rec); + } + + final var contact = HsOfficeContactEntity.builder().build(); + initContact(contact, rec); + + if (containsRole(rec, "partner")) { + assertThat(partner.getContact()).isNull(); + partner.setContact(contact); + } + if (containsRole(rec, "billing")) { + assertThat(debitor.getBillingContact()).isNull(); + debitor.setBillingContact(contact); + } + if (containsRole(rec, "operation")) { + addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.OPERATIONS); + } + if (containsRole(rec, "contractual")) { + addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.REPRESENTATIVE); + } + if (containsRole(rec, "ex-partner")) { + addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.EX_PARTNER); + } + if (containsRole(rec, "vip-contact")) { + addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.VIP_CONTACT); + } + verifyContainsOnly(rec.getString("roles"), "partner", "vip-contact", "ex-partner", "billing", "contractual", "operation"); + }); + + optionallyAddMissingContractualRelationships(); + } + + private static void optionallyAddMissingContractualRelationships() { + partners.forEach( (id, partner) -> { + final var partnerPerson = partner.getPerson(); + if (relationships.values().stream().filter(rel -> rel.getRelHolder() == partnerPerson && rel.getRelType() == HsOfficeRelationshipType.REPRESENTATIVE).findFirst().isEmpty()) { + addRelationship(partnerPerson, partnerPerson, partner.getContact(), HsOfficeRelationshipType.REPRESENTATIVE); + } + }); + } + + private static boolean containsRole(final Record rec, final String role) { + final var roles = rec.getString("roles"); + return ("," + roles + ",").contains("," + role + ","); + } + + private static boolean containsRole(final Record rec) { + return containsRole(rec, "partner"); + } + + private static void addRelationship( + final HsOfficePersonEntity partnerPerson, + final HsOfficePersonEntity contactPerson, + final HsOfficeContactEntity contact, + final HsOfficeRelationshipType representative) { + final var rel = HsOfficeRelationshipEntity.builder() + .relAnchor(partnerPerson) + .relHolder(contactPerson) + .contact(contact) + .relType(representative) + .build(); + relationships.put(relationshipId++, rel); + } + + private HsOfficePersonEntity initPerson(final HsOfficePersonEntity person, final Record contactRecord) { + // TODO: title+salutation: add to person + person.setGivenName(contactRecord.getString("first_name")); + person.setFamilyName(contactRecord.getString("last_name")); + person.setTradeName(contactRecord.getString("firma")); + determinePersonType(person, contactRecord.getString("roles")); + + persons.put(contactRecord.getInteger("contact_id"), person); + return person; + } + + private static void determinePersonType(final HsOfficePersonEntity person, final String roles) { + if (person.getTradeName().isBlank()) { + person.setPersonType(HsOfficePersonType.NATURAL); + } else if (roles.contains("partner")) { + person.setPersonType(HsOfficePersonType.LEGAL); + } else if (roles.contains("contractual") && + !person.getFamilyName().isBlank() && !person.getGivenName().isBlank()) { + person.setPersonType(HsOfficePersonType.NATURAL); + } else if ( endsWithWord(person.getTradeName(), "e.K.", "e.G.", "eG", "GmbH", "AG") ) { + person.setPersonType(HsOfficePersonType.LEGAL); + } else { + // TODO: detect the other person types as soon as we've switche to the new person types + person.setPersonType(HsOfficePersonType.UNKNOWN); + } + } + + private static boolean endsWithWord(final String value, final String... endings) { + final var lowerCaseValue = value.toLowerCase(); + for( String ending: endings ) { + if (lowerCaseValue.endsWith(" " + ending.toLowerCase())) { + return true; + } + } + return false; + } + + private void verifyContainsOnly(final String roles, final String... allowedRoles) { + final var givenRolesSet = stream(roles.replace(" ", "").split(",")).collect(Collectors.toSet()); + final var allowedRolesSet = stream(allowedRoles).collect(Collectors.toSet()); + final var unexpectedRolesSet = new HashSet<>(givenRolesSet); + unexpectedRolesSet.removeAll(allowedRolesSet); + assertThat(unexpectedRolesSet).isEmpty(); + } + + private HsOfficeContactEntity initContact(final HsOfficeContactEntity contact, final Record contactRecord) { + + contact.setLabel(toLabel( + contactRecord.getString("salut"), + contactRecord.getString("title"), + contactRecord.getString("first_name"), + contactRecord.getString("last_name"), + contactRecord.getString("firma"))); + contact.setEmailAddresses(contactRecord.getString("email")); + contact.setPostalAddress(toAddress(contactRecord)); + contact.setPhoneNumbers(toPhoneNumbers(contactRecord)); + + contacts.put(contactRecord.getInteger("contact_id"), contact); + return contact; + } + + private String toFormattedString(final Map map) { + if ( map.isEmpty() ) { + return "{}"; + } + return "{\n" + + map.keySet().stream() + .map(id -> " " + id + "=" + map.get(id).toString()) + .collect(Collectors.joining(",\n")) + + "\n}\n"; + } + + private String[] trimAll(final String[] record) { + for (int i = 0; i < record.length; ++i) { + if (record[i] != null) { + record[i] = record[i].trim(); + } + } + return record; + } + + private String toPhoneNumbers(final Record rec) { + final var result = new StringBuilder("{\n"); + if (isNotBlank(rec.getString("phone_private"))) + result.append(" \"private\": " + "\"" + rec.getString("phone_private") + "\",\n"); + if (isNotBlank(rec.getString("phone_office"))) + result.append(" \"office\": " + "\"" + rec.getString("phone_office") + "\",\n"); + if (isNotBlank(rec.getString("phone_mobile"))) + result.append(" \"mobile\": " + "\"" + rec.getString("phone_mobile") + "\",\n"); + if (isNotBlank(rec.getString("fax"))) + result.append(" \"fax\": " + "\"" + rec.getString("fax") + "\",\n"); + return (result + "}").replace("\",\n}", "\"\n}"); + } + + private String toAddress(final Record rec) { + final var result = new StringBuilder(); + final var name = toName( + rec.getString("salut"), + rec.getString("title"), + rec.getString("first_name"), + rec.getString("last_name")); + if (isNotBlank(name)) + result.append(name + "\n"); + if (isNotBlank(rec.getString("firma"))) + result.append(rec.getString("firma") + "\n"); + if (isNotBlank(rec.getString("co"))) + result.append("c/o " + rec.getString("co") + "\n"); + if (isNotBlank(rec.getString("street"))) + result.append(rec.getString("street") + "\n"); + final var zipcodeAndCity = toZipcodeAndCity(rec); + if (isNotBlank(zipcodeAndCity)) + result.append(zipcodeAndCity + "\n"); + return result.toString(); + } + + private String toZipcodeAndCity(final Record rec) { + final var result = new StringBuilder(); + if (isNotBlank(rec.getString("country"))) + result.append(rec.getString("country") + " "); + if (isNotBlank(rec.getString("zipcode"))) + result.append(rec.getString("zipcode") + " "); + if (isNotBlank(rec.getString("city"))) + result.append(rec.getString("city")); + return result.toString(); + } + + private String toLabel( + final String salut, + final String title, + final String firstname, + final String lastname, + final String firm) { + final var result = new StringBuilder(); + if (isNotBlank(salut)) + result.append(salut + " "); + if (isNotBlank(title)) + result.append(title + " "); + if (isNotBlank(firstname)) + result.append(firstname + " "); + if (isNotBlank(lastname)) + result.append(lastname + " "); + if (isNotBlank(firm)) { + result.append( (isBlank(result) ? "" : ", ") + firm); + } + return result.toString(); + } + + private String toName(final String salut, final String title, final String firstname, final String lastname) { + return toLabel(salut, title, firstname, lastname, null); + } + + private Reader resourceReader(@NotNull final String resourcePath) { + return new InputStreamReader(requireNonNull(getClass().getClassLoader().getResourceAsStream(resourcePath))); + } + + private Reader fileReader(@NotNull final Path filePath) throws IOException { + // Path path = Paths.get( + // ClassLoader.getSystemResource("csv/twoColumn.csv").toURI()) + // ); + return Files.newBufferedReader(filePath); + } + + private static String[] justHeader(final List lines) { + return stream(lines.getFirst()).map(String::trim).toArray(String[]::new); + } + + private List withoutHeader(final List records) { + return records.subList(1, records.size()); + } + +} + +class Columns { + + private final List columnNames; + + public Columns(final String[] header) { + columnNames = List.of(header); + } + + int indexOf(final String columnName) { + int index = columnNames.indexOf(columnName); + if (index < 0) { + throw new RuntimeException("column name '" + columnName + "' not found in: " + columnNames); + } + return index; + } +} + +class Record { + + private final Columns columns; + private final String[] row; + + public Record(final Columns columns, final String[] row) { + this.columns = columns; + this.row = row; + } + + String getString(final String columnName) { + return row[columns.indexOf(columnName)]; + } + + boolean isEmpty(final String columnName) { + final String value = getString(columnName); + return value == null || value.isBlank(); + } + + Byte getByte(final String columnName) { + final String value = getString(columnName); + return isNotBlank(value) ? Byte.valueOf(value.trim()) : 0; + } + + boolean getBoolean(final String columnName) { + final String value = getString(columnName); + return isNotBlank(value) && Boolean.parseBoolean(value.trim()); + } + + Integer getInteger(final String columnName) { + final String value = getString(columnName); + return isNotBlank(value) ? Integer.parseInt(value.trim()) : 0; + } + + BigDecimal getBigDecimal(final String columnName) { + final String value = getString(columnName); + if (isNotBlank(value)) { + return new BigDecimal(value); + } + return null; + } + + LocalDate getLocalDate(final String columnName) { + final String dateString = getString(columnName); + if (isNotBlank(dateString)) { + return LocalDate.parse(dateString); + } + return null; + } +} + +class OrderedDependedTestsExtension implements TestWatcher, BeforeEachCallback { + + private static boolean previousTestsPassed = true; + + public void testFailed(ExtensionContext context, Throwable cause) { + previousTestsPassed = false; + } + + @Override + public void beforeEach(final ExtensionContext extensionContext) { + if (!previousTestsPassed) { + System.err.println("ignoring because previous fest has failed"); + } + assumeThat(previousTestsPassed).isTrue(); + } +} diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerAcceptanceTest.java index 053b03e1..359c078d 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerAcceptanceTest.java @@ -125,6 +125,7 @@ class HsOfficePartnerControllerAcceptanceTest { .contentType(ContentType.JSON) .body(""" { + "debitorNumberPrefix": "12345", "contactUuid": "%s", "personUuid": "%s", "details": { @@ -166,6 +167,7 @@ class HsOfficePartnerControllerAcceptanceTest { .contentType(ContentType.JSON) .body(""" { + "debitorNumberPrefix": "12345", "contactUuid": "%s", "personUuid": "%s", "details": {} @@ -193,6 +195,7 @@ class HsOfficePartnerControllerAcceptanceTest { .contentType(ContentType.JSON) .body(""" { + "debitorNumberPrefix": "12345", "contactUuid": "%s", "personUuid": "%s", "details": {} @@ -294,6 +297,7 @@ class HsOfficePartnerControllerAcceptanceTest { .contentType(ContentType.JSON) .body(""" { + "debitorNumberPrefix": "12345", "contactUuid": "%s", "personUuid": "%s", "details": { diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntityPatcherUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntityPatcherUnitTest.java index 6dc2b66e..4f55d90b 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntityPatcherUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntityPatcherUnitTest.java @@ -28,8 +28,9 @@ class HsOfficePartnerDetailsEntityPatcherUnitTest extends PatchUnitTestBase< > { private static final UUID INITIAL_PARTNER_UUID = UUID.randomUUID(); - private static final UUID INITIAL_CONTACT_UUID = UUID.randomUUID(); - private static final UUID INITIAL_PERSON_UUID = UUID.randomUUID(); + + private static final String INITIAL_BIRTHPLACE = null; + private static final String PATCHED_BIRTHPLACE = "Essen (Ruhr)"; private static final LocalDate INITIAL_BIRTHDAY = LocalDate.parse("1900-01-01"); private static final LocalDate PATCHED_BIRTHDAY = LocalDate.parse("1990-12-31"); @@ -54,6 +55,7 @@ class HsOfficePartnerDetailsEntityPatcherUnitTest extends PatchUnitTestBase< entity.setUuid(INITIAL_PARTNER_UUID); entity.setRegistrationOffice("initial Reg-Office"); entity.setRegistrationNumber("initial Reg-Number"); + entity.setBirthPlace(INITIAL_BIRTHPLACE); entity.setBirthday(INITIAL_BIRTHDAY); entity.setBirthName("initial birth name"); entity.setDateOfDeath(INITIAL_DAY_OF_DEATH); @@ -78,6 +80,16 @@ class HsOfficePartnerDetailsEntityPatcherUnitTest extends PatchUnitTestBase< HsOfficePartnerDetailsPatchResource::setRegistrationOffice, "patched Reg-Office", HsOfficePartnerDetailsEntity::setRegistrationOffice), + new JsonNullableProperty<>( + "birthplace", + HsOfficePartnerDetailsPatchResource::setBirthPlace, + PATCHED_BIRTHPLACE, + HsOfficePartnerDetailsEntity::setBirthPlace), + new JsonNullableProperty<>( + "birthname", + HsOfficePartnerDetailsPatchResource::setBirthName, + "patched birth name", + HsOfficePartnerDetailsEntity::setBirthName), new JsonNullableProperty<>( "birthday", HsOfficePartnerDetailsPatchResource::setBirthday, diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntityUnitTest.java index 24228031..5a926f0c 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntityUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntityUnitTest.java @@ -21,7 +21,8 @@ class HsOfficePartnerDetailsEntityUnitTest { final var result = given.toString(); - assertThat(result).isEqualTo("partnerDetails(Hamburg, 12345, 2002-01-15, 2002-01-15, 2081-12-21)"); + assertThat(result).isEqualTo( + "partnerDetails(Hamburg, 12345, 2002-01-15, Melly Miller, 2081-12-21)"); } @Test diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntityUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntityUnitTest.java index a0ad4c46..87aeea12 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntityUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntityUnitTest.java @@ -2,6 +2,7 @@ package net.hostsharing.hsadminng.hs.office.partner; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; +import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; @@ -11,24 +12,30 @@ class HsOfficePartnerEntityUnitTest { @Test void toStringContainsPersonAndContact() { final var given = HsOfficePartnerEntity.builder() - .person(HsOfficePersonEntity.builder().tradeName("some trade name").build()) + .person(HsOfficePersonEntity.builder() + .personType(HsOfficePersonType.LEGAL) + .tradeName("some trade name") + .build()) .contact(HsOfficeContactEntity.builder().label("some label").build()) .build(); final var result = given.toString(); - assertThat(result).isEqualTo("partner(some trade name: some label)"); + assertThat(result).isEqualTo("partner(LEGAL some trade name: some label)"); } @Test void toShortStringContainsPersonAndContact() { final var given = HsOfficePartnerEntity.builder() - .person(HsOfficePersonEntity.builder().tradeName("some trade name").build()) + .person(HsOfficePersonEntity.builder() + .personType(HsOfficePersonType.LEGAL) + .tradeName("some trade name") + .build()) .contact(HsOfficeContactEntity.builder().label("some label").build()) .build(); final var result = given.toShortString(); - assertThat(result).isEqualTo("some trade name"); + assertThat(result).isEqualTo("LEGAL some trade name"); } } diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java index e03d8cbe..ad41e939 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java @@ -105,6 +105,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { final var givenPerson = personRepo.findPersonByOptionalNameLike("Erben Bessler").get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike("forth contact").get(0); final var newPartner = toCleanup(HsOfficePartnerEntity.builder() + .debitorNumberPrefix(22222) .person(givenPerson) .contact(givenContact) .details(HsOfficePartnerDetailsEntity.builder().build()) @@ -115,11 +116,11 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { // then assertThat(roleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from( initialRoleNames, - "hs_office_partner#ErbenBesslerMelBessler-forthcontact.admin", - "hs_office_partner#ErbenBesslerMelBessler-forthcontact.agent", - "hs_office_partner#ErbenBesslerMelBessler-forthcontact.owner", - "hs_office_partner#ErbenBesslerMelBessler-forthcontact.tenant", - "hs_office_partner#ErbenBesslerMelBessler-forthcontact.guest")); + "hs_office_partner#22222:ErbenBesslerMelBessler-forthcontact.admin", + "hs_office_partner#22222:ErbenBesslerMelBessler-forthcontact.agent", + "hs_office_partner#22222:ErbenBesslerMelBessler-forthcontact.owner", + "hs_office_partner#22222:ErbenBesslerMelBessler-forthcontact.tenant", + "hs_office_partner#22222:ErbenBesslerMelBessler-forthcontact.guest")); assertThat(grantDisplaysOf(rawGrantRepo.findAll())) .map(s -> s.replace("ErbenBesslerMelBessler", "EBess")) .map(s -> s.replace("forthcontact", "4th")) @@ -127,31 +128,31 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { .containsExactlyInAnyOrder(Array.fromFormatted( initialGrantNames, // owner - "{ grant perm * on partner#EBess-4th to role partner#EBess-4th.owner by system and assume }", - "{ grant perm * on partner_details#EBess-4th-details to role partner#EBess-4th.owner by system and assume }", - "{ grant role partner#EBess-4th.owner to role global#global.admin by system and assume }", + "{ grant perm * on partner#22222:EBess-4th to role partner#22222:EBess-4th.owner by system and assume }", + "{ grant perm * on partner_details#22222:EBess-4th-details to role partner#22222:EBess-4th.owner by system and assume }", + "{ grant role partner#22222:EBess-4th.owner to role global#global.admin by system and assume }", // admin - "{ grant perm edit on partner#EBess-4th to role partner#EBess-4th.admin by system and assume }", - "{ grant perm edit on partner_details#EBess-4th-details to role partner#EBess-4th.admin by system and assume }", - "{ grant role partner#EBess-4th.admin to role partner#EBess-4th.owner by system and assume }", - "{ grant role person#EBess.tenant to role partner#EBess-4th.admin by system and assume }", - "{ grant role contact#4th.tenant to role partner#EBess-4th.admin by system and assume }", + "{ grant perm edit on partner#22222:EBess-4th to role partner#22222:EBess-4th.admin by system and assume }", + "{ grant perm edit on partner_details#22222:EBess-4th-details to role partner#22222:EBess-4th.admin by system and assume }", + "{ grant role partner#22222:EBess-4th.admin to role partner#22222:EBess-4th.owner by system and assume }", + "{ grant role person#EBess.tenant to role partner#22222:EBess-4th.admin by system and assume }", + "{ grant role contact#4th.tenant to role partner#22222:EBess-4th.admin by system and assume }", // agent - "{ grant perm view on partner_details#EBess-4th-details to role partner#EBess-4th.agent by system and assume }", - "{ grant role partner#EBess-4th.agent to role partner#EBess-4th.admin by system and assume }", - "{ grant role partner#EBess-4th.agent to role person#EBess.admin by system and assume }", - "{ grant role partner#EBess-4th.agent to role contact#4th.admin by system and assume }", + "{ grant perm view on partner_details#22222:EBess-4th-details to role partner#22222:EBess-4th.agent by system and assume }", + "{ grant role partner#22222:EBess-4th.agent to role partner#22222:EBess-4th.admin by system and assume }", + "{ grant role partner#22222:EBess-4th.agent to role person#EBess.admin by system and assume }", + "{ grant role partner#22222:EBess-4th.agent to role contact#4th.admin by system and assume }", // tenant - "{ grant role partner#EBess-4th.tenant to role partner#EBess-4th.agent by system and assume }", - "{ grant role person#EBess.guest to role partner#EBess-4th.tenant by system and assume }", - "{ grant role contact#4th.guest to role partner#EBess-4th.tenant by system and assume }", + "{ grant role partner#22222:EBess-4th.tenant to role partner#22222:EBess-4th.agent by system and assume }", + "{ grant role person#EBess.guest to role partner#22222:EBess-4th.tenant by system and assume }", + "{ grant role contact#4th.guest to role partner#22222:EBess-4th.tenant by system and assume }", // guest - "{ grant perm view on partner#EBess-4th to role partner#EBess-4th.guest by system and assume }", - "{ grant role partner#EBess-4th.guest to role partner#EBess-4th.tenant by system and assume }", + "{ grant perm view on partner#22222:EBess-4th to role partner#22222:EBess-4th.guest by system and assume }", + "{ grant role partner#22222:EBess-4th.guest to role partner#22222:EBess-4th.tenant by system and assume }", null)); } @@ -176,9 +177,9 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { // then allThesePartnersAreReturned( result, - "partner(Third OHG: third contact)", - "partner(Second e.K.: second contact)", - "partner(First GmbH: first contact)"); + "partner(SOLE_REPRESENTATION Third OHG: third contact)", + "partner(LEGAL Second e.K.: second contact)", + "partner(LEGAL First GmbH: first contact)"); } @Test @@ -190,7 +191,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { final var result = partnerRepo.findPartnerByOptionalNameLike(null); // then: - exactlyThesePartnersAreReturned(result, "partner(First GmbH: first contact)"); + exactlyThesePartnersAreReturned(result, "partner(LEGAL First GmbH: first contact)"); } } @@ -206,7 +207,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { final var result = partnerRepo.findPartnerByOptionalNameLike("third contact"); // then - exactlyThesePartnersAreReturned(result, "partner(Third OHG: third contact)"); + exactlyThesePartnersAreReturned(result, "partner(SOLE_REPRESENTATION Third OHG: third contact)"); } } @@ -217,10 +218,10 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { public void hostsharingAdmin_withoutAssumedRole_canUpdateArbitraryPartner() { // given context("superuser-alex@hostsharing.net"); - final var givenPartner = givenSomeTemporaryPartnerBessler("fifth contact"); + final var givenPartner = givenSomeTemporaryPartnerBessler(22222, "Erben Bessler", "fifth contact"); assertThatPartnerIsVisibleForUserWithRole( givenPartner, - "hs_office_person#ErbenBesslerMelBessler.admin"); + "hs_office_partner#22222:ErbenBesslerMelBessler-fifthcontact.admin"); assertThatPartnerActuallyInDatabase(givenPartner); context("superuser-alex@hostsharing.net"); final var givenNewPerson = personRepo.findPersonByOptionalNameLike("Third OHG").get(0); @@ -253,16 +254,16 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { public void partnerAgent_canNotUpdateRelatedPartner() { // given context("superuser-alex@hostsharing.net"); - final var givenPartner = givenSomeTemporaryPartnerBessler("ninth"); + final var givenPartner = givenSomeTemporaryPartnerBessler(22222, "Erben Bessler", "ninth"); assertThatPartnerIsVisibleForUserWithRole( givenPartner, - "hs_office_partner#ErbenBesslerMelBessler-ninthcontact.agent"); + "hs_office_partner#22222:ErbenBesslerMelBessler-ninthcontact.agent"); assertThatPartnerActuallyInDatabase(givenPartner); - final var givenNewContact = contactRepo.findContactByOptionalLabelLike("tenth").get(0); // when final var result = jpaAttempt.transacted(() -> { - context("superuser-alex@hostsharing.net", "hs_office_partner#ErbenBesslerMelBessler-ninthcontact.agent"); + context("superuser-alex@hostsharing.net", + "hs_office_partner#22222:ErbenBesslerMelBessler-ninthcontact.agent"); givenPartner.getDetails().setBirthName("new birthname"); return partnerRepo.save(givenPartner); }); @@ -304,7 +305,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { public void globalAdmin_withoutAssumedRole_canDeleteAnyPartner() { // given context("superuser-alex@hostsharing.net", null); - final var givenPartner = givenSomeTemporaryPartnerBessler("tenth"); + final var givenPartner = givenSomeTemporaryPartnerBessler(22222, "Erben Bessler", "tenth"); // when final var result = jpaAttempt.transacted(() -> { @@ -324,7 +325,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { public void nonGlobalAdmin_canNotDeleteTheirRelatedPartner() { // given context("superuser-alex@hostsharing.net", null); - final var givenPartner = givenSomeTemporaryPartnerBessler("eleventh"); + final var givenPartner = givenSomeTemporaryPartnerBessler(22222, "Erben Bessler", "eleventh"); // when final var result = jpaAttempt.transacted(() -> { @@ -350,7 +351,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { context("superuser-alex@hostsharing.net"); final var initialRoleNames = Array.from(roleNamesOf(rawRoleRepo.findAll())); final var initialGrantNames = Array.from(grantDisplaysOf(rawGrantRepo.findAll())); - final var givenPartner = givenSomeTemporaryPartnerBessler("twelfth"); + final var givenPartner = givenSomeTemporaryPartnerBessler(22222, "Erben Bessler", "twelfth"); // when final var result = jpaAttempt.transacted(() -> { @@ -394,12 +395,14 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest { }); } - private HsOfficePartnerEntity givenSomeTemporaryPartnerBessler(final String contact) { + private HsOfficePartnerEntity givenSomeTemporaryPartnerBessler( + final Integer debitorNumberPrefix, final String person, final String contact) { return jpaAttempt.transacted(() -> { context("superuser-alex@hostsharing.net"); - final var givenPerson = personRepo.findPersonByOptionalNameLike("Erben Bessler").get(0); + final var givenPerson = personRepo.findPersonByOptionalNameLike(person).get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike(contact).get(0); final var newPartner = HsOfficePartnerEntity.builder() + .debitorNumberPrefix(debitorNumberPrefix) .person(givenPerson) .contact(givenContact) .details(HsOfficePartnerDetailsEntity.builder().build()) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/TestHsOfficePartner.java b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/TestHsOfficePartner.java index 19235167..8ac5ae85 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/partner/TestHsOfficePartner.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/partner/TestHsOfficePartner.java @@ -8,10 +8,11 @@ import static net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType.LEGA public class TestHsOfficePartner { - public static final HsOfficePartnerEntity TEST_PARTNER = HsOfficePartnerWithLegalPerson("Test Ltd."); + public static final HsOfficePartnerEntity TEST_PARTNER = hsOfficePartnerWithLegalPerson("Test Ltd."); - static public HsOfficePartnerEntity HsOfficePartnerWithLegalPerson(final String tradeName) { + static public HsOfficePartnerEntity hsOfficePartnerWithLegalPerson(final String tradeName) { return HsOfficePartnerEntity.builder() + .debitorNumberPrefix(10001) .person(HsOfficePersonEntity.builder() .personType(LEGAL) .tradeName(tradeName) 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 5b7ca93d..9502bfb3 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 @@ -11,29 +11,32 @@ class HsOfficePersonEntityUnitTest { @Test void getDisplayReturnsTradeNameIfAvailable() { final var givenPersonEntity = HsOfficePersonEntity.builder() + .personType(HsOfficePersonType.LEGAL) .tradeName("some trade name") .build(); final var actualDisplay = givenPersonEntity.toShortString(); - assertThat(actualDisplay).isEqualTo("some trade name"); + assertThat(actualDisplay).isEqualTo("LEGAL some trade name"); } @Test void getDisplayReturnsFamilyAndGivenNameIfNoTradeNameAvailable() { final var givenPersonEntity = HsOfficePersonEntity.builder() + .personType(HsOfficePersonType.NATURAL) .familyName("some family name") .givenName("some given name") .build(); final var actualDisplay = givenPersonEntity.toShortString(); - assertThat(actualDisplay).isEqualTo("some family name, some given name"); + assertThat(actualDisplay).isEqualTo("NATURAL some family name, some given name"); } @Test void toShortStringWithTradeNameReturnsTradeName() { final var givenPersonEntity = HsOfficePersonEntity.builder() + .personType(HsOfficePersonType.LEGAL) .tradeName("some trade name") .familyName("some family name") .givenName("some given name") @@ -41,19 +44,20 @@ class HsOfficePersonEntityUnitTest { final var actualDisplay = givenPersonEntity.toShortString(); - assertThat(actualDisplay).isEqualTo("some trade name"); + assertThat(actualDisplay).isEqualTo("LEGAL some trade name"); } @Test void toShortStringWithoutTradeNameReturnsFamilyAndGivenName() { final var givenPersonEntity = HsOfficePersonEntity.builder() + .personType(HsOfficePersonType.NATURAL) .familyName("some family name") .givenName("some given name") .build(); final var actualDisplay = givenPersonEntity.toShortString(); - assertThat(actualDisplay).isEqualTo("some family name, some given name"); + assertThat(actualDisplay).isEqualTo("NATURAL some family name, some given name"); } @Test diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonRepositoryIntegrationTest.java index 2405b237..59243daf 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/person/HsOfficePersonRepositoryIntegrationTest.java @@ -144,10 +144,10 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTest { // then allThesePersonsAreReturned( result, - "Smith, Peter", - "Second e.K.", - "Third OHG", - "Erben Bessler"); + "NATURAL Smith, Peter", + "LEGAL Second e.K.", + "SOLE_REPRESENTATION Third OHG", + "JOINT_REPRESENTATION Erben Bessler"); } @Test diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipControllerAcceptanceTest.java index f090295b..bbfa43ff 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipControllerAcceptanceTest.java @@ -75,7 +75,7 @@ class HsOfficeRelationshipControllerAcceptanceTest { .port(port) .when() .get("http://localhost/api/hs/office/relationships?personUuid=%s&relationshipType=%s" - .formatted(givenPerson.getUuid(), HsOfficeRelationshipTypeResource.SOLE_AGENT)) + .formatted(givenPerson.getUuid(), HsOfficeRelationshipTypeResource.REPRESENTATIVE)) .then().log().all().assertThat() .statusCode(200) .contentType("application/json") @@ -91,7 +91,7 @@ class HsOfficeRelationshipControllerAcceptanceTest { "givenName": "Peter", "familyName": "Smith" }, - "relType": "SOLE_AGENT", + "relType": "REPRESENTATIVE", "contact": { "label": "third contact" } }, { @@ -106,7 +106,7 @@ class HsOfficeRelationshipControllerAcceptanceTest { "givenName": "Peter", "familyName": "Smith" }, - "relType": "SOLE_AGENT", + "relType": "REPRESENTATIVE", "contact": { "label": "second contact" } }, { @@ -120,7 +120,7 @@ class HsOfficeRelationshipControllerAcceptanceTest { "givenName": "Peter", "familyName": "Smith" }, - "relType": "SOLE_AGENT", + "relType": "REPRESENTATIVE", "contact": { "label": "first contact" } } ] @@ -153,7 +153,7 @@ class HsOfficeRelationshipControllerAcceptanceTest { "contactUuid": "%s" } """.formatted( - HsOfficeRelationshipTypeResource.ACCOUNTING_CONTACT, + HsOfficeRelationshipTypeResource.ACCOUNTING, givenAnchorPerson.getUuid(), givenHolderPerson.getUuid(), givenContact.getUuid())) @@ -164,7 +164,7 @@ class HsOfficeRelationshipControllerAcceptanceTest { .statusCode(201) .contentType(ContentType.JSON) .body("uuid", isUuidValid()) - .body("relType", is("ACCOUNTING_CONTACT")) + .body("relType", is("ACCOUNTING")) .body("relAnchor.tradeName", is("Third OHG")) .body("relHolder.givenName", is("Paul")) .body("contact.label", is("forth contact")) @@ -197,7 +197,7 @@ class HsOfficeRelationshipControllerAcceptanceTest { "contactUuid": "%s" } """.formatted( - HsOfficeRelationshipTypeResource.ACCOUNTING_CONTACT, + HsOfficeRelationshipTypeResource.ACCOUNTING, givenAnchorPersonUuid, givenHolderPerson.getUuid(), givenContact.getUuid())) @@ -230,7 +230,7 @@ class HsOfficeRelationshipControllerAcceptanceTest { "contactUuid": "%s" } """.formatted( - HsOfficeRelationshipTypeResource.ACCOUNTING_CONTACT, + HsOfficeRelationshipTypeResource.ACCOUNTING, givenAnchorPerson.getUuid(), givenHolderPersonUuid, givenContact.getUuid())) @@ -263,7 +263,7 @@ class HsOfficeRelationshipControllerAcceptanceTest { "contactUuid": "%s" } """.formatted( - HsOfficeRelationshipTypeResource.ACCOUNTING_CONTACT, + HsOfficeRelationshipTypeResource.ACCOUNTING, givenAnchorPerson.getUuid(), givenHolderPerson.getUuid(), givenContactUuid)) @@ -387,7 +387,7 @@ class HsOfficeRelationshipControllerAcceptanceTest { .statusCode(200) .contentType(ContentType.JSON) .body("uuid", isUuidValid()) - .body("relType", is("JOINT_AGENT")) + .body("relType", is("REPRESENTATIVE")) .body("relAnchor.tradeName", is("Erben Bessler")) .body("relHolder.familyName", is("Winkler")) .body("contact.label", is("forth contact")); @@ -400,7 +400,7 @@ class HsOfficeRelationshipControllerAcceptanceTest { assertThat(rel.getRelAnchor().getTradeName()).contains("Bessler"); assertThat(rel.getRelHolder().getFamilyName()).contains("Winkler"); assertThat(rel.getContact().getLabel()).isEqualTo("forth contact"); - assertThat(rel.getRelType()).isEqualTo(HsOfficeRelationshipType.JOINT_AGENT); + assertThat(rel.getRelType()).isEqualTo(HsOfficeRelationshipType.REPRESENTATIVE); return true; }); } @@ -477,7 +477,7 @@ class HsOfficeRelationshipControllerAcceptanceTest { final var givenContact = contactRepo.findContactByOptionalLabelLike("seventh contact").get(0); final var newRelationship = HsOfficeRelationshipEntity.builder() .uuid(UUID.randomUUID()) - .relType(HsOfficeRelationshipType.JOINT_AGENT) + .relType(HsOfficeRelationshipType.REPRESENTATIVE) .relAnchor(givenAnchorPerson) .relHolder(givenHolderPerson) .contact(givenContact) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntityPatcherUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntityPatcherUnitTest.java index c3537764..1c12a629 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntityPatcherUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipEntityPatcherUnitTest.java @@ -52,7 +52,7 @@ class HsOfficeRelationshipEntityPatcherUnitTest extends PatchUnitTestBase< protected HsOfficeRelationshipEntity newInitialEntity() { final var entity = new HsOfficeRelationshipEntity(); entity.setUuid(INITIAL_RELATIONSHIP_UUID); - entity.setRelType(HsOfficeRelationshipType.SOLE_AGENT); + entity.setRelType(HsOfficeRelationshipType.REPRESENTATIVE); entity.setRelAnchor(givenInitialAnchorPerson); entity.setRelHolder(givenInitialHolderPerson); entity.setContact(givenInitialContact); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipRepositoryIntegrationTest.java index b76175d9..7b2603f8 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipRepositoryIntegrationTest.java @@ -77,7 +77,7 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTest { final var newRelationship = toCleanup(HsOfficeRelationshipEntity.builder() .relAnchor(givenAnchorPerson) .relHolder(givenHolderPerson) - .relType(HsOfficeRelationshipType.JOINT_AGENT) + .relType(HsOfficeRelationshipType.REPRESENTATIVE) .contact(givenContact) .build()); return relationshipRepo.save(newRelationship); @@ -105,7 +105,7 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTest { final var newRelationship = toCleanup(HsOfficeRelationshipEntity.builder() .relAnchor(givenAnchorPerson) .relHolder(givenHolderPerson) - .relType(HsOfficeRelationshipType.JOINT_AGENT) + .relType(HsOfficeRelationshipType.REPRESENTATIVE) .contact(givenContact) .build()); return relationshipRepo.save(newRelationship); @@ -114,26 +114,26 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTest { // then assertThat(roleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from( initialRoleNames, - "hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.admin", - "hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.owner", - "hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.tenant")); + "hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.admin", + "hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner", + "hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant")); assertThat(grantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(Array.fromFormatted( initialGrantNames, - "{ grant perm * on hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita to role hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.owner by system and assume }", - "{ grant role hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.owner to role global#global.admin by system and assume }", - "{ grant role hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.owner to role hs_office_person#BesslerAnita.admin by system and assume }", + "{ grant perm * on hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita to role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner by system and assume }", + "{ grant role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner to role global#global.admin by system and assume }", + "{ grant role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner to role hs_office_person#BesslerAnita.admin by system and assume }", - "{ grant perm edit on hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita to role hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.admin by system and assume }", - "{ grant role hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.admin to role hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.owner by system and assume }", + "{ grant perm edit on hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita to role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.admin by system and assume }", + "{ grant role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.admin to role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner by system and assume }", - "{ grant perm view on hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita to role hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.tenant by system and assume }", - "{ grant role hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.tenant to role hs_office_contact#forthcontact.admin by system and assume }", - "{ grant role hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.tenant to role hs_office_person#BesslerAnita.admin by system and assume }", + "{ grant perm view on hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita to role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant by system and assume }", + "{ grant role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant to role hs_office_contact#forthcontact.admin by system and assume }", + "{ grant role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant to role hs_office_person#BesslerAnita.admin by system and assume }", - "{ grant role hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.tenant to role hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.admin by system and assume }", - "{ grant role hs_office_contact#forthcontact.tenant to role hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.tenant by system and assume }", - "{ grant role hs_office_person#BesslerAnita.tenant to role hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.tenant by system and assume }", + "{ grant role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant to role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.admin by system and assume }", + "{ grant role hs_office_contact#forthcontact.tenant to role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant by system and assume }", + "{ grant role hs_office_person#BesslerAnita.tenant to role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant by system and assume }", null) ); } @@ -159,9 +159,9 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTest { // then allTheseRelationshipsAreReturned( result, - "rel(relAnchor='First GmbH', relType='SOLE_AGENT', relHolder='Smith, Peter', contact='first contact')", - "rel(relAnchor='Third OHG', relType='SOLE_AGENT', relHolder='Smith, Peter', contact='third contact')", - "rel(relAnchor='Second e.K.', relType='SOLE_AGENT', relHolder='Smith, Peter', contact='second contact')"); + "rel(relAnchor='LEGAL First GmbH', relType='REPRESENTATIVE', relHolder='NATURAL Smith, Peter', contact='first contact')", + "rel(relAnchor='SOLE_REPRESENTATION Third OHG', relType='REPRESENTATIVE', relHolder='NATURAL Smith, Peter', contact='third contact')", + "rel(relAnchor='LEGAL Second e.K.', relType='REPRESENTATIVE', relHolder='NATURAL Smith, Peter', contact='second contact')"); } @Test @@ -176,7 +176,7 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTest { // then: exactlyTheseRelationshipsAreReturned( result, - "rel(relAnchor='First GmbH', relType='SOLE_AGENT', relHolder='Smith, Peter', contact='first contact')"); + "rel(relAnchor='LEGAL First GmbH', relType='REPRESENTATIVE', relHolder='NATURAL Smith, Peter', contact='first contact')"); } } @@ -392,7 +392,7 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTest { final var givenHolderPerson = personRepo.findPersonByOptionalNameLike(holderPerson).get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike(contact).get(0); final var newRelationship = HsOfficeRelationshipEntity.builder() - .relType(HsOfficeRelationshipType.JOINT_AGENT) + .relType(HsOfficeRelationshipType.REPRESENTATIVE) .relAnchor(givenAnchorPerson) .relHolder(givenHolderPerson) .contact(givenContact) diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java index 31832119..70cf53da 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateControllerAcceptanceTest.java @@ -80,7 +80,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest { [ { "debitor": { - "debitorNumber": 10002, + "debitorNumber": 1000212, "billingContact": { "label": "second contact" } }, "bankAccount": { "holder": "Second e.K." }, @@ -90,7 +90,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest { }, { "debitor": { - "debitorNumber": 10001, + "debitorNumber": 1000111, "billingContact": { "label": "first contact" } }, "bankAccount": { "holder": "First GmbH" }, @@ -100,7 +100,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest { }, { "debitor": { - "debitorNumber": 10003, + "debitorNumber": 1000313, "billingContact": { "label": "third contact" } }, "bankAccount": { "holder": "Third OHG" }, @@ -269,7 +269,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest { .body("", lenientlyEquals(""" { "debitor": { - "debitorNumber": 10001, + "debitorNumber": 1000111, "billingContact": { "label": "first contact" } }, "bankAccount": { @@ -321,7 +321,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest { .body("", lenientlyEquals(""" { "debitor": { - "debitorNumber": 10001, + "debitorNumber": 1000111, "billingContact": { "label": "first contact" } }, "bankAccount": { @@ -376,7 +376,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest { context.define("superuser-alex@hostsharing.net"); assertThat(sepaMandateRepo.findByUuid(givenSepaMandate.getUuid())).isPresent().get() .matches(mandate -> { - assertThat(mandate.getDebitor().toString()).isEqualTo("debitor(10001: First GmbH)"); + assertThat(mandate.getDebitor().toString()).isEqualTo("debitor(1000111: LEGAL First GmbH: fir)"); assertThat(mandate.getBankAccount().toShortString()).isEqualTo("First GmbH"); assertThat(mandate.getReference()).isEqualTo("temp ref CAT Z - patched"); assertThat(mandate.getValidFrom()).isEqualTo("2020-06-05"); @@ -417,7 +417,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest { // finally, the sepaMandate is actually updated assertThat(sepaMandateRepo.findByUuid(givenSepaMandate.getUuid())).isPresent().get() .matches(mandate -> { - assertThat(mandate.getDebitor().toString()).isEqualTo("debitor(10001: First GmbH)"); + assertThat(mandate.getDebitor().toString()).isEqualTo("debitor(1000111: LEGAL First GmbH: fir)"); assertThat(mandate.getBankAccount().toShortString()).isEqualTo("First GmbH"); assertThat(mandate.getReference()).isEqualTo("temp ref CAT Z"); assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,2023-01-01)"); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateRepositoryIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateRepositoryIntegrationTest.java index 8e5f5c79..25d0343b 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateRepositoryIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateRepositoryIntegrationTest.java @@ -134,24 +134,24 @@ class HsOfficeSepaMandateRepositoryIntegrationTest extends ContextBasedTest { initialGrantNames, // owner - "{ grant perm * on sepamandate#temprefB to role sepamandate#temprefB.owner by system and assume }", - "{ grant role sepamandate#temprefB.owner to role global#global.admin by system and assume }", + "{ grant perm * on sepamandate#temprefB to role sepamandate#temprefB.owner by system and assume }", + "{ grant role sepamandate#temprefB.owner to role global#global.admin by system and assume }", // admin - "{ grant perm edit on sepamandate#temprefB to role sepamandate#temprefB.admin by system and assume }", - "{ grant role sepamandate#temprefB.admin to role sepamandate#temprefB.owner by system and assume }", - "{ grant role bankaccount#Paul....tenant to role sepamandate#temprefB.admin by system and assume }", + "{ grant perm edit on sepamandate#temprefB to role sepamandate#temprefB.admin by system and assume }", + "{ grant role sepamandate#temprefB.admin to role sepamandate#temprefB.owner by system and assume }", + "{ grant role bankaccount#Paul....tenant to role sepamandate#temprefB.admin by system and assume }", // agent - "{ grant role sepamandate#temprefB.agent to role sepamandate#temprefB.admin by system and assume }", - "{ grant role debitor#10001FirstGmbH-....tenant to role sepamandate#temprefB.agent by system and assume }", - "{ grant role sepamandate#temprefB.agent to role bankaccount#Paul....admin by system and assume }", - "{ grant role sepamandate#temprefB.agent to role debitor#10001FirstGmbH-....admin by system and assume }", + "{ grant role sepamandate#temprefB.agent to role sepamandate#temprefB.admin by system and assume }", + "{ grant role debitor#1000111:FirstGmbH-....tenant to role sepamandate#temprefB.agent by system and assume }", + "{ grant role sepamandate#temprefB.agent to role bankaccount#Paul....admin by system and assume }", + "{ grant role sepamandate#temprefB.agent to role debitor#1000111:FirstGmbH-....admin by system and assume }", // tenant - "{ grant role sepamandate#temprefB.tenant to role sepamandate#temprefB.agent by system and assume }", - "{ grant role debitor#10001FirstGmbH-....guest to role sepamandate#temprefB.tenant by system and assume }", - "{ grant role bankaccount#Paul....guest to role sepamandate#temprefB.tenant by system and assume }", + "{ grant role sepamandate#temprefB.tenant to role sepamandate#temprefB.agent by system and assume }", + "{ grant role debitor#1000111:FirstGmbH-....guest to role sepamandate#temprefB.tenant by system and assume }", + "{ grant role bankaccount#Paul....guest to role sepamandate#temprefB.tenant by system and assume }", // guest "{ grant perm view on sepamandate#temprefB to role sepamandate#temprefB.guest by system and assume }", diff --git a/src/test/java/net/hostsharing/hsadminng/mapper/PostgresArrayIntegrationTest.java b/src/test/java/net/hostsharing/hsadminng/mapper/PostgresArrayIntegrationTest.java index c76141b1..8f3e95e0 100644 --- a/src/test/java/net/hostsharing/hsadminng/mapper/PostgresArrayIntegrationTest.java +++ b/src/test/java/net/hostsharing/hsadminng/mapper/PostgresArrayIntegrationTest.java @@ -22,7 +22,7 @@ class PostgresArrayIntegrationTest { em.createNativeQuery(""" create or replace function returnEmptyArray() returns text[] - stable leakproof + stable -- leakproof language plpgsql as $$ declare emptyArray text[] = '{}'; @@ -42,7 +42,7 @@ class PostgresArrayIntegrationTest { em.createNativeQuery(""" create or replace function returnStringArray() returns varchar(63)[] - stable leakproof + stable -- leakproof language plpgsql as $$ declare text1 text = 'one'; @@ -65,7 +65,7 @@ class PostgresArrayIntegrationTest { em.createNativeQuery(""" create or replace function returnUuidArray() returns uuid[] - stable leakproof + stable -- leakproof language plpgsql as $$ declare uuid1 UUID = 'f47ac10b-58cc-4372-a567-0e02b2c3d479'; diff --git a/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleControllerAcceptanceTest.java index abed40ac..5de93348 100644 --- a/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/rbac/rbacrole/RbacRoleControllerAcceptanceTest.java @@ -10,9 +10,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; -import jakarta.persistence.EntityManager; -import jakarta.persistence.PersistenceContext; - import static org.hamcrest.Matchers.*; @SpringBootTest( @@ -25,9 +22,6 @@ class RbacRoleControllerAcceptanceTest { @LocalServerPort private Integer port; - @PersistenceContext - EntityManager em; - @Autowired Context context; diff --git a/src/test/java/net/hostsharing/test/PatchUnitTestBase.java b/src/test/java/net/hostsharing/test/PatchUnitTestBase.java index 7be61b19..51f78bb4 100644 --- a/src/test/java/net/hostsharing/test/PatchUnitTestBase.java +++ b/src/test/java/net/hostsharing/test/PatchUnitTestBase.java @@ -1,5 +1,6 @@ package net.hostsharing.test; +import net.hostsharing.hsadminng.hs.office.migration.HasUuid; import net.hostsharing.hsadminng.mapper.EntityPatcher; import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Test; @@ -232,7 +233,7 @@ public abstract class PatchUnitTestBase { } } - protected static class JsonNullableProperty extends Property { + protected static class JsonNullableProperty extends Property { private final BiConsumer> resourceSetter; public final RV givenPatchValue; diff --git a/src/test/resources/migration/asset-transactions.csv b/src/test/resources/migration/asset-transactions.csv new file mode 100644 index 00000000..12c2c39c --- /dev/null +++ b/src/test/resources/migration/asset-transactions.csv @@ -0,0 +1,9 @@ +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 diff --git a/src/test/resources/migration/business-partners.csv b/src/test/resources/migration/business-partners.csv new file mode 100644 index 00000000..a31c2e9d --- /dev/null +++ b/src/test/resources/migration/business-partners.csv @@ -0,0 +1,4 @@ +bp_id;member_id;member_code;member_since;member_until;member_role;author_contract;nondisc_contract;free;exempt_vat;indicator_vat;uid_vat +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; diff --git a/src/test/resources/migration/contacts.csv b/src/test/resources/migration/contacts.csv new file mode 100644 index 00000000..af67bce3 --- /dev/null +++ b/src/test/resources/migration/contacts.csv @@ -0,0 +1,13 @@ +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 + +# eine natürliche Person, implizites contractual +1101; 17; Herr; Michael; Mellies; ; ; ; Kleine Freiheit 50; 26524; Hage; DE; ; +49 4931 123456; +49 1522 123456;; mih@example.org; partner,billing,operation + +# eine juristische Person mit drei separaten Ansprechpartnern, vip-contact und ex-partner +1200; 20;; ; ; ; JM e.K.;; Wiesenweg 15; 12335; Berlin; DE; +49 30 6666666; +49 30 5555555; ; +49 30 6666666; jm-ex-partner@example.org; ex-partner +1201; 20; Frau; Jenny; Meyer-Billing; Dr.; JM GmbH;; Waldweg 5; 11001; Berlin; DE; +49 30 7777777; +49 30 1111111; ; +49 30 2222222; jm-billing@example.org; billing +1202; 20; Herr; Andrew; Meyer-Operation; ; JM GmbH;; Waldweg 5; 11001; Berlin; DE; +49 30 6666666; +49 30 3333333; ; +49 30 4444444; am-operation@example.org; operation,vip-contact +1203; 20; Herr; Philip; Meyer-Contract; ; JM GmbH;; Waldweg 5; 11001; Berlin; DE; +49 30 6666666; +49 30 5555555; ; +49 30 6666666; pm-partner@example.org; partner,contractual + +# eine juristische Person mit nur einem Ansprechpartner und explizitem contractual +1301; 22; ; Petra; Schmidt; ; Test PS;; ; ; ; ; ; ; ; ; ps@example.com; partner,billing,contractual,operation diff --git a/src/test/resources/migration/dump.sh b/src/test/resources/migration/dump.sh new file mode 100644 index 00000000..e5183164 --- /dev/null +++ b/src/test/resources/migration/dump.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +host="127.0.0.1" +port="5432" +dbname="hsh02_hsdb" +username="hsh02_hsdb_readonly" + +target="/tmp" + +dump() { + sql="copy ($1) to stdout with csv header delimiter ';' quote '\"'" + file="${target}/${2}" + psql --host ${host} --port ${port} --user ${username} --command "${sql}" ${dbname} >"${file}" +} + +dump "select bp_id, member_id, member_code, member_since, member_until, member_role, author_contract, nondisc_contract, free, exempt_vat, indicator_vat, uid_vat + from business_partner + order by bp_id" \ + "business-partners.csv" + +dump "select contact_id, bp_id, salut, first_name, last_name, title, firma, co, street, zipcode, city, country, phone_private, phone_office, phone_mobile, fax, email, array_to_string(array_agg(role), ',') as roles + from contact + left join contactrole_ref using(contact_id) + group by contact_id + order by contact_id" \ + "contacts.csv" + +dump "select sepa_mandat_id, bp_id, bank_customer, bank_name, bank_iban, bank_bic, mandat_ref, mandat_signed, mandat_since, mandat_until, mandat_used + from sepa_mandat + order by sepa_mandat_id" \ + "sepa-mandates.csv" + +dump "select member_asset_id, bp_id, date, action, amount, comment + from member_asset + WHERE bp_id NOT IN (511912) + order by member_asset_id" \ + "asset-transactions.csv" + +dump "select member_share_id, bp_id, date, action, quantity, comment + from member_share + WHERE bp_id NOT IN (511912) + order by member_share_id" \ + "share-transactions.csv" diff --git a/src/test/resources/migration/sepa-mandates.csv b/src/test/resources/migration/sepa-mandates.csv new file mode 100644 index 00000000..a76adc16 --- /dev/null +++ b/src/test/resources/migration/sepa-mandates.csv @@ -0,0 +1,4 @@ +sepa_mandat_id; bp_id; bank_customer; bank_name; bank_iban; bank_bic; mandat_ref; mandat_signed; mandat_since; mandat_until; mandat_used +234234; 17; Michael Mellies; ING Bank AG; DE37500105177419788228; INGDDEFFXXX; MH12345; 2004-06-12; 2004-06-15; ; 2022-10-20 +235600; 20; JM e.K.; Targobank AG; DE02300209000106531065; CMCIDEDD; JM33344; 2004-01-15; 2004-01-20;2005-06-27 ;2016-01-18 +235662; 20; JM GmbH; ING Bank AG; DE49500105174516484892; INGDDEFFXXX; JM33344; 2005-06-28; 2005-07-01; ; 2016-01-18 diff --git a/src/test/resources/migration/share-transactions.csv b/src/test/resources/migration/share-transactions.csv new file mode 100644 index 00000000..fa561419 --- /dev/null +++ b/src/test/resources/migration/share-transactions.csv @@ -0,0 +1,5 @@ +member_share_id;bp_id; date; action; quantity; comment +33443; 17; 2000-12-06; SUBSCRIPTION; 20; initial share subscription +33451; 20; 2000-12-06; SUBSCRIPTION; 2; initial share subscription +33701; 17; 2005-01-10; SUBSCRIPTION; 40; increase +33810; 20; 2016-12-31; UNSUBSCRIPTION; 22; membership ended