db-migration #10

Merged
hsh-michaelhoennig merged 74 commits from db-migration into master 2024-01-23 15:11:24 +01:00
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 () {
if [ ! -f gradlew ]; then
echo "No 'gradlew' found. Maybe you are not in the root dir of a gradle project?"
@ -36,9 +43,26 @@ postgresAutodoc () {
dot -Tsvg build/postgres-autodoc.neato >build/postgres-autodoc-rbac.svg && \
echo "generated $PWD/build/postgres-autodoc-rbac.svg"
}
alias postgres-autodoc=postgresAutodoc
function importOfficeData() {
export HSADMINNG_POSTGRES_JDBC=jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers
export HSADMINNG_POSTGRES_ADMIN_USERNAME=admin
export HSADMINNG_POSTGRES_ADMIN_PASSWORD=password
export HSADMINNG_POSTGRES_RESTRICTED_USERNAME=restricted
export HSADMINNG_SUPERUSER=superuser-alex@hostsharing.net
if [ -f .environment ]; then
source .environment
fi
echo "using environment (with ending ';' for use in IntelliJ IDEA):"
set | grep ^HSADMINNG_ | sed 's/$/;/'
hsh-michaelhoennig marked this conversation as resolved Outdated

rename: gw-importOfficeData

rename: gw-importOfficeData
./gradlew importOfficeData --rerun
}
alias gw-importOfficeData=importOfficeData
alias podman-start='systemctl --user enable --now podman.socket && systemctl --user status podman.socket && ls -la /run/user/$UID/podman/podman.sock'
alias podman-stop='systemctl --user disable --now podman.socket && systemctl --user status podman.socket && ls -la /run/user/$UID/podman/podman.sock'
alias podman-use='export DOCKER_HOST="unix:///run/user/$UID/podman/podman.sock"; export TESTCONTAINERS_RYUK_DISABLED=true'
@ -53,3 +77,6 @@ alias pg-sql-backup='docker exec -i hsadmin-ng-postgres /usr/bin/pg_dump --clean
alias pg-sql-restore='gunzip --stdout | docker exec -i hsadmin-ng-postgres psql -U postgres -d postgres'
alias fp='grep -r '@Accepts' src | sed -e 's/^.*@/@/g' | sort -u | wc -l'
alias gw-spotless='./gradlew spotlessApply -x pitest -x test -x :processResources'
alias gw-test='. .aliases; ./gradlew test'

1
.gitignore vendored
View File

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

View File

@ -100,6 +100,7 @@ dependencies {
testImplementation 'io.rest-assured:spring-mock-mvc'
testImplementation 'org.hamcrest:hamcrest-core:2.2'
testImplementation 'org.pitest:pitest-junit5-plugin:1.2.1'
testImplementation 'org.junit.jupiter:junit-jupiter-api'
}
dependencyManagement {
@ -129,7 +130,7 @@ openapiProcessor {
processor 'io.openapiprocessor:openapi-processor-spring:2022.5'
apiPath "$projectDir/src/main/resources/api-definition.yaml"
mapping "$projectDir/src/main/resources/api-mappings.yaml"
targetDir "$projectDir/build/generated/sources/openapi-javax"
targetDir "$buildDir/generated/sources/openapi-javax"
showWarnings true
openApiNullable true
}
@ -138,7 +139,7 @@ openapiProcessor {
processor 'io.openapiprocessor:openapi-processor-spring:2022.5'
apiPath "$projectDir/src/main/resources/api-definition/rbac/rbac.yaml"
mapping "$projectDir/src/main/resources/api-definition/rbac/api-mappings.yaml"
targetDir "$projectDir/build/generated/sources/openapi-javax"
targetDir "$buildDir/generated/sources/openapi-javax"
showWarnings true
openApiNullable true
}
@ -147,7 +148,7 @@ openapiProcessor {
processor 'io.openapiprocessor:openapi-processor-spring:2022.5'
apiPath "$projectDir/src/main/resources/api-definition/test/test.yaml"
mapping "$projectDir/src/main/resources/api-definition/test/api-mappings.yaml"
targetDir "$projectDir/build/generated/sources/openapi-javax"
targetDir "$buildDir/generated/sources/openapi-javax"
showWarnings true
openApiNullable true
}
@ -156,7 +157,7 @@ openapiProcessor {
processor 'io.openapiprocessor:openapi-processor-spring:2022.5'
apiPath "$projectDir/src/main/resources/api-definition/hs-office/hs-office.yaml"
mapping "$projectDir/src/main/resources/api-definition/hs-office/api-mappings.yaml"
targetDir "$projectDir/build/generated/sources/openapi-javax"
targetDir "$buildDir/generated/sources/openapi-javax"
showWarnings true
openApiNullable true
}
@ -237,6 +238,9 @@ test {
excludes = [
'net.hostsharing.hsadminng.**.generated.**',
]
useJUnitPlatform {
excludeTags 'import'
}
}
jacocoTestReport {
dependsOn test
@ -297,6 +301,16 @@ jacocoTestCoverageVerification {
}
}
tasks.register('importOfficeData', Test) {
useJUnitPlatform {
includeTags 'import'
}
group 'verification'
description 'run the import jobs as tests'
}
// pitest mutation testing
pitest {
targetClasses = ['net.hostsharing.hsadminng.**']

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

View File

@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.office.contact;
import lombok.*;
import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.migration.HasUuid;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator;
@ -21,7 +22,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@AllArgsConstructor
@FieldNameConstants
@DisplayName("Contact")
public class HsOfficeContactEntity implements Stringifyable {
public class HsOfficeContactEntity implements Stringifyable, HasUuid {
private static Stringify<HsOfficeContactEntity> toString = stringify(HsOfficeContactEntity.class, "contact")
.withProp(Fields.label, HsOfficeContactEntity::getLabel)
@ -38,10 +39,10 @@ public class HsOfficeContactEntity implements Stringifyable {
private String postalAddress;
@Column(name = "emailaddresses", columnDefinition = "json")
private String emailAddresses;
private String emailAddresses; // TODO: check if we can really add multiple. format: ["eins@...", "zwei@..."]
@Column(name = "phonenumbers", columnDefinition = "json")
private String phoneNumbers;
private String phoneNumbers; // TODO: check if we can really add multiple. format: { "office": "+49 40 12345-10", "fax": "+49 40 12345-05" }
@Override
public String toString() {

View File

@ -1,8 +1,10 @@
package net.hostsharing.hsadminng.hs.office.coopassets;
import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
import net.hostsharing.hsadminng.hs.office.migration.HasUuid;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator;
@ -13,6 +15,7 @@ import java.text.DecimalFormat;
import java.time.LocalDate;
import java.util.UUID;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity
@ -23,14 +26,15 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@NoArgsConstructor
@AllArgsConstructor
@DisplayName("CoopAssetsTransaction")
public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable {
public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, HasUuid {
private static Stringify<HsOfficeCoopAssetsTransactionEntity> stringify = stringify(HsOfficeCoopAssetsTransactionEntity.class)
.withProp(e -> e.getMembership().getMemberNumber())
.withProp(HsOfficeCoopAssetsTransactionEntity::getMemberNumber)
hsh-michaelhoennig marked this conversation as resolved Outdated

Diese Änderung bitte auch bei CoopShares

Diese Änderung bitte auch bei CoopShares
.withProp(HsOfficeCoopAssetsTransactionEntity::getValueDate)
.withProp(HsOfficeCoopAssetsTransactionEntity::getTransactionType)
.withProp(HsOfficeCoopAssetsTransactionEntity::getAssetValue)
.withProp(HsOfficeCoopAssetsTransactionEntity::getReference)
.withProp(HsOfficeCoopAssetsTransactionEntity::getComment)
.withSeparator(", ")
.quotedValues(false);
@ -54,11 +58,16 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable {
private BigDecimal assetValue;
@Column(name = "reference")
private String reference;
private String reference; // TODO: what is this for?
@Column(name = "comment")
private String comment;
public Integer getMemberNumber() {
return ofNullable(membership).map(HsOfficeMembershipEntity::getMemberNumber).orElse(null);
}
@Override
public String toString() {
return stringify.apply(this);
@ -66,6 +75,6 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable {
@Override
public String toShortString() {
return membership.getMemberNumber() + new DecimalFormat("+0.00").format(assetValue);
return "%s%+1.2f".formatted(getMemberNumber(), assetValue);
}
}

View File

@ -1,5 +1,12 @@
package net.hostsharing.hsadminng.hs.office.coopassets;
public enum HsOfficeCoopAssetsTransactionType {
ADJUSTMENT, DEPOSIT, DISBURSAL, TRANSFER, ADOPTION, CLEARING, LOSS
ADJUSTMENT, // correction of wrong bookings
DEPOSIT, // payment received from member after signing shares, >0
DISBURSAL, // payment send to member after cancellation of shares, <0
TRANSFER, // transferring shares to another member, <0
ADOPTION, // receiving shares from another member, >0
CLEARING, // settlement with members dept, <0
LOSS, // assignment of balance sheet loss in case of cancellation of shares, <0
LIMITATION // limitation period was reached after impossible disbursal, <0
}

View File

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

View File

@ -1,5 +1,7 @@
package net.hostsharing.hsadminng.hs.office.coopshares;
public enum HsOfficeCoopSharesTransactionType {
ADJUSTMENT, SUBSCRIPTION, CANCELLATION;
ADJUSTMENT, // correction of wrong bookings
SUBSCRIPTION, // shares signed, e.g. with the declaration of accession, >0
CANCELLATION; // shares terminated, e.g. when a membership is resigned, <0
}

View File

@ -4,12 +4,14 @@ import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.migration.HasUuid;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator;
import jakarta.persistence.*;
import java.util.Optional;
import java.util.UUID;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@ -22,12 +24,16 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@NoArgsConstructor
@AllArgsConstructor
@DisplayName("Debitor")
public class HsOfficeDebitorEntity implements Stringifyable {
public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
// TODO: I would rather like to generate something matching this example:
// debitor(1234500: Test AG, tes)
// maybe remove withSepararator (always use ', ') and add withBusinessIdProp (with ': ' afterwards)?
private static Stringify<HsOfficeDebitorEntity> stringify =
stringify(HsOfficeDebitorEntity.class, "debitor")
.withProp(HsOfficeDebitorEntity::getDebitorNumber)
.withProp(HsOfficeDebitorEntity::getPartner)
.withProp(HsOfficeDebitorEntity::getDefaultPrefix)
.withSeparator(": ")
.quotedValues(false);
@ -40,12 +46,15 @@ public class HsOfficeDebitorEntity implements Stringifyable {
@JoinColumn(name = "partneruuid")
private HsOfficePartnerEntity partner;
@Column(name = "debitornumber")
private Integer debitorNumber;
@Column(name = "debitornumbersuffix", columnDefinition = "numeric(2)")
private Byte debitorNumberSuffix; // TODO maybe rather as a formatted String?
@ManyToOne
@JoinColumn(name = "billingcontactuuid")
private HsOfficeContactEntity billingContact;
private HsOfficeContactEntity billingContact; // TODO: migrate to billingPerson
@Column(name = "billable", nullable = false)
private Boolean billable; // not a primitive because otherwise the default would be false
@Column(name = "vatid")
private String vatId;
@ -56,10 +65,34 @@ public class HsOfficeDebitorEntity implements Stringifyable {
@Column(name = "vatbusiness")
private boolean vatBusiness;
@Column(name = "vatreversecharge")
private boolean vatReverseCharge;
@ManyToOne
@JoinColumn(name = "refundbankaccountuuid")
private HsOfficeBankAccountEntity refundBankAccount;
@Column(name = "defaultprefix", columnDefinition = "char(3) not null")
private String defaultPrefix;
public String getDebitorNumberString() {
// TODO: refactor
if (partner.getDebitorNumberPrefix() == null ) {
if (debitorNumberSuffix == null) {
return null;
}
return String.format("%02d", debitorNumberSuffix);
}
if (debitorNumberSuffix == null) {
return partner.getDebitorNumberPrefix() + "??";
}
return partner.getDebitorNumberPrefix() + String.format("%02d", debitorNumberSuffix);
}
public Integer getDebitorNumber() {
return Optional.ofNullable(getDebitorNumberString()).map(Integer::parseInt).orElse(null);
}
@Override
public String toString() {
return stringify.apply(this);
@ -67,6 +100,6 @@ public class HsOfficeDebitorEntity implements Stringifyable {
@Override
public String toShortString() {
return debitorNumber.toString();
return getDebitorNumberString();
}
}

View File

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

View File

@ -13,9 +13,14 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
@Query("""
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("""
SELECT debitor FROM HsOfficeDebitorEntity debitor

View File

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

alle neuen Felder prüfen, ob sie im Patcher und Test berücksichtigt sind

alle neuen Felder prüfen, ob sie im Patcher und Test berücksichtigt sind
@Column(name = "reasonfortermination")
@Enumerated(EnumType.STRING)
private HsOfficeReasonForTermination reasonForTermination;

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,10 +6,12 @@ components:
HsOfficeRelationshipType:
type: string
enum:
- SOLE_AGENT # e.g. CEO
- JOINT_AGENT # e.g. heir
- ACCOUNTING_CONTACT
- TECHNICAL_CONTACT
- UNKNOWN
hsh-michaelhoennig marked this conversation as resolved Outdated

hier fehlen welche

hier fehlen welche
- EX_PARTNER
- REPRESENTATIVE,
- VIP_CONTACT
- ACCOUNTING,
- OPERATIONS
HsOfficeRelationship:
type: object

View File

@ -20,3 +20,7 @@ spring:
liquibase:
contexts: dev
hsadminng:
hsh-michaelhoennig marked this conversation as resolved

remove, unused

remove, unused

fixed

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

View File

@ -208,7 +208,7 @@ create type RbacRoleDescriptor as
create or replace function roleDescriptor(objectTable varchar(63), objectUuid uuid, roleType RbacRoleType)
returns RbacRoleDescriptor
returns null on null input
stable leakproof
stable -- leakproof
language sql as $$
select objectTable, objectUuid, roleType::RbacRoleType;
$$;
@ -432,7 +432,7 @@ $$;
create or replace function findPermissionId(forObjectUuid uuid, forOp RbacOp)
returns uuid
returns null on null input
stable leakproof
stable -- leakproof
language sql as $$
select uuid
from RbacPermission p
@ -515,7 +515,7 @@ end; $$;
create or replace function isPermissionGrantedToSubject(permissionId uuid, subjectId uuid)
returns BOOL
stable leakproof
stable -- leakproof
language sql as $$
select exists(
select *
@ -537,7 +537,7 @@ $$;
create or replace function hasGlobalRoleGranted(userUuid uuid)
returns bool
stable leakproof
stable -- leakproof
language sql as $$
select exists(
select r.uuid
@ -758,13 +758,20 @@ $$;
-- ============================================================================
--changeset rbac-base-PGSQL-ROLES:1 endDelimiter:--//
--changeset rbac-base-PGSQL-ROLES:1 context:dev,tc endDelimiter:--//
-- ----------------------------------------------------------------------------
create role admin;
grant all privileges on all tables in schema public to admin;
create role restricted;
grant all privileges on all tables in schema public to restricted;
do $$
begin
if '${HSADMINNG_POSTGRES_ADMIN_USERNAME}'='admin' then
create role admin;
grant all privileges on all tables in schema public to admin;
end if;
if '${HSADMINNG_POSTGRES_RESTRICTED_USERNAME}'='restricted' then
create role restricted;
end if;
-- grant all privileges on all tables in schema public to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME};
end $$
--//

View File

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

View File

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

View File

@ -41,7 +41,7 @@ select *
) as unordered
-- @formatter:on
order by objectTable || '#' || objectIdName || '.' || roleType;
grant all privileges on rbacrole_rv to restricted;
grant all privileges on rbacrole_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME};
--//
@ -126,7 +126,7 @@ select o.objectTable || '#' || findIdNameByObjectUuid(o.objectTable, o.uuid) ||
join RbacObject as o on o.uuid = r.objectUuid
order by grantedRoleIdName;
-- @formatter:on
grant all privileges on rbacrole_rv to restricted;
grant all privileges on rbacrole_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME};
--//
@ -240,7 +240,7 @@ create or replace view RbacUser_rv as
) as unordered
-- @formatter:on
order by unordered.name;
grant all privileges on RbacUser_rv to restricted;
grant all privileges on RbacUser_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME};
--//
-- ============================================================================
@ -326,7 +326,7 @@ select r.uuid as roleuuid, p.uuid as permissionUuid,
join rbacgrants g on g.ascendantuuid = r.uuid
join rbacpermission p on p.uuid = g.descendantuuid
join rbacobject o on o.uuid = p.objectuuid;
grant all privileges on RbacOwnGrantedPermissions_rv to restricted;
grant all privileges on RbacOwnGrantedPermissions_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME};
-- @formatter:om
-- ============================================================================

View File

@ -104,7 +104,7 @@ begin
create or replace view %1$s_iv as
select target.uuid, cleanIdentifier(%2$s) as idName
from %1$s as target;
grant all privileges on %1$s_iv to restricted;
grant all privileges on %1$s_iv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME};
$sql$, targetTable, idNameExpression);
execute sql;
@ -157,7 +157,7 @@ begin
from %1$s as target
where target.uuid in (select * from accessibleObjects)
order by %2$s;
grant all privileges on %1$s_rv to restricted;
grant all privileges on %1$s_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME};
$sql$, targetTable, orderBy);
execute sql;

View File

@ -18,7 +18,7 @@ create table Global
);
create unique index Global_Singleton on Global ((0));
grant select on global to restricted;
grant select on global to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME};
--//
@ -48,7 +48,7 @@ drop view if exists global_iv;
create or replace view global_iv as
select target.uuid, target.name as idName
from global as target;
grant all privileges on global_iv to restricted;
grant all privileges on global_iv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME};
/*
Returns the objectUuid for a given identifying name (in this case the idName).
@ -99,7 +99,7 @@ commit;
create or replace function globalAdmin()
returns RbacRoleDescriptor
returns null on null input
stable leakproof
stable -- leakproof
language sql as $$
select 'global', (select uuid from RbacObject where objectTable = 'global'), 'admin'::RbacRoleType;
$$;

View File

@ -93,7 +93,7 @@ call generateRbacIdentityView('test_package', 'target.name');
-- from test_package as target
-- where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'test_package', currentSubjectsUuids()))
-- order by target.name;
-- grant all privileges on test_package_rv to restricted;
-- grant all privileges on test_package_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME};
call generateRbacRestrictedView('test_package', 'target.name',
$updates$

View File

@ -110,5 +110,5 @@ create or replace view test_domain_rv as
select target.*
from test_domain as target
where target.uuid in (select queryAccessibleObjectUuidsOfSubjectIds('view', 'domain', currentSubjectsUuids()));
grant all privileges on test_domain_rv to restricted;
grant all privileges on test_domain_rv to ${HSADMINNG_POSTGRES_RESTRICTED_USERNAME};
--//

View File

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

View File

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

View File

@ -4,7 +4,7 @@
--changeset hs-office-person-MAIN-TABLE:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
CREATE TYPE HsOfficePersonType AS ENUM ('NATURAL', 'LEGAL', 'SOLE_REPRESENTATION', 'JOINT_REPRESENTATION');
CREATE TYPE HsOfficePersonType AS ENUM ('UNKNOWN', 'NATURAL', 'LEGAL', 'SOLE_REPRESENTATION', 'JOINT_REPRESENTATION');
CREATE CAST (character varying as HsOfficePersonType) WITH INOUT AS IMPLICIT;

View File

@ -37,6 +37,7 @@ begin
grantedByRole => globalAdmin()
);
-- TODO: who is admin? the person itself? is it allowed for the person itself or a representative to edit the data?
perform createRoleWithGrants(
hsOfficePersonAdmin(NEW),
permissions => array['edit'],

View File

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

View File

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

View File

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

View File

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

View File

@ -4,7 +4,13 @@
--changeset hs-office-relationship-MAIN-TABLE:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
CREATE TYPE HsOfficeRelationshipType AS ENUM ('SOLE_AGENT', 'JOINT_AGENT', 'CO_OWNER', 'ACCOUNTING_CONTACT', 'TECHNICAL_CONTACT');
CREATE TYPE HsOfficeRelationshipType AS ENUM (
'UNKNOWN',
'EX_PARTNER',
'REPRESENTATIVE',
'VIP_CONTACT',
'ACCOUNTING',
'OPERATIONS');
CREATE CAST (character varying as HsOfficeRelationshipType) WITH INOUT AS IMPLICIT;

View File

@ -58,7 +58,7 @@ begin
select p.* from hs_office_person p where tradeName = intToVarChar(t, 4) into person;
select c.* from hs_office_contact c where c.label = intToVarChar(t, 4) || '#' || t into contact;
call createHsOfficeRelationshipTestData(person.uuid, contact.uuid, 'SOLE_AGENT');
call createHsOfficeRelationshipTestData(person.uuid, contact.uuid, 'REPRESENTATIVE');
commit;
end loop;
end; $$;
@ -71,11 +71,11 @@ end; $$;
do language plpgsql $$
begin
call createHsOfficeRelationshipTestData('First GmbH', 'Smith', 'SOLE_AGENT', 'first contact');
call createHsOfficeRelationshipTestData('First GmbH', 'Smith', 'REPRESENTATIVE', 'first contact');
call createHsOfficeRelationshipTestData('Second e.K.', 'Smith', 'SOLE_AGENT', 'second contact');
call createHsOfficeRelationshipTestData('Second e.K.', 'Smith', 'REPRESENTATIVE', 'second contact');
call createHsOfficeRelationshipTestData('Third OHG', 'Smith', 'SOLE_AGENT', 'third contact');
call createHsOfficeRelationshipTestData('Third OHG', 'Smith', 'REPRESENTATIVE', 'third contact');
end;
$$;
--//

View File

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

View File

@ -10,7 +10,7 @@
CREATE TABLE hs_office_sepamandate_legacy_id
(
uuid uuid NOT NULL REFERENCES hs_office_sepamandate(uuid),
sepa_mandat_id integer NOT NULL
sepa_mandate_id integer NOT NULL
);
--//
@ -22,7 +22,7 @@ CREATE TABLE hs_office_sepamandate_legacy_id
CREATE SEQUENCE IF NOT EXISTS hs_office_sepamandate_legacy_id_seq
AS integer
START 1000000000
OWNED BY hs_office_sepamandate_legacy_id.sepa_mandat_id;
OWNED BY hs_office_sepamandate_legacy_id.sepa_mandate_id;
--//
@ -31,7 +31,7 @@ CREATE SEQUENCE IF NOT EXISTS hs_office_sepamandate_legacy_id_seq
-- ----------------------------------------------------------------------------
ALTER TABLE hs_office_sepamandate_legacy_id
ALTER COLUMN sepa_mandat_id
ALTER COLUMN sepa_mandate_id
SET DEFAULT nextVal('hs_office_sepamandate_legacy_id_seq');
--/
@ -42,7 +42,7 @@ ALTER TABLE hs_office_sepamandate_legacy_id
-- ----------------------------------------------------------------------------
CALL defineContext('schema-migration');
INSERT INTO hs_office_sepamandate_legacy_id(uuid, sepa_mandat_id)
INSERT INTO hs_office_sepamandate_legacy_id(uuid, sepa_mandate_id)
SELECT uuid, nextVal('hs_office_sepamandate_legacy_id_seq') FROM hs_office_sepamandate;
--/

View File

@ -8,12 +8,18 @@ create table hs_office_debitor
(
uuid uuid unique references RbacObject (uuid) initially deferred,
partnerUuid uuid not null references hs_office_partner(uuid),
debitorNumber numeric(5) not null,
billable boolean not null default true,
debitorNumberSuffix numeric(2) not null,
billingContactUuid uuid not null references hs_office_contact(uuid),
vatId varchar(24), -- TODO.spec: here or in person?
vatCountryCode varchar(2),
vatBusiness boolean not null, -- TODO.spec: more of such?
refundBankAccountUuid uuid references hs_office_bankaccount(uuid)
vatBusiness boolean not null,
vatReverseCharge boolean not null,
refundBankAccountUuid uuid references hs_office_bankaccount(uuid),
defaultPrefix char(3) not null unique
hsh-michaelhoennig marked this conversation as resolved Outdated

unique

unique

fixed

fixed
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
);
--//

View File

@ -172,8 +172,10 @@ execute procedure hsOfficeDebitorRbacRolesTrigger();
--changeset hs-office-debitor-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityView('hs_office_debitor', $idName$
'#' || debitorNumber || ':' ||
(select idName from hs_office_partner_iv p where p.uuid = target.partnerUuid)
'#' ||
(select debitornumberprefix from hs_office_partner p where p.uuid = target.partnerUuid) ||
to_char(debitorNumberSuffix, 'fm00') ||
':' || (select split_part(idName, ':', 2) from hs_office_partner_iv pi where pi.uuid = target.partnerUuid)
$idName$);
--//
@ -181,14 +183,18 @@ call generateRbacIdentityView('hs_office_debitor', $idName$
-- ============================================================================
--changeset hs-office-debitor-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_debitor', 'target.debitorNumber',
call generateRbacRestrictedView('hs_office_debitor', 'target.debitorNumberSuffix',
$updates$
partnerUuid = new.partnerUuid,
partnerUuid = new.partnerUuid, -- TODO: remove? should never do anything
billable = new.billable,
billingContactUuid = new.billingContactUuid,
debitorNumberSuffix = new.debitorNumberSuffix, -- TODO: Should it be allowed to updated this value?
hsh-michaelhoennig marked this conversation as resolved Outdated

add TODO: prüfen, ob wir das wollen

add TODO: prüfen, ob wir das wollen
refundBankAccountUuid = new.refundBankAccountUuid,
vatId = new.vatId,
vatCountryCode = new.vatCountryCode,
vatBusiness = new.vatBusiness
vatBusiness = new.vatBusiness,
vatreversecharge = new.vatreversecharge,
defaultPrefix = new.defaultPrefix -- TODO: Should it be allowed to updated this value?
hsh-michaelhoennig marked this conversation as resolved Outdated

add TODO: prüfen, ob wir das wollen

add TODO: prüfen, ob wir das wollen
$updates$);
--//

View File

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

View File

@ -4,7 +4,7 @@
--changeset hs-office-membership-MAIN-TABLE:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
CREATE TYPE HsOfficeReasonForTermination AS ENUM ('NONE', 'CANCELLATION', 'TRANSFER', 'DEATH', 'LIQUIDATION', 'EXPULSION');
CREATE TYPE HsOfficeReasonForTermination AS ENUM ('NONE', 'CANCELLATION', 'TRANSFER', 'DEATH', 'LIQUIDATION', 'EXPULSION', 'UNKNOWN');
CREATE CAST (character varying as HsOfficeReasonForTermination) WITH INOUT AS IMPLICIT;
@ -15,7 +15,8 @@ create table if not exists hs_office_membership
mainDebitorUuid uuid not null references hs_office_debitor(uuid),
memberNumber numeric(5) not null unique,
validity daterange not null,
reasonForTermination HsOfficeReasonForTermination not null default 'NONE'
reasonForTermination HsOfficeReasonForTermination not null default 'NONE',
membershipFeeBillable boolean not null default true
);
--//

View File

@ -27,7 +27,7 @@ create or replace function hsOfficeMembershipRbacRolesTrigger()
language plpgsql
strict as $$
declare
newHsOfficePartner hs_office_partner;
newHsOfficePartner hs_office_partner;
newHsOfficeDebitor hs_office_debitor;
begin
@ -92,7 +92,8 @@ execute procedure hsOfficeMembershipRbacRolesTrigger();
--changeset hs-office-membership-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityView('hs_office_membership', idNameExpression => $idName$
target.memberNumber || (select idName from hs_office_partner_iv p where p.uuid = target.partnerUuid)
target.memberNumber ||
':' || (select split_part(idName, ':', 2) from hs_office_partner_iv p where p.uuid = target.partnerUuid)
$idName$);
--//
@ -104,7 +105,8 @@ call generateRbacRestrictedView('hs_office_membership',
orderby => 'target.memberNumber',
columnUpdates => $updates$
validity = new.validity,
reasonForTermination = new.reasonForTermination
reasonForTermination = new.reasonForTermination,
membershipFeeBillable = new.membershipFeeBillable
$updates$);
--//

View File

@ -11,11 +11,11 @@
create or replace procedure createHsOfficeMembershipTestData( forPartnerTradeName varchar, forMainDebitorNumber numeric )
hsh-michaelhoennig marked this conversation as resolved Outdated

remove

remove
language plpgsql as $$
declare
currentTask varchar;
idName varchar;
relatedPartner hs_office_partner;
relatedDebitor hs_office_debitor;
newMemberNumber numeric;
currentTask varchar;
idName varchar;
relatedPartner hs_office_partner;
relatedDebitor hs_office_debitor;
hsh-michaelhoennig marked this conversation as resolved

remove

remove

fixed

fixed
newMemberNumber numeric;
begin
idName := cleanIdentifier( forPartnerTradeName || '#' || forMainDebitorNumber);
currentTask := 'creating Membership test-data ' || idName;
@ -25,7 +25,7 @@ begin
select partner.* from hs_office_partner partner
join hs_office_person person on person.uuid = partner.personUuid
where person.tradeName = forPartnerTradeName into relatedPartner;
select d.* from hs_office_debitor d where d.debitorNumber = forMainDebitorNumber into relatedDebitor;
select d.* from hs_office_debitor d where d.debitorNumberSuffix = forMainDebitorNumber into relatedDebitor;
select coalesce(max(memberNumber)+1, 10001) from hs_office_membership into newMemberNumber;
raise notice 'creating test Membership: %', idName;
hsh-michaelhoennig marked this conversation as resolved Outdated

remove

remove
@ -44,9 +44,9 @@ end; $$;
do language plpgsql $$
begin
call createHsOfficeMembershipTestData('First GmbH', 10001);
call createHsOfficeMembershipTestData('Second e.K.', 10002);
call createHsOfficeMembershipTestData('Third OHG', 10003);
call createHsOfficeMembershipTestData('First GmbH', 11);
call createHsOfficeMembershipTestData('Second e.K.', 12);
call createHsOfficeMembershipTestData('Third OHG', 13);
end;
$$;
--//

View File

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

View File

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

View File

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

View File

@ -17,6 +17,7 @@ class HsOfficeCoopAssetsTransactionEntityUnitTest {
.transactionType(HsOfficeCoopAssetsTransactionType.DEPOSIT)
.assetValue(new BigDecimal("128.00"))
.build();
final HsOfficeCoopAssetsTransactionEntity givenEmptyCoopAssetsTransaction = HsOfficeCoopAssetsTransactionEntity.builder().build();
@Test
void toStringContainsAlmostAllPropertiesAccount() {
@ -31,4 +32,18 @@ class HsOfficeCoopAssetsTransactionEntityUnitTest {
assertThat(result).isEqualTo("300001+128.00");
}
@Test
void toStringWithEmptyTransactionDoesNotThrowException() {
final var result = givenEmptyCoopAssetsTransaction.toString();
assertThat(result).isEqualTo("CoopAssetsTransaction()");
}
@Test
void toShortStringEmptyTransactionDoesNotThrowException() {
final var result = givenEmptyCoopAssetsTransaction.toShortString();
assertThat(result).isEqualTo("nullnu");
}
}

View File

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

View File

@ -16,6 +16,7 @@ class HsOfficeCoopSharesTransactionEntityUnitTest {
.transactionType(HsOfficeCoopSharesTransactionType.SUBSCRIPTION)
.shareCount(4)
.build();
final HsOfficeCoopSharesTransactionEntity givenEmptyCoopSharesTransaction = HsOfficeCoopSharesTransactionEntity.builder().build();
@Test
void toStringContainsAlmostAllPropertiesAccount() {
@ -25,9 +26,23 @@ class HsOfficeCoopSharesTransactionEntityUnitTest {
}
@Test
void toShortStringContainsOnlyMemberNumberAndshareCountOnly() {
void toShortStringContainsOnlyMemberNumberAndShareCountOnly() {
final var result = givenCoopSharesTransaction.toShortString();
assertThat(result).isEqualTo("300001+4");
}
@Test
void toStringEmptyTransactionDoesNotThrowException() {
final var result = givenEmptyCoopSharesTransaction.toString();
assertThat(result).isEqualTo("CoopShareTransaction(0)");
}
@Test
void toShortStringEmptyTransactionDoesNotThrowException() {
final var result = givenEmptyCoopSharesTransaction.toShortString();
assertThat(result).isEqualTo("null+0");
}
}

View File

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

View File

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

View File

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

copy+paste Fehler: vatBusiness

copy+paste Fehler: vatBusiness
HsOfficeDebitorPatchResource::setVatReverseCharge,
PATCHED_BILLABLE,
HsOfficeDebitorEntity::setVatReverseCharge)
.notNullable(),
new JsonNullableProperty<>(
"defaultPrefix",
HsOfficeDebitorPatchResource::setDefaultPrefix,
hsh-michaelhoennig marked this conversation as resolved Outdated

copy+paste Fehler: billable

copy+paste Fehler: billable
PATCHED_DEFAULT_PREFIX,
HsOfficeDebitorEntity::setDefaultPrefix)
.notNullable(),
new JsonNullableProperty<>(
"refundBankAccount",
HsOfficeDebitorPatchResource::setRefundBankAccountUuid,
PATCHED_REFUND_BANK_ACCOUNT_UUID,
HsOfficeDebitorEntity::setRefundBankAccount,
newBankAccount(PATCHED_REFUND_BANK_ACCOUNT_UUID))
.notNullable()
);
}
@ -109,4 +153,10 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase<
newContact.setUuid(uuid);
return newContact;
}
private HsOfficeBankAccountEntity newBankAccount(final UUID uuid) {
final var newBankAccount = new HsOfficeBankAccountEntity();
newBankAccount.setUuid(uuid);
return newBankAccount;
}
}

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

View File

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

View File

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

View File

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

View File

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

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 PATCHED_VALID_TO = LocalDate.parse("2022-12-31");
private static final Boolean GIVEN_MEMBERSHIP_FEE_BILLABLE = true;
private static final Boolean PATCHED_MEMBERSHIP_FEE_BILLABLE = false;
@Mock
private EntityManager em;
@ -56,6 +59,7 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase<
entity.setMainDebitor(TEST_DEBITOR);
entity.setPartner(TEST_PARTNER);
entity.setValidity(Range.closedInfinite(GIVEN_VALID_FROM));
entity.setMembershipFeeBillable(GIVEN_MEMBERSHIP_FEE_BILLABLE);
return entity;
}
@ -90,7 +94,12 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase<
HsOfficeReasonForTerminationResource.CANCELLATION,
HsOfficeMembershipEntity::setReasonForTermination,
HsOfficeReasonForTermination.CANCELLATION)
.notNullable()
.notNullable(),
new JsonNullableProperty<>(
"membershipFeeBillable",
HsOfficeMembershipPatchResource::setMembershipFeeBillable,
PATCHED_MEMBERSHIP_FEE_BILLABLE,
HsOfficeMembershipEntity::setMembershipFeeBillable)
);
}
@ -99,10 +108,4 @@ class HsOfficeMembershipEntityPatcherUnitTest extends PatchUnitTestBase<
newDebitor.setUuid(uuid);
return newDebitor;
}
private HsOfficeMembershipEntity newMembership(final UUID uuid) {
final var newMembership = new HsOfficeMembershipEntity();
newMembership.setUuid(uuid);
return newMembership;
}
}

View File

@ -27,7 +27,7 @@ class HsOfficeMembershipEntityUnitTest {
void toStringContainsAllProps() {
final var result = givenMembership.toString();
assertThat(result).isEqualTo("Membership(10001, Test Ltd., 10001, [2020-01-01,))");
assertThat(result).isEqualTo("Membership(10001, LEGAL Test Ltd., 1000100, [2020-01-01,))");
}
@Test

View File

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

File diff suppressed because it is too large Load Diff

View File

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

typo: debitorNumberPrefix

typo: debitorNumberPrefix
"contactUuid": "%s",
"personUuid": "%s",
"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_CONTACT_UUID = UUID.randomUUID();
private static final UUID INITIAL_PERSON_UUID = UUID.randomUUID();
private static final String INITIAL_BIRTHPLACE = null;
private static final String PATCHED_BIRTHPLACE = "Essen (Ruhr)";
private static final LocalDate INITIAL_BIRTHDAY = LocalDate.parse("1900-01-01");
private static final LocalDate PATCHED_BIRTHDAY = LocalDate.parse("1990-12-31");
@ -54,6 +55,7 @@ class HsOfficePartnerDetailsEntityPatcherUnitTest extends PatchUnitTestBase<
entity.setUuid(INITIAL_PARTNER_UUID);
entity.setRegistrationOffice("initial Reg-Office");
entity.setRegistrationNumber("initial Reg-Number");
entity.setBirthPlace(INITIAL_BIRTHPLACE);
entity.setBirthday(INITIAL_BIRTHDAY);
entity.setBirthName("initial birth name");
entity.setDateOfDeath(INITIAL_DAY_OF_DEATH);
@ -78,6 +80,16 @@ class HsOfficePartnerDetailsEntityPatcherUnitTest extends PatchUnitTestBase<
HsOfficePartnerDetailsPatchResource::setRegistrationOffice,
"patched Reg-Office",
HsOfficePartnerDetailsEntity::setRegistrationOffice),
new JsonNullableProperty<>(
"birthplace",
HsOfficePartnerDetailsPatchResource::setBirthPlace,
PATCHED_BIRTHPLACE,
HsOfficePartnerDetailsEntity::setBirthPlace),
new JsonNullableProperty<>(
"birthname",
HsOfficePartnerDetailsPatchResource::setBirthName,
"patched birth name",
HsOfficePartnerDetailsEntity::setBirthName),
new JsonNullableProperty<>(
"birthday",
HsOfficePartnerDetailsPatchResource::setBirthday,

View File

@ -21,7 +21,8 @@ class HsOfficePartnerDetailsEntityUnitTest {
final var result = given.toString();
assertThat(result).isEqualTo("partnerDetails(Hamburg, 12345, 2002-01-15, 2002-01-15, 2081-12-21)");
assertThat(result).isEqualTo(
"partnerDetails(Hamburg, 12345, 2002-01-15, Melly Miller, 2081-12-21)");
}
@Test

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

View File

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

View File

@ -8,10 +8,11 @@ import static net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType.LEGA
public class TestHsOfficePartner {
public static final HsOfficePartnerEntity TEST_PARTNER = HsOfficePartnerWithLegalPerson("Test Ltd.");
public static final HsOfficePartnerEntity TEST_PARTNER = hsOfficePartnerWithLegalPerson("Test Ltd.");
static public HsOfficePartnerEntity HsOfficePartnerWithLegalPerson(final String tradeName) {
static public HsOfficePartnerEntity hsOfficePartnerWithLegalPerson(final String tradeName) {
return HsOfficePartnerEntity.builder()
.debitorNumberPrefix(10001)
.person(HsOfficePersonEntity.builder()
.personType(LEGAL)
.tradeName(tradeName)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -134,24 +134,24 @@ class HsOfficeSepaMandateRepositoryIntegrationTest extends ContextBasedTest {
initialGrantNames,
// owner
"{ grant perm * on sepamandate#temprefB to role sepamandate#temprefB.owner by system and assume }",
"{ grant role sepamandate#temprefB.owner to role global#global.admin by system and assume }",
"{ grant perm * on sepamandate#temprefB to role sepamandate#temprefB.owner by system and assume }",
"{ grant role sepamandate#temprefB.owner to role global#global.admin by system and assume }",
// admin
"{ grant perm edit on sepamandate#temprefB to role sepamandate#temprefB.admin by system and assume }",
"{ grant role sepamandate#temprefB.admin to role sepamandate#temprefB.owner by system and assume }",
"{ grant role bankaccount#Paul....tenant to role sepamandate#temprefB.admin by system and assume }",
"{ grant perm edit on sepamandate#temprefB to role sepamandate#temprefB.admin by system and assume }",
"{ grant role sepamandate#temprefB.admin to role sepamandate#temprefB.owner by system and assume }",
"{ grant role bankaccount#Paul....tenant to role sepamandate#temprefB.admin by system and assume }",
// agent
"{ grant role sepamandate#temprefB.agent to role sepamandate#temprefB.admin by system and assume }",
"{ grant role debitor#10001FirstGmbH-....tenant to role sepamandate#temprefB.agent by system and assume }",
"{ grant role sepamandate#temprefB.agent to role bankaccount#Paul....admin by system and assume }",
"{ grant role sepamandate#temprefB.agent to role debitor#10001FirstGmbH-....admin by system and assume }",
"{ grant role sepamandate#temprefB.agent to role sepamandate#temprefB.admin by system and assume }",
"{ grant role debitor#1000111:FirstGmbH-....tenant to role sepamandate#temprefB.agent by system and assume }",
"{ grant role sepamandate#temprefB.agent to role bankaccount#Paul....admin by system and assume }",
"{ grant role sepamandate#temprefB.agent to role debitor#1000111:FirstGmbH-....admin by system and assume }",
// tenant
"{ grant role sepamandate#temprefB.tenant to role sepamandate#temprefB.agent by system and assume }",
"{ grant role debitor#10001FirstGmbH-....guest to role sepamandate#temprefB.tenant by system and assume }",
"{ grant role bankaccount#Paul....guest to role sepamandate#temprefB.tenant by system and assume }",
"{ grant role sepamandate#temprefB.tenant to role sepamandate#temprefB.agent by system and assume }",
"{ grant role debitor#1000111:FirstGmbH-....guest to role sepamandate#temprefB.tenant by system and assume }",
"{ grant role bankaccount#Paul....guest to role sepamandate#temprefB.tenant by system and assume }",
// guest
"{ grant perm view on sepamandate#temprefB to role sepamandate#temprefB.guest by system and assume }",

View File

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

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.web.server.LocalServerPort;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import static org.hamcrest.Matchers.*;
@SpringBootTest(
@ -25,9 +22,6 @@ class RbacRoleControllerAcceptanceTest {
@LocalServerPort
private Integer port;
@PersistenceContext
EntityManager em;
@Autowired
Context context;

View File

@ -1,5 +1,6 @@
package net.hostsharing.test;
import net.hostsharing.hsadminng.hs.office.migration.HasUuid;
import net.hostsharing.hsadminng.mapper.EntityPatcher;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.Test;
@ -232,7 +233,7 @@ public abstract class PatchUnitTestBase<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;
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