initial data import for hs-office tables (db-migration #10)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Co-authored-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
Reviewed-on: #10
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
This commit is contained in:
Michael Hoennig 2024-01-23 15:11:23 +01:00
parent e427bb1784
commit fd1bd897b1
98 changed files with 2278 additions and 471 deletions

View File

@ -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 () { gradleWrapper () {
if [ ! -f gradlew ]; then if [ ! -f gradlew ]; then
echo "No 'gradlew' found. Maybe you are not in the root dir of a gradle project?" 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 && \ dot -Tsvg build/postgres-autodoc.neato >build/postgres-autodoc-rbac.svg && \
echo "generated $PWD/build/postgres-autodoc-rbac.svg" echo "generated $PWD/build/postgres-autodoc-rbac.svg"
} }
alias postgres-autodoc=postgresAutodoc 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-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-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' 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 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 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'

1
.gitignore vendored
View File

@ -137,3 +137,4 @@ Desktop.ini
# ESLint # ESLint
###################### ######################
.eslintcache .eslintcache
/.environment*

View File

@ -100,6 +100,7 @@ dependencies {
testImplementation 'io.rest-assured:spring-mock-mvc' testImplementation 'io.rest-assured:spring-mock-mvc'
testImplementation 'org.hamcrest:hamcrest-core:2.2' testImplementation 'org.hamcrest:hamcrest-core:2.2'
testImplementation 'org.pitest:pitest-junit5-plugin:1.2.1' testImplementation 'org.pitest:pitest-junit5-plugin:1.2.1'
testImplementation 'org.junit.jupiter:junit-jupiter-api'
} }
dependencyManagement { dependencyManagement {
@ -129,7 +130,7 @@ openapiProcessor {
processor 'io.openapiprocessor:openapi-processor-spring:2022.5' processor 'io.openapiprocessor:openapi-processor-spring:2022.5'
apiPath "$projectDir/src/main/resources/api-definition.yaml" apiPath "$projectDir/src/main/resources/api-definition.yaml"
mapping "$projectDir/src/main/resources/api-mappings.yaml" mapping "$projectDir/src/main/resources/api-mappings.yaml"
targetDir "$projectDir/build/generated/sources/openapi-javax" targetDir "$buildDir/generated/sources/openapi-javax"
showWarnings true showWarnings true
openApiNullable true openApiNullable true
} }
@ -138,7 +139,7 @@ openapiProcessor {
processor 'io.openapiprocessor:openapi-processor-spring:2022.5' processor 'io.openapiprocessor:openapi-processor-spring:2022.5'
apiPath "$projectDir/src/main/resources/api-definition/rbac/rbac.yaml" apiPath "$projectDir/src/main/resources/api-definition/rbac/rbac.yaml"
mapping "$projectDir/src/main/resources/api-definition/rbac/api-mappings.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 showWarnings true
openApiNullable true openApiNullable true
} }
@ -147,7 +148,7 @@ openapiProcessor {
processor 'io.openapiprocessor:openapi-processor-spring:2022.5' processor 'io.openapiprocessor:openapi-processor-spring:2022.5'
apiPath "$projectDir/src/main/resources/api-definition/test/test.yaml" apiPath "$projectDir/src/main/resources/api-definition/test/test.yaml"
mapping "$projectDir/src/main/resources/api-definition/test/api-mappings.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 showWarnings true
openApiNullable true openApiNullable true
} }
@ -156,7 +157,7 @@ openapiProcessor {
processor 'io.openapiprocessor:openapi-processor-spring:2022.5' processor 'io.openapiprocessor:openapi-processor-spring:2022.5'
apiPath "$projectDir/src/main/resources/api-definition/hs-office/hs-office.yaml" apiPath "$projectDir/src/main/resources/api-definition/hs-office/hs-office.yaml"
mapping "$projectDir/src/main/resources/api-definition/hs-office/api-mappings.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 showWarnings true
openApiNullable true openApiNullable true
} }
@ -237,6 +238,9 @@ test {
excludes = [ excludes = [
'net.hostsharing.hsadminng.**.generated.**', 'net.hostsharing.hsadminng.**.generated.**',
] ]
useJUnitPlatform {
excludeTags 'import'
}
} }
jacocoTestReport { jacocoTestReport {
dependsOn test 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 mutation testing
pitest { pitest {
targetClasses = ['net.hostsharing.hsadminng.**'] targetClasses = ['net.hostsharing.hsadminng.**']

View File

@ -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 {
<<enumeration>>
UNKNOWN
REPRESENTATIVE
ACCOUNTING
OPERATIONS
}
class PersonType {
<<enumeration>>
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
```

View File

@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.office.bankaccount;
import lombok.*; import lombok.*;
import lombok.experimental.FieldNameConstants; import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.errors.DisplayName; 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.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
@ -23,7 +24,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@AllArgsConstructor @AllArgsConstructor
@FieldNameConstants @FieldNameConstants
@DisplayName("BankAccount") @DisplayName("BankAccount")
public class HsOfficeBankAccountEntity implements Stringifyable { public class HsOfficeBankAccountEntity implements HasUuid, Stringifyable {
private static Stringify<HsOfficeBankAccountEntity> toString = stringify(HsOfficeBankAccountEntity.class, "bankAccount") private static Stringify<HsOfficeBankAccountEntity> toString = stringify(HsOfficeBankAccountEntity.class, "bankAccount")
.withProp(Fields.holder, HsOfficeBankAccountEntity::getHolder) .withProp(Fields.holder, HsOfficeBankAccountEntity::getHolder)

View File

@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.office.contact;
import lombok.*; import lombok.*;
import lombok.experimental.FieldNameConstants; import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.errors.DisplayName; 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.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.GenericGenerator;
@ -21,7 +22,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@AllArgsConstructor @AllArgsConstructor
@FieldNameConstants @FieldNameConstants
@DisplayName("Contact") @DisplayName("Contact")
public class HsOfficeContactEntity implements Stringifyable { public class HsOfficeContactEntity implements Stringifyable, HasUuid {
private static Stringify<HsOfficeContactEntity> toString = stringify(HsOfficeContactEntity.class, "contact") private static Stringify<HsOfficeContactEntity> toString = stringify(HsOfficeContactEntity.class, "contact")
.withProp(Fields.label, HsOfficeContactEntity::getLabel) .withProp(Fields.label, HsOfficeContactEntity::getLabel)
@ -38,10 +39,10 @@ public class HsOfficeContactEntity implements Stringifyable {
private String postalAddress; private String postalAddress;
@Column(name = "emailaddresses", columnDefinition = "json") @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") @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 @Override
public String toString() { public String toString() {

View File

@ -1,8 +1,10 @@
package net.hostsharing.hsadminng.hs.office.coopassets; package net.hostsharing.hsadminng.hs.office.coopassets;
import lombok.*; import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity; 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.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.GenericGenerator;
@ -13,6 +15,7 @@ import java.text.DecimalFormat;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.UUID; import java.util.UUID;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity @Entity
@ -23,14 +26,15 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@DisplayName("CoopAssetsTransaction") @DisplayName("CoopAssetsTransaction")
public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable { public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, HasUuid {
private static Stringify<HsOfficeCoopAssetsTransactionEntity> stringify = stringify(HsOfficeCoopAssetsTransactionEntity.class) private static Stringify<HsOfficeCoopAssetsTransactionEntity> stringify = stringify(HsOfficeCoopAssetsTransactionEntity.class)
.withProp(e -> e.getMembership().getMemberNumber()) .withProp(HsOfficeCoopAssetsTransactionEntity::getMemberNumber)
.withProp(HsOfficeCoopAssetsTransactionEntity::getValueDate) .withProp(HsOfficeCoopAssetsTransactionEntity::getValueDate)
.withProp(HsOfficeCoopAssetsTransactionEntity::getTransactionType) .withProp(HsOfficeCoopAssetsTransactionEntity::getTransactionType)
.withProp(HsOfficeCoopAssetsTransactionEntity::getAssetValue) .withProp(HsOfficeCoopAssetsTransactionEntity::getAssetValue)
.withProp(HsOfficeCoopAssetsTransactionEntity::getReference) .withProp(HsOfficeCoopAssetsTransactionEntity::getReference)
.withProp(HsOfficeCoopAssetsTransactionEntity::getComment)
.withSeparator(", ") .withSeparator(", ")
.quotedValues(false); .quotedValues(false);
@ -54,11 +58,16 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable {
private BigDecimal assetValue; private BigDecimal assetValue;
@Column(name = "reference") @Column(name = "reference")
private String reference; private String reference; // TODO: what is this for?
@Column(name = "comment") @Column(name = "comment")
private String comment; private String comment;
public Integer getMemberNumber() {
return ofNullable(membership).map(HsOfficeMembershipEntity::getMemberNumber).orElse(null);
}
@Override @Override
public String toString() { public String toString() {
return stringify.apply(this); return stringify.apply(this);
@ -66,6 +75,6 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable {
@Override @Override
public String toShortString() { public String toShortString() {
return membership.getMemberNumber() + new DecimalFormat("+0.00").format(assetValue); return "%s%+1.2f".formatted(getMemberNumber(), assetValue);
} }
} }

View File

@ -1,5 +1,12 @@
package net.hostsharing.hsadminng.hs.office.coopassets; package net.hostsharing.hsadminng.hs.office.coopassets;
public enum HsOfficeCoopAssetsTransactionType { 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
} }

View File

@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.office.coopshares;
import lombok.*; import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity; 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.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
@ -10,6 +11,7 @@ import jakarta.persistence.*;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.UUID; import java.util.UUID;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity @Entity
@ -20,14 +22,15 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@DisplayName("CoopShareTransaction") @DisplayName("CoopShareTransaction")
public class HsOfficeCoopSharesTransactionEntity implements Stringifyable { public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, HasUuid {
private static Stringify<HsOfficeCoopSharesTransactionEntity> stringify = stringify(HsOfficeCoopSharesTransactionEntity.class) private static Stringify<HsOfficeCoopSharesTransactionEntity> stringify = stringify(HsOfficeCoopSharesTransactionEntity.class)
.withProp(e -> e.getMembership().getMemberNumber()) .withProp(HsOfficeCoopSharesTransactionEntity::getMemberNumber)
.withProp(HsOfficeCoopSharesTransactionEntity::getValueDate) .withProp(HsOfficeCoopSharesTransactionEntity::getValueDate)
.withProp(HsOfficeCoopSharesTransactionEntity::getTransactionType) .withProp(HsOfficeCoopSharesTransactionEntity::getTransactionType)
.withProp(HsOfficeCoopSharesTransactionEntity::getShareCount) .withProp(HsOfficeCoopSharesTransactionEntity::getShareCount)
.withProp(HsOfficeCoopSharesTransactionEntity::getReference) .withProp(HsOfficeCoopSharesTransactionEntity::getReference)
.withProp(HsOfficeCoopSharesTransactionEntity::getComment)
.withSeparator(", ") .withSeparator(", ")
.quotedValues(false); .quotedValues(false);
@ -50,7 +53,7 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable {
private int shareCount; private int shareCount;
@Column(name = "reference") @Column(name = "reference")
private String reference; private String reference; // TODO: what is this for?
@Column(name = "comment") @Column(name = "comment")
private String comment; private String comment;
@ -60,8 +63,12 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable {
return stringify.apply(this); return stringify.apply(this);
} }
public Integer getMemberNumber() {
return ofNullable(membership).map(HsOfficeMembershipEntity::getMemberNumber).orElse(null);
}
@Override @Override
public String toShortString() { public String toShortString() {
return "%s%+d".formatted(membership.getMemberNumber(), shareCount); return "%s%+d".formatted(getMemberNumber(), shareCount);
} }
} }

View File

@ -1,5 +1,7 @@
package net.hostsharing.hsadminng.hs.office.coopshares; package net.hostsharing.hsadminng.hs.office.coopshares;
public enum HsOfficeCoopSharesTransactionType { 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
} }

View File

@ -4,12 +4,14 @@ import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; 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.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.GenericGenerator;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@ -22,12 +24,16 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@DisplayName("Debitor") @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<HsOfficeDebitorEntity> stringify = private static Stringify<HsOfficeDebitorEntity> stringify =
stringify(HsOfficeDebitorEntity.class, "debitor") stringify(HsOfficeDebitorEntity.class, "debitor")
.withProp(HsOfficeDebitorEntity::getDebitorNumber) .withProp(HsOfficeDebitorEntity::getDebitorNumber)
.withProp(HsOfficeDebitorEntity::getPartner) .withProp(HsOfficeDebitorEntity::getPartner)
.withProp(HsOfficeDebitorEntity::getDefaultPrefix)
.withSeparator(": ") .withSeparator(": ")
.quotedValues(false); .quotedValues(false);
@ -40,12 +46,15 @@ public class HsOfficeDebitorEntity implements Stringifyable {
@JoinColumn(name = "partneruuid") @JoinColumn(name = "partneruuid")
private HsOfficePartnerEntity partner; private HsOfficePartnerEntity partner;
@Column(name = "debitornumber") @Column(name = "debitornumbersuffix", columnDefinition = "numeric(2)")
private Integer debitorNumber; private Byte debitorNumberSuffix; // TODO maybe rather as a formatted String?
@ManyToOne @ManyToOne
@JoinColumn(name = "billingcontactuuid") @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") @Column(name = "vatid")
private String vatId; private String vatId;
@ -56,10 +65,34 @@ public class HsOfficeDebitorEntity implements Stringifyable {
@Column(name = "vatbusiness") @Column(name = "vatbusiness")
private boolean vatBusiness; private boolean vatBusiness;
@Column(name = "vatreversecharge")
private boolean vatReverseCharge;
@ManyToOne @ManyToOne
@JoinColumn(name = "refundbankaccountuuid") @JoinColumn(name = "refundbankaccountuuid")
private HsOfficeBankAccountEntity refundBankAccount; 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 @Override
public String toString() { public String toString() {
return stringify.apply(this); return stringify.apply(this);
@ -67,6 +100,6 @@ public class HsOfficeDebitorEntity implements Stringifyable {
@Override @Override
public String toShortString() { public String toShortString() {
return debitorNumber.toString(); return getDebitorNumberString();
} }
} }

View File

@ -1,11 +1,13 @@
package net.hostsharing.hsadminng.hs.office.debitor; 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.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorPatchResource;
import net.hostsharing.hsadminng.mapper.EntityPatcher; import net.hostsharing.hsadminng.mapper.EntityPatcher;
import net.hostsharing.hsadminng.mapper.OptionalFromJson; import net.hostsharing.hsadminng.mapper.OptionalFromJson;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import java.util.Optional;
class HsOfficeDebitorEntityPatcher implements EntityPatcher<HsOfficeDebitorPatchResource> { class HsOfficeDebitorEntityPatcher implements EntityPatcher<HsOfficeDebitorPatchResource> {
@ -25,11 +27,18 @@ class HsOfficeDebitorEntityPatcher implements EntityPatcher<HsOfficeDebitorPatch
verifyNotNull(newValue, "billingContact"); verifyNotNull(newValue, "billingContact");
entity.setBillingContact(em.getReference(HsOfficeContactEntity.class, newValue)); entity.setBillingContact(em.getReference(HsOfficeContactEntity.class, newValue));
}); });
Optional.ofNullable(resource.getBillable()).ifPresent(entity::setBillable);
OptionalFromJson.of(resource.getVatId()).ifPresent(entity::setVatId); OptionalFromJson.of(resource.getVatId()).ifPresent(entity::setVatId);
OptionalFromJson.of(resource.getVatCountryCode()).ifPresent(entity::setVatCountryCode); OptionalFromJson.of(resource.getVatCountryCode()).ifPresent(entity::setVatCountryCode);
OptionalFromJson.of(resource.getVatBusiness()).ifPresent(newValue -> { Optional.ofNullable(resource.getVatBusiness()).ifPresent(entity::setVatBusiness);
verifyNotNull(newValue, "vatBusiness"); Optional.ofNullable(resource.getVatReverseCharge()).ifPresent(entity::setVatReverseCharge);
entity.setVatBusiness(newValue); 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));
}); });
} }

View File

@ -13,9 +13,14 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
@Query(""" @Query("""
SELECT debitor FROM HsOfficeDebitorEntity debitor SELECT debitor FROM HsOfficeDebitorEntity debitor
WHERE debitor.debitorNumber = :debitorNumber WHERE cast(debitor.partner.debitorNumberPrefix as integer) = :debitorNumberPrefix
AND cast(debitor.debitorNumberSuffix as integer) = :debitorNumberSuffix
""") """)
List<HsOfficeDebitorEntity> findDebitorByDebitorNumber(int debitorNumber); List<HsOfficeDebitorEntity> findDebitorByDebitorNumber(int debitorNumberPrefix, byte debitorNumberSuffix);
default List<HsOfficeDebitorEntity> findDebitorByDebitorNumber(int debitorNumber) {
return findDebitorByDebitorNumber( debitorNumber/100, (byte) (debitorNumber%100));
}
@Query(""" @Query("""
SELECT debitor FROM HsOfficeDebitorEntity debitor SELECT debitor FROM HsOfficeDebitorEntity debitor

View File

@ -5,6 +5,7 @@ import com.vladmihalcea.hibernate.type.range.Range;
import lombok.*; import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; 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.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
@ -27,7 +28,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@DisplayName("Membership") @DisplayName("Membership")
public class HsOfficeMembershipEntity implements Stringifyable { public class HsOfficeMembershipEntity implements HasUuid, Stringifyable {
private static Stringify<HsOfficeMembershipEntity> stringify = stringify(HsOfficeMembershipEntity.class) private static Stringify<HsOfficeMembershipEntity> stringify = stringify(HsOfficeMembershipEntity.class)
.withProp(HsOfficeMembershipEntity::getMemberNumber) .withProp(HsOfficeMembershipEntity::getMemberNumber)
@ -52,12 +53,15 @@ public class HsOfficeMembershipEntity implements Stringifyable {
private HsOfficeDebitorEntity mainDebitor; private HsOfficeDebitorEntity mainDebitor;
@Column(name = "membernumber") @Column(name = "membernumber")
private int memberNumber; private int memberNumber; // TODO: migrate to suffix, like debitorNumberSuffix
@Column(name = "validity", columnDefinition = "daterange") @Column(name = "validity", columnDefinition = "daterange")
@Type(PostgreSQLRangeType.class) @Type(PostgreSQLRangeType.class)
private Range<LocalDate> validity; private Range<LocalDate> validity;
@Column(name = "membershipfeebillable", nullable = false)
private Boolean membershipFeeBillable; // not primitive to force setting the value
@Column(name = "reasonfortermination") @Column(name = "reasonfortermination")
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
private HsOfficeReasonForTermination reasonForTermination; private HsOfficeReasonForTermination reasonForTermination;

View File

@ -37,6 +37,8 @@ public class HsOfficeMembershipEntityPatcher implements EntityPatcher<HsOfficeMe
Optional.ofNullable(resource.getReasonForTermination()) Optional.ofNullable(resource.getReasonForTermination())
.map(v -> mapper.map(v, HsOfficeReasonForTermination.class)) .map(v -> mapper.map(v, HsOfficeReasonForTermination.class))
.ifPresent(entity::setReasonForTermination); .ifPresent(entity::setReasonForTermination);
OptionalFromJson.of(resource.getMembershipFeeBillable()).ifPresent(
entity::setMembershipFeeBillable);
} }
private void verifyNotNull(final UUID newValue, final String propertyName) { private void verifyNotNull(final UUID newValue, final String propertyName) {

View File

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

View File

@ -0,0 +1,7 @@
package net.hostsharing.hsadminng.hs.office.migration;
import java.util.UUID;
public interface HasUuid {
UUID getUuid();
}

View File

@ -2,6 +2,7 @@ package net.hostsharing.hsadminng.hs.office.partner;
import lombok.*; import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName; 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.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
@ -19,15 +20,16 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@DisplayName("PartnerDetails") @DisplayName("PartnerDetails")
public class HsOfficePartnerDetailsEntity implements Stringifyable { public class HsOfficePartnerDetailsEntity implements HasUuid, Stringifyable {
private static Stringify<HsOfficePartnerDetailsEntity> stringify = stringify( private static Stringify<HsOfficePartnerDetailsEntity> stringify = stringify(
HsOfficePartnerDetailsEntity.class, HsOfficePartnerDetailsEntity.class,
"partnerDetails") "partnerDetails")
.withProp(HsOfficePartnerDetailsEntity::getRegistrationOffice) .withProp(HsOfficePartnerDetailsEntity::getRegistrationOffice)
.withProp(HsOfficePartnerDetailsEntity::getRegistrationNumber) .withProp(HsOfficePartnerDetailsEntity::getRegistrationNumber)
.withProp(HsOfficePartnerDetailsEntity::getBirthPlace)
.withProp(HsOfficePartnerDetailsEntity::getBirthday) .withProp(HsOfficePartnerDetailsEntity::getBirthday)
.withProp(HsOfficePartnerDetailsEntity::getBirthday) .withProp(HsOfficePartnerDetailsEntity::getBirthName)
.withProp(HsOfficePartnerDetailsEntity::getDateOfDeath) .withProp(HsOfficePartnerDetailsEntity::getDateOfDeath)
.withSeparator(", ") .withSeparator(", ")
.quotedValues(false); .quotedValues(false);
@ -39,6 +41,7 @@ public class HsOfficePartnerDetailsEntity implements Stringifyable {
private @Column(name = "registrationoffice") String registrationOffice; private @Column(name = "registrationoffice") String registrationOffice;
private @Column(name = "registrationnumber") String registrationNumber; private @Column(name = "registrationnumber") String registrationNumber;
private @Column(name = "birthname") String birthName; private @Column(name = "birthname") String birthName;
private @Column(name = "birthplace") String birthPlace;
private @Column(name = "birthday") LocalDate birthday; private @Column(name = "birthday") LocalDate birthday;
private @Column(name = "dateofdeath") LocalDate dateOfDeath; private @Column(name = "dateofdeath") LocalDate dateOfDeath;

View File

@ -22,6 +22,7 @@ class HsOfficePartnerDetailsEntityPatcher implements EntityPatcher<HsOfficePartn
OptionalFromJson.of(resource.getRegistrationOffice()).ifPresent(entity::setRegistrationOffice); OptionalFromJson.of(resource.getRegistrationOffice()).ifPresent(entity::setRegistrationOffice);
OptionalFromJson.of(resource.getRegistrationNumber()).ifPresent(entity::setRegistrationNumber); OptionalFromJson.of(resource.getRegistrationNumber()).ifPresent(entity::setRegistrationNumber);
OptionalFromJson.of(resource.getBirthday()).ifPresent(entity::setBirthday); OptionalFromJson.of(resource.getBirthday()).ifPresent(entity::setBirthday);
OptionalFromJson.of(resource.getBirthPlace()).ifPresent(entity::setBirthPlace);
OptionalFromJson.of(resource.getBirthName()).ifPresent(entity::setBirthName); OptionalFromJson.of(resource.getBirthName()).ifPresent(entity::setBirthName);
OptionalFromJson.of(resource.getDateOfDeath()).ifPresent(entity::setDateOfDeath); OptionalFromJson.of(resource.getDateOfDeath()).ifPresent(entity::setDateOfDeath);
} }

View File

@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.office.partner;
import lombok.*; import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; 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.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
@ -10,6 +11,7 @@ import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.NotFoundAction;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@ -22,7 +24,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@DisplayName("Partner") @DisplayName("Partner")
public class HsOfficePartnerEntity implements Stringifyable { public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
private static Stringify<HsOfficePartnerEntity> stringify = stringify(HsOfficePartnerEntity.class, "partner") private static Stringify<HsOfficePartnerEntity> stringify = stringify(HsOfficePartnerEntity.class, "partner")
.withProp(HsOfficePartnerEntity::getPerson) .withProp(HsOfficePartnerEntity::getPerson)
@ -34,6 +36,9 @@ public class HsOfficePartnerEntity implements Stringifyable {
@GeneratedValue @GeneratedValue
private UUID uuid; private UUID uuid;
@Column(name = "debitornumberprefix", columnDefinition = "numeric(5) not null")
private Integer debitorNumberPrefix;
@ManyToOne @ManyToOne
@JoinColumn(name = "personuuid", nullable = false) @JoinColumn(name = "personuuid", nullable = false)
private HsOfficePersonEntity person; private HsOfficePersonEntity person;
@ -43,7 +48,7 @@ public class HsOfficePartnerEntity implements Stringifyable {
private HsOfficeContactEntity contact; private HsOfficeContactEntity contact;
@ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH }, optional = true) @ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH }, optional = true)
@JoinColumn(name = "detailsuuid", nullable = true) @JoinColumn(name = "detailsuuid")
@NotFound(action = NotFoundAction.IGNORE) @NotFound(action = NotFoundAction.IGNORE)
private HsOfficePartnerDetailsEntity details; private HsOfficePartnerDetailsEntity details;
@ -54,6 +59,6 @@ public class HsOfficePartnerEntity implements Stringifyable {
@Override @Override
public String toShortString() { public String toShortString() {
return person.toShortString(); return Optional.ofNullable(person).map(HsOfficePersonEntity::toShortString).orElse("<person=null>");
} }
} }

View File

@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.office.person;
import lombok.*; import lombok.*;
import lombok.experimental.FieldNameConstants; import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.errors.DisplayName; 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.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -21,7 +22,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@AllArgsConstructor @AllArgsConstructor
@FieldNameConstants @FieldNameConstants
@DisplayName("Person") @DisplayName("Person")
public class HsOfficePersonEntity implements Stringifyable { public class HsOfficePersonEntity implements HasUuid, Stringifyable {
private static Stringify<HsOfficePersonEntity> toString = stringify(HsOfficePersonEntity.class, "person") private static Stringify<HsOfficePersonEntity> toString = stringify(HsOfficePersonEntity.class, "person")
.withProp(Fields.personType, HsOfficePersonEntity::getPersonType) .withProp(Fields.personType, HsOfficePersonEntity::getPersonType)
@ -53,6 +54,7 @@ public class HsOfficePersonEntity implements Stringifyable {
@Override @Override
public String toShortString() { public String toShortString() {
return !StringUtils.isEmpty(tradeName) ? tradeName : (familyName + ", " + givenName); return personType + " " +
(!StringUtils.isEmpty(tradeName) ? tradeName : (familyName + ", " + givenName));
} }
} }

View File

@ -1,6 +1,7 @@
package net.hostsharing.hsadminng.hs.office.person; package net.hostsharing.hsadminng.hs.office.person;
public enum HsOfficePersonType { public enum HsOfficePersonType {
UNKNOWN,
NATURAL, NATURAL,
LEGAL, LEGAL,
SOLE_REPRESENTATION, SOLE_REPRESENTATION,

View File

@ -3,8 +3,10 @@ package net.hostsharing.hsadminng.hs.office.relationship;
import lombok.*; import lombok.*;
import lombok.experimental.FieldNameConstants; import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; 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.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.stringify.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.util.UUID; import java.util.UUID;
@ -19,7 +21,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@FieldNameConstants @FieldNameConstants
public class HsOfficeRelationshipEntity { public class HsOfficeRelationshipEntity implements HasUuid, Stringifyable {
private static Stringify<HsOfficeRelationshipEntity> toString = stringify(HsOfficeRelationshipEntity.class, "rel") private static Stringify<HsOfficeRelationshipEntity> toString = stringify(HsOfficeRelationshipEntity.class, "rel")
.withProp(Fields.relAnchor, HsOfficeRelationshipEntity::getRelAnchor) .withProp(Fields.relAnchor, HsOfficeRelationshipEntity::getRelAnchor)
@ -27,6 +29,11 @@ public class HsOfficeRelationshipEntity {
.withProp(Fields.relHolder, HsOfficeRelationshipEntity::getRelHolder) .withProp(Fields.relHolder, HsOfficeRelationshipEntity::getRelHolder)
.withProp(Fields.contact, HsOfficeRelationshipEntity::getContact); .withProp(Fields.contact, HsOfficeRelationshipEntity::getContact);
private static Stringify<HsOfficeRelationshipEntity> toShortString = stringify(HsOfficeRelationshipEntity.class, "rel")
.withProp(Fields.relAnchor, HsOfficeRelationshipEntity::getRelAnchor)
.withProp(Fields.relType, HsOfficeRelationshipEntity::getRelType)
.withProp(Fields.relHolder, HsOfficeRelationshipEntity::getRelHolder);
@Id @Id
@GeneratedValue @GeneratedValue
private UUID uuid; private UUID uuid;
@ -51,4 +58,9 @@ public class HsOfficeRelationshipEntity {
public String toString() { public String toString() {
return toString.apply(this); return toString.apply(this);
} }
@Override
public String toShortString() {
return toShortString.apply(this);
}
} }

View File

@ -1,8 +1,10 @@
package net.hostsharing.hsadminng.hs.office.relationship; package net.hostsharing.hsadminng.hs.office.relationship;
public enum HsOfficeRelationshipType { public enum HsOfficeRelationshipType {
SOLE_AGENT, UNKNOWN,
JOINT_AGENT, EX_PARTNER,
ACCOUNTING_CONTACT, REPRESENTATIVE,
TECHNICAL_CONTACT VIP_CONTACT,
ACCOUNTING,
OPERATIONS
} }

View File

@ -6,6 +6,7 @@ import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName; import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity; 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.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.Type; import org.hibernate.annotations.Type;
@ -25,7 +26,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@DisplayName("SEPA-Mandate") @DisplayName("SEPA-Mandate")
public class HsOfficeSepaMandateEntity implements Stringifyable { public class HsOfficeSepaMandateEntity implements Stringifyable, HasUuid {
private static Stringify<HsOfficeSepaMandateEntity> stringify = stringify(HsOfficeSepaMandateEntity.class) private static Stringify<HsOfficeSepaMandateEntity> stringify = stringify(HsOfficeSepaMandateEntity.class)
.withProp(e -> e.getBankAccount().getIban()) .withProp(e -> e.getBankAccount().getIban())

View File

@ -12,12 +12,19 @@ components:
debitorNumber: debitorNumber:
type: integer type: integer
format: int32 format: int32
minimum: 10000 minimum: 1000000
maximum: 99999 maximum: 9999999
debitorNumberSuffix:
type: integer
format: int8
minimum: 00
maximum: 99
partner: partner:
$ref: './hs-office-partner-schemas.yaml#/components/schemas/HsOfficePartner' $ref: './hs-office-partner-schemas.yaml#/components/schemas/HsOfficePartner'
billingContact: billingContact:
$ref: './hs-office-contact-schemas.yaml#/components/schemas/HsOfficeContact' $ref: './hs-office-contact-schemas.yaml#/components/schemas/HsOfficeContact'
billable:
type: boolean
vatId: vatId:
type: string type: string
vatCountryCode: vatCountryCode:
@ -25,8 +32,13 @@ components:
pattern: '^[A-Z][A-Z]$' pattern: '^[A-Z][A-Z]$'
vatBusiness: vatBusiness:
type: boolean type: boolean
vatReverseCharge:
type: boolean
refundBankAccount: refundBankAccount:
$ref: './hs-office-bankaccount-schemas.yaml#/components/schemas/HsOfficeBankAccount' $ref: './hs-office-bankaccount-schemas.yaml#/components/schemas/HsOfficeBankAccount'
defaultPrefix:
type: string
pattern: '^[a-z0-9]{3}$'
HsOfficeDebitorPatch: HsOfficeDebitorPatch:
type: object type: object
@ -35,6 +47,9 @@ components:
type: string type: string
format: uuid format: uuid
nullable: true nullable: true
billable:
type: boolean
nullable: false
vatId: vatId:
type: string type: string
nullable: true nullable: true
@ -44,11 +59,18 @@ components:
nullable: true nullable: true
vatBusiness: vatBusiness:
type: boolean type: boolean
nullable: true nullable: false
vatReverseCharge:
type: boolean
nullable: false
refundBankAccountUuid: refundBankAccountUuid:
type: string type: string
format: uuid format: uuid
nullable: true nullable: true
defaultPrefix:
type: string
pattern: '^[a-z0-9]{3}$'
nullable: true
HsOfficeDebitorInsert: HsOfficeDebitorInsert:
type: object type: object
@ -61,11 +83,13 @@ components:
type: string type: string
format: uuid format: uuid
nullable: false nullable: false
debitorNumber: debitorNumberSuffix:
type: integer type: integer
format: int32 format: int8
minimum: 10000 minimum: 00
maximum: 99999 maximum: 99
billable:
type: boolean
vatId: vatId:
type: string type: string
vatCountryCode: vatCountryCode:
@ -73,9 +97,17 @@ components:
pattern: '^[A-Z][A-Z]$' pattern: '^[A-Z][A-Z]$'
vatBusiness: vatBusiness:
type: boolean type: boolean
vatReverseCharge:
type: boolean
refundBankAccountUuid: refundBankAccountUuid:
type: string type: string
format: uuid format: uuid
defaultPrefix:
type: string
pattern: '^[a-z]{3}$'
required: required:
- partnerUuid - partnerUuid
- billingContactUuid - billingContactUuid
- defaultPrefix
- billable

View File

@ -33,6 +33,8 @@ components:
format: date format: date
reasonForTermination: reasonForTermination:
$ref: '#/components/schemas/HsOfficeReasonForTermination' $ref: '#/components/schemas/HsOfficeReasonForTermination'
membershipFeeBillable:
type: boolean
HsOfficeMembershipPatch: HsOfficeMembershipPatch:
type: object type: object
@ -48,6 +50,9 @@ components:
reasonForTermination: reasonForTermination:
nullable: true nullable: true
$ref: '#/components/schemas/HsOfficeReasonForTermination' $ref: '#/components/schemas/HsOfficeReasonForTermination'
membershipFeeBillable:
nullable: true
type: boolean
additionalProperties: false additionalProperties: false
HsOfficeMembershipInsert: HsOfficeMembershipInsert:
@ -74,8 +79,12 @@ components:
nullable: true nullable: true
reasonForTermination: reasonForTermination:
$ref: '#/components/schemas/HsOfficeReasonForTermination' $ref: '#/components/schemas/HsOfficeReasonForTermination'
membershipFeeBillable:
nullable: false
type: boolean
required: required:
- partnerUuid - partnerUuid
- mainDebitorUuid - mainDebitorUuid
- validFrom - validFrom
- membershipFeeBillable
additionalProperties: false additionalProperties: false

View File

@ -9,6 +9,11 @@ components:
uuid: uuid:
type: string type: string
format: uuid format: uuid
debitorNumberPrefix:
type: integer
format: int8
minimum: 10000
maximum: 99999
person: person:
$ref: './hs-office-person-schemas.yaml#/components/schemas/HsOfficePerson' $ref: './hs-office-person-schemas.yaml#/components/schemas/HsOfficePerson'
contact: contact:
@ -32,6 +37,9 @@ components:
birthName: birthName:
type: string type: string
nullable: true nullable: true
birthPlace:
type: string
nullable: true
birthday: birthday:
type: string type: string
format: date format: date
@ -68,6 +76,9 @@ components:
birthName: birthName:
type: string type: string
nullable: true nullable: true
birthPlace:
type: string
nullable: true
birthday: birthday:
type: string type: string
format: date format: date
@ -80,6 +91,11 @@ components:
HsOfficePartnerInsert: HsOfficePartnerInsert:
type: object type: object
properties: properties:
debitorNumberPrefix:
type: integer
format: int8
minimum: 10000
maximum: 99999
personUuid: personUuid:
type: string type: string
format: uuid format: uuid
@ -89,6 +105,7 @@ components:
details: details:
$ref: '#/components/schemas/HsOfficePartnerDetailsInsert' $ref: '#/components/schemas/HsOfficePartnerDetailsInsert'
required: required:
- debitorNumberPrefix
- personUuid - personUuid
- contactUuid - contactUuid
- details - details
@ -106,6 +123,9 @@ components:
birthName: birthName:
type: string type: string
nullable: true nullable: true
birthPlace:
type: string
nullable: true
birthday: birthday:
type: string type: string
format: date format: date

View File

@ -6,10 +6,12 @@ components:
HsOfficeRelationshipType: HsOfficeRelationshipType:
type: string type: string
enum: enum:
- SOLE_AGENT # e.g. CEO - UNKNOWN
- JOINT_AGENT # e.g. heir - EX_PARTNER
- ACCOUNTING_CONTACT - REPRESENTATIVE,
- TECHNICAL_CONTACT - VIP_CONTACT
- ACCOUNTING,
- OPERATIONS
HsOfficeRelationship: HsOfficeRelationship:
type: object type: object

View File

@ -20,3 +20,7 @@ spring:
liquibase: liquibase:
contexts: dev contexts: dev
hsadminng:
postgres:
leakproof:

View File

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

View File

@ -55,7 +55,7 @@ end; $$;
*/ */
create or replace function currentTask() create or replace function currentTask()
returns varchar(96) returns varchar(96)
stable leakproof stable -- leakproof
language plpgsql as $$ language plpgsql as $$
declare declare
currentTask varchar(96); currentTask varchar(96);
@ -83,7 +83,7 @@ end; $$;
*/ */
create or replace function currentRequest() create or replace function currentRequest()
returns varchar(512) returns varchar(512)
stable leakproof stable -- leakproof
language plpgsql as $$ language plpgsql as $$
declare declare
currentRequest varchar(512); currentRequest varchar(512);
@ -107,7 +107,7 @@ end; $$;
*/ */
create or replace function currentUser() create or replace function currentUser()
returns varchar(63) returns varchar(63)
stable leakproof stable -- leakproof
language plpgsql as $$ language plpgsql as $$
declare declare
currentUser varchar(63); currentUser varchar(63);
@ -131,7 +131,7 @@ end; $$;
*/ */
create or replace function assumedRoles() create or replace function assumedRoles()
returns varchar(63)[] returns varchar(63)[]
stable leakproof stable -- leakproof
language plpgsql as $$ language plpgsql as $$
declare declare
currentSubject varchar(63); currentSubject varchar(63);
@ -155,7 +155,7 @@ create or replace function cleanIdentifier(rawIdentifier varchar)
declare declare
cleanIdentifier varchar; cleanIdentifier varchar;
begin begin
cleanIdentifier := regexp_replace(rawIdentifier, '[^A-Za-z0-9\-._]+', '', 'g'); cleanIdentifier := regexp_replace(rawIdentifier, '[^A-Za-z0-9\-._:]+', '', 'g');
return cleanIdentifier; return cleanIdentifier;
end; $$; end; $$;
@ -214,7 +214,7 @@ end ; $$;
create or replace function currentSubjects() create or replace function currentSubjects()
returns varchar(63)[] returns varchar(63)[]
stable leakproof stable -- leakproof
language plpgsql as $$ language plpgsql as $$
declare declare
assumedRoles varchar(63)[]; assumedRoles varchar(63)[];
@ -229,7 +229,7 @@ end; $$;
create or replace function hasAssumedRole() create or replace function hasAssumedRole()
returns boolean returns boolean
stable leakproof stable -- leakproof
language plpgsql as $$ language plpgsql as $$
begin begin
return array_length(assumedRoles(), 1) > 0; return array_length(assumedRoles(), 1) > 0;

View File

@ -208,7 +208,7 @@ create type RbacRoleDescriptor as
create or replace function roleDescriptor(objectTable varchar(63), objectUuid uuid, roleType RbacRoleType) create or replace function roleDescriptor(objectTable varchar(63), objectUuid uuid, roleType RbacRoleType)
returns RbacRoleDescriptor returns RbacRoleDescriptor
returns null on null input returns null on null input
stable leakproof stable -- leakproof
language sql as $$ language sql as $$
select objectTable, objectUuid, roleType::RbacRoleType; select objectTable, objectUuid, roleType::RbacRoleType;
$$; $$;
@ -432,7 +432,7 @@ $$;
create or replace function findPermissionId(forObjectUuid uuid, forOp RbacOp) create or replace function findPermissionId(forObjectUuid uuid, forOp RbacOp)
returns uuid returns uuid
returns null on null input returns null on null input
stable leakproof stable -- leakproof
language sql as $$ language sql as $$
select uuid select uuid
from RbacPermission p from RbacPermission p
@ -515,7 +515,7 @@ end; $$;
create or replace function isPermissionGrantedToSubject(permissionId uuid, subjectId uuid) create or replace function isPermissionGrantedToSubject(permissionId uuid, subjectId uuid)
returns BOOL returns BOOL
stable leakproof stable -- leakproof
language sql as $$ language sql as $$
select exists( select exists(
select * select *
@ -537,7 +537,7 @@ $$;
create or replace function hasGlobalRoleGranted(userUuid uuid) create or replace function hasGlobalRoleGranted(userUuid uuid)
returns bool returns bool
stable leakproof stable -- leakproof
language sql as $$ language sql as $$
select exists( select exists(
select r.uuid 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; do $$
grant all privileges on all tables in schema public to admin; begin
if '${HSADMINNG_POSTGRES_ADMIN_USERNAME}'='admin' then
create role restricted; create role admin;
grant all privileges on all tables in schema public to restricted; 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 $$
--// --//

View File

@ -6,7 +6,7 @@
create or replace function assumedRoleUuid() create or replace function assumedRoleUuid()
returns uuid returns uuid
stable leakproof stable -- leakproof
language plpgsql as $$ language plpgsql as $$
declare declare
currentSubjectsUuids uuid[]; currentSubjectsUuids uuid[];

View File

@ -7,7 +7,7 @@
create or replace function determineCurrentUserUuid(currentUser varchar) create or replace function determineCurrentUserUuid(currentUser varchar)
returns uuid returns uuid
stable leakproof stable -- leakproof
language plpgsql as $$ language plpgsql as $$
declare declare
currentUserUuid uuid; currentUserUuid uuid;
@ -25,11 +25,11 @@ end; $$;
create or replace function determineCurrentSubjectsUuids(currentUserUuid uuid, assumedRoles varchar) create or replace function determineCurrentSubjectsUuids(currentUserUuid uuid, assumedRoles varchar)
returns uuid[] returns uuid[]
stable leakproof stable -- leakproof
language plpgsql as $$ language plpgsql as $$
declare declare
roleName varchar(63); roleName text;
roleNameParts varchar(63); roleNameParts text;
objectTableToAssume varchar(63); objectTableToAssume varchar(63);
objectNameToAssume varchar(63); objectNameToAssume varchar(63);
objectUuidToAssume uuid; objectUuidToAssume uuid;
@ -116,7 +116,7 @@ end; $$;
create or replace function currentUserUuid() create or replace function currentUserUuid()
returns uuid returns uuid
stable leakproof stable -- leakproof
language plpgsql as $$ language plpgsql as $$
declare declare
currentUserUuid text; currentUserUuid text;
@ -150,7 +150,7 @@ end; $$;
*/ */
create or replace function currentSubjectsUuids() create or replace function currentSubjectsUuids()
returns uuid[] returns uuid[]
stable leakproof stable -- leakproof
language plpgsql as $$ language plpgsql as $$
declare declare
currentSubjectsUuids text; currentSubjectsUuids text;

View File

@ -41,7 +41,7 @@ select *
) as unordered ) as unordered
-- @formatter:on -- @formatter:on
order by objectTable || '#' || objectIdName || '.' || roleType; 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 join RbacObject as o on o.uuid = r.objectUuid
order by grantedRoleIdName; order by grantedRoleIdName;
-- @formatter:on -- @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 ) as unordered
-- @formatter:on -- @formatter:on
order by unordered.name; 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 rbacgrants g on g.ascendantuuid = r.uuid
join rbacpermission p on p.uuid = g.descendantuuid join rbacpermission p on p.uuid = g.descendantuuid
join rbacobject o on o.uuid = p.objectuuid; 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 -- @formatter:om
-- ============================================================================ -- ============================================================================

View File

@ -104,7 +104,7 @@ begin
create or replace view %1$s_iv as create or replace view %1$s_iv as
select target.uuid, cleanIdentifier(%2$s) as idName select target.uuid, cleanIdentifier(%2$s) as idName
from %1$s as target; 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); $sql$, targetTable, idNameExpression);
execute sql; execute sql;
@ -157,7 +157,7 @@ begin
from %1$s as target from %1$s as target
where target.uuid in (select * from accessibleObjects) where target.uuid in (select * from accessibleObjects)
order by %2$s; 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); $sql$, targetTable, orderBy);
execute sql; execute sql;

View File

@ -18,7 +18,7 @@ create table Global
); );
create unique index Global_Singleton on Global ((0)); 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 create or replace view global_iv as
select target.uuid, target.name as idName select target.uuid, target.name as idName
from global as target; 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). Returns the objectUuid for a given identifying name (in this case the idName).
@ -99,7 +99,7 @@ commit;
create or replace function globalAdmin() create or replace function globalAdmin()
returns RbacRoleDescriptor returns RbacRoleDescriptor
returns null on null input returns null on null input
stable leakproof stable -- leakproof
language sql as $$ language sql as $$
select 'global', (select uuid from RbacObject where objectTable = 'global'), 'admin'::RbacRoleType; select 'global', (select uuid from RbacObject where objectTable = 'global'), 'admin'::RbacRoleType;
$$; $$;

View File

@ -93,7 +93,7 @@ call generateRbacIdentityView('test_package', 'target.name');
-- from test_package as target -- from test_package as target
-- where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'test_package', currentSubjectsUuids())) -- where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'test_package', currentSubjectsUuids()))
-- order by target.name; -- 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', call generateRbacRestrictedView('test_package', 'target.name',
$updates$ $updates$

View File

@ -110,5 +110,5 @@ create or replace view test_domain_rv as
select target.* select target.*
from test_domain as target from test_domain as target
where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'domain', currentSubjectsUuids())); 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};
--// --//

View File

@ -7,7 +7,7 @@
create table if not exists hs_office_contact create table if not exists hs_office_contact
( (
uuid uuid unique references RbacObject (uuid) initially deferred, uuid uuid unique references RbacObject (uuid) initially deferred,
label varchar(96) not null, label varchar(128) not null,
postalAddress text, postalAddress text,
emailAddresses text, -- TODO.feat: change to json emailAddresses text, -- TODO.feat: change to json
phoneNumbers text -- TODO.feat: change to json phoneNumbers text -- TODO.feat: change to json

View File

@ -26,9 +26,6 @@ create or replace function createRbacRolesForHsOfficeContact()
returns trigger returns trigger
language plpgsql language plpgsql
strict as $$ strict as $$
declare
ownerRole uuid;
adminRole uuid;
begin begin
if TG_OP <> 'INSERT' then if TG_OP <> 'INSERT' then
raise exception 'invalid usage of TRIGGER AFTER INSERT'; raise exception 'invalid usage of TRIGGER AFTER INSERT';
@ -107,7 +104,7 @@ do language plpgsql $$
declare declare
addCustomerPermissions uuid[]; addCustomerPermissions uuid[];
globalObjectUuid uuid; globalObjectUuid uuid;
globalAdminRoleUuid uuid ; globalAdminRoleUuid uuid;
begin begin
call defineContext('granting global new-contact permission to global admin role', null, null, null); call defineContext('granting global new-contact permission to global admin role', null, null, null);

View File

@ -4,7 +4,7 @@
--changeset hs-office-person-MAIN-TABLE:1 endDelimiter:--// --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; CREATE CAST (character varying as HsOfficePersonType) WITH INOUT AS IMPLICIT;

View File

@ -37,6 +37,7 @@ begin
grantedByRole => globalAdmin() 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( perform createRoleWithGrants(
hsOfficePersonAdmin(NEW), hsOfficePersonAdmin(NEW),
permissions => array['edit'], permissions => array['edit'],

View File

@ -10,6 +10,7 @@ create table hs_office_partner_details
uuid uuid unique references RbacObject (uuid) initially deferred, uuid uuid unique references RbacObject (uuid) initially deferred,
registrationOffice varchar(96), registrationOffice varchar(96),
registrationNumber varchar(96), registrationNumber varchar(96),
birthPlace varchar(96),
birthName varchar(96), birthName varchar(96),
birthday date, birthday date,
dateOfDeath date dateOfDeath date
@ -31,6 +32,7 @@ call create_journal('hs_office_partner_details');
create table hs_office_partner create table hs_office_partner
( (
uuid uuid unique references RbacObject (uuid) initially deferred, uuid uuid unique references RbacObject (uuid) initially deferred,
debitorNumberPrefix varchar(5),
personUuid uuid not null references hs_office_person(uuid), personUuid uuid not null references hs_office_person(uuid),
contactUuid uuid not null references hs_office_contact(uuid), contactUuid uuid not null references hs_office_contact(uuid),
detailsUuid uuid not null references hs_office_partner_details(uuid) on delete cascade detailsUuid uuid not null references hs_office_partner_details(uuid) on delete cascade

View File

@ -27,7 +27,6 @@ create or replace function hsOfficePartnerRbacRolesTrigger()
language plpgsql language plpgsql
strict as $$ strict as $$
declare declare
hsOfficePartnerTenant RbacRoleDescriptor;
oldPerson hs_office_person; oldPerson hs_office_person;
newPerson hs_office_person; newPerson hs_office_person;
oldContact hs_office_contact; oldContact hs_office_contact;
@ -166,6 +165,8 @@ execute procedure hsOfficePartnerRbacRolesTrigger();
--changeset hs-office-partner-rbac-IDENTITY-VIEW:1 endDelimiter:--// --changeset hs-office-partner-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacIdentityView('hs_office_partner', $idName$ 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_person_iv p where p.uuid = target.personuuid)
|| '-' || || '-' ||
(select idName from hs_office_contact_iv c where c.uuid = target.contactuuid) (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', call generateRbacRestrictedView('hs_office_partner',
'(select idName from hs_office_person_iv p where p.uuid = target.personUuid)', '(select idName from hs_office_person_iv p where p.uuid = target.personUuid)',
$updates$ $updates$
debitorNumberPrefix = new.debitorNumberPrefix,
personUuid = new.personUuid, personUuid = new.personUuid,
contactUuid = new.contactUuid contactUuid = new.contactUuid
$updates$); $updates$);

View File

@ -29,6 +29,7 @@ call generateRbacRestrictedView('hs_office_partner_details',
$updates$ $updates$
registrationOffice = new.registrationOffice, registrationOffice = new.registrationOffice,
registrationNumber = new.registrationNumber, registrationNumber = new.registrationNumber,
birthPlace = new.birthPlace,
birthName = new.birthName, birthName = new.birthName,
birthday = new.birthday, birthday = new.birthday,
dateOfDeath = new.dateOfDeath dateOfDeath = new.dateOfDeath

View File

@ -8,7 +8,10 @@
/* /*
Creates a single partner test record. 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 $$ language plpgsql as $$
declare declare
currentTask varchar; currentTask varchar;
@ -51,8 +54,8 @@ begin
end if; end if;
insert insert
into hs_office_partner (uuid, personuuid, contactuuid, detailsUuid) into hs_office_partner (uuid, debitorNumberPrefix, personuuid, contactuuid, detailsUuid)
values (uuid_generate_v4(), relatedPerson.uuid, relatedContact.uuid, relatedDetailsUuid); values (uuid_generate_v4(), debitorNumberPrefix, relatedPerson.uuid, relatedContact.uuid, relatedDetailsUuid);
end; $$; end; $$;
--// --//
@ -64,11 +67,11 @@ end; $$;
do language plpgsql $$ do language plpgsql $$
begin begin
call createHsOfficePartnerTestData('First GmbH', 'first contact'); call createHsOfficePartnerTestData(10001, 'First GmbH', 'first contact');
call createHsOfficePartnerTestData('Second e.K.', 'second contact'); call createHsOfficePartnerTestData(10002, 'Second e.K.', 'second contact');
call createHsOfficePartnerTestData('Third OHG', 'third contact'); call createHsOfficePartnerTestData(10003, 'Third OHG', 'third contact');
call createHsOfficePartnerTestData('Fourth e.G.', 'forth contact'); call createHsOfficePartnerTestData(10004, 'Fourth e.G.', 'forth contact');
call createHsOfficePartnerTestData('Smith', 'fifth contact'); call createHsOfficePartnerTestData(10010, 'Smith', 'fifth contact');
end; end;
$$; $$;
--// --//

View File

@ -4,7 +4,13 @@
--changeset hs-office-relationship-MAIN-TABLE:1 endDelimiter:--// --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; CREATE CAST (character varying as HsOfficeRelationshipType) WITH INOUT AS IMPLICIT;

View File

@ -58,7 +58,7 @@ begin
select p.* from hs_office_person p where tradeName = intToVarChar(t, 4) into person; 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; 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; commit;
end loop; end loop;
end; $$; end; $$;
@ -71,11 +71,11 @@ end; $$;
do language plpgsql $$ do language plpgsql $$
begin 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; end;
$$; $$;
--// --//

View File

@ -6,7 +6,7 @@
create table hs_office_bankaccount create table hs_office_bankaccount
( (
uuid uuid unique references RbacObject (uuid) initially deferred, uuid uuid unique references RbacObject (uuid) initially deferred,
holder varchar(27) not null, holder varchar(64) not null,
iban varchar(34) not null, iban varchar(34) not null,
bic varchar(11) not null bic varchar(11) not null
); );

View File

@ -10,7 +10,7 @@
CREATE TABLE hs_office_sepamandate_legacy_id CREATE TABLE hs_office_sepamandate_legacy_id
( (
uuid uuid NOT NULL REFERENCES hs_office_sepamandate(uuid), 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 CREATE SEQUENCE IF NOT EXISTS hs_office_sepamandate_legacy_id_seq
AS integer AS integer
START 1000000000 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 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'); SET DEFAULT nextVal('hs_office_sepamandate_legacy_id_seq');
--/ --/
@ -42,7 +42,7 @@ ALTER TABLE hs_office_sepamandate_legacy_id
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
CALL defineContext('schema-migration'); 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; SELECT uuid, nextVal('hs_office_sepamandate_legacy_id_seq') FROM hs_office_sepamandate;
--/ --/

View File

@ -8,12 +8,18 @@ create table hs_office_debitor
( (
uuid uuid unique references RbacObject (uuid) initially deferred, uuid uuid unique references RbacObject (uuid) initially deferred,
partnerUuid uuid not null references hs_office_partner(uuid), 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), billingContactUuid uuid not null references hs_office_contact(uuid),
vatId varchar(24), -- TODO.spec: here or in person? vatId varchar(24), -- TODO.spec: here or in person?
vatCountryCode varchar(2), vatCountryCode varchar(2),
vatBusiness boolean not null, -- TODO.spec: more of such? vatBusiness boolean not null,
refundBankAccountUuid uuid references hs_office_bankaccount(uuid) 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 -- TODO.impl: SEPA-mandate
); );
--// --//

View File

@ -172,8 +172,10 @@ execute procedure hsOfficeDebitorRbacRolesTrigger();
--changeset hs-office-debitor-rbac-IDENTITY-VIEW:1 endDelimiter:--// --changeset hs-office-debitor-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacIdentityView('hs_office_debitor', $idName$ 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$); $idName$);
--// --//
@ -181,14 +183,18 @@ call generateRbacIdentityView('hs_office_debitor', $idName$
-- ============================================================================ -- ============================================================================
--changeset hs-office-debitor-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset hs-office-debitor-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_debitor', 'target.debitorNumber', call generateRbacRestrictedView('hs_office_debitor', 'target.debitorNumberSuffix',
$updates$ $updates$
partnerUuid = new.partnerUuid, partnerUuid = new.partnerUuid, -- TODO: remove? should never do anything
billable = new.billable,
billingContactUuid = new.billingContactUuid, billingContactUuid = new.billingContactUuid,
debitorNumberSuffix = new.debitorNumberSuffix, -- TODO: Should it be allowed to updated this value?
refundBankAccountUuid = new.refundBankAccountUuid, refundBankAccountUuid = new.refundBankAccountUuid,
vatId = new.vatId, vatId = new.vatId,
vatCountryCode = new.vatCountryCode, 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$); $updates$);
--// --//

View File

@ -8,7 +8,12 @@
/* /*
Creates a single debitor test record. 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 $$ language plpgsql as $$
declare declare
currentTask varchar; currentTask varchar;
@ -16,7 +21,6 @@ declare
relatedPartner hs_office_partner; relatedPartner hs_office_partner;
relatedContact hs_office_contact; relatedContact hs_office_contact;
relatedBankAccountUuid uuid; relatedBankAccountUuid uuid;
newDebitorNumber numeric(6);
begin begin
idName := cleanIdentifier( partnerTradeName|| '-' || billingContactLabel); idName := cleanIdentifier( partnerTradeName|| '-' || billingContactLabel);
currentTask := 'creating debitor test-data ' || idName; currentTask := 'creating debitor test-data ' || idName;
@ -28,14 +32,13 @@ begin
where person.tradeName = partnerTradeName into relatedPartner; where person.tradeName = partnerTradeName into relatedPartner;
select c.* from hs_office_contact c where c.label = billingContactLabel into relatedContact; 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 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 partner (%): %', relatedPartner.uuid, relatedPartner;
raise notice '- using billingContact (%): %', relatedContact.uuid, relatedContact; raise notice '- using billingContact (%): %', relatedContact.uuid, relatedContact;
insert insert
into hs_office_debitor (uuid, partneruuid, debitornumber, billingcontactuuid, vatbusiness, refundbankaccountuuid) into hs_office_debitor (uuid, partneruuid, debitornumbersuffix, billable, billingcontactuuid, vatbusiness, vatreversecharge, refundbankaccountuuid, defaultprefix)
values (uuid_generate_v4(), relatedPartner.uuid, newDebitorNumber, relatedContact.uuid, true, relatedBankAccountUuid); values (uuid_generate_v4(), relatedPartner.uuid, debitorNumberSuffix, true, relatedContact.uuid, true, false, relatedBankAccountUuid, defaultPrefix);
end; $$; end; $$;
--// --//
@ -46,9 +49,9 @@ end; $$;
do language plpgsql $$ do language plpgsql $$
begin begin
call createHsOfficeDebitorTestData('First GmbH', 'first contact'); call createHsOfficeDebitorTestData(11, 'First GmbH', 'first contact', 'fir');
call createHsOfficeDebitorTestData('Second e.K.', 'second contact'); call createHsOfficeDebitorTestData(12, 'Second e.K.', 'second contact', 'sec');
call createHsOfficeDebitorTestData('Third OHG', 'third contact'); call createHsOfficeDebitorTestData(13, 'Third OHG', 'third contact', 'thi');
end; end;
$$; $$;
--// --//

View File

@ -4,7 +4,7 @@
--changeset hs-office-membership-MAIN-TABLE:1 endDelimiter:--// --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; 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), mainDebitorUuid uuid not null references hs_office_debitor(uuid),
memberNumber numeric(5) not null unique, memberNumber numeric(5) not null unique,
validity daterange not null, validity daterange not null,
reasonForTermination HsOfficeReasonForTermination not null default 'NONE' reasonForTermination HsOfficeReasonForTermination not null default 'NONE',
membershipFeeBillable boolean not null default true
); );
--// --//

View File

@ -92,7 +92,8 @@ execute procedure hsOfficeMembershipRbacRolesTrigger();
--changeset hs-office-membership-rbac-IDENTITY-VIEW:1 endDelimiter:--// --changeset hs-office-membership-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacIdentityView('hs_office_membership', idNameExpression => $idName$ 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$); $idName$);
--// --//
@ -104,7 +105,8 @@ call generateRbacRestrictedView('hs_office_membership',
orderby => 'target.memberNumber', orderby => 'target.memberNumber',
columnUpdates => $updates$ columnUpdates => $updates$
validity = new.validity, validity = new.validity,
reasonForTermination = new.reasonForTermination reasonForTermination = new.reasonForTermination,
membershipFeeBillable = new.membershipFeeBillable
$updates$); $updates$);
--// --//

View File

@ -25,7 +25,7 @@ begin
select partner.* from hs_office_partner partner select partner.* from hs_office_partner partner
join hs_office_person person on person.uuid = partner.personUuid join hs_office_person person on person.uuid = partner.personUuid
where person.tradeName = forPartnerTradeName into relatedPartner; 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; select coalesce(max(memberNumber)+1, 10001) from hs_office_membership into newMemberNumber;
raise notice 'creating test Membership: %', idName; raise notice 'creating test Membership: %', idName;
@ -44,9 +44,9 @@ end; $$;
do language plpgsql $$ do language plpgsql $$
begin begin
call createHsOfficeMembershipTestData('First GmbH', 10001); call createHsOfficeMembershipTestData('First GmbH', 11);
call createHsOfficeMembershipTestData('Second e.K.', 10002); call createHsOfficeMembershipTestData('Second e.K.', 12);
call createHsOfficeMembershipTestData('Third OHG', 10003); call createHsOfficeMembershipTestData('Third OHG', 13);
end; end;
$$; $$;
--// --//

View File

@ -10,7 +10,8 @@ CREATE TYPE HsOfficeCoopAssetsTransactionType AS ENUM ('ADJUSTMENT',
'TRANSFER', 'TRANSFER',
'ADOPTION', 'ADOPTION',
'CLEARING', 'CLEARING',
'LOSS'); 'LOSS',
'LIMITATION');
CREATE CAST (character varying as HsOfficeCoopAssetsTransactionType) WITH INOUT AS IMPLICIT; CREATE CAST (character varying as HsOfficeCoopAssetsTransactionType) WITH INOUT AS IMPLICIT;

View File

@ -11,6 +11,8 @@ databaseChangeLog:
file: db/changelog/005-uuid-ossp-extension.sql file: db/changelog/005-uuid-ossp-extension.sql
- include: - include:
file: db/changelog/006-numeric-hash-functions.sql file: db/changelog/006-numeric-hash-functions.sql
- include:
file: db/changelog/009-check-environment.sql
- include: - include:
file: db/changelog/010-context.sql file: db/changelog/010-context.sql
- include: - include:

View File

@ -30,16 +30,17 @@ public class ArchitectureTest {
"..test.pac", "..test.pac",
"..context", "..context",
"..generated..", "..generated..",
"..hs.office.person",
"..hs.office.partner",
"..hs.office.bankaccount", "..hs.office.bankaccount",
"..hs.office.debitor",
"..hs.office.relationship",
"..hs.office.contact", "..hs.office.contact",
"..hs.office.sepamandate",
"..hs.office.coopassets", "..hs.office.coopassets",
"..hs.office.coopshares", "..hs.office.coopshares",
"..hs.office.debitor",
"..hs.office.membership", "..hs.office.membership",
"..hs.office.migration",
"..hs.office.partner",
"..hs.office.person",
"..hs.office.relationship",
"..hs.office.sepamandate",
"..errors", "..errors",
"..mapper", "..mapper",
"..ping", "..ping",
@ -121,14 +122,19 @@ public class ArchitectureTest {
public static final ArchRule hsOfficeBankAccountPackageRule = classes() public static final ArchRule hsOfficeBankAccountPackageRule = classes()
.that().resideInAPackage("..hs.office.bankaccount..") .that().resideInAPackage("..hs.office.bankaccount..")
.should().onlyBeAccessed().byClassesThat() .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 @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static final ArchRule hsOfficeSepaMandatePackageRule = classes() public static final ArchRule hsOfficeSepaMandatePackageRule = classes()
.that().resideInAPackage("..hs.office.sepamandate..") .that().resideInAPackage("..hs.office.sepamandate..")
.should().onlyBeAccessed().byClassesThat() .should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage("..hs.office.sepamandate..", "..hs.office.debitor.."); .resideInAnyPackage("..hs.office.sepamandate..",
"..hs.office.debitor..",
"..hs.office.migration..");
@ArchTest @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -136,7 +142,10 @@ public class ArchitectureTest {
.that().resideInAPackage("..hs.office.contact..") .that().resideInAPackage("..hs.office.contact..")
.should().onlyBeAccessed().byClassesThat() .should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage("..hs.office.contact..", "..hs.office.relationship..", .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 @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -144,42 +153,63 @@ public class ArchitectureTest {
.that().resideInAPackage("..hs.office.person..") .that().resideInAPackage("..hs.office.person..")
.should().onlyBeAccessed().byClassesThat() .should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage("..hs.office.person..", "..hs.office.relationship..", .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 @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static final ArchRule hsOfficeRelationshipPackageRule = classes() public static final ArchRule hsOfficeRelationshipPackageRule = classes()
.that().resideInAPackage("..hs.office.relationship..") .that().resideInAPackage("..hs.office.relationship..")
.should().onlyBeAccessed().byClassesThat() .should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage("..hs.office.relationship.."); .resideInAnyPackage("..hs.office.relationship..",
"..hs.office.migration..");
@ArchTest @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static final ArchRule hsOfficePartnerPackageRule = classes() public static final ArchRule hsOfficePartnerPackageRule = classes()
.that().resideInAPackage("..hs.office.partner..") .that().resideInAPackage("..hs.office.partner..")
.should().onlyBeAccessed().byClassesThat() .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 @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static final ArchRule hsOfficeMembershipPackageRule = classes() public static final ArchRule hsOfficeMembershipPackageRule = classes()
.that().resideInAPackage("..hs.office.membership..") .that().resideInAPackage("..hs.office.membership..")
.should().onlyBeAccessed().byClassesThat() .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 @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static final ArchRule hsOfficeCoopAssetsPackageRule = classes() public static final ArchRule hsOfficeCoopAssetsPackageRule = classes()
.that().resideInAPackage("..hs.office.coopassets..") .that().resideInAPackage("..hs.office.coopassets..")
.should().onlyBeAccessed().byClassesThat() .should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage("..hs.office.coopassets.."); .resideInAnyPackage(
"..hs.office.coopassets..",
"..hs.office.migration..");
@ArchTest @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static final ArchRule hsOfficeCoopSharesPackageRule = classes() public static final ArchRule hsOfficeCoopSharesPackageRule = classes()
.that().resideInAPackage("..hs.office.coopshares..") .that().resideInAPackage("..hs.office.coopshares..")
.should().onlyBeAccessed().byClassesThat() .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 @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -197,7 +227,7 @@ public class ArchitectureTest {
@ArchTest @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static final ArchRule doNotUsejakartaTransactionAnnotationAtClassLevel = noClasses() public static final ArchRule doNotUseJakartaTransactionAnnotationAtClassLevel = noClasses()
.should().beAnnotatedWith(jakarta.transaction.Transactional.class.getName()) .should().beAnnotatedWith(jakarta.transaction.Transactional.class.getName())
.as("Use @%s instead of @%s.".formatted( .as("Use @%s instead of @%s.".formatted(
org.springframework.transaction.annotation.Transactional.class.getName(), org.springframework.transaction.annotation.Transactional.class.getName(),
@ -205,7 +235,7 @@ public class ArchitectureTest {
@ArchTest @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static final ArchRule doNotUsejakartaTransactionAnnotationAtMethodLevel = noMethods() public static final ArchRule doNotUseJakartaTransactionAnnotationAtMethodLevel = noMethods()
.should().beAnnotatedWith(jakarta.transaction.Transactional.class) .should().beAnnotatedWith(jakarta.transaction.Transactional.class)
.as("Use @%s instead of @%s.".formatted( .as("Use @%s instead of @%s.".formatted(
org.springframework.transaction.annotation.Transactional.class.getName(), org.springframework.transaction.annotation.Transactional.class.getName(),

View File

@ -17,6 +17,7 @@ class HsOfficeCoopAssetsTransactionEntityUnitTest {
.transactionType(HsOfficeCoopAssetsTransactionType.DEPOSIT) .transactionType(HsOfficeCoopAssetsTransactionType.DEPOSIT)
.assetValue(new BigDecimal("128.00")) .assetValue(new BigDecimal("128.00"))
.build(); .build();
final HsOfficeCoopAssetsTransactionEntity givenEmptyCoopAssetsTransaction = HsOfficeCoopAssetsTransactionEntity.builder().build();
@Test @Test
void toStringContainsAlmostAllPropertiesAccount() { void toStringContainsAlmostAllPropertiesAccount() {
@ -31,4 +32,18 @@ class HsOfficeCoopAssetsTransactionEntityUnitTest {
assertThat(result).isEqualTo("300001+128.00"); 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");
}
} }

View File

@ -117,7 +117,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
.map(s -> s.replace("hs_office_", "")) .map(s -> s.replace("hs_office_", ""))
.containsExactlyInAnyOrder(Array.fromFormatted( .containsExactlyInAnyOrder(Array.fromFormatted(
initialGrantNames, 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)); null));
} }
@ -144,17 +144,17 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
// then // then
allTheseCoopAssetsTransactionsAreReturned( allTheseCoopAssetsTransactionsAreReturned(
result, result,
"CoopAssetsTransaction(10001, 2010-03-15, DEPOSIT, 320.00, ref 10001-1)", "CoopAssetsTransaction(10001, 2010-03-15, DEPOSIT, 320.00, ref 10001-1, initial deposit)",
"CoopAssetsTransaction(10001, 2021-09-01, DISBURSAL, -128.00, ref 10001-2)", "CoopAssetsTransaction(10001, 2021-09-01, DISBURSAL, -128.00, ref 10001-2, partial disbursal)",
"CoopAssetsTransaction(10001, 2022-10-20, ADJUSTMENT, 128.00, ref 10001-3)", "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, 2010-03-15, DEPOSIT, 320.00, ref 10002-1, initial deposit)",
"CoopAssetsTransaction(10002, 2021-09-01, DISBURSAL, -128.00, ref 10002-2)", "CoopAssetsTransaction(10002, 2021-09-01, DISBURSAL, -128.00, ref 10002-2, partial disbursal)",
"CoopAssetsTransaction(10002, 2022-10-20, ADJUSTMENT, 128.00, ref 10002-3)", "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, 2010-03-15, DEPOSIT, 320.00, ref 10003-1, initial deposit)",
"CoopAssetsTransaction(10003, 2021-09-01, DISBURSAL, -128.00, ref 10003-2)", "CoopAssetsTransaction(10003, 2021-09-01, DISBURSAL, -128.00, ref 10003-2, partial disbursal)",
"CoopAssetsTransaction(10003, 2022-10-20, ADJUSTMENT, 128.00, ref 10003-3)"); "CoopAssetsTransaction(10003, 2022-10-20, ADJUSTMENT, 128.00, ref 10003-3, some adjustment)");
} }
@Test @Test
@ -173,9 +173,9 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
// then // then
allTheseCoopAssetsTransactionsAreReturned( allTheseCoopAssetsTransactionsAreReturned(
result, result,
"CoopAssetsTransaction(10002, 2010-03-15, DEPOSIT, 320.00, ref 10002-1)", "CoopAssetsTransaction(10002, 2010-03-15, DEPOSIT, 320.00, ref 10002-1, initial deposit)",
"CoopAssetsTransaction(10002, 2021-09-01, DISBURSAL, -128.00, ref 10002-2)", "CoopAssetsTransaction(10002, 2021-09-01, DISBURSAL, -128.00, ref 10002-2, partial disbursal)",
"CoopAssetsTransaction(10002, 2022-10-20, ADJUSTMENT, 128.00, ref 10002-3)"); "CoopAssetsTransaction(10002, 2022-10-20, ADJUSTMENT, 128.00, ref 10002-3, some adjustment)");
} }
@Test @Test
@ -194,14 +194,13 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
// then // then
allTheseCoopAssetsTransactionsAreReturned( allTheseCoopAssetsTransactionsAreReturned(
result, 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 @Test
public void normalUser_canViewOnlyRelatedCoopAssetsTransactions() { public void normalUser_canViewOnlyRelatedCoopAssetsTransactions() {
// given: // given:
context("superuser-alex@hostsharing.net", "hs_office_partner#FirstGmbH-firstcontact.admin"); context("superuser-alex@hostsharing.net", "hs_office_partner#10001:FirstGmbH-firstcontact.admin");
// "hs_office_person#FirstGmbH.admin",
// when: // when:
final var result = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange( final var result = coopAssetsTransactionRepo.findCoopAssetsTransactionByOptionalMembershipUuidAndDateRange(
@ -212,9 +211,9 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
// then: // then:
exactlyTheseCoopAssetsTransactionsAreReturned( exactlyTheseCoopAssetsTransactionsAreReturned(
result, result,
"CoopAssetsTransaction(10001, 2010-03-15, DEPOSIT, 320.00, ref 10001-1)", "CoopAssetsTransaction(10001, 2010-03-15, DEPOSIT, 320.00, ref 10001-1, initial deposit)",
"CoopAssetsTransaction(10001, 2021-09-01, DISBURSAL, -128.00, ref 10001-2)", "CoopAssetsTransaction(10001, 2021-09-01, DISBURSAL, -128.00, ref 10001-2, partial disbursal)",
"CoopAssetsTransaction(10001, 2022-10-20, ADJUSTMENT, 128.00, ref 10001-3)"); "CoopAssetsTransaction(10001, 2022-10-20, ADJUSTMENT, 128.00, ref 10001-3, some adjustment)");
} }
} }

View File

@ -16,6 +16,7 @@ class HsOfficeCoopSharesTransactionEntityUnitTest {
.transactionType(HsOfficeCoopSharesTransactionType.SUBSCRIPTION) .transactionType(HsOfficeCoopSharesTransactionType.SUBSCRIPTION)
.shareCount(4) .shareCount(4)
.build(); .build();
final HsOfficeCoopSharesTransactionEntity givenEmptyCoopSharesTransaction = HsOfficeCoopSharesTransactionEntity.builder().build();
@Test @Test
void toStringContainsAlmostAllPropertiesAccount() { void toStringContainsAlmostAllPropertiesAccount() {
@ -25,9 +26,23 @@ class HsOfficeCoopSharesTransactionEntityUnitTest {
} }
@Test @Test
void toShortStringContainsOnlyMemberNumberAndshareCountOnly() { void toShortStringContainsOnlyMemberNumberAndShareCountOnly() {
final var result = givenCoopSharesTransaction.toShortString(); final var result = givenCoopSharesTransaction.toShortString();
assertThat(result).isEqualTo("300001+4"); 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");
}
} }

View File

@ -116,7 +116,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
.map(s -> s.replace("hs_office_", "")) .map(s -> s.replace("hs_office_", ""))
.containsExactlyInAnyOrder(Array.fromFormatted( .containsExactlyInAnyOrder(Array.fromFormatted(
initialGrantNames, 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)); null));
} }
@ -143,17 +143,17 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
// then // then
allTheseCoopSharesTransactionsAreReturned( allTheseCoopSharesTransactionsAreReturned(
result, result,
"CoopShareTransaction(10001, 2010-03-15, SUBSCRIPTION, 4, ref 10001-1)", "CoopShareTransaction(10001, 2010-03-15, SUBSCRIPTION, 4, ref 10001-1, initial subscription)",
"CoopShareTransaction(10001, 2021-09-01, CANCELLATION, -2, ref 10001-2)", "CoopShareTransaction(10001, 2021-09-01, CANCELLATION, -2, ref 10001-2, cancelling some)",
"CoopShareTransaction(10001, 2022-10-20, ADJUSTMENT, 2, ref 10001-3)", "CoopShareTransaction(10001, 2022-10-20, ADJUSTMENT, 2, ref 10001-3, some adjustment)",
"CoopShareTransaction(10002, 2010-03-15, SUBSCRIPTION, 4, ref 10002-1)", "CoopShareTransaction(10002, 2010-03-15, SUBSCRIPTION, 4, ref 10002-1, initial subscription)",
"CoopShareTransaction(10002, 2021-09-01, CANCELLATION, -2, ref 10002-2)", "CoopShareTransaction(10002, 2021-09-01, CANCELLATION, -2, ref 10002-2, cancelling some)",
"CoopShareTransaction(10002, 2022-10-20, ADJUSTMENT, 2, ref 10002-3)", "CoopShareTransaction(10002, 2022-10-20, ADJUSTMENT, 2, ref 10002-3, some adjustment)",
"CoopShareTransaction(10003, 2010-03-15, SUBSCRIPTION, 4, ref 10003-1)", "CoopShareTransaction(10003, 2010-03-15, SUBSCRIPTION, 4, ref 10003-1, initial subscription)",
"CoopShareTransaction(10003, 2021-09-01, CANCELLATION, -2, ref 10003-2)", "CoopShareTransaction(10003, 2021-09-01, CANCELLATION, -2, ref 10003-2, cancelling some)",
"CoopShareTransaction(10003, 2022-10-20, ADJUSTMENT, 2, ref 10003-3)"); "CoopShareTransaction(10003, 2022-10-20, ADJUSTMENT, 2, ref 10003-3, some adjustment)");
} }
@Test @Test
@ -172,9 +172,9 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
// then // then
allTheseCoopSharesTransactionsAreReturned( allTheseCoopSharesTransactionsAreReturned(
result, result,
"CoopShareTransaction(10002, 2010-03-15, SUBSCRIPTION, 4, ref 10002-1)", "CoopShareTransaction(10002, 2010-03-15, SUBSCRIPTION, 4, ref 10002-1, initial subscription)",
"CoopShareTransaction(10002, 2021-09-01, CANCELLATION, -2, ref 10002-2)", "CoopShareTransaction(10002, 2021-09-01, CANCELLATION, -2, ref 10002-2, cancelling some)",
"CoopShareTransaction(10002, 2022-10-20, ADJUSTMENT, 2, ref 10002-3)"); "CoopShareTransaction(10002, 2022-10-20, ADJUSTMENT, 2, ref 10002-3, some adjustment)");
} }
@Test @Test
@ -193,14 +193,13 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
// then // then
allTheseCoopSharesTransactionsAreReturned( allTheseCoopSharesTransactionsAreReturned(
result, result,
"CoopShareTransaction(10002, 2021-09-01, CANCELLATION, -2, ref 10002-2)"); "CoopShareTransaction(10002, 2021-09-01, CANCELLATION, -2, ref 10002-2, cancelling some)");
} }
@Test @Test
public void normalUser_canViewOnlyRelatedCoopSharesTransactions() { public void normalUser_canViewOnlyRelatedCoopSharesTransactions() {
// given: // given:
context("superuser-alex@hostsharing.net", "hs_office_partner#FirstGmbH-firstcontact.admin"); context("superuser-alex@hostsharing.net", "hs_office_partner#10001:FirstGmbH-firstcontact.admin");
// "hs_office_person#FirstGmbH.admin",
// when: // when:
final var result = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange( final var result = coopSharesTransactionRepo.findCoopSharesTransactionByOptionalMembershipUuidAndDateRange(
@ -211,9 +210,9 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
// then: // then:
exactlyTheseCoopSharesTransactionsAreReturned( exactlyTheseCoopSharesTransactionsAreReturned(
result, result,
"CoopShareTransaction(10001, 2010-03-15, SUBSCRIPTION, 4, ref 10001-1)", "CoopShareTransaction(10001, 2010-03-15, SUBSCRIPTION, 4, ref 10001-1, initial subscription)",
"CoopShareTransaction(10001, 2021-09-01, CANCELLATION, -2, ref 10001-2)", "CoopShareTransaction(10001, 2021-09-01, CANCELLATION, -2, ref 10001-2, cancelling some)",
"CoopShareTransaction(10001, 2022-10-20, ADJUSTMENT, 2, ref 10001-3)"); "CoopShareTransaction(10001, 2022-10-20, ADJUSTMENT, 2, ref 10001-3, some adjustment)");
} }
} }

View File

@ -35,8 +35,8 @@ import static org.hamcrest.Matchers.*;
@Transactional @Transactional
class HsOfficeDebitorControllerAcceptanceTest { class HsOfficeDebitorControllerAcceptanceTest {
private static final int LOWEST_TEMP_DEBITOR_NUMBER = 20000; private static final int LOWEST_TEMP_DEBITOR_SUFFIX = 90;
private static int nextDebitorNumber = LOWEST_TEMP_DEBITOR_NUMBER; private static byte nextDebitorSuffix = LOWEST_TEMP_DEBITOR_SUFFIX;
@LocalServerPort @LocalServerPort
private Integer port; private Integer port;
@ -81,7 +81,8 @@ class HsOfficeDebitorControllerAcceptanceTest {
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
[ [
{ {
"debitorNumber": 10001, "debitorNumber": 1000111,
"debitorNumberSuffix": 11,
"partner": { "person": { "personType": "LEGAL" } }, "partner": { "person": { "personType": "LEGAL" } },
"billingContact": { "label": "first contact" }, "billingContact": { "label": "first contact" },
"vatId": null, "vatId": null,
@ -90,7 +91,8 @@ class HsOfficeDebitorControllerAcceptanceTest {
"refundBankAccount": { "holder": "First GmbH" } "refundBankAccount": { "holder": "First GmbH" }
}, },
{ {
"debitorNumber": 10002, "debitorNumber": 1000212,
"debitorNumberSuffix": 12,
"partner": { "person": { "tradeName": "Second e.K." } }, "partner": { "person": { "tradeName": "Second e.K." } },
"billingContact": { "label": "second contact" }, "billingContact": { "label": "second contact" },
"vatId": null, "vatId": null,
@ -99,7 +101,8 @@ class HsOfficeDebitorControllerAcceptanceTest {
"refundBankAccount": { "holder": "Second e.K." } "refundBankAccount": { "holder": "Second e.K." }
}, },
{ {
"debitorNumber": 10003, "debitorNumber": 1000313,
"debitorNumberSuffix": 13,
"partner": { "person": { "tradeName": "Third OHG" } }, "partner": { "person": { "tradeName": "Third OHG" } },
"billingContact": { "label": "third contact" }, "billingContact": { "label": "third contact" },
"vatId": null, "vatId": null,
@ -120,14 +123,14 @@ class HsOfficeDebitorControllerAcceptanceTest {
.header("current-user", "superuser-alex@hostsharing.net") .header("current-user", "superuser-alex@hostsharing.net")
.port(port) .port(port)
.when() .when()
.get("http://localhost/api/hs/office/debitors?debitorNumber=10002") .get("http://localhost/api/hs/office/debitors?debitorNumber=1000212")
.then().log().all().assertThat() .then().log().all().assertThat()
.statusCode(200) .statusCode(200)
.contentType("application/json") .contentType("application/json")
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
[ [
{ {
"debitorNumber": 10002, "debitorNumber": 1000212,
"partner": { "person": { "tradeName": "Second e.K." } }, "partner": { "person": { "tradeName": "Second e.K." } },
"billingContact": { "label": "second contact" }, "billingContact": { "label": "second contact" },
"vatId": null, "vatId": null,
@ -160,13 +163,16 @@ class HsOfficeDebitorControllerAcceptanceTest {
{ {
"partnerUuid": "%s", "partnerUuid": "%s",
"billingContactUuid": "%s", "billingContactUuid": "%s",
"debitorNumber": "%s", "debitorNumberSuffix": "%s",
"billable": "true",
"vatId": "VAT123456", "vatId": "VAT123456",
"vatCountryCode": "DE", "vatCountryCode": "DE",
"vatBusiness": true, "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) .port(port)
.when() .when()
.post("http://localhost/api/hs/office/debitors") .post("http://localhost/api/hs/office/debitors")
@ -175,6 +181,7 @@ class HsOfficeDebitorControllerAcceptanceTest {
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body("uuid", isUuidValid()) .body("uuid", isUuidValid())
.body("vatId", is("VAT123456")) .body("vatId", is("VAT123456"))
.body("defaultPrefix", is("for"))
.body("billingContact.label", is(givenContact.getLabel())) .body("billingContact.label", is(givenContact.getLabel()))
.body("partner.person.tradeName", is(givenPartner.getPerson().getTradeName())) .body("partner.person.tradeName", is(givenPartner.getPerson().getTradeName()))
.body("refundBankAccount.holder", is(givenBankAccount.getHolder())) .body("refundBankAccount.holder", is(givenBankAccount.getHolder()))
@ -202,9 +209,12 @@ class HsOfficeDebitorControllerAcceptanceTest {
{ {
"partnerUuid": "%s", "partnerUuid": "%s",
"billingContactUuid": "%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) .port(port)
.when() .when()
.post("http://localhost/api/hs/office/debitors") .post("http://localhost/api/hs/office/debitors")
@ -218,6 +228,7 @@ class HsOfficeDebitorControllerAcceptanceTest {
.body("vatCountryCode", equalTo(null)) .body("vatCountryCode", equalTo(null))
.body("vatBusiness", equalTo(false)) .body("vatBusiness", equalTo(false))
.body("refundBankAccount", equalTo(null)) .body("refundBankAccount", equalTo(null))
.body("defaultPrefix", equalTo("for"))
.header("Location", startsWith("http://localhost")) .header("Location", startsWith("http://localhost"))
.extract().header("Location"); // @formatter:on .extract().header("Location"); // @formatter:on
@ -242,12 +253,16 @@ class HsOfficeDebitorControllerAcceptanceTest {
{ {
"partnerUuid": "%s", "partnerUuid": "%s",
"billingContactUuid": "%s", "billingContactUuid": "%s",
"debitorNumber": "%s", "debitorNumberSuffix": "%s",
"billable": "true",
"vatId": "VAT123456", "vatId": "VAT123456",
"vatCountryCode": "DE", "vatCountryCode": "DE",
"vatBusiness": true "vatBusiness": true,
"vatReverseCharge": "false",
"defaultPrefix": "thi"
} }
""".formatted( givenPartner.getUuid(), givenContactUuid, ++nextDebitorNumber)) """
.formatted( givenPartner.getUuid(), givenContactUuid, ++nextDebitorSuffix))
.port(port) .port(port)
.when() .when()
.post("http://localhost/api/hs/office/debitors") .post("http://localhost/api/hs/office/debitors")
@ -272,12 +287,15 @@ class HsOfficeDebitorControllerAcceptanceTest {
{ {
"partnerUuid": "%s", "partnerUuid": "%s",
"billingContactUuid": "%s", "billingContactUuid": "%s",
"debitorNumber": "%s", "debitorNumberSuffix": "%s",
"billable": "true",
"vatId": "VAT123456", "vatId": "VAT123456",
"vatCountryCode": "DE", "vatCountryCode": "DE",
"vatBusiness": true "vatBusiness": true,
"vatReverseCharge": "false",
"defaultPrefix": "for"
} }
""".formatted( givenPartnerUuid, givenContact.getUuid(), ++nextDebitorNumber)) """.formatted( givenPartnerUuid, givenContact.getUuid(), ++nextDebitorSuffix))
.port(port) .port(port)
.when() .when()
.post("http://localhost/api/hs/office/debitors") .post("http://localhost/api/hs/office/debitors")
@ -375,7 +393,8 @@ class HsOfficeDebitorControllerAcceptanceTest {
"contactUuid": "%s", "contactUuid": "%s",
"vatId": "VAT222222", "vatId": "VAT222222",
"vatCountryCode": "AA", "vatCountryCode": "AA",
"vatBusiness": true "vatBusiness": true,
"defaultPrefix": "for"
} }
""".formatted(givenContact.getUuid())) """.formatted(givenContact.getUuid()))
.port(port) .port(port)
@ -388,6 +407,7 @@ class HsOfficeDebitorControllerAcceptanceTest {
.body("vatId", is("VAT222222")) .body("vatId", is("VAT222222"))
.body("vatCountryCode", is("AA")) .body("vatCountryCode", is("AA"))
.body("vatBusiness", is(true)) .body("vatBusiness", is(true))
.body("defaultPrefix", is("for"))
.body("billingContact.label", is(givenContact.getLabel())) .body("billingContact.label", is(givenContact.getLabel()))
.body("partner.person.tradeName", is(givenDebitor.getPartner().getPerson().getTradeName())); .body("partner.person.tradeName", is(givenDebitor.getPartner().getPerson().getTradeName()));
// @formatter:on // @formatter:on
@ -522,9 +542,12 @@ class HsOfficeDebitorControllerAcceptanceTest {
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("Fourth").get(0); final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("Fourth").get(0);
final var givenContact = contactRepo.findContactByOptionalLabelLike("forth contact").get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike("forth contact").get(0);
final var newDebitor = HsOfficeDebitorEntity.builder() final var newDebitor = HsOfficeDebitorEntity.builder()
.debitorNumber(++nextDebitorNumber) .debitorNumberSuffix(++nextDebitorSuffix)
.billable(true)
.partner(givenPartner) .partner(givenPartner)
.billingContact(givenContact) .billingContact(givenContact)
.defaultPrefix("abc")
.vatReverseCharge(false)
.build(); .build();
return debitorRepo.save(newDebitor); return debitorRepo.save(newDebitor);
@ -537,7 +560,7 @@ class HsOfficeDebitorControllerAcceptanceTest {
jpaAttempt.transacted(() -> { jpaAttempt.transacted(() -> {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var count = em.createQuery( 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(); .executeUpdate();
System.out.printf("deleted %d entities%n", count); System.out.printf("deleted %d entities%n", count);
}); });

View File

@ -1,5 +1,6 @@
package net.hostsharing.hsadminng.hs.office.debitor; 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.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorPatchResource;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; 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 INITIAL_CONTACT_UUID = UUID.randomUUID();
private static final UUID PATCHED_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 String PATCHED_VAT_COUNTRY_CODE = "ZZ";
private static final boolean PATCHED_VAT_BUSINESS = false; 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() private final HsOfficePartnerEntity givenInitialPartner = HsOfficePartnerEntity.builder()
.uuid(INITIAL_PARTNER_UUID) .uuid(INITIAL_PARTNER_UUID)
.build(); .build();
@ -42,6 +53,10 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase<
private final HsOfficeContactEntity givenInitialContact = HsOfficeContactEntity.builder() private final HsOfficeContactEntity givenInitialContact = HsOfficeContactEntity.builder()
.uuid(INITIAL_CONTACT_UUID) .uuid(INITIAL_CONTACT_UUID)
.build(); .build();
private final HsOfficeBankAccountEntity givenInitialBankAccount = HsOfficeBankAccountEntity.builder()
.uuid(INITIAL_REFUND_BANK_ACCOUNT_UUID)
.build();
@Mock @Mock
private EntityManager em; private EntityManager em;
@ -49,8 +64,8 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase<
void initMocks() { void initMocks() {
lenient().when(em.getReference(eq(HsOfficeContactEntity.class), any())).thenAnswer(invocation -> lenient().when(em.getReference(eq(HsOfficeContactEntity.class), any())).thenAnswer(invocation ->
HsOfficeContactEntity.builder().uuid(invocation.getArgument(1)).build()); HsOfficeContactEntity.builder().uuid(invocation.getArgument(1)).build());
lenient().when(em.getReference(eq(HsOfficeContactEntity.class), any())).thenAnswer(invocation -> lenient().when(em.getReference(eq(HsOfficeBankAccountEntity.class), any())).thenAnswer(invocation ->
HsOfficeContactEntity.builder().uuid(invocation.getArgument(1)).build()); HsOfficeBankAccountEntity.builder().uuid(invocation.getArgument(1)).build());
} }
@Override @Override
@ -59,9 +74,13 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase<
entity.setUuid(INITIAL_DEBITOR_UUID); entity.setUuid(INITIAL_DEBITOR_UUID);
entity.setPartner(givenInitialPartner); entity.setPartner(givenInitialPartner);
entity.setBillingContact(givenInitialContact); entity.setBillingContact(givenInitialContact);
entity.setBillable(INITIAL_BILLABLE);
entity.setVatId("initial VAT-ID"); entity.setVatId("initial VAT-ID");
entity.setVatCountryCode("AA"); entity.setVatCountryCode("AA");
entity.setVatBusiness(true); entity.setVatBusiness(true);
entity.setVatReverseCharge(INITIAL_VAT_REVERSE_CHARGE);
entity.setDefaultPrefix("abc");
entity.setRefundBankAccount(givenInitialBankAccount);
return entity; return entity;
} }
@ -85,6 +104,12 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase<
HsOfficeDebitorEntity::setBillingContact, HsOfficeDebitorEntity::setBillingContact,
newBillingContact(PATCHED_CONTACT_UUID)) newBillingContact(PATCHED_CONTACT_UUID))
.notNullable(), .notNullable(),
new SimpleProperty<>(
"billable",
HsOfficeDebitorPatchResource::setBillable,
PATCHED_BILLABLE,
HsOfficeDebitorEntity::setBillable)
.notNullable(),
new JsonNullableProperty<>( new JsonNullableProperty<>(
"vatId", "vatId",
HsOfficeDebitorPatchResource::setVatId, HsOfficeDebitorPatchResource::setVatId,
@ -95,11 +120,30 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase<
HsOfficeDebitorPatchResource::setVatCountryCode, HsOfficeDebitorPatchResource::setVatCountryCode,
PATCHED_VAT_COUNTRY_CODE, PATCHED_VAT_COUNTRY_CODE,
HsOfficeDebitorEntity::setVatCountryCode), HsOfficeDebitorEntity::setVatCountryCode),
new JsonNullableProperty<>( new SimpleProperty<>(
"vatBusiness", "vatBusiness",
HsOfficeDebitorPatchResource::setVatBusiness, HsOfficeDebitorPatchResource::setVatBusiness,
PATCHED_VAT_BUSINESS, PATCHED_VAT_BUSINESS,
HsOfficeDebitorEntity::setVatBusiness) 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() .notNullable()
); );
} }
@ -109,4 +153,10 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase<
newContact.setUuid(uuid); newContact.setUuid(uuid);
return newContact; return newContact;
} }
private HsOfficeBankAccountEntity newBankAccount(final UUID uuid) {
final var newBankAccount = new HsOfficeBankAccountEntity();
newBankAccount.setUuid(uuid);
return newBankAccount;
}
} }

View File

@ -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.HsOfficePartnerDetailsEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -13,30 +14,52 @@ class HsOfficeDebitorEntityUnitTest {
@Test @Test
void toStringContainsPartnerAndContact() { void toStringContainsPartnerAndContact() {
final var given = HsOfficeDebitorEntity.builder() final var given = HsOfficeDebitorEntity.builder()
.debitorNumber(123456) .debitorNumberSuffix((byte)67)
.partner(HsOfficePartnerEntity.builder() .partner(HsOfficePartnerEntity.builder()
.person(HsOfficePersonEntity.builder() .person(HsOfficePersonEntity.builder()
.personType(HsOfficePersonType.LEGAL)
.tradeName("some trade name") .tradeName("some trade name")
.build()) .build())
.details(HsOfficePartnerDetailsEntity.builder().birthName("some birth 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()) .build())
.billingContact(HsOfficeContactEntity.builder().label("some label").build()) .billingContact(HsOfficeContactEntity.builder().label("some label").build())
.build(); .build();
final var result = given.toString(); final var result = given.toString();
assertThat(result).isEqualTo("debitor(123456: some trade name)"); assertThat(result).isEqualTo("debitor(1234567: <person=null>)");
} }
@Test @Test
void toShortStringContainsPartnerAndContact() { void toShortStringContainsDebitorNumber() {
final var given = HsOfficeDebitorEntity.builder() final var given = HsOfficeDebitorEntity.builder()
.debitorNumber(123456) .partner(HsOfficePartnerEntity.builder()
.debitorNumberPrefix(12345)
.build())
.debitorNumberSuffix((byte)67)
.build(); .build();
final var result = given.toShortString(); final var result = given.toShortString();
assertThat(result).isEqualTo("123456"); assertThat(result).isEqualTo("1234567");
} }
} }

View File

@ -9,8 +9,6 @@ import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository; import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
import net.hostsharing.test.Array; import net.hostsharing.test.Array;
import net.hostsharing.test.JpaAttempt; 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.Nested;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; 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.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.orm.jpa.JpaSystemException; import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.transaction.annotation.Transactional;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext; import jakarta.persistence.PersistenceContext;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.grantDisplaysOf; import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.grantDisplaysOf;
import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf; import static net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleEntity.roleNamesOf;
@ -65,8 +62,6 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
@MockBean @MockBean
HttpServletRequest request; HttpServletRequest request;
Set<HsOfficeDebitorEntity> tempDebitors = new HashSet<>();
@Nested @Nested
class CreateDebitor { class CreateDebitor {
@ -81,9 +76,11 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
// when // when
final var result = attempt(em, () -> { final var result = attempt(em, () -> {
final var newDebitor = HsOfficeDebitorEntity.builder() final var newDebitor = HsOfficeDebitorEntity.builder()
.debitorNumber(20001) .debitorNumberSuffix((byte)21)
.partner(givenPartner) .partner(givenPartner)
.billingContact(givenContact) .billingContact(givenContact)
.defaultPrefix("abc")
.billable(false)
.build(); .build();
return debitorRepo.save(newDebitor); return debitorRepo.save(newDebitor);
}); });
@ -95,14 +92,43 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
assertThat(debitorRepo.count()).isEqualTo(count + 1); 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 @Test
public void createsAndGrantsRoles() { public void createsAndGrantsRoles() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var initialRoleNames = roleNamesOf(rawRoleRepo.findAll()); final var initialRoleNames = roleNamesOf(rawRoleRepo.findAll());
final var initialGrantNames = grantDisplaysOf(rawGrantRepo.findAll()).stream() 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("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("Fourthe.G.-forthcontact", "FeG"))
.map(s -> s.replace("forthcontact", "4th")) .map(s -> s.replace("forthcontact", "4th"))
.map(s -> s.replace("hs_office_", "")) .map(s -> s.replace("hs_office_", ""))
@ -113,9 +139,11 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("Fourth").get(0); final var givenPartner = partnerRepo.findPartnerByOptionalNameLike("Fourth").get(0);
final var givenContact = contactRepo.findContactByOptionalLabelLike("forth contact").get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike("forth contact").get(0);
final var newDebitor = HsOfficeDebitorEntity.builder() final var newDebitor = HsOfficeDebitorEntity.builder()
.debitorNumber(20002) .debitorNumberSuffix((byte)22)
.partner(givenPartner) .partner(givenPartner)
.billingContact(givenContact) .billingContact(givenContact)
.defaultPrefix("abc")
.billable(false)
.build(); .build();
return debitorRepo.save(newDebitor); return debitorRepo.save(newDebitor);
}).assertSuccessful(); }).assertSuccessful();
@ -123,42 +151,42 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
// then // then
assertThat(roleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from( assertThat(roleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from(
initialRoleNames, initialRoleNames,
"hs_office_debitor#20002Fourthe.G.-forthcontact.owner", "hs_office_debitor#1000422:Fourthe.G.-forthcontact.owner",
"hs_office_debitor#20002Fourthe.G.-forthcontact.admin", "hs_office_debitor#1000422:Fourthe.G.-forthcontact.admin",
"hs_office_debitor#20002Fourthe.G.-forthcontact.agent", "hs_office_debitor#1000422:Fourthe.G.-forthcontact.agent",
"hs_office_debitor#20002Fourthe.G.-forthcontact.tenant", "hs_office_debitor#1000422:Fourthe.G.-forthcontact.tenant",
"hs_office_debitor#20002Fourthe.G.-forthcontact.guest")); "hs_office_debitor#1000422:Fourthe.G.-forthcontact.guest"));
assertThat(grantDisplaysOf(rawGrantRepo.findAll())) assertThat(grantDisplaysOf(rawGrantRepo.findAll()))
.map(s -> s.replace("superuser-alex@hostsharing.net", "superuser-alex")) .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("Fourthe.G.-forthcontact", "FeG"))
.map(s -> s.replace("forthcontact", "4th")) .map(s -> s.replace("forthcontact", "4th"))
.map(s -> s.replace("hs_office_", "")) .map(s -> s.replace("hs_office_", ""))
.containsExactlyInAnyOrder(Array.fromFormatted( .containsExactlyInAnyOrder(Array.fromFormatted(
initialGrantNames, initialGrantNames,
// owner // owner
"{ grant perm * on debitor#FeG to role debitor#FeG.owner by system and assume }", "{ grant perm * on debitor#1000422:FeG to role debitor#1000422:FeG.owner by system and assume }",
"{ grant role debitor#FeG.owner to role global#global.admin by system and assume }", "{ grant role debitor#1000422: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 role debitor#1000422:FeG.owner to user superuser-alex by global#global.admin and assume }",
// admin // admin
"{ grant perm edit on debitor#FeG to role debitor#FeG.admin by system and assume }", "{ grant perm edit on debitor#1000422:FeG to role debitor#1000422:FeG.admin by system and assume }",
"{ grant role debitor#FeG.admin to role debitor#FeG.owner by system and assume }", "{ grant role debitor#1000422:FeG.admin to role debitor#1000422:FeG.owner by system and assume }",
// agent // agent
"{ grant role debitor#FeG.agent to role debitor#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#FeG.agent to role contact#4th.admin by system and assume }", "{ grant role debitor#1000422: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 partner#10004:FeG.admin by system and assume }",
// tenant // tenant
"{ grant role contact#4th.guest 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#FeG.tenant to role debitor#FeG.agent by system and assume }", "{ grant role debitor#1000422:FeG.tenant to role debitor#1000422:FeG.agent by system and assume }",
"{ grant role debitor#FeG.tenant to role partner#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#FeG.tenant to role debitor#FeG.tenant by system and assume }", "{ grant role partner#10004:FeG.tenant to role debitor#1000422:FeG.tenant by system and assume }",
// guest // guest
"{ grant perm view on debitor#FeG to role debitor#FeG.guest by system and assume }", "{ grant perm view on debitor#1000422:FeG to role debitor#1000422:FeG.guest by system and assume }",
"{ grant role debitor#FeG.guest to role debitor#FeG.tenant by system and assume }", "{ grant role debitor#1000422:FeG.guest to role debitor#1000422:FeG.tenant by system and assume }",
null)); null));
} }
@ -183,14 +211,14 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
// then // then
allTheseDebitorsAreReturned( allTheseDebitorsAreReturned(
result, result,
"debitor(10001: First GmbH)", "debitor(1000111: LEGAL First GmbH: fir)",
"debitor(10002: Second e.K.)", "debitor(1000212: LEGAL Second e.K.: sec)",
"debitor(10003: Third OHG)"); "debitor(1000313: SOLE_REPRESENTATION Third OHG: thi)");
} }
@ParameterizedTest @ParameterizedTest
@ValueSource(strings = { @ValueSource(strings = {
"hs_office_partner#FirstGmbH-firstcontact.admin", "hs_office_partner#10001:FirstGmbH-firstcontact.admin",
"hs_office_person#FirstGmbH.admin", "hs_office_person#FirstGmbH.admin",
"hs_office_contact#firstcontact.admin", "hs_office_contact#firstcontact.admin",
}) })
@ -202,7 +230,9 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
final var result = debitorRepo.findDebitorByOptionalNameLike(null); final var result = debitorRepo.findDebitorByOptionalNameLike(null);
// then: // then:
exactlyTheseDebitorsAreReturned(result, "debitor(10001: First GmbH)"); exactlyTheseDebitorsAreReturned(result,
"debitor(1000111: LEGAL First GmbH: fir)",
"debitor(1000120: LEGAL First GmbH: fif)");
} }
@Test @Test
@ -227,10 +257,10 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
// when // when
final var result = debitorRepo.findDebitorByDebitorNumber(10003); final var result = debitorRepo.findDebitorByDebitorNumber(1000313);
// then // 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"); final var result = debitorRepo.findDebitorByOptionalNameLike("third contact");
// then // 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() { public void globalAdmin_canUpdateArbitraryDebitor() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", "Fourth"); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", "Fourth", "fif");
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
givenDebitor, givenDebitor,
"hs_office_partner#Fourthe.G.-forthcontact.admin"); "hs_office_partner#10004:Fourthe.G.-forthcontact.admin");
assertThatDebitorActuallyInDatabase(givenDebitor); assertThatDebitorActuallyInDatabase(givenDebitor);
final var givenNewPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0); final var givenNewPartner = partnerRepo.findPartnerByOptionalNameLike("First").get(0);
final var givenNewContact = contactRepo.findContactByOptionalLabelLike("sixth contact").get(0); final var givenNewContact = contactRepo.findContactByOptionalLabelLike("sixth contact").get(0);
@ -290,10 +320,10 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
// ... partner role was reassigned: // ... partner role was reassigned:
assertThatDebitorIsNotVisibleForUserWithRole( assertThatDebitorIsNotVisibleForUserWithRole(
result.returnedValue(), result.returnedValue(),
"hs_office_partner#Fourthe.G.-forthcontact.agent"); "hs_office_partner#10004:Fourthe.G.-forthcontact.agent");
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
result.returnedValue(), result.returnedValue(),
"hs_office_partner#FirstGmbH-firstcontact.agent"); "hs_office_partner#10001:FirstGmbH-firstcontact.agent");
// ... contact role was reassigned: // ... contact role was reassigned:
assertThatDebitorIsNotVisibleForUserWithRole( assertThatDebitorIsNotVisibleForUserWithRole(
@ -316,10 +346,10 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
public void globalAdmin_canUpdateNullRefundBankAccountToNotNullBankAccountForArbitraryDebitor() { public void globalAdmin_canUpdateNullRefundBankAccountToNotNullBankAccountForArbitraryDebitor() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", null); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", null, "fig");
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
givenDebitor, givenDebitor,
"hs_office_partner#Fourthe.G.-forthcontact.admin"); "hs_office_partner#10004:Fourthe.G.-forthcontact.admin");
assertThatDebitorActuallyInDatabase(givenDebitor); assertThatDebitorActuallyInDatabase(givenDebitor);
final var givenNewBankAccount = bankAccountRepo.findByOptionalHolderLike("first").get(0); final var givenNewBankAccount = bankAccountRepo.findByOptionalHolderLike("first").get(0);
@ -346,10 +376,10 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
public void globalAdmin_canUpdateRefundBankAccountToNullForArbitraryDebitor() { public void globalAdmin_canUpdateRefundBankAccountToNullForArbitraryDebitor() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", "Fourth"); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact", "Fourth", "fih");
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
givenDebitor, givenDebitor,
"hs_office_partner#Fourthe.G.-forthcontact.admin"); "hs_office_partner#10004:Fourthe.G.-forthcontact.admin");
assertThatDebitorActuallyInDatabase(givenDebitor); assertThatDebitorActuallyInDatabase(givenDebitor);
// when // when
@ -375,15 +405,15 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
public void partnerAdmin_canNotUpdateRelatedDebitor() { public void partnerAdmin_canNotUpdateRelatedDebitor() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "eighth", "Fourth"); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "eighth", "Fourth", "eig");
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
givenDebitor, givenDebitor,
"hs_office_partner#Fourthe.G.-forthcontact.admin"); "hs_office_partner#10004:Fourthe.G.-forthcontact.admin");
assertThatDebitorActuallyInDatabase(givenDebitor); assertThatDebitorActuallyInDatabase(givenDebitor);
// when // when
final var result = jpaAttempt.transacted(() -> { 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"); givenDebitor.setVatId("NEW-VAT-ID");
return debitorRepo.save(givenDebitor); return debitorRepo.save(givenDebitor);
}); });
@ -397,7 +427,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
public void contactAdmin_canNotUpdateRelatedDebitor() { public void contactAdmin_canNotUpdateRelatedDebitor() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "ninth", "Fourth"); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "ninth", "Fourth", "nin");
assertThatDebitorIsVisibleForUserWithRole( assertThatDebitorIsVisibleForUserWithRole(
givenDebitor, givenDebitor,
"hs_office_contact#ninthcontact.admin"); "hs_office_contact#ninthcontact.admin");
@ -448,7 +478,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
public void globalAdmin_canDeleteAnyDebitor() { public void globalAdmin_canDeleteAnyDebitor() {
// given // given
context("superuser-alex@hostsharing.net", null); context("superuser-alex@hostsharing.net", null);
final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "tenth", "Fourth"); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "tenth", "Fourth", "ten");
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
@ -468,7 +498,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
public void relatedPerson_canNotDeleteTheirRelatedDebitor() { public void relatedPerson_canNotDeleteTheirRelatedDebitor() {
// given // given
context("superuser-alex@hostsharing.net", null); context("superuser-alex@hostsharing.net", null);
final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "eleventh", "Fourth"); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "eleventh", "Fourth", "ele");
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
@ -494,7 +524,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var initialRoleNames = Array.from(roleNamesOf(rawRoleRepo.findAll())); final var initialRoleNames = Array.from(roleNamesOf(rawRoleRepo.findAll()));
final var initialGrantNames = Array.from(grantDisplaysOf(rawGrantRepo.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") assertThat(rawRoleRepo.findAll().size()).as("precondition failed: unexpected number of roles created")
.isEqualTo(initialRoleNames.length + 5); .isEqualTo(initialRoleNames.length + 5);
assertThat(rawGrantRepo.findAll().size()).as("precondition failed: unexpected number of grants created") assertThat(rawGrantRepo.findAll().size()).as("precondition failed: unexpected number of grants created")
@ -536,7 +566,8 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
private HsOfficeDebitorEntity givenSomeTemporaryDebitor( private HsOfficeDebitorEntity givenSomeTemporaryDebitor(
final String partner, final String partner,
final String contact, final String contact,
final String bankAccount) { final String bankAccount,
final String defaultPrefix) {
return jpaAttempt.transacted(() -> { return jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenPartner = partnerRepo.findPartnerByOptionalNameLike(partner).get(0); final var givenPartner = partnerRepo.findPartnerByOptionalNameLike(partner).get(0);
@ -544,23 +575,18 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
final var givenBankAccount = final var givenBankAccount =
bankAccount != null ? bankAccountRepo.findByOptionalHolderLike(bankAccount).get(0) : null; bankAccount != null ? bankAccountRepo.findByOptionalHolderLike(bankAccount).get(0) : null;
final var newDebitor = HsOfficeDebitorEntity.builder() final var newDebitor = HsOfficeDebitorEntity.builder()
.debitorNumber(20000) .debitorNumberSuffix((byte)20)
.partner(givenPartner) .partner(givenPartner)
.billingContact(givenContact) .billingContact(givenContact)
.refundBankAccount(givenBankAccount) .refundBankAccount(givenBankAccount)
.defaultPrefix(defaultPrefix)
.billable(true)
.build(); .build();
return debitorRepo.save(newDebitor); return debitorRepo.save(newDebitor);
}).assertSuccessful().returnedValue(); }).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<HsOfficeDebitorEntity> actualResult, final String... debitorNames) { void exactlyTheseDebitorsAreReturned(final List<HsOfficeDebitorEntity> actualResult, final String... debitorNames) {
assertThat(actualResult) assertThat(actualResult)
.extracting(HsOfficeDebitorEntity::toString) .extracting(HsOfficeDebitorEntity::toString)

View File

@ -9,8 +9,10 @@ import static net.hostsharing.hsadminng.hs.office.partner.TestHsOfficePartner.TE
@UtilityClass @UtilityClass
public class TestHsOfficeDebitor { public class TestHsOfficeDebitor {
public byte DEFAULT_DEBITOR_SUFFIX = 0;
public static final HsOfficeDebitorEntity TEST_DEBITOR = HsOfficeDebitorEntity.builder() public static final HsOfficeDebitorEntity TEST_DEBITOR = HsOfficeDebitorEntity.builder()
.debitorNumber(10001) .debitorNumberSuffix(DEFAULT_DEBITOR_SUFFIX)
.partner(TEST_PARTNER) .partner(TEST_PARTNER)
.billingContact(TEST_CONTACT) .billingContact(TEST_CONTACT)
.build(); .build();

View File

@ -83,7 +83,7 @@ class HsOfficeMembershipControllerAcceptanceTest {
[ [
{ {
"partner": { "person": { "tradeName": "First GmbH" } }, "partner": { "person": { "tradeName": "First GmbH" } },
"mainDebitor": { "debitorNumber": 10001 }, "mainDebitor": { "debitorNumber": 1000111 },
"memberNumber": 10001, "memberNumber": 10001,
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
"validTo": null, "validTo": null,
@ -91,7 +91,7 @@ class HsOfficeMembershipControllerAcceptanceTest {
}, },
{ {
"partner": { "person": { "tradeName": "Second e.K." } }, "partner": { "person": { "tradeName": "Second e.K." } },
"mainDebitor": { "debitorNumber": 10002 }, "mainDebitor": { "debitorNumber": 1000212 },
"memberNumber": 10002, "memberNumber": 10002,
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
"validTo": null, "validTo": null,
@ -99,7 +99,7 @@ class HsOfficeMembershipControllerAcceptanceTest {
}, },
{ {
"partner": { "person": { "tradeName": "Third OHG" } }, "partner": { "person": { "tradeName": "Third OHG" } },
"mainDebitor": { "debitorNumber": 10003 }, "mainDebitor": { "debitorNumber": 1000313 },
"memberNumber": 10003, "memberNumber": 10003,
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
"validTo": null, "validTo": null,
@ -131,7 +131,8 @@ class HsOfficeMembershipControllerAcceptanceTest {
"partnerUuid": "%s", "partnerUuid": "%s",
"mainDebitorUuid": "%s", "mainDebitorUuid": "%s",
"memberNumber": 20001, "memberNumber": 20001,
"validFrom": "2022-10-13" "validFrom": "2022-10-13",
"membershipFeeBillable": "true"
} }
""".formatted(givenPartner.getUuid(), givenDebitor.getUuid())) """.formatted(givenPartner.getUuid(), givenDebitor.getUuid()))
.port(port) .port(port)
@ -142,6 +143,7 @@ class HsOfficeMembershipControllerAcceptanceTest {
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body("uuid", isUuidValid()) .body("uuid", isUuidValid())
.body("mainDebitor.debitorNumber", is(givenDebitor.getDebitorNumber())) .body("mainDebitor.debitorNumber", is(givenDebitor.getDebitorNumber()))
.body("mainDebitor.debitorNumberSuffix", is((int) givenDebitor.getDebitorNumberSuffix()))
.body("partner.person.tradeName", is("Third OHG")) .body("partner.person.tradeName", is("Third OHG"))
.body("memberNumber", is(20001)) .body("memberNumber", is(20001))
.body("validFrom", is("2022-10-13")) .body("validFrom", is("2022-10-13"))
@ -182,7 +184,7 @@ class HsOfficeMembershipControllerAcceptanceTest {
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"partner": { "person": { "tradeName": "First GmbH" } }, "partner": { "person": { "tradeName": "First GmbH" } },
"mainDebitor": { "debitorNumber": 10001 }, "mainDebitor": { "debitorNumber": 1000111 },
"memberNumber": 10001, "memberNumber": 10001,
"validFrom": "2022-10-01", "validFrom": "2022-10-01",
"validTo": null, "validTo": null,
@ -224,7 +226,7 @@ class HsOfficeMembershipControllerAcceptanceTest {
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
.header("current-user", "superuser-alex@hostsharing.net") .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) .port(port)
.when() .when()
.get("http://localhost/api/hs/office/memberships/" + givenMembershipUuid) .get("http://localhost/api/hs/office/memberships/" + givenMembershipUuid)
@ -235,7 +237,7 @@ class HsOfficeMembershipControllerAcceptanceTest {
{ {
"partner": { "person": { "tradeName": "Third OHG" } }, "partner": { "person": { "tradeName": "Third OHG" } },
"mainDebitor": { "mainDebitor": {
"debitorNumber": 10003, "debitorNumber": 1000313,
"billingContact": { "label": "third contact" } "billingContact": { "label": "third contact" }
}, },
"memberNumber": 10003, "memberNumber": 10003,
@ -276,6 +278,7 @@ class HsOfficeMembershipControllerAcceptanceTest {
.body("uuid", isUuidValid()) .body("uuid", isUuidValid())
.body("partner.person.tradeName", is(givenMembership.getPartner().getPerson().getTradeName())) .body("partner.person.tradeName", is(givenMembership.getPartner().getPerson().getTradeName()))
.body("mainDebitor.debitorNumber", is(givenMembership.getMainDebitor().getDebitorNumber())) .body("mainDebitor.debitorNumber", is(givenMembership.getMainDebitor().getDebitorNumber()))
.body("mainDebitor.debitorNumberSuffix", is((int) givenMembership.getMainDebitor().getDebitorNumberSuffix()))
.body("memberNumber", is(givenMembership.getMemberNumber())) .body("memberNumber", is(givenMembership.getMemberNumber()))
.body("validFrom", is("2022-11-01")) .body("validFrom", is("2022-11-01"))
.body("validTo", is("2023-12-31")) .body("validTo", is("2023-12-31"))
@ -285,7 +288,7 @@ class HsOfficeMembershipControllerAcceptanceTest {
// finally, the Membership is actually updated // finally, the Membership is actually updated
assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent().get() assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent().get()
.matches(mandate -> { .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.getMainDebitor().toString()).isEqualTo(givenMembership.getMainDebitor().toString());
assertThat(mandate.getMemberNumber()).isEqualTo(givenMembership.getMemberNumber()); assertThat(mandate.getMemberNumber()).isEqualTo(givenMembership.getMemberNumber());
assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,2024-01-01)"); assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,2024-01-01)");
@ -299,7 +302,7 @@ class HsOfficeMembershipControllerAcceptanceTest {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var givenMembership = givenSomeTemporaryMembershipBessler(); final var givenMembership = givenSomeTemporaryMembershipBessler();
final var givenNewMainDebitor = debitorRepo.findDebitorByDebitorNumber(10003).get(0); final var givenNewMainDebitor = debitorRepo.findDebitorByDebitorNumber(1000313).get(0);
RestAssured // @formatter:off RestAssured // @formatter:off
.given() .given()
@ -318,7 +321,7 @@ class HsOfficeMembershipControllerAcceptanceTest {
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body("uuid", isUuidValid()) .body("uuid", isUuidValid())
.body("partner.person.tradeName", is(givenMembership.getPartner().getPerson().getTradeName())) .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("memberNumber", is(givenMembership.getMemberNumber()))
.body("validFrom", is("2022-11-01")) .body("validFrom", is("2022-11-01"))
.body("validTo", nullValue()) .body("validTo", nullValue())
@ -328,7 +331,7 @@ class HsOfficeMembershipControllerAcceptanceTest {
// finally, the Membership is actually updated // finally, the Membership is actually updated
assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent().get() assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent().get()
.matches(mandate -> { .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.getMainDebitor().toString()).isEqualTo(givenMembership.getMainDebitor().toString());
assertThat(mandate.getMemberNumber()).isEqualTo(givenMembership.getMemberNumber()); assertThat(mandate.getMemberNumber()).isEqualTo(givenMembership.getMemberNumber());
assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,)"); assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,)");
@ -340,13 +343,13 @@ class HsOfficeMembershipControllerAcceptanceTest {
@Test @Test
void partnerAgent_canViewButNotPatchValidityOfRelatedMembership() { 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 givenMembership = givenSomeTemporaryMembershipBessler();
final var location = RestAssured // @formatter:off final var location = RestAssured // @formatter:off
.given() .given()
.header("current-user", "superuser-alex@hostsharing.net") .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) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
@ -444,6 +447,7 @@ class HsOfficeMembershipControllerAcceptanceTest {
.memberNumber(++tempMemberNumber) .memberNumber(++tempMemberNumber)
.validity(Range.closedInfinite(LocalDate.parse("2022-11-01"))) .validity(Range.closedInfinite(LocalDate.parse("2022-11-01")))
.reasonForTermination(NONE) .reasonForTermination(NONE)
.membershipFeeBillable(true)
.build(); .build();
return membershipRepo.save(newMembership); return membershipRepo.save(newMembership);

View File

@ -72,7 +72,8 @@ public class HsOfficeMembershipControllerRestTest {
"partnerUuid": null, "partnerUuid": null,
"mainDebitorUuid": "%s", "mainDebitorUuid": "%s",
"memberNumber": 20001, "memberNumber": 20001,
"validFrom": "2022-10-13" "validFrom": "2022-10-13",
"membershipFeeBillable": "true"
} }
""".formatted(UUID.randomUUID())) """.formatted(UUID.randomUUID()))
.accept(MediaType.APPLICATION_JSON)) .accept(MediaType.APPLICATION_JSON))
@ -97,7 +98,8 @@ public class HsOfficeMembershipControllerRestTest {
"partnerUuid": "%s", "partnerUuid": "%s",
"mainDebitorUuid": null, "mainDebitorUuid": null,
"memberNumber": 20001, "memberNumber": 20001,
"validFrom": "2022-10-13" "validFrom": "2022-10-13",
"membershipFeeBillable": "true"
} }
""".formatted(UUID.randomUUID())) """.formatted(UUID.randomUUID()))
.accept(MediaType.APPLICATION_JSON)) .accept(MediaType.APPLICATION_JSON))
@ -128,7 +130,8 @@ public class HsOfficeMembershipControllerRestTest {
"partnerUuid": "%s", "partnerUuid": "%s",
"mainDebitorUuid": "%s", "mainDebitorUuid": "%s",
"memberNumber": 20001, "memberNumber": 20001,
"validFrom": "2022-10-13" "validFrom": "2022-10-13",
"membershipFeeBillable": "true"
} }
""".formatted(givenPartnerUuid, givenMainDebitorUuid)) """.formatted(givenPartnerUuid, givenMainDebitorUuid))
.accept(MediaType.APPLICATION_JSON)) .accept(MediaType.APPLICATION_JSON))
@ -159,7 +162,8 @@ public class HsOfficeMembershipControllerRestTest {
"partnerUuid": "%s", "partnerUuid": "%s",
"mainDebitorUuid": "%s", "mainDebitorUuid": "%s",
"memberNumber": 20001, "memberNumber": 20001,
"validFrom": "2022-10-13" "validFrom": "2022-10-13",
"membershipFeeBillable": "true"
} }
""".formatted(givenPartnerUuid, givenMainDebitorUuid)) """.formatted(givenPartnerUuid, givenMainDebitorUuid))
.accept(MediaType.APPLICATION_JSON)) .accept(MediaType.APPLICATION_JSON))

View File

@ -36,6 +36,9 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase<
private static final LocalDate GIVEN_VALID_FROM = LocalDate.parse("2020-04-15"); 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 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 @Mock
private EntityManager em; private EntityManager em;
@ -56,6 +59,7 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase<
entity.setMainDebitor(TEST_DEBITOR); entity.setMainDebitor(TEST_DEBITOR);
entity.setPartner(TEST_PARTNER); entity.setPartner(TEST_PARTNER);
entity.setValidity(Range.closedInfinite(GIVEN_VALID_FROM)); entity.setValidity(Range.closedInfinite(GIVEN_VALID_FROM));
entity.setMembershipFeeBillable(GIVEN_MEMBERSHIP_FEE_BILLABLE);
return entity; return entity;
} }
@ -90,7 +94,12 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase<
HsOfficeReasonForTerminationResource.CANCELLATION, HsOfficeReasonForTerminationResource.CANCELLATION,
HsOfficeMembershipEntity::setReasonForTermination, HsOfficeMembershipEntity::setReasonForTermination,
HsOfficeReasonForTermination.CANCELLATION) 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); newDebitor.setUuid(uuid);
return newDebitor; return newDebitor;
} }
private HsOfficeMembershipEntity newMembership(final UUID uuid) {
final var newMembership = new HsOfficeMembershipEntity();
newMembership.setUuid(uuid);
return newMembership;
}
} }

View File

@ -27,7 +27,7 @@ class HsOfficeMembershipEntityUnitTest {
void toStringContainsAllProps() { void toStringContainsAllProps() {
final var result = givenMembership.toString(); 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 @Test

View File

@ -81,6 +81,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest {
.partner(givenPartner) .partner(givenPartner)
.mainDebitor(givenDebitor) .mainDebitor(givenDebitor)
.validity(Range.closedInfinite(LocalDate.parse("2020-01-01"))) .validity(Range.closedInfinite(LocalDate.parse("2020-01-01")))
.membershipFeeBillable(true)
.build()); .build());
return membershipRepo.save(newMembership); return membershipRepo.save(newMembership);
}); });
@ -111,6 +112,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest {
.partner(givenPartner) .partner(givenPartner)
.mainDebitor(givenDebitor) .mainDebitor(givenDebitor)
.validity(Range.closedInfinite(LocalDate.parse("2020-01-01"))) .validity(Range.closedInfinite(LocalDate.parse("2020-01-01")))
.membershipFeeBillable(true)
.build()); .build());
return membershipRepo.save(newMembership); return membershipRepo.save(newMembership);
}); });
@ -119,11 +121,11 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest {
final var all = rawRoleRepo.findAll(); final var all = rawRoleRepo.findAll();
assertThat(roleNamesOf(all)).containsExactlyInAnyOrder(Array.from( assertThat(roleNamesOf(all)).containsExactlyInAnyOrder(Array.from(
initialRoleNames, initialRoleNames,
"hs_office_membership#20002FirstGmbH-firstcontact.admin", "hs_office_membership#20002:FirstGmbH-firstcontact.admin",
"hs_office_membership#20002FirstGmbH-firstcontact.agent", "hs_office_membership#20002:FirstGmbH-firstcontact.agent",
"hs_office_membership#20002FirstGmbH-firstcontact.guest", "hs_office_membership#20002:FirstGmbH-firstcontact.guest",
"hs_office_membership#20002FirstGmbH-firstcontact.owner", "hs_office_membership#20002:FirstGmbH-firstcontact.owner",
"hs_office_membership#20002FirstGmbH-firstcontact.tenant")); "hs_office_membership#20002:FirstGmbH-firstcontact.tenant"));
assertThat(grantDisplaysOf(rawGrantRepo.findAll())) assertThat(grantDisplaysOf(rawGrantRepo.findAll()))
.map(s -> s.replace("GmbH-firstcontact", "")) .map(s -> s.replace("GmbH-firstcontact", ""))
.map(s -> s.replace("hs_office_", "")) .map(s -> s.replace("hs_office_", ""))
@ -131,32 +133,33 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest {
initialGrantNames, initialGrantNames,
// owner // owner
"{ grant perm * on membership#20002First to role membership#20002First.owner by system and assume }", "{ grant perm * on membership#20002:First to role membership#20002:First.owner by system and assume }",
"{ grant role membership#20002First.owner to role global#global.admin by system and assume }", "{ grant role membership#20002:First.owner to role global#global.admin by system and assume }",
// admin // admin
"{ grant perm edit on membership#20002First to role membership#20002First.admin by system and assume }", "{ grant perm edit on membership#20002:First to role membership#20002:First.admin by system and assume }",
"{ grant role membership#20002First.admin to role membership#20002First.owner by system and assume }", "{ grant role membership#20002:First.admin to role membership#20002:First.owner by system and assume }",
// agent // agent
"{ grant role membership#20002First.agent to role membership#20002First.admin by system and assume }", "{ grant role membership#20002:First.agent to role membership#20002:First.admin by system and assume }",
"{ grant role partner#First.tenant to role membership#20002First.agent by system and assume }", "{ grant role partner#10001:First.tenant to role membership#20002:First.agent by system and assume }",
"{ grant role membership#20002First.agent to role debitor#10001First.admin by system and assume }", "{ grant role membership#20002:First.agent to role debitor#1000111:First.admin by system and assume }",
"{ grant role membership#20002First.agent to role partner#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#10001First.tenant to role membership#20002First.agent by system and assume }", "{ grant role debitor#1000111:First.tenant to role membership#20002:First.agent by system and assume }",
// tenant // tenant
"{ grant role membership#20002First.tenant to role membership#20002First.agent by system and assume }", "{ grant role membership#20002:First.tenant to role membership#20002:First.agent by system and assume }",
"{ grant role partner#First.guest to role membership#20002First.tenant by system and assume }", "{ grant role partner#10001:First.guest to role membership#20002:First.tenant by system and assume }",
"{ grant role debitor#10001First.guest to role membership#20002First.tenant by system and assume }", "{ grant role debitor#1000111:First.guest to role membership#20002:First.tenant by system and assume }",
"{ grant role membership#20002First.tenant to role debitor#10001First.agent by system and assume }", "{ grant role membership#20002:First.tenant to role debitor#1000111:First.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 partner#10001:First.agent by system and assume }",
// guest // guest
"{ grant perm view on membership#20002First to role membership#20002First.guest by system and assume }", "{ grant perm view on membership#20002:First to role membership#20002:First.guest by system and assume }",
"{ grant role membership#20002First.guest to role membership#20002First.tenant by system and assume }", "{ grant role membership#20002:First.guest to role membership#20002:First.tenant by system and assume }",
"{ grant role membership#20002First.guest to role partner#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#20002First.guest to role debitor#10001First.tenant by system and assume }", "{ grant role membership#20002:First.guest to role debitor#1000111:First.tenant by system and assume }",
null)); null));
} }
@ -181,9 +184,9 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest {
// then // then
exactlyTheseMembershipsAreReturned( exactlyTheseMembershipsAreReturned(
result, result,
"Membership(10001, First GmbH, 10001, [2022-10-01,), NONE)", "Membership(10001, LEGAL First GmbH, 1000111, [2022-10-01,), NONE)",
"Membership(10002, Second e.K., 10002, [2022-10-01,), NONE)", "Membership(10002, LEGAL Second e.K., 1000212, [2022-10-01,), NONE)",
"Membership(10003, Third OHG, 10003, [2022-10-01,), NONE)"); "Membership(10003, SOLE_REPRESENTATION Third OHG, 1000313, [2022-10-01,), NONE)");
} }
@Test @Test
@ -198,7 +201,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest {
null); null);
// then // 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 @Test
@ -210,7 +213,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest {
final var result = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10002); final var result = membershipRepo.findMembershipsByOptionalPartnerUuidAndOptionalMemberNumber(null, 10002);
// then // 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"); final var givenMembership = givenSomeTemporaryMembership("First", "First");
assertThatMembershipIsVisibleForUserWithRole( assertThatMembershipIsVisibleForUserWithRole(
givenMembership, givenMembership,
"hs_office_debitor#10001FirstGmbH-firstcontact.admin"); "hs_office_debitor#1000111:FirstGmbH-firstcontact.admin");
assertThatMembershipExistsAndIsAccessibleToCurrentContext(givenMembership); assertThatMembershipExistsAndIsAccessibleToCurrentContext(givenMembership);
final var newValidityEnd = LocalDate.now(); final var newValidityEnd = LocalDate.now();
@ -251,13 +254,13 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest {
final var givenMembership = givenSomeTemporaryMembership("First", "First"); final var givenMembership = givenSomeTemporaryMembership("First", "First");
assertThatMembershipIsVisibleForUserWithRole( assertThatMembershipIsVisibleForUserWithRole(
givenMembership, givenMembership,
"hs_office_debitor#10001FirstGmbH-firstcontact.admin"); "hs_office_debitor#1000111:FirstGmbH-firstcontact.admin");
assertThatMembershipExistsAndIsAccessibleToCurrentContext(givenMembership); assertThatMembershipExistsAndIsAccessibleToCurrentContext(givenMembership);
final var newValidityEnd = LocalDate.now(); final var newValidityEnd = LocalDate.now();
// when // when
final var result = jpaAttempt.transacted(() -> { 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.setValidity(Range.closedOpen(
givenMembership.getValidity().lower(), newValidityEnd)); givenMembership.getValidity().lower(), newValidityEnd));
return membershipRepo.save(givenMembership); return membershipRepo.save(givenMembership);
@ -325,7 +328,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest {
// when // when
final var result = jpaAttempt.transacted(() -> { 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(); assertThat(membershipRepo.findByUuid(givenMembership.getUuid())).isPresent();
membershipRepo.deleteByUuid(givenMembership.getUuid()); membershipRepo.deleteByUuid(givenMembership.getUuid());
@ -382,8 +385,8 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest {
// then // then
assertThat(customerLogEntries).map(Arrays::toString).contains( assertThat(customerLogEntries).map(Arrays::toString).contains(
"[creating Membership test-data FirstGmbH10001, hs_office_membership, INSERT]", "[creating Membership test-data FirstGmbH11, hs_office_membership, INSERT]",
"[creating Membership test-data Seconde.K.10002, hs_office_membership, INSERT]"); "[creating Membership test-data Seconde.K.12, hs_office_membership, INSERT]");
} }
@BeforeEach @BeforeEach
@ -412,6 +415,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest {
.partner(givenPartner) .partner(givenPartner)
.mainDebitor(givenDebitor) .mainDebitor(givenDebitor)
.validity(Range.closedInfinite(LocalDate.parse("2020-01-01"))) .validity(Range.closedInfinite(LocalDate.parse("2020-01-01")))
.membershipFeeBillable(true)
.build(); .build();
toCleanup(newMembership); toCleanup(newMembership);

File diff suppressed because it is too large Load Diff

View File

@ -125,6 +125,7 @@ class HsOfficePartnerControllerAcceptanceTest {
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"debitorNumberPrefix": "12345",
"contactUuid": "%s", "contactUuid": "%s",
"personUuid": "%s", "personUuid": "%s",
"details": { "details": {
@ -166,6 +167,7 @@ class HsOfficePartnerControllerAcceptanceTest {
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"debitorNumberPrefix": "12345",
"contactUuid": "%s", "contactUuid": "%s",
"personUuid": "%s", "personUuid": "%s",
"details": {} "details": {}
@ -193,6 +195,7 @@ class HsOfficePartnerControllerAcceptanceTest {
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"debitorNumberPrefix": "12345",
"contactUuid": "%s", "contactUuid": "%s",
"personUuid": "%s", "personUuid": "%s",
"details": {} "details": {}
@ -294,6 +297,7 @@ class HsOfficePartnerControllerAcceptanceTest {
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"debitorNumberPrefix": "12345",
"contactUuid": "%s", "contactUuid": "%s",
"personUuid": "%s", "personUuid": "%s",
"details": { "details": {

View File

@ -28,8 +28,9 @@ class HsOfficePartnerDetailsEntityPatcherUnitTest extends PatchUnitTestBase<
> { > {
private static final UUID INITIAL_PARTNER_UUID = UUID.randomUUID(); 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 INITIAL_BIRTHDAY = LocalDate.parse("1900-01-01");
private static final LocalDate PATCHED_BIRTHDAY = LocalDate.parse("1990-12-31"); 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.setUuid(INITIAL_PARTNER_UUID);
entity.setRegistrationOffice("initial Reg-Office"); entity.setRegistrationOffice("initial Reg-Office");
entity.setRegistrationNumber("initial Reg-Number"); entity.setRegistrationNumber("initial Reg-Number");
entity.setBirthPlace(INITIAL_BIRTHPLACE);
entity.setBirthday(INITIAL_BIRTHDAY); entity.setBirthday(INITIAL_BIRTHDAY);
entity.setBirthName("initial birth name"); entity.setBirthName("initial birth name");
entity.setDateOfDeath(INITIAL_DAY_OF_DEATH); entity.setDateOfDeath(INITIAL_DAY_OF_DEATH);
@ -78,6 +80,16 @@ class HsOfficePartnerDetailsEntityPatcherUnitTest extends PatchUnitTestBase<
HsOfficePartnerDetailsPatchResource::setRegistrationOffice, HsOfficePartnerDetailsPatchResource::setRegistrationOffice,
"patched Reg-Office", "patched Reg-Office",
HsOfficePartnerDetailsEntity::setRegistrationOffice), HsOfficePartnerDetailsEntity::setRegistrationOffice),
new JsonNullableProperty<>(
"birthplace",
HsOfficePartnerDetailsPatchResource::setBirthPlace,
PATCHED_BIRTHPLACE,
HsOfficePartnerDetailsEntity::setBirthPlace),
new JsonNullableProperty<>(
"birthname",
HsOfficePartnerDetailsPatchResource::setBirthName,
"patched birth name",
HsOfficePartnerDetailsEntity::setBirthName),
new JsonNullableProperty<>( new JsonNullableProperty<>(
"birthday", "birthday",
HsOfficePartnerDetailsPatchResource::setBirthday, HsOfficePartnerDetailsPatchResource::setBirthday,

View File

@ -21,7 +21,8 @@ class HsOfficePartnerDetailsEntityUnitTest {
final var result = given.toString(); 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 @Test

View File

@ -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.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -11,24 +12,30 @@ class HsOfficePartnerEntityUnitTest {
@Test @Test
void toStringContainsPersonAndContact() { void toStringContainsPersonAndContact() {
final var given = HsOfficePartnerEntity.builder() 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()) .contact(HsOfficeContactEntity.builder().label("some label").build())
.build(); .build();
final var result = given.toString(); 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 @Test
void toShortStringContainsPersonAndContact() { void toShortStringContainsPersonAndContact() {
final var given = HsOfficePartnerEntity.builder() 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()) .contact(HsOfficeContactEntity.builder().label("some label").build())
.build(); .build();
final var result = given.toShortString(); final var result = given.toShortString();
assertThat(result).isEqualTo("some trade name"); assertThat(result).isEqualTo("LEGAL some trade name");
} }
} }

View File

@ -105,6 +105,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
final var givenPerson = personRepo.findPersonByOptionalNameLike("Erben Bessler").get(0); final var givenPerson = personRepo.findPersonByOptionalNameLike("Erben Bessler").get(0);
final var givenContact = contactRepo.findContactByOptionalLabelLike("forth contact").get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike("forth contact").get(0);
final var newPartner = toCleanup(HsOfficePartnerEntity.builder() final var newPartner = toCleanup(HsOfficePartnerEntity.builder()
.debitorNumberPrefix(22222)
.person(givenPerson) .person(givenPerson)
.contact(givenContact) .contact(givenContact)
.details(HsOfficePartnerDetailsEntity.builder().build()) .details(HsOfficePartnerDetailsEntity.builder().build())
@ -115,11 +116,11 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
// then // then
assertThat(roleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from( assertThat(roleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from(
initialRoleNames, initialRoleNames,
"hs_office_partner#ErbenBesslerMelBessler-forthcontact.admin", "hs_office_partner#22222:ErbenBesslerMelBessler-forthcontact.admin",
"hs_office_partner#ErbenBesslerMelBessler-forthcontact.agent", "hs_office_partner#22222:ErbenBesslerMelBessler-forthcontact.agent",
"hs_office_partner#ErbenBesslerMelBessler-forthcontact.owner", "hs_office_partner#22222:ErbenBesslerMelBessler-forthcontact.owner",
"hs_office_partner#ErbenBesslerMelBessler-forthcontact.tenant", "hs_office_partner#22222:ErbenBesslerMelBessler-forthcontact.tenant",
"hs_office_partner#ErbenBesslerMelBessler-forthcontact.guest")); "hs_office_partner#22222:ErbenBesslerMelBessler-forthcontact.guest"));
assertThat(grantDisplaysOf(rawGrantRepo.findAll())) assertThat(grantDisplaysOf(rawGrantRepo.findAll()))
.map(s -> s.replace("ErbenBesslerMelBessler", "EBess")) .map(s -> s.replace("ErbenBesslerMelBessler", "EBess"))
.map(s -> s.replace("forthcontact", "4th")) .map(s -> s.replace("forthcontact", "4th"))
@ -127,31 +128,31 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
.containsExactlyInAnyOrder(Array.fromFormatted( .containsExactlyInAnyOrder(Array.fromFormatted(
initialGrantNames, initialGrantNames,
// owner // owner
"{ grant perm * on partner#EBess-4th to role partner#EBess-4th.owner 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#EBess-4th-details to role partner#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#EBess-4th.owner to role global#global.admin by system and assume }", "{ grant role partner#22222:EBess-4th.owner to role global#global.admin by system and assume }",
// admin // admin
"{ grant perm edit on partner#EBess-4th 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#EBess-4th-details to role partner#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#EBess-4th.admin to role partner#EBess-4th.owner 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#EBess-4th.admin 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#EBess-4th.admin by system and assume }", "{ grant role contact#4th.tenant to role partner#22222:EBess-4th.admin by system and assume }",
// agent // agent
"{ grant perm view on partner_details#EBess-4th-details to role partner#EBess-4th.agent 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#EBess-4th.agent to role partner#EBess-4th.admin 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#EBess-4th.agent to role person#EBess.admin by system and assume }", "{ grant role partner#22222: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 role partner#22222:EBess-4th.agent to role contact#4th.admin by system and assume }",
// tenant // tenant
"{ grant role partner#EBess-4th.tenant to role partner#EBess-4th.agent 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#EBess-4th.tenant 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#EBess-4th.tenant by system and assume }", "{ grant role contact#4th.guest to role partner#22222:EBess-4th.tenant by system and assume }",
// guest // guest
"{ grant perm view on partner#EBess-4th to role partner#EBess-4th.guest 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#EBess-4th.guest to role partner#EBess-4th.tenant by system and assume }", "{ grant role partner#22222:EBess-4th.guest to role partner#22222:EBess-4th.tenant by system and assume }",
null)); null));
} }
@ -176,9 +177,9 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
// then // then
allThesePartnersAreReturned( allThesePartnersAreReturned(
result, result,
"partner(Third OHG: third contact)", "partner(SOLE_REPRESENTATION Third OHG: third contact)",
"partner(Second e.K.: second contact)", "partner(LEGAL Second e.K.: second contact)",
"partner(First GmbH: first contact)"); "partner(LEGAL First GmbH: first contact)");
} }
@Test @Test
@ -190,7 +191,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
final var result = partnerRepo.findPartnerByOptionalNameLike(null); final var result = partnerRepo.findPartnerByOptionalNameLike(null);
// then: // 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"); final var result = partnerRepo.findPartnerByOptionalNameLike("third contact");
// then // 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() { public void hostsharingAdmin_withoutAssumedRole_canUpdateArbitraryPartner() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenPartner = givenSomeTemporaryPartnerBessler("fifth contact"); final var givenPartner = givenSomeTemporaryPartnerBessler(22222, "Erben Bessler", "fifth contact");
assertThatPartnerIsVisibleForUserWithRole( assertThatPartnerIsVisibleForUserWithRole(
givenPartner, givenPartner,
"hs_office_person#ErbenBesslerMelBessler.admin"); "hs_office_partner#22222:ErbenBesslerMelBessler-fifthcontact.admin");
assertThatPartnerActuallyInDatabase(givenPartner); assertThatPartnerActuallyInDatabase(givenPartner);
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenNewPerson = personRepo.findPersonByOptionalNameLike("Third OHG").get(0); final var givenNewPerson = personRepo.findPersonByOptionalNameLike("Third OHG").get(0);
@ -253,16 +254,16 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
public void partnerAgent_canNotUpdateRelatedPartner() { public void partnerAgent_canNotUpdateRelatedPartner() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenPartner = givenSomeTemporaryPartnerBessler("ninth"); final var givenPartner = givenSomeTemporaryPartnerBessler(22222, "Erben Bessler", "ninth");
assertThatPartnerIsVisibleForUserWithRole( assertThatPartnerIsVisibleForUserWithRole(
givenPartner, givenPartner,
"hs_office_partner#ErbenBesslerMelBessler-ninthcontact.agent"); "hs_office_partner#22222:ErbenBesslerMelBessler-ninthcontact.agent");
assertThatPartnerActuallyInDatabase(givenPartner); assertThatPartnerActuallyInDatabase(givenPartner);
final var givenNewContact = contactRepo.findContactByOptionalLabelLike("tenth").get(0);
// when // when
final var result = jpaAttempt.transacted(() -> { 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"); givenPartner.getDetails().setBirthName("new birthname");
return partnerRepo.save(givenPartner); return partnerRepo.save(givenPartner);
}); });
@ -304,7 +305,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
public void globalAdmin_withoutAssumedRole_canDeleteAnyPartner() { public void globalAdmin_withoutAssumedRole_canDeleteAnyPartner() {
// given // given
context("superuser-alex@hostsharing.net", null); context("superuser-alex@hostsharing.net", null);
final var givenPartner = givenSomeTemporaryPartnerBessler("tenth"); final var givenPartner = givenSomeTemporaryPartnerBessler(22222, "Erben Bessler", "tenth");
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
@ -324,7 +325,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
public void nonGlobalAdmin_canNotDeleteTheirRelatedPartner() { public void nonGlobalAdmin_canNotDeleteTheirRelatedPartner() {
// given // given
context("superuser-alex@hostsharing.net", null); context("superuser-alex@hostsharing.net", null);
final var givenPartner = givenSomeTemporaryPartnerBessler("eleventh"); final var givenPartner = givenSomeTemporaryPartnerBessler(22222, "Erben Bessler", "eleventh");
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
@ -350,7 +351,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var initialRoleNames = Array.from(roleNamesOf(rawRoleRepo.findAll())); final var initialRoleNames = Array.from(roleNamesOf(rawRoleRepo.findAll()));
final var initialGrantNames = Array.from(grantDisplaysOf(rawGrantRepo.findAll())); final var initialGrantNames = Array.from(grantDisplaysOf(rawGrantRepo.findAll()));
final var givenPartner = givenSomeTemporaryPartnerBessler("twelfth"); final var givenPartner = givenSomeTemporaryPartnerBessler(22222, "Erben Bessler", "twelfth");
// when // when
final var result = jpaAttempt.transacted(() -> { 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(() -> { return jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net"); 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 givenContact = contactRepo.findContactByOptionalLabelLike(contact).get(0);
final var newPartner = HsOfficePartnerEntity.builder() final var newPartner = HsOfficePartnerEntity.builder()
.debitorNumberPrefix(debitorNumberPrefix)
.person(givenPerson) .person(givenPerson)
.contact(givenContact) .contact(givenContact)
.details(HsOfficePartnerDetailsEntity.builder().build()) .details(HsOfficePartnerDetailsEntity.builder().build())

View File

@ -8,10 +8,11 @@ import static net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType.LEGA
public class TestHsOfficePartner { 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() return HsOfficePartnerEntity.builder()
.debitorNumberPrefix(10001)
.person(HsOfficePersonEntity.builder() .person(HsOfficePersonEntity.builder()
.personType(LEGAL) .personType(LEGAL)
.tradeName(tradeName) .tradeName(tradeName)

View File

@ -11,29 +11,32 @@ class HsOfficePersonEntityUnitTest {
@Test @Test
void getDisplayReturnsTradeNameIfAvailable() { void getDisplayReturnsTradeNameIfAvailable() {
final var givenPersonEntity = HsOfficePersonEntity.builder() final var givenPersonEntity = HsOfficePersonEntity.builder()
.personType(HsOfficePersonType.LEGAL)
.tradeName("some trade name") .tradeName("some trade name")
.build(); .build();
final var actualDisplay = givenPersonEntity.toShortString(); final var actualDisplay = givenPersonEntity.toShortString();
assertThat(actualDisplay).isEqualTo("some trade name"); assertThat(actualDisplay).isEqualTo("LEGAL some trade name");
} }
@Test @Test
void getDisplayReturnsFamilyAndGivenNameIfNoTradeNameAvailable() { void getDisplayReturnsFamilyAndGivenNameIfNoTradeNameAvailable() {
final var givenPersonEntity = HsOfficePersonEntity.builder() final var givenPersonEntity = HsOfficePersonEntity.builder()
.personType(HsOfficePersonType.NATURAL)
.familyName("some family name") .familyName("some family name")
.givenName("some given name") .givenName("some given name")
.build(); .build();
final var actualDisplay = givenPersonEntity.toShortString(); 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 @Test
void toShortStringWithTradeNameReturnsTradeName() { void toShortStringWithTradeNameReturnsTradeName() {
final var givenPersonEntity = HsOfficePersonEntity.builder() final var givenPersonEntity = HsOfficePersonEntity.builder()
.personType(HsOfficePersonType.LEGAL)
.tradeName("some trade name") .tradeName("some trade name")
.familyName("some family name") .familyName("some family name")
.givenName("some given name") .givenName("some given name")
@ -41,19 +44,20 @@ class HsOfficePersonEntityUnitTest {
final var actualDisplay = givenPersonEntity.toShortString(); final var actualDisplay = givenPersonEntity.toShortString();
assertThat(actualDisplay).isEqualTo("some trade name"); assertThat(actualDisplay).isEqualTo("LEGAL some trade name");
} }
@Test @Test
void toShortStringWithoutTradeNameReturnsFamilyAndGivenName() { void toShortStringWithoutTradeNameReturnsFamilyAndGivenName() {
final var givenPersonEntity = HsOfficePersonEntity.builder() final var givenPersonEntity = HsOfficePersonEntity.builder()
.personType(HsOfficePersonType.NATURAL)
.familyName("some family name") .familyName("some family name")
.givenName("some given name") .givenName("some given name")
.build(); .build();
final var actualDisplay = givenPersonEntity.toShortString(); 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 @Test

View File

@ -144,10 +144,10 @@ class HsOfficePersonRepositoryIntegrationTest extends ContextBasedTest {
// then // then
allThesePersonsAreReturned( allThesePersonsAreReturned(
result, result,
"Smith, Peter", "NATURAL Smith, Peter",
"Second e.K.", "LEGAL Second e.K.",
"Third OHG", "SOLE_REPRESENTATION Third OHG",
"Erben Bessler"); "JOINT_REPRESENTATION Erben Bessler");
} }
@Test @Test

View File

@ -75,7 +75,7 @@ class HsOfficeRelationshipControllerAcceptanceTest {
.port(port) .port(port)
.when() .when()
.get("http://localhost/api/hs/office/relationships?personUuid=%s&relationshipType=%s" .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() .then().log().all().assertThat()
.statusCode(200) .statusCode(200)
.contentType("application/json") .contentType("application/json")
@ -91,7 +91,7 @@ class HsOfficeRelationshipControllerAcceptanceTest {
"givenName": "Peter", "givenName": "Peter",
"familyName": "Smith" "familyName": "Smith"
}, },
"relType": "SOLE_AGENT", "relType": "REPRESENTATIVE",
"contact": { "label": "third contact" } "contact": { "label": "third contact" }
}, },
{ {
@ -106,7 +106,7 @@ class HsOfficeRelationshipControllerAcceptanceTest {
"givenName": "Peter", "givenName": "Peter",
"familyName": "Smith" "familyName": "Smith"
}, },
"relType": "SOLE_AGENT", "relType": "REPRESENTATIVE",
"contact": { "label": "second contact" } "contact": { "label": "second contact" }
}, },
{ {
@ -120,7 +120,7 @@ class HsOfficeRelationshipControllerAcceptanceTest {
"givenName": "Peter", "givenName": "Peter",
"familyName": "Smith" "familyName": "Smith"
}, },
"relType": "SOLE_AGENT", "relType": "REPRESENTATIVE",
"contact": { "label": "first contact" } "contact": { "label": "first contact" }
} }
] ]
@ -153,7 +153,7 @@ class HsOfficeRelationshipControllerAcceptanceTest {
"contactUuid": "%s" "contactUuid": "%s"
} }
""".formatted( """.formatted(
HsOfficeRelationshipTypeResource.ACCOUNTING_CONTACT, HsOfficeRelationshipTypeResource.ACCOUNTING,
givenAnchorPerson.getUuid(), givenAnchorPerson.getUuid(),
givenHolderPerson.getUuid(), givenHolderPerson.getUuid(),
givenContact.getUuid())) givenContact.getUuid()))
@ -164,7 +164,7 @@ class HsOfficeRelationshipControllerAcceptanceTest {
.statusCode(201) .statusCode(201)
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body("uuid", isUuidValid()) .body("uuid", isUuidValid())
.body("relType", is("ACCOUNTING_CONTACT")) .body("relType", is("ACCOUNTING"))
.body("relAnchor.tradeName", is("Third OHG")) .body("relAnchor.tradeName", is("Third OHG"))
.body("relHolder.givenName", is("Paul")) .body("relHolder.givenName", is("Paul"))
.body("contact.label", is("forth contact")) .body("contact.label", is("forth contact"))
@ -197,7 +197,7 @@ class HsOfficeRelationshipControllerAcceptanceTest {
"contactUuid": "%s" "contactUuid": "%s"
} }
""".formatted( """.formatted(
HsOfficeRelationshipTypeResource.ACCOUNTING_CONTACT, HsOfficeRelationshipTypeResource.ACCOUNTING,
givenAnchorPersonUuid, givenAnchorPersonUuid,
givenHolderPerson.getUuid(), givenHolderPerson.getUuid(),
givenContact.getUuid())) givenContact.getUuid()))
@ -230,7 +230,7 @@ class HsOfficeRelationshipControllerAcceptanceTest {
"contactUuid": "%s" "contactUuid": "%s"
} }
""".formatted( """.formatted(
HsOfficeRelationshipTypeResource.ACCOUNTING_CONTACT, HsOfficeRelationshipTypeResource.ACCOUNTING,
givenAnchorPerson.getUuid(), givenAnchorPerson.getUuid(),
givenHolderPersonUuid, givenHolderPersonUuid,
givenContact.getUuid())) givenContact.getUuid()))
@ -263,7 +263,7 @@ class HsOfficeRelationshipControllerAcceptanceTest {
"contactUuid": "%s" "contactUuid": "%s"
} }
""".formatted( """.formatted(
HsOfficeRelationshipTypeResource.ACCOUNTING_CONTACT, HsOfficeRelationshipTypeResource.ACCOUNTING,
givenAnchorPerson.getUuid(), givenAnchorPerson.getUuid(),
givenHolderPerson.getUuid(), givenHolderPerson.getUuid(),
givenContactUuid)) givenContactUuid))
@ -387,7 +387,7 @@ class HsOfficeRelationshipControllerAcceptanceTest {
.statusCode(200) .statusCode(200)
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body("uuid", isUuidValid()) .body("uuid", isUuidValid())
.body("relType", is("JOINT_AGENT")) .body("relType", is("REPRESENTATIVE"))
.body("relAnchor.tradeName", is("Erben Bessler")) .body("relAnchor.tradeName", is("Erben Bessler"))
.body("relHolder.familyName", is("Winkler")) .body("relHolder.familyName", is("Winkler"))
.body("contact.label", is("forth contact")); .body("contact.label", is("forth contact"));
@ -400,7 +400,7 @@ class HsOfficeRelationshipControllerAcceptanceTest {
assertThat(rel.getRelAnchor().getTradeName()).contains("Bessler"); assertThat(rel.getRelAnchor().getTradeName()).contains("Bessler");
assertThat(rel.getRelHolder().getFamilyName()).contains("Winkler"); assertThat(rel.getRelHolder().getFamilyName()).contains("Winkler");
assertThat(rel.getContact().getLabel()).isEqualTo("forth contact"); assertThat(rel.getContact().getLabel()).isEqualTo("forth contact");
assertThat(rel.getRelType()).isEqualTo(HsOfficeRelationshipType.JOINT_AGENT); assertThat(rel.getRelType()).isEqualTo(HsOfficeRelationshipType.REPRESENTATIVE);
return true; return true;
}); });
} }
@ -477,7 +477,7 @@ class HsOfficeRelationshipControllerAcceptanceTest {
final var givenContact = contactRepo.findContactByOptionalLabelLike("seventh contact").get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike("seventh contact").get(0);
final var newRelationship = HsOfficeRelationshipEntity.builder() final var newRelationship = HsOfficeRelationshipEntity.builder()
.uuid(UUID.randomUUID()) .uuid(UUID.randomUUID())
.relType(HsOfficeRelationshipType.JOINT_AGENT) .relType(HsOfficeRelationshipType.REPRESENTATIVE)
.relAnchor(givenAnchorPerson) .relAnchor(givenAnchorPerson)
.relHolder(givenHolderPerson) .relHolder(givenHolderPerson)
.contact(givenContact) .contact(givenContact)

View File

@ -52,7 +52,7 @@ class HsOfficeRelationshipEntityPatcherUnitTest extends PatchUnitTestBase<
protected HsOfficeRelationshipEntity newInitialEntity() { protected HsOfficeRelationshipEntity newInitialEntity() {
final var entity = new HsOfficeRelationshipEntity(); final var entity = new HsOfficeRelationshipEntity();
entity.setUuid(INITIAL_RELATIONSHIP_UUID); entity.setUuid(INITIAL_RELATIONSHIP_UUID);
entity.setRelType(HsOfficeRelationshipType.SOLE_AGENT); entity.setRelType(HsOfficeRelationshipType.REPRESENTATIVE);
entity.setRelAnchor(givenInitialAnchorPerson); entity.setRelAnchor(givenInitialAnchorPerson);
entity.setRelHolder(givenInitialHolderPerson); entity.setRelHolder(givenInitialHolderPerson);
entity.setContact(givenInitialContact); entity.setContact(givenInitialContact);

View File

@ -77,7 +77,7 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTest {
final var newRelationship = toCleanup(HsOfficeRelationshipEntity.builder() final var newRelationship = toCleanup(HsOfficeRelationshipEntity.builder()
.relAnchor(givenAnchorPerson) .relAnchor(givenAnchorPerson)
.relHolder(givenHolderPerson) .relHolder(givenHolderPerson)
.relType(HsOfficeRelationshipType.JOINT_AGENT) .relType(HsOfficeRelationshipType.REPRESENTATIVE)
.contact(givenContact) .contact(givenContact)
.build()); .build());
return relationshipRepo.save(newRelationship); return relationshipRepo.save(newRelationship);
@ -105,7 +105,7 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTest {
final var newRelationship = toCleanup(HsOfficeRelationshipEntity.builder() final var newRelationship = toCleanup(HsOfficeRelationshipEntity.builder()
.relAnchor(givenAnchorPerson) .relAnchor(givenAnchorPerson)
.relHolder(givenHolderPerson) .relHolder(givenHolderPerson)
.relType(HsOfficeRelationshipType.JOINT_AGENT) .relType(HsOfficeRelationshipType.REPRESENTATIVE)
.contact(givenContact) .contact(givenContact)
.build()); .build());
return relationshipRepo.save(newRelationship); return relationshipRepo.save(newRelationship);
@ -114,26 +114,26 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTest {
// then // then
assertThat(roleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from( assertThat(roleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from(
initialRoleNames, initialRoleNames,
"hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.admin", "hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.admin",
"hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.owner", "hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner",
"hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.tenant")); "hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant"));
assertThat(grantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(Array.fromFormatted( assertThat(grantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(Array.fromFormatted(
initialGrantNames, 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 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-JOINT_AGENT-BesslerAnita.owner to role global#global.admin 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-JOINT_AGENT-BesslerAnita.owner to role hs_office_person#BesslerAnita.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 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-JOINT_AGENT-BesslerAnita.admin to role hs_office_relationship#BesslerAnita-with-JOINT_AGENT-BesslerAnita.owner 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 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-JOINT_AGENT-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_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 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_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-JOINT_AGENT-BesslerAnita.tenant 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-JOINT_AGENT-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) null)
); );
} }
@ -159,9 +159,9 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTest {
// then // then
allTheseRelationshipsAreReturned( allTheseRelationshipsAreReturned(
result, 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')",
"rel(relAnchor='Third OHG', relType='SOLE_AGENT', relHolder='Smith, Peter', contact='third contact')", "rel(relAnchor='SOLE_REPRESENTATION Third OHG', relType='REPRESENTATIVE', relHolder='NATURAL Smith, Peter', contact='third contact')",
"rel(relAnchor='Second e.K.', relType='SOLE_AGENT', relHolder='Smith, Peter', contact='second contact')"); "rel(relAnchor='LEGAL Second e.K.', relType='REPRESENTATIVE', relHolder='NATURAL Smith, Peter', contact='second contact')");
} }
@Test @Test
@ -176,7 +176,7 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTest {
// then: // then:
exactlyTheseRelationshipsAreReturned( exactlyTheseRelationshipsAreReturned(
result, 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 givenHolderPerson = personRepo.findPersonByOptionalNameLike(holderPerson).get(0);
final var givenContact = contactRepo.findContactByOptionalLabelLike(contact).get(0); final var givenContact = contactRepo.findContactByOptionalLabelLike(contact).get(0);
final var newRelationship = HsOfficeRelationshipEntity.builder() final var newRelationship = HsOfficeRelationshipEntity.builder()
.relType(HsOfficeRelationshipType.JOINT_AGENT) .relType(HsOfficeRelationshipType.REPRESENTATIVE)
.relAnchor(givenAnchorPerson) .relAnchor(givenAnchorPerson)
.relHolder(givenHolderPerson) .relHolder(givenHolderPerson)
.contact(givenContact) .contact(givenContact)

View File

@ -80,7 +80,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest {
[ [
{ {
"debitor": { "debitor": {
"debitorNumber": 10002, "debitorNumber": 1000212,
"billingContact": { "label": "second contact" } "billingContact": { "label": "second contact" }
}, },
"bankAccount": { "holder": "Second e.K." }, "bankAccount": { "holder": "Second e.K." },
@ -90,7 +90,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest {
}, },
{ {
"debitor": { "debitor": {
"debitorNumber": 10001, "debitorNumber": 1000111,
"billingContact": { "label": "first contact" } "billingContact": { "label": "first contact" }
}, },
"bankAccount": { "holder": "First GmbH" }, "bankAccount": { "holder": "First GmbH" },
@ -100,7 +100,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest {
}, },
{ {
"debitor": { "debitor": {
"debitorNumber": 10003, "debitorNumber": 1000313,
"billingContact": { "label": "third contact" } "billingContact": { "label": "third contact" }
}, },
"bankAccount": { "holder": "Third OHG" }, "bankAccount": { "holder": "Third OHG" },
@ -269,7 +269,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest {
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"debitor": { "debitor": {
"debitorNumber": 10001, "debitorNumber": 1000111,
"billingContact": { "label": "first contact" } "billingContact": { "label": "first contact" }
}, },
"bankAccount": { "bankAccount": {
@ -321,7 +321,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest {
.body("", lenientlyEquals(""" .body("", lenientlyEquals("""
{ {
"debitor": { "debitor": {
"debitorNumber": 10001, "debitorNumber": 1000111,
"billingContact": { "label": "first contact" } "billingContact": { "label": "first contact" }
}, },
"bankAccount": { "bankAccount": {
@ -376,7 +376,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest {
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
assertThat(sepaMandateRepo.findByUuid(givenSepaMandate.getUuid())).isPresent().get() assertThat(sepaMandateRepo.findByUuid(givenSepaMandate.getUuid())).isPresent().get()
.matches(mandate -> { .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.getBankAccount().toShortString()).isEqualTo("First GmbH");
assertThat(mandate.getReference()).isEqualTo("temp ref CAT Z - patched"); assertThat(mandate.getReference()).isEqualTo("temp ref CAT Z - patched");
assertThat(mandate.getValidFrom()).isEqualTo("2020-06-05"); assertThat(mandate.getValidFrom()).isEqualTo("2020-06-05");
@ -417,7 +417,7 @@ class HsOfficeSepaMandateControllerAcceptanceTest {
// finally, the sepaMandate is actually updated // finally, the sepaMandate is actually updated
assertThat(sepaMandateRepo.findByUuid(givenSepaMandate.getUuid())).isPresent().get() assertThat(sepaMandateRepo.findByUuid(givenSepaMandate.getUuid())).isPresent().get()
.matches(mandate -> { .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.getBankAccount().toShortString()).isEqualTo("First GmbH");
assertThat(mandate.getReference()).isEqualTo("temp ref CAT Z"); assertThat(mandate.getReference()).isEqualTo("temp ref CAT Z");
assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,2023-01-01)"); assertThat(mandate.getValidity().asString()).isEqualTo("[2022-11-01,2023-01-01)");

View File

@ -144,13 +144,13 @@ class HsOfficeSepaMandateRepositoryIntegrationTest extends ContextBasedTest {
// agent // agent
"{ grant role sepamandate#temprefB.agent to role sepamandate#temprefB.admin by system and assume }", "{ 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 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 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 debitor#1000111:FirstGmbH-....admin by system and assume }",
// tenant // tenant
"{ grant role sepamandate#temprefB.tenant to role sepamandate#temprefB.agent by system and assume }", "{ 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 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 }", "{ grant role bankaccount#Paul....guest to role sepamandate#temprefB.tenant by system and assume }",
// guest // guest

View File

@ -22,7 +22,7 @@ class PostgresArrayIntegrationTest {
em.createNativeQuery(""" em.createNativeQuery("""
create or replace function returnEmptyArray() create or replace function returnEmptyArray()
returns text[] returns text[]
stable leakproof stable -- leakproof
language plpgsql as $$ language plpgsql as $$
declare declare
emptyArray text[] = '{}'; emptyArray text[] = '{}';
@ -42,7 +42,7 @@ class PostgresArrayIntegrationTest {
em.createNativeQuery(""" em.createNativeQuery("""
create or replace function returnStringArray() create or replace function returnStringArray()
returns varchar(63)[] returns varchar(63)[]
stable leakproof stable -- leakproof
language plpgsql as $$ language plpgsql as $$
declare declare
text1 text = 'one'; text1 text = 'one';
@ -65,7 +65,7 @@ class PostgresArrayIntegrationTest {
em.createNativeQuery(""" em.createNativeQuery("""
create or replace function returnUuidArray() create or replace function returnUuidArray()
returns uuid[] returns uuid[]
stable leakproof stable -- leakproof
language plpgsql as $$ language plpgsql as $$
declare declare
uuid1 UUID = 'f47ac10b-58cc-4372-a567-0e02b2c3d479'; uuid1 UUID = 'f47ac10b-58cc-4372-a567-0e02b2c3d479';

View File

@ -10,9 +10,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.boot.test.web.server.LocalServerPort;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
@SpringBootTest( @SpringBootTest(
@ -25,9 +22,6 @@ class RbacRoleControllerAcceptanceTest {
@LocalServerPort @LocalServerPort
private Integer port; private Integer port;
@PersistenceContext
EntityManager em;
@Autowired @Autowired
Context context; Context context;

View File

@ -1,5 +1,6 @@
package net.hostsharing.test; package net.hostsharing.test;
import net.hostsharing.hsadminng.hs.office.migration.HasUuid;
import net.hostsharing.hsadminng.mapper.EntityPatcher; import net.hostsharing.hsadminng.mapper.EntityPatcher;
import org.junit.jupiter.api.Named; import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -232,7 +233,7 @@ public abstract class PatchUnitTestBase<R, E> {
} }
} }
protected static class JsonNullableProperty<R, RV, E, EV> extends Property<R, RV, E, EV> { protected static class JsonNullableProperty<R, RV, E extends HasUuid, EV> extends Property<R, RV, E, EV> {
private final BiConsumer<R, JsonNullable<RV>> resourceSetter; private final BiConsumer<R, JsonNullable<RV>> resourceSetter;
public final RV givenPatchValue; public final RV givenPatchValue;

View File

@ -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
1 member_asset_id bp_id date action amount comment
2 30000 17 2000-12-06 PAYMENT 1280.00 for subscription A
3 31000 20 2000-12-06 PAYMENT 128.00 for subscription B
4 32000 17 2005-01-10 PAYMENT 2560.00 for subscription C
5 33001 17 2005-01-10 HANDOVER -512.00 for transfer to 10
6 33002 20 2005-01-10 ADOPTION 512.00 for transfer from 7
7 34001 20 2016-12-31 CLEARING -8.00 for cancellation D
8 34002 20 2016-12-31 PAYBACK -100.00 for cancellation D
9 34003 20 2016-12-31 LOSS -20.00 for cancellation D

View File

@ -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;
1 bp_id member_id member_code member_since member_until member_role author_contract nondisc_contract free exempt_vat indicator_vat uid_vat
2 17 10017 hsh00-mih 2000-12-06 Aufsichtsrat 2006-10-15 2001-10-15 false false NET DE-VAT-007
3 20 10020 hsh00-xyz 2000-12-06 2015-12-31 false false GROSS
4 22 11022 hsh00-xxx 2021-04-01 true true GROSS

View File

@ -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
1 contact_id; bp_id; salut; first_name; last_name; title; firma; co; street; zipcode;city; country; phone_private; phone_office; phone_mobile; fax; email; roles
2 # eine natürliche Person, implizites contractual
3 1101; 17; Herr; Michael; Mellies; ; ; ; Kleine Freiheit 50; 26524; Hage; DE; ; +49 4931 123456; +49 1522 123456;; mih@example.org; partner,billing,operation
4 # eine juristische Person mit drei separaten Ansprechpartnern, vip-contact und ex-partner
5 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
6 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
7 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
8 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
9 # eine juristische Person mit nur einem Ansprechpartner und explizitem contractual
10 1301; 22; ; Petra; Schmidt; ; Test PS;; ; ; ; ; ; ; ; ; ps@example.com; partner,billing,contractual,operation

View File

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

View File

@ -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
1 sepa_mandat_id bp_id bank_customer bank_name bank_iban bank_bic mandat_ref mandat_signed mandat_since mandat_until mandat_used
2 234234 17 Michael Mellies ING Bank AG DE37500105177419788228 INGDDEFFXXX MH12345 2004-06-12 2004-06-15 2022-10-20
3 235600 20 JM e.K. Targobank AG DE02300209000106531065 CMCIDEDD JM33344 2004-01-15 2004-01-20 2005-06-27 2016-01-18
4 235662 20 JM GmbH ING Bank AG DE49500105174516484892 INGDDEFFXXX JM33344 2005-06-28 2005-07-01 2016-01-18

View File

@ -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
1 member_share_id bp_id date action quantity comment
2 33443 17 2000-12-06 SUBSCRIPTION 20 initial share subscription
3 33451 20 2000-12-06 SUBSCRIPTION 2 initial share subscription
4 33701 17 2005-01-10 SUBSCRIPTION 40 increase
5 33810 20 2016-12-31 UNSUBSCRIPTION 22 membership ended