Compare commits

...

17 Commits

Author SHA1 Message Date
Michael Hoennig
368170d27e Merge remote-tracking branch 'origin/master' into db-migration 2024-01-10 17:51:02 +01:00
Michael Hoennig
980524e7ab add: membership-fee-billable, debitor.billable, debitor.billingContact, debitor.defaultPrefix 2024-01-10 17:38:11 +01:00
e427bb1784 Merge pull request 'fix+improve-project-setup' (#8) from fix+improve-project-setup into master
Reviewed-on: #8
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
2024-01-09 09:01:12 +01:00
Timotheus Pokorra
238e413aa7 Merge branch 'master' into fix+improve-project-setup 2024-01-09 09:00:26 +01:00
5f9a6d53d8 Merge pull request 'Add information related to Podman storage to README.' (#3) from readme-podman-storage into master
Reviewed-on: #3
Reviewed-by: Michael Hoennig <michael.hoennig@hostsharing.net>
2024-01-09 08:58:57 +01:00
Timotheus Pokorra
fef9fba073 Merge branch 'master' into readme-podman-storage 2024-01-09 08:58:00 +01:00
3d77dcc2ce Merge pull request 'Requirements aktualisiert' (#4) from TP-20240104-readme_requirements into master
Reviewed-on: #4
Reviewed-by: Michael Hoennig <michael.hoennig@hostsharing.net>
2024-01-09 08:56:18 +01:00
Timotheus Pokorra
38e4b00107 Merge branch 'master' into TP-20240104-readme_requirements 2024-01-09 08:55:29 +01:00
Michael Hoennig
7e31e95d57 persist shares+assets 2024-01-08 13:48:31 +01:00
Michael Hoennig
53d46da49a import coop assets 2024-01-08 13:13:24 +01:00
Michael Hoennig
b0bfb127b6 implement import of coop-share-transactions 2024-01-08 11:36:47 +01:00
Michael Hoennig
96ef490207 code cleanup, removing commented code 2024-01-06 18:23:13 +01:00
Michael Hoennig
0a996a9a8f remove SDKMAN from README, JDK is now downloaded by Gradle Toolchain 2024-01-06 18:12:25 +01:00
Michael Hoennig
242b6f88c9 make OWASP_API_KEY optionally 2024-01-06 18:11:24 +01:00
63b02ff9cb Small fix in README 2024-01-04 22:46:40 +01:00
845857c14d Requirements aktualisiert 2024-01-04 09:13:27 +01:00
Michael Hierweck
cec31055a5 Add information related to Podman storage to README. 2024-01-02 12:20:19 +01:00
45 changed files with 685 additions and 388 deletions

View File

@ -5,7 +5,6 @@ For architecture consider the files in the `doc` and `adr` folder.
<!-- generated TOC begin: -->
- [Setting up the Development Environment](#setting-up-the-development-environment)
- [SDKMAN](#sdkman)
- [PostgreSQL Server](#postgresql-server)
- [Markdown](#markdown)
- [Render Markdown embedded PlantUML](#render-markdown-embedded-plantuml)
@ -51,15 +50,13 @@ Everything is tested on _Ubuntu Linux 22.04_ and _MacOS Monterey (12.4)_.
To be able to build and run the Java Spring Boot application, you need the following tools:
- Docker 20.x (on MacOS you also need *Docker Desktop* or similar)
- PostgreSQL Server 15.5-bookworm
- Docker 20.x (on MacOS you also need *Docker Desktop* or similar) or Podman
- optionally: PostgreSQL Server 15.5-bookworm
(see instructions below to install and run in Docker)
- Java JDK at least recent enough to run Gradle
(JDK 17.x will be automatically installed by Gradle toolchain support)
- The matching Java JDK at will be automatically installed by Gradle toolchain support.
- You also might need an IDE (e.g. *IntelliJ IDEA* or *Eclipse* or *VS Code* with *[STS](https://spring.io/tools)* and a GUI Frontend for *PostgreSQL* like *Postbird*.
You also might need an IDE (e.g. *IntelliJ IDEA* or *Eclipse* or *VS Code* with *[STS](https://spring.io/tools)* and a GUI Frontend for *PostgreSQL* like *Postbird*.
If you have at least Docker, the Java JDK and Gradle installed in appropriate versions and in your `PATH`, then you can start like this:
If you have at least Docker and the Java JDK installed in appropriate versions and in your `PATH`, then you can start like this:
cd your-hsadmin-ng-directory
@ -105,30 +102,9 @@ And to see the full, currently implemented, API, open http://localhost:8080/swag
If you still need to install some of these tools, find some hints in the next chapters.
### SDKMAN
*SdkMan* is not necessary, but helpful to install and switch between different versions of SDKs (Software-Development-Kits) and development tools in general, e.g. *JDK* and *Gradle*.
It is available for _Linux_ and _MacOS_, _WSL_, _Cygwin_, _Solaris_ and _FreeBSD_.
You can get it from: https://sdkman.io/.
<big>**&#9888;**</big>
Yeah, the `curl ... | bash` install method looks quite scary;
but in a development environment you're downloading executables all the time,
e.g. through `npm`, `Maven` or `Gradle` when downloading dependencies.
Thus, maybe you should at least use a separate Linux account for development.
Once it's installed, you can install *JDK* and *Gradle*:
sdk install java 17.0.3-tem
sdk install gradle
sdk use java 17.0.3-tem # use this to switch between installed JDK versions
### PostgreSQL Server
You could use any PostgreSQL Server (from version 13 on) installed on your machine.
You could use any PostgreSQL Server (version 15) installed on your machine.
You might amend the port and user settings in `src/main/resources/application.yml`, though.
But the easiest way to run PostgreSQL is via Docker.
@ -616,7 +592,16 @@ Summary for Debian-based Linux systems:
sudo apt-get -y install podman
```
Then start it like this:
It is possible to move the storage directory to /tmp, e.g. to increase performance or to avoid issues with NFS mounted home directories:
```shell
cat .config/containers/storage.conf
[storage]
driver = "vfs"
graphRoot = "/tmp/containers/storage"
```
2. Then start it like this:
```shell
systemctl --user enable --now podman.socket

View File

@ -207,7 +207,7 @@ project.tasks.spotlessJava.dependsOn(
// OWASP Dependency Security Test
dependencyCheck {
nvd {
apiKey = project.property('OWASP_API_KEY') // set it in ~/.gradle/gradle.properties
apiKey = project.properties['OWASP_API_KEY'] // set it in ~/.gradle/gradle.properties
delay = 16000
}
format = 'ALL'

View File

@ -59,9 +59,6 @@ public class HsOfficeBankAccountController implements HsOfficeBankAccountsApi {
final var saved = bankAccountRepo.save(entityToSave);
// em.persist(entityToSave);
// final var saved = entityToSave;
// em.flush();
final var uri =
MvcUriComponentsBuilder.fromController(getClass())

View File

@ -3,7 +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.repository.HasUuid;
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;

View File

@ -3,6 +3,7 @@ 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;
@ -23,7 +24,7 @@ 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())

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;
@ -20,7 +21,7 @@ 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())

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

@ -40,13 +40,16 @@ 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;
@Column(name = "billable")
private boolean billable;
@Column(name = "vatid")
private String vatId;
@ -60,6 +63,17 @@ public class HsOfficeDebitorEntity implements Stringifyable {
@JoinColumn(name = "refundbankaccountuuid")
private HsOfficeBankAccountEntity refundBankAccount;
public String getDebitorNumberString() {
return partner.getDebitorNumberPrefix() + String.format("%02d", debitorNumberSuffix);
}
public int getDebitorNumber() {
return Integer.parseInt(getDebitorNumberString());
}
@Column(name = "defaultprefix", columnDefinition = "char(3) not null")
private String defaultPrefix;
@Override
public String toString() {
return stringify.apply(this);
@ -67,6 +81,6 @@ public class HsOfficeDebitorEntity implements Stringifyable {
@Override
public String toShortString() {
return debitorNumber.toString();
return getDebitorNumberString();
}
}

View File

@ -31,6 +31,10 @@ class HsOfficeDebitorEntityPatcher implements EntityPatcher<HsOfficeDebitorPatch
verifyNotNull(newValue, "vatBusiness");
entity.setVatBusiness(newValue);
});
OptionalFromJson.of(resource.getDefaultPrefix()).ifPresent(newValue -> {
verifyNotNull(newValue, "defaultPrefix");
entity.setDefaultPrefix(newValue);
});
}
private void verifyNotNull(final Object newValue, final String propertyName) {

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

@ -58,6 +58,9 @@ public class HsOfficeMembershipEntity implements Stringifyable {
@Type(PostgreSQLRangeType.class)
private Range<LocalDate> validity;
@Column(name = "membership_fee_billable")
private boolean membershipFeeBillable;
@Column(name = "reasonfortermination")
@Enumerated(EnumType.STRING)
private HsOfficeReasonForTermination reasonForTermination;

View File

@ -1,4 +1,4 @@
package net.hostsharing.hsadminng.repository;
package net.hostsharing.hsadminng.hs.office.migration;
import java.util.UUID;

View File

@ -3,14 +3,15 @@ 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.repository.HasUuid;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
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;
@ -35,6 +36,9 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
@GeneratedValue
private UUID uuid;
@Column(name = "debitornumberprefix", columnDefinition = "numeric(5) not null")
private Integer debitorNumberPrefix;
@ManyToOne
@JoinColumn(name = "personuuid", nullable = false)
private HsOfficePersonEntity person;
@ -44,7 +48,7 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
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;
@ -55,6 +59,6 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
@Override
public String toShortString() {
return person.toShortString();
return Optional.ofNullable(person).map(HsOfficePersonEntity::toShortString).orElse("<person=null>");
}
}

View File

@ -6,7 +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.repository.HasUuid;
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;

View File

@ -12,8 +12,13 @@ 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:
@ -27,6 +32,9 @@ components:
type: boolean
refundBankAccount:
$ref: './hs-office-bankaccount-schemas.yaml#/components/schemas/HsOfficeBankAccount'
defaultPrefix:
type: string
pattern: '^[a-z0-9]{3}$'
HsOfficeDebitorPatch:
type: object
@ -49,6 +57,10 @@ components:
type: string
format: uuid
nullable: true
defaultPrefix:
type: string
pattern: '^[a-z0-9]{3}$'
nullable: true
HsOfficeDebitorInsert:
type: object
@ -61,11 +73,11 @@ components:
type: string
format: uuid
nullable: false
debitorNumber:
debitorNumberSuffix:
type: integer
format: int32
minimum: 10000
maximum: 99999
format: int8
minimum: 00
maximum: 99
vatId:
type: string
vatCountryCode:
@ -76,6 +88,11 @@ components:
refundBankAccountUuid:
type: string
format: uuid
defaultPrefix:
type: string
pattern: '^[a-z]{3}$'
required:
- partnerUuid
- billingContactUuid
- defaultPrefix

View File

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

View File

@ -28,8 +28,8 @@ create or replace function determineCurrentSubjectsUuids(currentUserUuid uuid, a
stable leakproof
language plpgsql as $$
declare
roleName varchar(63);
roleNameParts varchar(63);
roleName text;
roleNameParts text;
objectTableToAssume varchar(63);
objectNameToAssume varchar(63);
objectUuidToAssume uuid;

View File

@ -31,6 +31,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)

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

@ -8,12 +8,17 @@ 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)
refundBankAccountUuid uuid references hs_office_bankaccount(uuid),
defaultPrefix char(3) not null
constraint check_member_code 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,17 @@ 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,
billable = new.billable,
billingContactUuid = new.billingContactUuid,
debitorNumberSuffix = new.debitorNumberSuffix,
refundBankAccountUuid = new.refundBankAccountUuid,
vatId = new.vatId,
vatCountryCode = new.vatCountryCode,
vatBusiness = new.vatBusiness
vatBusiness = new.vatBusiness,
defaultPrefix = new.defaultPrefix
$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,14 @@ 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, relatedPartner.debitorNumberPrefix || '00';
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, refundbankaccountuuid, defaultprefix)
values (uuid_generate_v4(), relatedPartner.uuid, debitorNumberSuffix, true, relatedContact.uuid, true, relatedBankAccountUuid, defaultPrefix);
end; $$;
--//
@ -46,9 +50,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

@ -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',
membership_fee_billable 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,
membership_fee_billable = new.membership_fee_billable
$updates$);
--//

View File

@ -8,14 +8,17 @@
/*
Creates a single membership test record.
*/
-- create or replace procedure createHsOfficeMembershipTestData( forPartnerTradeName varchar, forMainDebitorNumber integer )
create or replace procedure createHsOfficeMembershipTestData( forPartnerTradeName varchar, forMainDebitorNumber numeric )
language plpgsql as $$
declare
currentTask varchar;
idName varchar;
relatedPartner hs_office_partner;
relatedDebitor hs_office_debitor;
newMemberNumber numeric;
currentTask varchar;
idName varchar;
-- forDebitorNumberPrefix integer;
-- forDebitorNumberSuffix integer;
relatedPartner hs_office_partner;
relatedDebitor hs_office_debitor;
newMemberNumber numeric;
begin
idName := cleanIdentifier( forPartnerTradeName || '#' || forMainDebitorNumber);
currentTask := 'creating Membership test-data ' || idName;
@ -25,7 +28,10 @@ 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;
-- forDebitorNumberPrefix := forMainDebitorNumber/ 100;
-- forDebitorNumberSuffix := mod(forMainDebitorNumber, 100);
-- select d.* from hs_office_debitor d where d.debitorNumberSuffix = forDebitorNumberSuffix 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;
@ -33,6 +39,7 @@ begin
raise notice '- using debitor (%): %', relatedDebitor.uuid, relatedDebitor;
insert
into hs_office_membership (uuid, partneruuid, maindebitoruuid, membernumber, validity, reasonfortermination)
-- values (uuid_generate_v4(), relatedPartner.uuid, relatedDebitor.uuid, forDebitorNumberPrefix, daterange('20221001' , null, '[]'), 'NONE');
values (uuid_generate_v4(), relatedPartner.uuid, relatedDebitor.uuid, newMemberNumber, daterange('20221001' , null, '[]'), 'NONE');
end; $$;
--//
@ -44,9 +51,14 @@ 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;
-- begin
-- call createHsOfficeMembershipTestData('First GmbH', 1000100);
-- call createHsOfficeMembershipTestData('Second e.K.', 1000200);
-- call createHsOfficeMembershipTestData('Third OHG', 1000300);
-- end;
$$;
--//

View File

@ -200,7 +200,9 @@ public class ArchitectureTest {
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")

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));
}
@ -200,8 +200,7 @@ class HsOfficeCoopAssetsTransactionRepositoryIntegrationTest extends ContextBase
@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(

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));
}
@ -199,8 +199,7 @@ class HsOfficeCoopSharesTransactionRepositoryIntegrationTest extends ContextBase
@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(

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,14 @@ class HsOfficeDebitorControllerAcceptanceTest {
{
"partnerUuid": "%s",
"billingContactUuid": "%s",
"debitorNumber": "%s",
"debitorNumberSuffix": "%s",
"vatId": "VAT123456",
"vatCountryCode": "DE",
"vatBusiness": true,
"refundBankAccountUuid": "%s"
"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 +179,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 +207,10 @@ class HsOfficeDebitorControllerAcceptanceTest {
{
"partnerUuid": "%s",
"billingContactUuid": "%s",
"debitorNumber": "%s"
"debitorNumberSuffix": "%s",
"defaultPrefix": "for"
}
""".formatted( givenPartner.getUuid(), givenContact.getUuid(), ++nextDebitorNumber))
""".formatted( givenPartner.getUuid(), givenContact.getUuid(), ++nextDebitorSuffix))
.port(port)
.when()
.post("http://localhost/api/hs/office/debitors")
@ -218,6 +224,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 +249,14 @@ class HsOfficeDebitorControllerAcceptanceTest {
{
"partnerUuid": "%s",
"billingContactUuid": "%s",
"debitorNumber": "%s",
"debitorNumberSuffix": "%s",
"vatId": "VAT123456",
"vatCountryCode": "DE",
"vatBusiness": true
"vatBusiness": true,
"defaultPrefix": "thi"
}
""".formatted( givenPartner.getUuid(), givenContactUuid, ++nextDebitorNumber))
"""
.formatted( givenPartner.getUuid(), givenContactUuid, ++nextDebitorSuffix))
.port(port)
.when()
.post("http://localhost/api/hs/office/debitors")
@ -272,12 +281,13 @@ class HsOfficeDebitorControllerAcceptanceTest {
{
"partnerUuid": "%s",
"billingContactUuid": "%s",
"debitorNumber": "%s",
"debitorNumberSuffix": "%s",
"vatId": "VAT123456",
"vatCountryCode": "DE",
"vatBusiness": true
"vatBusiness": true,
"defaultPrefix": "for"
}
""".formatted( givenPartnerUuid, givenContact.getUuid(), ++nextDebitorNumber))
""".formatted( givenPartnerUuid, givenContact.getUuid(), ++nextDebitorSuffix))
.port(port)
.when()
.post("http://localhost/api/hs/office/debitors")
@ -375,7 +385,8 @@ class HsOfficeDebitorControllerAcceptanceTest {
"contactUuid": "%s",
"vatId": "VAT222222",
"vatCountryCode": "AA",
"vatBusiness": true
"vatBusiness": true,
"defaultPrefix": "for"
}
""".formatted(givenContact.getUuid()))
.port(port)
@ -388,6 +399,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 +534,10 @@ 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)
.partner(givenPartner)
.billingContact(givenContact)
.defaultPrefix("abc")
.build();
return debitorRepo.save(newDebitor);
@ -537,7 +550,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

@ -31,6 +31,7 @@ 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;
@ -62,6 +63,7 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase<
entity.setVatId("initial VAT-ID");
entity.setVatCountryCode("AA");
entity.setVatBusiness(true);
entity.setDefaultPrefix("abc");
return entity;
}
@ -100,6 +102,12 @@ class HsOfficeDebitorEntityPatcherUnitTest extends PatchUnitTestBase<
HsOfficeDebitorPatchResource::setVatBusiness,
PATCHED_VAT_BUSINESS,
HsOfficeDebitorEntity::setVatBusiness)
.notNullable(),
new JsonNullableProperty<>(
"defaultPrefix",
HsOfficeDebitorPatchResource::setDefaultPrefix,
PATCHED_DEFAULT_PREFIX,
HsOfficeDebitorEntity::setDefaultPrefix)
.notNullable()
);
}

View File

@ -13,30 +13,50 @@ class HsOfficeDebitorEntityUnitTest {
@Test
void toStringContainsPartnerAndContact() {
final var given = HsOfficeDebitorEntity.builder()
.debitorNumber(123456)
.debitorNumberSuffix((byte)67)
.partner(HsOfficePartnerEntity.builder()
.person(HsOfficePersonEntity.builder()
.tradeName("some trade name")
.build())
.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: some trade name)");
}
@Test
void toShortStringContainsPartnerAndContact() {
void toStringWithoutPersonContainsDebitorNumber() {
final var given = HsOfficeDebitorEntity.builder()
.debitorNumber(123456)
.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(1234567: <person=null>)");
}
@Test
void toShortStringContainsDebitorNumber() {
final var given = HsOfficeDebitorEntity.builder()
.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

@ -81,9 +81,10 @@ 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")
.build();
return debitorRepo.save(newDebitor);
});
@ -95,14 +96,43 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
assertThat(debitorRepo.count()).isEqualTo(count + 1);
}
// @ParameterizedTest
// @ValueSource(strings = {"", "a", "ab", "a12", "123", "12a"})
// 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)
// .defaultPrefix(givenPrefix)
// .build();
// return debitorRepo.save(newDebitor);
// });
//
// // then
// result.assertSuccessful();
// assertThat(result.returnedValue()).isNotNull().extracting(HsOfficeDebitorEntity::getUuid).isNotNull();
// assertThatDebitorIsPersisted(result.returnedValue());
// assertThat(debitorRepo.count()).isEqualTo(count + 1);
// }
@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("1000422Fourthe.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 +143,10 @@ 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")
.build();
return debitorRepo.save(newDebitor);
}).assertSuccessful();
@ -123,42 +154,43 @@ 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("1000422Fourthe.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 +215,14 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
// then
allTheseDebitorsAreReturned(
result,
"debitor(10001: First GmbH)",
"debitor(10002: Second e.K.)",
"debitor(10003: Third OHG)");
"debitor(1000111: First GmbH)",
"debitor(1000212: Second e.K.)",
"debitor(1000313: Third OHG)");
}
@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 +234,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
final var result = debitorRepo.findDebitorByOptionalNameLike(null);
// then:
exactlyTheseDebitorsAreReturned(result, "debitor(10001: First GmbH)");
exactlyTheseDebitorsAreReturned(result, "debitor(1000111: First GmbH)", "debitor(1000120: First GmbH)");
}
@Test
@ -227,10 +259,11 @@ 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: Third OHG)", "debitor(1000413: Fourth e.G.)");
exactlyTheseDebitorsAreReturned(result, "debitor(1000313: Third OHG)");
}
}
@ -246,7 +279,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
final var result = debitorRepo.findDebitorByOptionalNameLike("third contact");
// then
exactlyTheseDebitorsAreReturned(result, "debitor(10003: Third OHG)");
exactlyTheseDebitorsAreReturned(result, "debitor(1000313: Third OHG)");
}
}
@ -257,10 +290,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 +323,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 +349,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, "fif");
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 +379,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", "fif");
assertThatDebitorIsVisibleForUserWithRole(
givenDebitor,
"hs_office_partner#Fourthe.G.-forthcontact.admin");
"hs_office_partner#10004:Fourthe.G.-forthcontact.admin");
assertThatDebitorActuallyInDatabase(givenDebitor);
// when
@ -375,15 +408,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 +430,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 +481,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 +501,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 +527,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 +569,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,10 +578,11 @@ 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)
.build();
return debitorRepo.save(newDebitor);
@ -558,7 +593,8 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
@AfterEach
void cleanup() {
context("superuser-alex@hostsharing.net");
em.createQuery("DELETE FROM HsOfficeDebitorEntity d where d.debitorNumber >= 20000").executeUpdate();
// TODO em.createQuery("DELETE FROM HsOfficeDebitorEntity d where d.debitorNumberSuffix >= 20").executeUpdate();
em.createQuery("DELETE FROM HsOfficeDebitorEntity d where d.debitorNumberSuffix >= 20000").executeUpdate();
}
void exactlyTheseDebitorsAreReturned(final List<HsOfficeDebitorEntity> actualResult, final String... debitorNames) {

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,
@ -142,6 +142,7 @@ class HsOfficeMembershipControllerAcceptanceTest {
.contentType(ContentType.JSON)
.body("uuid", isUuidValid())
.body("mainDebitor.debitorNumber", is(givenDebitor.getDebitorNumber()))
// .body("mainDebitor.debitorNumber", is(givenDebitor.getDebitorNumberSuffix()))
.body("partner.person.tradeName", is("Third OHG"))
.body("memberNumber", is(20001))
.body("validFrom", is("2022-10-13"))
@ -182,7 +183,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 +225,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 +236,7 @@ class HsOfficeMembershipControllerAcceptanceTest {
{
"partner": { "person": { "tradeName": "Third OHG" } },
"mainDebitor": {
"debitorNumber": 10003,
"debitorNumber": 1000313,
"billingContact": { "label": "third contact" }
},
"memberNumber": 10003,
@ -276,6 +277,7 @@ class HsOfficeMembershipControllerAcceptanceTest {
.body("uuid", isUuidValid())
.body("partner.person.tradeName", is(givenMembership.getPartner().getPerson().getTradeName()))
.body("mainDebitor.debitorNumber", is(givenMembership.getMainDebitor().getDebitorNumber()))
// .body("mainDebitor.debitorNumber", is(givenMembership.getMainDebitor().getDebitorNumberSuffix()))
.body("memberNumber", is(givenMembership.getMemberNumber()))
.body("validFrom", is("2022-11-01"))
.body("validTo", is("2023-12-31"))
@ -299,7 +301,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 +320,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())
@ -340,13 +342,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("""
{

View File

@ -27,7 +27,8 @@ 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, Test Ltd., 1000100, [2020-01-01,))");
// assertThat(result).isEqualTo("Membership(10001, Test Ltd., 1000100, [2020-01-01,))");
}
@Test

View File

@ -119,11 +119,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 +131,36 @@ 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#1100First.guest to role membership#20002:First.tenant by system and assume }",
// "{ grant role membership#20002:First.tenant to role debitor#1100First.agent 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#1100First.tenant by system and assume }",
"{ grant role membership#20002:First.guest to role debitor#1000111:First.tenant by system and assume }",
null));
}
@ -181,9 +185,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, First GmbH, 1000111, [2022-10-01,), NONE)",
"Membership(10002, Second e.K., 1000212, [2022-10-01,), NONE)",
"Membership(10003, Third OHG, 1000313, [2022-10-01,), NONE)");
}
@Test
@ -198,7 +202,7 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest {
null);
// then
exactlyTheseMembershipsAreReturned(result, "Membership(10001, First GmbH, 10001, [2022-10-01,), NONE)");
exactlyTheseMembershipsAreReturned(result, "Membership(10001, First GmbH, 1000111, [2022-10-01,), NONE)");
}
@Test
@ -210,7 +214,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, Second e.K., 1000212, [2022-10-01,), NONE)");
}
}
@ -224,7 +228,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 +255,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 +329,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 +386,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

View File

@ -7,6 +7,10 @@ import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.context.ContextBasedTest;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionEntity;
import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionType;
import net.hostsharing.hsadminng.hs.office.coopshares.HsOfficeCoopSharesTransactionEntity;
import net.hostsharing.hsadminng.hs.office.coopshares.HsOfficeCoopSharesTransactionType;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeReasonForTermination;
@ -15,23 +19,14 @@ import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType;
import net.hostsharing.hsadminng.hs.office.sepamandate.HsOfficeSepaMandateEntity;
import net.hostsharing.hsadminng.repository.HasUuid;
import net.hostsharing.test.JpaAttempt;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.Commit;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.ReflectionUtils;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
@ -40,12 +35,11 @@ import jakarta.validation.constraints.NotNull;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import static java.util.Arrays.stream;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
@ -57,23 +51,26 @@ import static org.assertj.core.api.Assertions.assertThat;
* This 'test' includes the complete legacy 'office' data import.
*
* There is no code in 'main' because the import is not needed a normal runtime.
* There is some test data in Java resources to verfiy the data conversion.
* There is some test data in Java resources to verify the data conversion.
* For a real import a main method will be added later
* which reads CSV files from the file system.
*/
@Disabled
@DataJpaTest
@Import({ Context.class, JpaAttempt.class })
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class ImportOfficeTables extends ContextBasedTest {
private static Map<Integer, HsOfficeContactEntity> contacts = new HashMap<>();
private static Map<Integer, HsOfficePersonEntity> persons = new HashMap<>();
private static Map<Integer, HsOfficePartnerEntity> partners = new HashMap<>();
private static Map<Integer, HsOfficeDebitorEntity> debitors = new HashMap<>();
private static Map<Integer, HsOfficeMembershipEntity> memberships = new HashMap<>();
private static Map<Integer, HsOfficeSepaMandateEntity> sepaMandates = new HashMap<>();
private static Map<Integer, HsOfficeBankAccountEntity> bankAccounts = new HashMap<>();
private static NavigableMap<Integer, HsOfficeContactEntity> contacts = new TreeMap<>();
private static NavigableMap<Integer, HsOfficePersonEntity> persons = new TreeMap<>();
private static NavigableMap<Integer, HsOfficePartnerEntity> partners = new TreeMap<>();
private static NavigableMap<Integer, HsOfficeDebitorEntity> debitors = new TreeMap<>();
private static NavigableMap<Integer, HsOfficeMembershipEntity> memberships = new TreeMap<>();
private static NavigableMap<Integer, HsOfficeSepaMandateEntity> sepaMandates = new TreeMap<>();
private static NavigableMap<Integer, HsOfficeBankAccountEntity> bankAccounts = new TreeMap<>();
private static NavigableMap<Integer, HsOfficeCoopSharesTransactionEntity> coopShares = new TreeMap<>();
private static NavigableMap<Integer, HsOfficeCoopAssetsTransactionEntity> coopAssets = new TreeMap<>();
@PersistenceContext
EntityManager em;
@ -142,9 +139,9 @@ public class ImportOfficeTables extends ContextBasedTest {
""");
assertThat(contacts.toString()).isEqualToIgnoringWhitespace("""
{
71=contact(label='Herr Michael Mellies ', emailAddresses='mih@example.org'),
101=contact(label='Frau Dr. Jenny Meyer , JM e.K.', emailAddresses='jm@example.org'),
102=contact(label='Herr Andrew Meyer , JM e.K.', emailAddresses='am@example.org'),
71=contact(label='Herr Michael Mellies ', emailAddresses='mih@example.org'),
121=contact(label='Paule Schmidt , Test PS', emailAddresses='ps@example.com')
}
""");
@ -196,6 +193,52 @@ public class ImportOfficeTables extends ContextBasedTest {
""");
}
@Test
@Order(4)
void importCoopShares() {
try (Reader reader = resourceReader("migration/share-transactions.csv")) {
final var lines = readAllLines(reader);
importCoopShares(justHeader(lines), withoutHeader(lines));
} catch (Exception e) {
throw new RuntimeException(e);
}
assertThat(coopShares.toString()).isEqualToIgnoringWhitespace("""
{
33443=CoopShareTransaction(10007, 2000-12-06, SUBSCRIPTION, 20, initial share subscription),
33451=CoopShareTransaction(10010, 2000-12-06, SUBSCRIPTION, 2, initial share subscription),
33701=CoopShareTransaction(10007, 2005-01-10, SUBSCRIPTION, 40, increase),
33810=CoopShareTransaction(10010, 2016-12-31, CANCELLATION, 22, membership ended)
}
""");
}
@Test
@Order(5)
void importCoopAssets() {
try (Reader reader = resourceReader("migration/asset-transactions.csv")) {
final var lines = readAllLines(reader);
importCoopAssets(justHeader(lines), withoutHeader(lines));
} catch (Exception e) {
throw new RuntimeException(e);
}
assertThat(coopAssets.toString()).isEqualToIgnoringWhitespace("""
{
30000=CoopAssetsTransaction(10007, 2000-12-06, DEPOSIT, 1280.00, for subscription A),
31000=CoopAssetsTransaction(10010, 2000-12-06, DEPOSIT, 128.00, for subscription B),
32000=CoopAssetsTransaction(10007, 2005-01-10, DEPOSIT, 2560.00, for subscription C),
33001=CoopAssetsTransaction(10007, 2005-01-10, TRANSFER, -512.00, for transfer to 10),
33002=CoopAssetsTransaction(10010, 2005-01-10, ADOPTION, 512.00, for transfer from 7),
34001=CoopAssetsTransaction(10010, 2016-12-31, CLEARING, -8.00, for cancellation D),
34002=CoopAssetsTransaction(10010, 2016-12-31, DISBURSAL, -100.00, for cancellation D),
34003=CoopAssetsTransaction(10010, 2016-12-31, LOSS, -20.00, for cancellation D)
}
""");
}
@Test
@Order(10)
@Commit
@ -219,7 +262,11 @@ public class ImportOfficeTables extends ContextBasedTest {
sepaMandates.forEach((id, mandate) -> em.persist(mandate));
updateLegacyIds(sepaMandates, "hs_office_sepamandate_legacy_id", "sepa_mandate_id");
// TODO: coopshares+coopassets
coopShares.forEach((id, shareTransaction) -> em.persist(shareTransaction));
updateLegacyIds(coopShares, "hs_office_coopsharestransaction_legacy_id", "member_share_id");
coopAssets.forEach((id, assetTransaction) -> em.persist(assetTransaction));
updateLegacyIds(coopShares, "hs_office_coopassetstransaction_legacy_id", "member_asset_id");
em.flush();
}
@ -263,49 +310,117 @@ public class ImportOfficeTables extends ContextBasedTest {
final var person = HsOfficePersonEntity.builder()
.personType(HsOfficePersonType.UNKNOWN) // TODO
.build();
persons.put(toInt(rec.get("bp_id")), person);
persons.put(rec.getInteger("bp_id"), person);
final var partner = HsOfficePartnerEntity.builder()
.details(HsOfficePartnerDetailsEntity.builder().build())
.contact(HsOfficeContactEntity.builder().build())
.person(person)
.build();
partners.put(toInt(rec.get("bp_id")), partner);
partners.put(rec.getInteger("bp_id"), partner);
final var debitor = HsOfficeDebitorEntity.builder()
.partner(partner)
.debitorNumber(toInt(rec.get("member_id")))
// .memberCode(rec.get("member_code")) TODO
// .debitorNumberSuffix(rec.getByte("member_id"))
// .defaultPrefix(rec.getString("member_code"))
.partner(partner)
.billingContact(partner.getContact())
// .memberRoles(toBool(rec.get("member_role")) TODO
// .authorContract(toBool(rec.get("author_contract")) TODO
// .nonDisclosureContract(toBool(rec.get("nondisc_contract")) TODO
// .free(toBool(rec.get("free")) TODO
// .vatExempt(toBool(rec.get("exempt_vat")) TODO
.vatBusiness("GROSS".equals(rec.get("indicator_vat")))
.vatId(rec.get("uid_vat"))
.billable(rec.isEmpty("free"))
// .vatExempt(toBool(rec.get("exempt_vat")) (reverse-charge) TODO: add as vat-reverse-charge to debitor
.vatBusiness("GROSS".equals(rec.getString("indicator_vat"))) // TODO: remove
.vatId(rec.getString("uid_vat"))
.build();
debitors.put(toInt(rec.get("bp_id")), debitor);
debitors.put(rec.getInteger("bp_id"), debitor);
partners.put(toInt(rec.get("bp_id")), partner);
partners.put(rec.getInteger("bp_id"), partner);
if (isNotBlank(rec.get("member_since"))) {
if (isNotBlank(rec.getString("member_since"))) {
final var membership = HsOfficeMembershipEntity.builder()
.partner(partner)
.memberNumber(toInt(rec.get("member_id")))
.validity(toPostgresDateRange(localDate(rec.get("member_since")), localDate(rec.get("member_until"))))
.memberNumber(rec.getInteger("member_id"))
.validity(toPostgresDateRange(rec.getLocalDate("member_since"), rec.getLocalDate("member_until")))
.membershipFeeBillable(rec.isEmpty("member_role"))
.reasonForTermination(
isBlank(rec.get("member_until"))
isBlank(rec.getString("member_until"))
? HsOfficeReasonForTermination.NONE
: HsOfficeReasonForTermination.UNKNOWN) // TODO
: HsOfficeReasonForTermination.UNKNOWN)
.mainDebitor(debitor)
.build();
memberships.put(toInt(rec.get("bp_id")), membership);
memberships.put(rec.getInteger("bp_id"), membership);
}
});
}
private void importCoopShares(final String[] header, final List<String[]> records) {
final var columns = new Columns(header);
records.stream()
.map(this::trimAll)
.map(row -> new Record(columns, row))
.forEach(rec -> {
final var member = memberships.get(rec.getInteger("bp_id"));
final var shareTransaction = HsOfficeCoopSharesTransactionEntity.builder()
.membership(member)
.valueDate(rec.getLocalDate("date"))
.transactionType(
"SUBSCRIPTION".equals(rec.getString("action"))
? HsOfficeCoopSharesTransactionType.SUBSCRIPTION
: "UNSUBSCRIPTION".equals(rec.getString("action"))
? HsOfficeCoopSharesTransactionType.CANCELLATION
: HsOfficeCoopSharesTransactionType.ADJUSTMENT
)
.shareCount(rec.getInteger("quantity"))
.reference(rec.getString("comment"))
.build();
coopShares.put(rec.getInteger("member_share_id"), shareTransaction);
});
}
private void importCoopAssets(final String[] header, final List<String[]> records) {
final var columns = new Columns(header);
records.stream()
.map(this::trimAll)
.map(row -> new Record(columns, row))
.forEach(rec -> {
final var member = memberships.get(rec.getInteger("bp_id"));
final var assetTypeMapping = new HashMap<String, HsOfficeCoopAssetsTransactionType>() {
{
put("HANDOVER", HsOfficeCoopAssetsTransactionType.TRANSFER);
put("ADOPTION", HsOfficeCoopAssetsTransactionType.ADOPTION);
put("LOSS", HsOfficeCoopAssetsTransactionType.LOSS);
put("CLEARING", HsOfficeCoopAssetsTransactionType.CLEARING);
put("PRESCRIPTION", HsOfficeCoopAssetsTransactionType.LIMITATION);
put("PAYBACK", HsOfficeCoopAssetsTransactionType.DISBURSAL);
put("PAYMENT", HsOfficeCoopAssetsTransactionType.DEPOSIT);
}
public HsOfficeCoopAssetsTransactionType get(final String key) {
final var value = super.get(key);
if ( value != null ) {
return value;
}
throw new IllegalStateException("no mapping value found for: " + key);
}
};
final var assetTransaction = HsOfficeCoopAssetsTransactionEntity.builder()
.membership(member)
.valueDate(rec.getLocalDate("date"))
.transactionType(assetTypeMapping.get(rec.getString("action")))
.assetValue(rec.getBigDecimal("amount"))
.reference(rec.getString("comment"))
.build();
coopAssets.put(rec.getInteger("member_asset_id"), assetTransaction);
});
}
private void importSepaMandates(final String[] header, final List<String[]> records) {
final var columns = new Columns(header);
@ -314,25 +429,25 @@ public class ImportOfficeTables extends ContextBasedTest {
.map(this::trimAll)
.map(row -> new Record(columns, row))
.forEach(rec -> {
final var debitor = debitors.get(toInt(rec.get("bp_id")));
final var debitor = debitors.get(rec.getInteger("bp_id"));
final var sepaMandate = HsOfficeSepaMandateEntity.builder()
.debitor(debitor)
.bankAccount(HsOfficeBankAccountEntity.builder()
.holder(rec.get("bank_customer"))
// .bankName(rec.get("bank_name")) // TODO
.iban(rec.get("bank_iban"))
.bic(rec.get("bank_bic"))
.holder(rec.getString("bank_customer"))
// .bankName(rec.get("bank_name")) // not supported
.iban(rec.getString("bank_iban"))
.bic(rec.getString("bank_bic"))
.build())
.reference(rec.get("mandat_ref"))
.agreement(LocalDate.parse(rec.get("mandat_signed")))
.reference(rec.getString("mandat_ref"))
.agreement(LocalDate.parse(rec.getString("mandat_signed")))
.validity(toPostgresDateRange(
toLocalDate(rec.get("mandat_since")),
toLocalDate(rec.get("mandat_until"))))
rec.getLocalDate("mandat_since"),
rec.getLocalDate("mandat_until")))
.build();
sepaMandates.put(toInt(rec.get("sepa_mandat_id")), sepaMandate);
bankAccounts.put(toInt(rec.get("sepa_mandat_id")), sepaMandate.getBankAccount());
sepaMandates.put(rec.getInteger("sepa_mandat_id"), sepaMandate);
bankAccounts.put(rec.getInteger("sepa_mandat_id"), sepaMandate.getBankAccount());
});
}
@ -344,15 +459,15 @@ public class ImportOfficeTables extends ContextBasedTest {
.map(this::trimAll)
.map(row -> new Record(columns, row))
.forEach(rec -> {
if (isNotBlank(rec.get("roles")) && rec.get("roles").contains("billing")) {
if (isNotBlank(rec.getString("roles")) && rec.getString("roles").contains("billing")) {
final var partner = partners.get(toInt(rec.get("bp_id")));
final var partner = partners.get(rec.getInteger("bp_id"));
final var person = partner.getPerson();
person.setTradeName(rec.get("firma"));
// TODO: title+salutation
person.setGivenName(rec.get("first_name"));
person.setFamilyName(rec.get("last_name"));
person.setTradeName(rec.getString("firma"));
// TODO: title+salutation: add to person
person.setGivenName(rec.getString("first_name"));
person.setFamilyName(rec.getString("last_name"));
initContact(partner.getContact(), rec);
@ -364,10 +479,10 @@ public class ImportOfficeTables extends ContextBasedTest {
}
private void initContact(final HsOfficeContactEntity contact, final Record rec) {
contacts.put(toInt(rec.get("contact_id")), contact);
contacts.put(rec.getInteger("contact_id"), contact);
contact.setLabel(toLabel(rec.get("salut"), rec.get("title"), rec.get("first_name"), rec.get("last_name"), rec.get("firma")));
contact.setEmailAddresses(rec.get("email"));
contact.setLabel(toLabel(rec.getString("salut"), rec.getString("title"), rec.getString("first_name"), rec.getString("last_name"), rec.getString("firma")));
contact.setEmailAddresses(rec.getString("email"));
contact.setPostalAddress(toAddress(rec));
contact.setPhoneNumbers(toPhoneNumbers(rec));
}
@ -382,28 +497,28 @@ public class ImportOfficeTables extends ContextBasedTest {
}
private String toPhoneNumbers(final Record rec) {
final var result = new StringBuilder("{\n");
if (isNotBlank(rec.get("phone_private")))
result.append(" \"private\": " + "\"" + rec.get("phone_private") + "\",\n");
if (isNotBlank(rec.get("phone_office")))
result.append(" \"office\": " + "\"" + rec.get("phone_office") + "\",\n");
if (isNotBlank(rec.get("phone_mobile")))
result.append(" \"mobile\": " + "\"" + rec.get("phone_mobile") + "\",\n");
if (isNotBlank(rec.get("fax")))
result.append(" \"fax\": " + "\"" + rec.get("fax") + "\",\n");
if (isNotBlank(rec.getString("phone_private")))
result.append(" \"private\": " + "\"" + rec.getString("phone_private") + "\",\n");
if (isNotBlank(rec.getString("phone_office")))
result.append(" \"office\": " + "\"" + rec.getString("phone_office") + "\",\n");
if (isNotBlank(rec.getString("phone_mobile")))
result.append(" \"mobile\": " + "\"" + rec.getString("phone_mobile") + "\",\n");
if (isNotBlank(rec.getString("fax")))
result.append(" \"fax\": " + "\"" + rec.getString("fax") + "\",\n");
return (result + "}").replace("\",\n}", "\"\n}");
}
private String toAddress(final Record rec) {
final var result = new StringBuilder();
final var name = toName(rec.get("salut"), rec.get("title"), rec.get("first_name"), rec.get("last_name"));
final var name = toName(rec.getString("salut"), rec.getString("title"), rec.getString("first_name"), rec.getString("last_name"));
if (isNotBlank(name))
result.append(name + "\n");
if (isNotBlank(rec.get("firma")))
result.append(rec.get("firma") + "\n");
if (isNotBlank(rec.get("co")))
result.append("c/o " + rec.get("co") + "\n");
if (isNotBlank(rec.get("street")))
result.append(rec.get("street") + "\n");
if (isNotBlank(rec.getString("firma")))
result.append(rec.getString("firma") + "\n");
if (isNotBlank(rec.getString("co")))
result.append("c/o " + rec.getString("co") + "\n");
if (isNotBlank(rec.getString("street")))
result.append(rec.getString("street") + "\n");
final var zipcodeAndCity = toZipcodeAndCity(rec);
if (isNotBlank(zipcodeAndCity))
result.append(zipcodeAndCity + "\n");
@ -412,12 +527,12 @@ public class ImportOfficeTables extends ContextBasedTest {
private String toZipcodeAndCity(final Record rec) {
final var result = new StringBuilder();
if (isNotBlank(rec.get("country")))
result.append(rec.get("country") + " ");
if (isNotBlank(rec.get("zipcode")))
result.append(rec.get("zipcode") + " ");
if (isNotBlank(rec.get("city")))
result.append(rec.get("city"));
if (isNotBlank(rec.getString("country")))
result.append(rec.getString("country") + " ");
if (isNotBlank(rec.getString("zipcode")))
result.append(rec.getString("zipcode") + " ");
if (isNotBlank(rec.getString("city")))
result.append(rec.getString("city"));
return result.toString();
}
@ -446,23 +561,6 @@ public class ImportOfficeTables extends ContextBasedTest {
return toLabel(salut, title, firstname, lastname, null);
}
private LocalDate toLocalDate(final String dateString) {
if (isNotBlank(dateString)) {
return LocalDate.parse(dateString);
}
return null;
}
private static Integer toInt(final String value) {
return isNotBlank(value) ? Integer.parseInt(value.trim()) : 0;
}
private LocalDate localDate(final String dateStringNullOrBlank) {
if (isNotBlank(dateStringNullOrBlank)) {
return LocalDate.parse(dateStringNullOrBlank);
}
return null;
}
private Reader resourceReader(@NotNull final String resourcePath) {
return new InputStreamReader(getClass().getClassLoader().getResourceAsStream(resourcePath));
@ -512,7 +610,38 @@ class Record {
this.row = row;
}
String get(final String columnName) {
String getString(final String columnName) {
return row[columns.indexOf(columnName)];
}
boolean isEmpty(final String columnName) {
final String value = getString(columnName);
return value == null || value.isBlank();
}
Byte getByte(final String columnName) {
final String value = getString(columnName);
return isNotBlank(value) ? Byte.valueOf(value.trim()) : 0;
}
Integer getInteger(final String columnName) {
final String value = getString(columnName);
return isNotBlank(value) ? Integer.valueOf(value.trim()) : 0;
}
BigDecimal getBigDecimal(final String columnName) {
final String value = getString(columnName);
if (isNotBlank(value)) {
return new BigDecimal(value);
}
return null;
}
LocalDate getLocalDate(final String columnName) {
final String dateString = getString(columnName);
if (isNotBlank(dateString)) {
return LocalDate.parse(dateString);
}
return null;
}
}

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

@ -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: First GmbH)");
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: First GmbH)");
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

@ -1,5 +1,9 @@
member_asset_id; bp_id; date; action; amount
33443; 7; 2000-12-06; PAYMENT; 1280
33451; 10; 2000-12-06; PAYMENT; 128
33701; 7; 2005-01-10; PAYMENT; 2560
33810; 10; 2016-12-31; PAYBACK; 128
member_asset_id; bp_id; date; action; amount; comment
30000; 7; 2000-12-06; PAYMENT; 1280.00; for subscription A
31000; 10; 2000-12-06; PAYMENT; 128.00; for subscription B
32000; 7; 2005-01-10; PAYMENT; 2560.00; for subscription C
33001; 7; 2005-01-10; HANDOVER; -512.00; for transfer to 10
33002; 10; 2005-01-10; ADOPTION; 512.00; for transfer from 7
34001; 10; 2016-12-31; CLEARING; -8.00; for cancellation D
34002; 10; 2016-12-31; PAYBACK; -100.00; for cancellation D
34003; 10; 2016-12-31; LOSS; -20.00; for cancellation D

1 member_asset_id bp_id date action amount comment
2 33443 30000 7 2000-12-06 PAYMENT 1280 1280.00 for subscription A
3 33451 31000 10 2000-12-06 PAYMENT 128 128.00 for subscription B
4 33701 32000 7 2005-01-10 PAYMENT 2560 2560.00 for subscription C
5 33810 33001 10 7 2016-12-31 2005-01-10 PAYBACK HANDOVER 128 -512.00 for transfer to 10
6 33002 10 2005-01-10 ADOPTION 512.00 for transfer from 7
7 34001 10 2016-12-31 CLEARING -8.00 for cancellation D
8 34002 10 2016-12-31 PAYBACK -100.00 for cancellation D
9 34003 10 2016-12-31 LOSS -20.00 for cancellation D

View File

@ -1,4 +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
7;10007;mih;2000-12-06;;Aufsichtsrat;2006-10-15;2001-10-15;false;false;NET;DE-VAT-007
10;10010;xyz;2000-12-06;2015-12-31;;;;false;false;GROSS;
12;11012;xxx;2021-04-01;;;;;true;true;GROSS;
7;10007;hsh00-mih;2000-12-06;;Aufsichtsrat;2006-10-15;2001-10-15;false;false;NET;DE-VAT-007
10;10010;hsh00-xyz;2000-12-06;2015-12-31;;;;false;false;GROSS;
12;11012;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 7 10007 mih hsh00-mih 2000-12-06 Aufsichtsrat 2006-10-15 2001-10-15 false false NET DE-VAT-007
3 10 10010 xyz hsh00-xyz 2000-12-06 2015-12-31 false false GROSS
4 12 11012 xxx hsh00-xxx 2021-04-01 true true GROSS