move Parter+Debitor person+contact to related Relationsship #20

Merged
hsh-michaelhoennig merged 101 commits from remove-direct-partner-person-and-contact into master 2024-03-28 12:15:14 +01:00
33 changed files with 463 additions and 539 deletions
Showing only changes of commit 6e663cf525 - Show all commits

View File

@ -10,7 +10,7 @@ classDiagram
namespace Partner { namespace Partner {
class partner-MeierGmbH class partner-MeierGmbH
class role-MeierGmbH class rel-MeierGmbH
class personDetails-MeierGmbH class personDetails-MeierGmbH
class contactData-MeierGmbH class contactData-MeierGmbH
class person-MeierGmbH class person-MeierGmbH
@ -19,28 +19,29 @@ classDiagram
namespace Representatives { namespace Representatives {
class person-FrankMeier class person-FrankMeier
class contactData-FrankMeier class contactData-FrankMeier
class role-MeierGmbH-FrankMeier class rel-MeierGmbH-FrankMeier
} }
namespace Debitors { namespace Debitors {
class debitor-MeierGmbH class debitor-MeierGmbH
class contactData-MeierGmbH-Buha class contactData-MeierGmbH-Buha
class role-MeierGmbH-Buha class rel-MeierGmbH-Buha
} }
namespace Operations { namespace Operations {
class person-SabineMeier class person-SabineMeier
class contactData-SabineMeier class contactData-SabineMeier
class role-MeierGmbH-SabineMeier class rel-MeierGmbH-SabineMeier
} }
namespace Enums { namespace Enums {
class RoleType { class RelationType {
<<enumeration>> <<enumeration>>
UNKNOWN UNKNOWN
PARTNER
DEBITOR
REPRESENTATIVE REPRESENTATIVE
ACCOUNTING
OPERATIONS OPERATIONS
} }
@ -64,9 +65,9 @@ classDiagram
class partner-MeierGmbH { class partner-MeierGmbH {
+Numeric partnerNumber: 12345 +Numeric partnerNumber: 12345
+Role partnerRole +Relation partnerRel
} }
partner-MeierGmbH *-- role-MeierGmbH partner-MeierGmbH *-- rel-MeierGmbH
class person-MeierGmbH { class person-MeierGmbH {
+personType: LEGAL +personType: LEGAL
@ -90,32 +91,32 @@ classDiagram
+emailAddresses: office@meier-gmbh.de +emailAddresses: office@meier-gmbh.de
} }
class role-MeierGmbH { class rel-MeierGmbH {
+RoleType RoleType PARTNER +RelationType type PARTNER
+Person anchor +Person anchor
+Person holder +Person holder
+Contact roleContact +Contact contact
} }
role-MeierGmbH o-- person-HostsharingEG : anchor rel-MeierGmbH o-- person-HostsharingEG : anchor
role-MeierGmbH o-- person-MeierGmbH : holder rel-MeierGmbH o-- person-MeierGmbH : holder
role-MeierGmbH o-- contactData-MeierGmbH rel-MeierGmbH o-- contactData-MeierGmbH
%% --- Debitors --- %% --- Debitors ---
class debitor-MeierGmbH { class debitor-MeierGmbH {
+Partner partner +Partner partner
+Numeric[2] debitorNumberSuffix: 00 +Numeric[2] debitorNumberSuffix: 00
+Role billingRole +Relation debitorRel
+boolean billable: true +boolean billable: true
+String vatId: ID123456789 +String vatId: ID123456789
+String vatCountryCode: DE +String vatCountryCode: DE
+boolean vatBusiness: true +boolean vatBusiness: true
+boolean vatReverseCharge: false +boolean vatReverseCharge: false
+BankAccount refundBankAccount +BankAccount refundBankAccount
+String defaultPrefix: mei +String defaultPrefix: mei
} }
debitor-MeierGmbH o-- partner-MeierGmbH debitor-MeierGmbH o-- partner-MeierGmbH
debitor-MeierGmbH *-- role-MeierGmbH-Buha debitor-MeierGmbH *-- rel-MeierGmbH-Buha
class contactData-MeierGmbH-Buha { class contactData-MeierGmbH-Buha {
+postalAddress: Hauptstraße 5, 22345 Hamburg +postalAddress: Hauptstraße 5, 22345 Hamburg
@ -123,15 +124,15 @@ classDiagram
+emailAddresses: buha@meier-gmbh.de +emailAddresses: buha@meier-gmbh.de
} }
class role-MeierGmbH-Buha { class rel-MeierGmbH-Buha {
+RoleType RoleType ACCOUNTING +RelationType type DEBITOR
+Person anchor +Person anchor
+Person holder +Person holder
+Contact roleContact +Contact contact
} }
role-MeierGmbH-Buha o-- person-MeierGmbH : anchor rel-MeierGmbH-Buha o-- person-MeierGmbH : anchor
role-MeierGmbH-Buha o-- person-MeierGmbH : holder rel-MeierGmbH-Buha o-- person-MeierGmbH : holder
role-MeierGmbH-Buha o-- contactData-MeierGmbH-Buha rel-MeierGmbH-Buha o-- contactData-MeierGmbH-Buha
%% --- Representatives --- %% --- Representatives ---
@ -148,15 +149,15 @@ classDiagram
+emailAddresses: frank.meier@meier-gmbh.de +emailAddresses: frank.meier@meier-gmbh.de
} }
class role-MeierGmbH-FrankMeier { class rel-MeierGmbH-FrankMeier {
+RoleType RoleType REPRESENTATIVE +RelationType type REPRESENTATIVE
+Person anchor +Person anchor
+Person holder +Person holder
+Contact roleContact +Contact contact
} }
role-MeierGmbH-FrankMeier o-- person-MeierGmbH : anchor rel-MeierGmbH-FrankMeier o-- person-MeierGmbH : anchor
role-MeierGmbH-FrankMeier o-- person-FrankMeier : holder rel-MeierGmbH-FrankMeier o-- person-FrankMeier : holder
role-MeierGmbH-FrankMeier o-- contactData-FrankMeier rel-MeierGmbH-FrankMeier o-- contactData-FrankMeier
%% --- Operations --- %% --- Operations ---
@ -173,14 +174,14 @@ classDiagram
+emailAddresses: sabine.meier@meier-gmbh.de +emailAddresses: sabine.meier@meier-gmbh.de
} }
class role-MeierGmbH-SabineMeier { class rel-MeierGmbH-SabineMeier {
+RoleType RoleType OPERATIONAL +RelationType type OPERATIONAL
+Person anchor +Person anchor
+Person holder +Person holder
+Contact roleContact +Contact contact
} }
role-MeierGmbH-SabineMeier o-- person-MeierGmbH : anchor rel-MeierGmbH-SabineMeier o-- person-MeierGmbH : anchor
role-MeierGmbH-SabineMeier o-- person-SabineMeier : holder rel-MeierGmbH-SabineMeier o-- person-SabineMeier : holder
role-MeierGmbH-SabineMeier o-- contactData-SabineMeier rel-MeierGmbH-SabineMeier o-- contactData-SabineMeier
``` ```

View File

@ -1,8 +1,8 @@
package net.hostsharing.hsadminng.hs.office.relationship; package net.hostsharing.hsadminng.hs.office.relation;
import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeRelationshipsApi; import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeRelationsApi;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
import net.hostsharing.hsadminng.mapper.Mapper; import net.hostsharing.hsadminng.mapper.Mapper;
@ -22,7 +22,7 @@ import java.util.function.BiConsumer;
@RestController @RestController
public class HsOfficeRelationshipController implements HsOfficeRelationshipsApi { public class HsOfficeRelationController implements HsOfficeRelationsApi {
@Autowired @Autowired
private Context context; private Context context;
@ -31,10 +31,10 @@ public class HsOfficeRelationshipController implements HsOfficeRelationshipsApi
private Mapper mapper; private Mapper mapper;
@Autowired @Autowired
private HsOfficeRelationshipRepository relationshipRepo; private HsOfficeRelationRepository relationRepo;
@Autowired @Autowired
private HsOfficePersonRepository relHolderRepo; private HsOfficePersonRepository holderRepo;
@Autowired @Autowired
private HsOfficeContactRepository contactRepo; private HsOfficeContactRepository contactRepo;
@ -44,79 +44,79 @@ public class HsOfficeRelationshipController implements HsOfficeRelationshipsApi
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public ResponseEntity<List<HsOfficeRelationshipResource>> listRelationships( public ResponseEntity<List<HsOfficeRelationResource>> listRelations(
final String currentUser, final String currentUser,
final String assumedRoles, final String assumedRoles,
final UUID personUuid, final UUID personUuid,
final HsOfficeRelationshipTypeResource relationshipType) { final HsOfficeRelationTypeResource relationType) {
context.define(currentUser, assumedRoles); context.define(currentUser, assumedRoles);
final var entities = relationshipRepo.findRelationshipRelatedToPersonUuidAndRelationshipType(personUuid, final var entities = relationRepo.findRelationRelatedToPersonUuidAndRelationType(personUuid,
mapper.map(relationshipType, HsOfficeRelationshipType.class)); mapper.map(relationType, HsOfficeRelationType.class));
final var resources = mapper.mapList(entities, HsOfficeRelationshipResource.class, final var resources = mapper.mapList(entities, HsOfficeRelationResource.class,
RELATIONSHIP_ENTITY_TO_RESOURCE_POSTMAPPER); RELATION_ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(resources); return ResponseEntity.ok(resources);
} }
@Override @Override
@Transactional @Transactional
public ResponseEntity<HsOfficeRelationshipResource> addRelationship( public ResponseEntity<HsOfficeRelationResource> addRelation(
final String currentUser, final String currentUser,
final String assumedRoles, final String assumedRoles,
final HsOfficeRelationshipInsertResource body) { final HsOfficeRelationInsertResource body) {
context.define(currentUser, assumedRoles); context.define(currentUser, assumedRoles);
final var entityToSave = new HsOfficeRelationshipEntity(); final var entityToSave = new HsOfficeRelationEntity();
entityToSave.setRelType(HsOfficeRelationshipType.valueOf(body.getRelType())); entityToSave.setType(HsOfficeRelationType.valueOf(body.getType()));
entityToSave.setRelAnchor(relHolderRepo.findByUuid(body.getRelAnchorUuid()).orElseThrow( entityToSave.setAnchor(holderRepo.findByUuid(body.getAnchorUuid()).orElseThrow(
() -> new NoSuchElementException("cannot find relAnchorUuid " + body.getRelAnchorUuid()) () -> new NoSuchElementException("cannot find anchorUuid " + body.getAnchorUuid())
)); ));
entityToSave.setRelHolder(relHolderRepo.findByUuid(body.getRelHolderUuid()).orElseThrow( entityToSave.setHolder(holderRepo.findByUuid(body.getHolderUuid()).orElseThrow(
() -> new NoSuchElementException("cannot find relHolderUuid " + body.getRelHolderUuid()) () -> new NoSuchElementException("cannot find holderUuid " + body.getHolderUuid())
)); ));
entityToSave.setContact(contactRepo.findByUuid(body.getContactUuid()).orElseThrow( entityToSave.setContact(contactRepo.findByUuid(body.getContactUuid()).orElseThrow(
() -> new NoSuchElementException("cannot find contactUuid " + body.getContactUuid()) () -> new NoSuchElementException("cannot find contactUuid " + body.getContactUuid())
)); ));
final var saved = relationshipRepo.save(entityToSave); final var saved = relationRepo.save(entityToSave);
final var uri = final var uri =
MvcUriComponentsBuilder.fromController(getClass()) MvcUriComponentsBuilder.fromController(getClass())
.path("/api/hs/office/relationships/{id}") .path("/api/hs/office/relations/{id}")
.buildAndExpand(saved.getUuid()) .buildAndExpand(saved.getUuid())
.toUri(); .toUri();
final var mapped = mapper.map(saved, HsOfficeRelationshipResource.class, final var mapped = mapper.map(saved, HsOfficeRelationResource.class,
RELATIONSHIP_ENTITY_TO_RESOURCE_POSTMAPPER); RELATION_ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.created(uri).body(mapped); return ResponseEntity.created(uri).body(mapped);
} }
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public ResponseEntity<HsOfficeRelationshipResource> getRelationshipByUuid( public ResponseEntity<HsOfficeRelationResource> getRelationByUuid(
final String currentUser, final String currentUser,
final String assumedRoles, final String assumedRoles,
final UUID relationshipUuid) { final UUID relationUuid) {
context.define(currentUser, assumedRoles); context.define(currentUser, assumedRoles);
final var result = relationshipRepo.findByUuid(relationshipUuid); final var result = relationRepo.findByUuid(relationUuid);
if (result.isEmpty()) { if (result.isEmpty()) {
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();
} }
return ResponseEntity.ok(mapper.map(result.get(), HsOfficeRelationshipResource.class, RELATIONSHIP_ENTITY_TO_RESOURCE_POSTMAPPER)); return ResponseEntity.ok(mapper.map(result.get(), HsOfficeRelationResource.class, RELATION_ENTITY_TO_RESOURCE_POSTMAPPER));
} }
@Override @Override
@Transactional @Transactional
public ResponseEntity<Void> deleteRelationshipByUuid( public ResponseEntity<Void> deleteRelationByUuid(
final String currentUser, final String currentUser,
final String assumedRoles, final String assumedRoles,
final UUID relationshipUuid) { final UUID relationUuid) {
context.define(currentUser, assumedRoles); context.define(currentUser, assumedRoles);
final var result = relationshipRepo.deleteByUuid(relationshipUuid); final var result = relationRepo.deleteByUuid(relationUuid);
if (result == 0) { if (result == 0) {
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();
} }
@ -126,27 +126,27 @@ public class HsOfficeRelationshipController implements HsOfficeRelationshipsApi
@Override @Override
@Transactional @Transactional
public ResponseEntity<HsOfficeRelationshipResource> patchRelationship( public ResponseEntity<HsOfficeRelationResource> patchRelation(
final String currentUser, final String currentUser,
final String assumedRoles, final String assumedRoles,
final UUID relationshipUuid, final UUID relationUuid,
final HsOfficeRelationshipPatchResource body) { final HsOfficeRelationPatchResource body) {
context.define(currentUser, assumedRoles); context.define(currentUser, assumedRoles);
final var current = relationshipRepo.findByUuid(relationshipUuid).orElseThrow(); final var current = relationRepo.findByUuid(relationUuid).orElseThrow();
new HsOfficeRelationshipEntityPatcher(em, current).apply(body); new HsOfficeRelationEntityPatcher(em, current).apply(body);
final var saved = relationshipRepo.save(current); final var saved = relationRepo.save(current);
final var mapped = mapper.map(saved, HsOfficeRelationshipResource.class); final var mapped = mapper.map(saved, HsOfficeRelationResource.class);
return ResponseEntity.ok(mapped); return ResponseEntity.ok(mapped);
} }
final BiConsumer<HsOfficeRelationshipEntity, HsOfficeRelationshipResource> RELATIONSHIP_ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> { final BiConsumer<HsOfficeRelationEntity, HsOfficeRelationResource> RELATION_ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
resource.setRelAnchor(mapper.map(entity.getRelAnchor(), HsOfficePersonResource.class)); resource.setAnchor(mapper.map(entity.getAnchor(), HsOfficePersonResource.class));
resource.setRelHolder(mapper.map(entity.getRelHolder(), HsOfficePersonResource.class)); resource.setHolder(mapper.map(entity.getHolder(), HsOfficePersonResource.class));
resource.setContact(mapper.map(entity.getContact(), HsOfficeContactResource.class)); resource.setContact(mapper.map(entity.getContact(), HsOfficeContactResource.class));
}; };
} }

View File

@ -1,25 +1,25 @@
package net.hostsharing.hsadminng.hs.office.relationship; package net.hostsharing.hsadminng.hs.office.relation;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationshipPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationPatchResource;
import net.hostsharing.hsadminng.mapper.EntityPatcher; import net.hostsharing.hsadminng.mapper.EntityPatcher;
import net.hostsharing.hsadminng.mapper.OptionalFromJson; import net.hostsharing.hsadminng.mapper.OptionalFromJson;
import jakarta.persistence.EntityManager; import jakarta.persistence.EntityManager;
import java.util.UUID; import java.util.UUID;
class HsOfficeRelationshipEntityPatcher implements EntityPatcher<HsOfficeRelationshipPatchResource> { class HsOfficeRelationEntityPatcher implements EntityPatcher<HsOfficeRelationPatchResource> {
private final EntityManager em; private final EntityManager em;
private final HsOfficeRelationshipEntity entity; private final HsOfficeRelationEntity entity;
HsOfficeRelationshipEntityPatcher(final EntityManager em, final HsOfficeRelationshipEntity entity) { HsOfficeRelationEntityPatcher(final EntityManager em, final HsOfficeRelationEntity entity) {
this.em = em; this.em = em;
this.entity = entity; this.entity = entity;
} }
@Override @Override
public void apply(final HsOfficeRelationshipPatchResource resource) { public void apply(final HsOfficeRelationPatchResource resource) {
OptionalFromJson.of(resource.getContactUuid()).ifPresent(newValue -> { OptionalFromJson.of(resource.getContactUuid()).ifPresent(newValue -> {
verifyNotNull(newValue, "contact"); verifyNotNull(newValue, "contact");
entity.setContact(em.getReference(HsOfficeContactEntity.class, newValue)); entity.setContact(em.getReference(HsOfficeContactEntity.class, newValue));

View File

@ -0,0 +1,37 @@
package net.hostsharing.hsadminng.hs.office.relation;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface HsOfficeRelationRepository extends Repository<HsOfficeRelationEntity, UUID> {
Optional<HsOfficeRelationEntity> findByUuid(UUID id);
default List<HsOfficeRelationEntity> findRelationRelatedToPersonUuidAndRelationType(@NotNull UUID personUuid, HsOfficeRelationType relationType) {
return findRelationRelatedToPersonUuidAndRelationTypeString(personUuid, relationType.toString());
}
@Query(value = """
SELECT p.* FROM hs_office_relation_rv AS p
WHERE p.anchorUuid = :personUuid OR p.holderUuid = :personUuid
""", nativeQuery = true)
List<HsOfficeRelationEntity> findRelationRelatedToPersonUuid(@NotNull UUID personUuid);
@Query(value = """
SELECT p.* FROM hs_office_relation_rv AS p
WHERE (:relationType IS NULL OR p.type = cast(:relationType AS HsOfficeRelationType))
AND ( p.anchorUuid = :personUuid OR p.holderUuid = :personUuid)
""", nativeQuery = true)
List<HsOfficeRelationEntity> findRelationRelatedToPersonUuidAndRelationTypeString(@NotNull UUID personUuid, String relationType);
HsOfficeRelationEntity save(final HsOfficeRelationEntity entity);
long count();
int deleteByUuid(UUID uuid);
}

View File

@ -1,12 +1,12 @@
package net.hostsharing.hsadminng.hs.office.relationship; package net.hostsharing.hsadminng.hs.office.relation;
public enum HsOfficeRelationshipType { public enum HsOfficeRelationType {
UNKNOWN, UNKNOWN,
PARTNER, PARTNER,
EX_PARTNER, EX_PARTNER,
REPRESENTATIVE, REPRESENTATIVE,
VIP_CONTACT, VIP_CONTACT,
ACCOUNTING, DEBITOR,
OPERATIONS, OPERATIONS,
SUBSCRIBER SUBSCRIBER
} }

View File

@ -1,37 +0,0 @@
package net.hostsharing.hsadminng.hs.office.relationship;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface HsOfficeRelationshipRepository extends Repository<HsOfficeRelationshipEntity, UUID> {
Optional<HsOfficeRelationshipEntity> findByUuid(UUID id);
default List<HsOfficeRelationshipEntity> findRelationshipRelatedToPersonUuidAndRelationshipType(@NotNull UUID personUuid, HsOfficeRelationshipType relationshipType) {
return findRelationshipRelatedToPersonUuidAndRelationshipTypeString(personUuid, relationshipType.toString());
}
@Query(value = """
SELECT p.* FROM hs_office_relationship_rv AS p
WHERE p.relAnchorUuid = :personUuid OR p.relHolderUuid = :personUuid
""", nativeQuery = true)
List<HsOfficeRelationshipEntity> findRelationshipRelatedToPersonUuid(@NotNull UUID personUuid);
@Query(value = """
SELECT p.* FROM hs_office_relationship_rv AS p
WHERE (:relationshipType IS NULL OR p.relType = cast(:relationshipType AS HsOfficeRelationshipType))
AND ( p.relAnchorUuid = :personUuid OR p.relHolderUuid = :personUuid)
""", nativeQuery = true)
List<HsOfficeRelationshipEntity> findRelationshipRelatedToPersonUuidAndRelationshipTypeString(@NotNull UUID personUuid, String relationshipType);
HsOfficeRelationshipEntity save(final HsOfficeRelationshipEntity entity);
long count();
int deleteByUuid(UUID uuid);
}

View File

@ -23,7 +23,7 @@ public class InsertTriggerGenerator {
void generateTo(final StringWriter plPgSql) { void generateTo(final StringWriter plPgSql) {
generateLiquibaseChangesetHeader(plPgSql); generateLiquibaseChangesetHeader(plPgSql);
generateGrantInsertRoleToExistingCustomers(plPgSql); generateGrantInsertRoleToExistingObjects(plPgSql);
generateInsertPermissionGrantTrigger(plPgSql); generateInsertPermissionGrantTrigger(plPgSql);
generateInsertCheckTrigger(plPgSql); generateInsertCheckTrigger(plPgSql);
plPgSql.writeLn("--//"); plPgSql.writeLn("--//");
@ -38,7 +38,7 @@ public class InsertTriggerGenerator {
with("liquibaseTagPrefix", liquibaseTagPrefix)); with("liquibaseTagPrefix", liquibaseTagPrefix));
} }
private void generateGrantInsertRoleToExistingCustomers(final StringWriter plPgSql) { private void generateGrantInsertRoleToExistingObjects(final StringWriter plPgSql) {
getOptionalInsertSuperRole().ifPresent( superRoleDef -> { getOptionalInsertSuperRole().ifPresent( superRoleDef -> {
plPgSql.writeLn(""" plPgSql.writeLn("""
/* /*
@ -100,13 +100,7 @@ public class InsertTriggerGenerator {
private void generateInsertCheckTrigger(final StringWriter plPgSql) { private void generateInsertCheckTrigger(final StringWriter plPgSql) {
getOptionalInsertGrant().ifPresentOrElse(g -> { getOptionalInsertGrant().ifPresentOrElse(g -> {
if (!g.getSuperRoleDef().getEntityAlias().isGlobal()) { if (g.getSuperRoleDef().getEntityAlias().isGlobal()) {
if (rbacDef.isRootEntityAlias(g.getSuperRoleDef().getEntityAlias())) {
generateInsertPermissionTriggerAllowByDirectRole(plPgSql, g);
} else {
generateInsertPermissionTriggerAllowByIndirectRole(plPgSql, g);
}
} else {
switch (g.getSuperRoleDef().getRole()) { switch (g.getSuperRoleDef().getRole()) {
case ADMIN -> { case ADMIN -> {
generateInsertPermissionTriggerAllowOnlyGlobalAdmin(plPgSql); generateInsertPermissionTriggerAllowOnlyGlobalAdmin(plPgSql);
@ -119,27 +113,23 @@ public class InsertTriggerGenerator {
"invalid global role for INSERT permission: " + g.getSuperRoleDef().getRole()); "invalid global role for INSERT permission: " + g.getSuperRoleDef().getRole());
} }
} }
} else {
generateInsertPermissionTriggerAllowByRoleOfDirectForeignKey(plPgSql, g);
} }
}, },
() -> { () -> {
plPgSql.writeLn(""" System.err.println("WARNING: no explicit INSERT grant for " + rbacDef.getRootEntityAlias().simpleName() + " => implicitly grant INSERT to global.admin");
-- FIXME: Where is this case necessary? generateInsertPermissionTriggerAllowOnlyGlobalAdmin(plPgSql);
create trigger ${rawSubTable}_insert_permission_check_tg
before insert on ${rawSubTable}
for each row
-- As there is no explicit INSERT grant specified for this table,
-- only global admins are allowed to insert any rows.
when ( not isGlobalAdmin() )
execute procedure ${rawSubTable}_insert_permission_missing_tf();
""",
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()));
}); });
} }
private void generateInsertPermissionTriggerAllowByDirectRole(final StringWriter plPgSql, final RbacView.RbacGrantDefinition g) { private void generateInsertPermissionTriggerAllowByRoleOfDirectForeignKey(final StringWriter plPgSql, final RbacView.RbacGrantDefinition g) {
plPgSql.writeLn(""" plPgSql.writeLn("""
/** /**
Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}. Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable},
where the check is performed by a direct role.
A direct role is a role depending on a foreign key directly available in the NEW row.
*/ */
create or replace function ${rawSubTable}_insert_permission_missing_tf() create or replace function ${rawSubTable}_insert_permission_missing_tf()
returns trigger returns trigger
@ -159,51 +149,11 @@ public class InsertTriggerGenerator {
with("referenceColumn", g.getSuperRoleDef().getEntityAlias().dependsOnColumName())); with("referenceColumn", g.getSuperRoleDef().getEntityAlias().dependsOnColumName()));
} }
private void generateInsertPermissionTriggerAllowByIndirectRole(
final StringWriter plPgSql,
final RbacView.RbacGrantDefinition g) {
plPgSql.writeLn("""
/**
Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}.
*/
create or replace function ${rawSubTable}_insert_permission_missing_tf()
returns trigger
language plpgsql as $$
begin
if ( not hasInsertPermission(
( SELECT ${varName}.uuid FROM
""",
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()),
with("varName", g.getSuperRoleDef().getEntityAlias().aliasName()));
plPgSql.indented(3, () -> {
plPgSql.writeLn(
"(" + g.getSuperRoleDef().getEntityAlias().fetchSql().sql + ") AS ${varName}",
with("varName", g.getSuperRoleDef().getEntityAlias().aliasName()),
with("ref", NEW.name()));
});
plPgSql.writeLn("""
), 'INSERT', '${rawSubTable}') ) then
raise exception
'[403] insert into ${rawSubTable} not allowed for current subjects % (%)',
currentSubjects(), currentSubjectsUuids();
end if;
return NEW;
end; $$;
create trigger ${rawSubTable}_insert_permission_check_tg
before insert on ${rawSubTable}
for each row
execute procedure ${rawSubTable}_insert_permission_missing_tf();
""",
with("rawSubTable", rbacDef.getRootEntityAlias().getRawTableName()));
}
private void generateInsertPermissionTriggerAllowOnlyGlobalAdmin(final StringWriter plPgSql) { private void generateInsertPermissionTriggerAllowOnlyGlobalAdmin(final StringWriter plPgSql) {
plPgSql.writeLn(""" plPgSql.writeLn("""
/** /**
Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable}. Checks if the user or assumed roles are allowed to insert a row to ${rawSubTable},
where only global-admin has that permission.
*/ */
create or replace function ${rawSubTable}_insert_permission_missing_tf() create or replace function ${rawSubTable}_insert_permission_missing_tf()
returns trigger returns trigger

View File

@ -8,13 +8,11 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with;
public class RbacRestrictedViewGenerator { public class RbacRestrictedViewGenerator {
private final RbacView rbacDef; private final RbacView rbacDef;
private final String liquibaseTagPrefix; private final String liquibaseTagPrefix;
private final String simpleEntityVarName;
private final String rawTableName; private final String rawTableName;
public RbacRestrictedViewGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) { public RbacRestrictedViewGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) {
this.rbacDef = rbacDef; this.rbacDef = rbacDef;
this.liquibaseTagPrefix = liquibaseTagPrefix; this.liquibaseTagPrefix = liquibaseTagPrefix;
this.simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName();
this.rawTableName = rbacDef.getRootEntityAlias().getRawTableName(); this.rawTableName = rbacDef.getRootEntityAlias().getRawTableName();
} }
@ -25,16 +23,16 @@ public class RbacRestrictedViewGenerator {
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacRestrictedView('${rawTableName}', call generateRbacRestrictedView('${rawTableName}',
$orderBy$ $orderBy$
${orderBy} ${orderBy}
$orderBy$, $orderBy$,
$updates$ $updates$
${updates} ${updates}
$updates$); $updates$);
--// --//
""", """,
with("liquibaseTagPrefix", liquibaseTagPrefix), with("liquibaseTagPrefix", liquibaseTagPrefix),
with("orderBy", rbacDef.getOrderBySqlExpression().sql), with("orderBy", indented(rbacDef.getOrderBySqlExpression().sql, 2)),
with("updates", indented(rbacDef.getUpdatableColumns().stream() with("updates", indented(rbacDef.getUpdatableColumns().stream()
.map(c -> c + " = new." + c) .map(c -> c + " = new." + c)
.collect(joining(",\n")), 2)), .collect(joining(",\n")), 2)),

View File

@ -11,7 +11,7 @@ import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerDetailsEntity; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerDetailsEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.hs.office.sepamandate.HsOfficeSepaMandateEntity; import net.hostsharing.hsadminng.hs.office.sepamandate.HsOfficeSepaMandateEntity;
import net.hostsharing.hsadminng.persistence.HasUuid; import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject; import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
@ -812,7 +812,7 @@ public class RbacView {
HsOfficePartnerDetailsEntity.class, HsOfficePartnerDetailsEntity.class,
HsOfficeBankAccountEntity.class, HsOfficeBankAccountEntity.class,
HsOfficeDebitorEntity.class, HsOfficeDebitorEntity.class,
HsOfficeRelationshipEntity.class, HsOfficeRelationEntity.class,
HsOfficeCoopAssetsTransactionEntity.class, HsOfficeCoopAssetsTransactionEntity.class,
HsOfficeContactEntity.class, HsOfficeContactEntity.class,
HsOfficeSepaMandateEntity.class, HsOfficeSepaMandateEntity.class,
@ -831,7 +831,7 @@ public class RbacView {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} else { } else {
System.err.println("no main method in: " + c.getName()); System.err.println("WARNING: no main method in: " + c.getName() + " => no RBAC rules generated");
} }
}); });
} }

View File

@ -23,7 +23,7 @@ map:
null: org.openapitools.jackson.nullable.JsonNullable null: org.openapitools.jackson.nullable.JsonNullable
/api/hs/office/persons/{personUUID}: /api/hs/office/persons/{personUUID}:
null: org.openapitools.jackson.nullable.JsonNullable null: org.openapitools.jackson.nullable.JsonNullable
/api/hs/office/relationships/{relationshipUUID}: /api/hs/office/relations/{relationUUID}:
null: org.openapitools.jackson.nullable.JsonNullable null: org.openapitools.jackson.nullable.JsonNullable
/api/hs/office/bankaccounts/{bankAccountUUID}: /api/hs/office/bankaccounts/{bankAccountUUID}:
null: org.openapitools.jackson.nullable.JsonNullable null: org.openapitools.jackson.nullable.JsonNullable

View File

@ -3,37 +3,37 @@ components:
schemas: schemas:
HsOfficeRelationshipType: HsOfficeRelationType:
type: string type: string
enum: enum:
- UNKNOWN - UNKNOWN
- PARTNER - PARTNER
- EX_PARTNER - EX_PARTNER
- REPRESENTATIVE, - DEBITOR
- REPRESENTATIVE
- VIP_CONTACT - VIP_CONTACT
- ACCOUNTING,
- OPERATIONS - OPERATIONS
- SUBSCRIBER - SUBSCRIBER
HsOfficeRelationship: HsOfficeRelation:
type: object type: object
properties: properties:
uuid: uuid:
type: string type: string
format: uuid format: uuid
relAnchor: anchor:
$ref: './hs-office-person-schemas.yaml#/components/schemas/HsOfficePerson' $ref: './hs-office-person-schemas.yaml#/components/schemas/HsOfficePerson'
relHolder: holder:
$ref: './hs-office-person-schemas.yaml#/components/schemas/HsOfficePerson' $ref: './hs-office-person-schemas.yaml#/components/schemas/HsOfficePerson'
relType: type:
type: string type: string
relMark: mark:
type: string type: string
nullable: true nullable: true
contact: contact:
$ref: './hs-office-contact-schemas.yaml#/components/schemas/HsOfficeContact' $ref: './hs-office-contact-schemas.yaml#/components/schemas/HsOfficeContact'
HsOfficeRelationshipPatch: HsOfficeRelationPatch:
type: object type: object
properties: properties:
contactUuid: contactUuid:
@ -41,25 +41,25 @@ components:
format: uuid format: uuid
nullable: true nullable: true
HsOfficeRelationshipInsert: HsOfficeRelationInsert:
type: object type: object
properties: properties:
relAnchorUuid: anchorUuid:
type: string type: string
format: uuid format: uuid
relHolderUuid: holderUuid:
type: string type: string
format: uuid format: uuid
relType: type:
type: string type: string
nullable: true nullable: true
relMark: mark:
type: string type: string
contactUuid: contactUuid:
type: string type: string
format: uuid format: uuid
required: required:
- relAnchorUuid - anchorUuid
- relHolderUuid - holderUuid
- relType - type
- relContactUuid - relContactUuid

View File

@ -1,25 +1,25 @@
get: get:
tags: tags:
- hs-office-relationships - hs-office-relations
description: 'Fetch a single person relationship by its uuid, if visible for the current subject.' description: 'Fetch a single person relation by its uuid, if visible for the current subject.'
operationId: getRelationshipByUuid operationId: getRelationByUuid
parameters: parameters:
- $ref: './auth.yaml#/components/parameters/currentUser' - $ref: './auth.yaml#/components/parameters/currentUser'
- $ref: './auth.yaml#/components/parameters/assumedRoles' - $ref: './auth.yaml#/components/parameters/assumedRoles'
- name: relationshipUUID - name: relationUUID
in: path in: path
required: true required: true
schema: schema:
type: string type: string
format: uuid format: uuid
description: UUID of the relationship to fetch. description: UUID of the relation to fetch.
responses: responses:
"200": "200":
description: OK description: OK
content: content:
'application/json': 'application/json':
schema: schema:
$ref: './hs-office-relationship-schemas.yaml#/components/schemas/HsOfficeRelationship' $ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelation'
"401": "401":
$ref: './error-responses.yaml#/components/responses/Unauthorized' $ref: './error-responses.yaml#/components/responses/Unauthorized'
@ -28,13 +28,13 @@ get:
patch: patch:
tags: tags:
- hs-office-relationships - hs-office-relations
description: 'Updates a single person relationship by its uuid, if permitted for the current subject.' description: 'Updates a single person relation by its uuid, if permitted for the current subject.'
operationId: patchRelationship operationId: patchRelation
parameters: parameters:
- $ref: './auth.yaml#/components/parameters/currentUser' - $ref: './auth.yaml#/components/parameters/currentUser'
- $ref: './auth.yaml#/components/parameters/assumedRoles' - $ref: './auth.yaml#/components/parameters/assumedRoles'
- name: relationshipUUID - name: relationUUID
in: path in: path
required: true required: true
schema: schema:
@ -44,14 +44,14 @@ patch:
content: content:
'application/json': 'application/json':
schema: schema:
$ref: './hs-office-relationship-schemas.yaml#/components/schemas/HsOfficeRelationshipPatch' $ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelationPatch'
responses: responses:
"200": "200":
description: OK description: OK
content: content:
'application/json': 'application/json':
schema: schema:
$ref: './hs-office-relationship-schemas.yaml#/components/schemas/HsOfficeRelationship' $ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelation'
"401": "401":
$ref: './error-responses.yaml#/components/responses/Unauthorized' $ref: './error-responses.yaml#/components/responses/Unauthorized'
"403": "403":
@ -59,19 +59,19 @@ patch:
delete: delete:
tags: tags:
- hs-office-relationships - hs-office-relations
description: 'Delete a single person relationship by its uuid, if permitted for the current subject.' description: 'Delete a single person relation by its uuid, if permitted for the current subject.'
operationId: deleteRelationshipByUuid operationId: deleteRelationByUuid
parameters: parameters:
- $ref: './auth.yaml#/components/parameters/currentUser' - $ref: './auth.yaml#/components/parameters/currentUser'
- $ref: './auth.yaml#/components/parameters/assumedRoles' - $ref: './auth.yaml#/components/parameters/assumedRoles'
- name: relationshipUUID - name: relationUUID
in: path in: path
required: true required: true
schema: schema:
type: string type: string
format: uuid format: uuid
description: UUID of the relationship to delete. description: UUID of the relation to delete.
responses: responses:
"204": "204":
description: No Content description: No Content

View File

@ -1,9 +1,9 @@
get: get:
summary: Returns a list of (optionally filtered) person relationships for a given person. summary: Returns a list of (optionally filtered) person relations for a given person.
description: Returns the list of (optionally filtered) person relationships of a given person and which are visible to the current user or any of it's assumed roles. description: Returns the list of (optionally filtered) person relations of a given person and which are visible to the current user or any of it's assumed roles.
tags: tags:
- hs-office-relationships - hs-office-relations
operationId: listRelationships operationId: listRelations
parameters: parameters:
- $ref: './auth.yaml#/components/parameters/currentUser' - $ref: './auth.yaml#/components/parameters/currentUser'
- $ref: './auth.yaml#/components/parameters/assumedRoles' - $ref: './auth.yaml#/components/parameters/assumedRoles'
@ -13,13 +13,13 @@ get:
schema: schema:
type: string type: string
format: uuid format: uuid
description: Prefix of name properties from relHolder or contact to filter the results. description: Prefix of name properties from holder or contact to filter the results.
- name: relationshipType - name: relationType
in: query in: query
required: false required: false
schema: schema:
$ref: './hs-office-relationship-schemas.yaml#/components/schemas/HsOfficeRelationshipType' $ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelationType'
description: Prefix of name properties from relHolder or contact to filter the results. description: Prefix of name properties from holder or contact to filter the results.
responses: responses:
"200": "200":
description: OK description: OK
@ -28,17 +28,17 @@ get:
schema: schema:
type: array type: array
items: items:
$ref: './hs-office-relationship-schemas.yaml#/components/schemas/HsOfficeRelationship' $ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelation'
"401": "401":
$ref: './error-responses.yaml#/components/responses/Unauthorized' $ref: './error-responses.yaml#/components/responses/Unauthorized'
"403": "403":
$ref: './error-responses.yaml#/components/responses/Forbidden' $ref: './error-responses.yaml#/components/responses/Forbidden'
post: post:
summary: Adds a new person relationship. summary: Adds a new person relation.
tags: tags:
- hs-office-relationships - hs-office-relations
operationId: addRelationship operationId: addRelation
parameters: parameters:
- $ref: './auth.yaml#/components/parameters/currentUser' - $ref: './auth.yaml#/components/parameters/currentUser'
- $ref: './auth.yaml#/components/parameters/assumedRoles' - $ref: './auth.yaml#/components/parameters/assumedRoles'
@ -46,7 +46,7 @@ post:
content: content:
'application/json': 'application/json':
schema: schema:
$ref: './hs-office-relationship-schemas.yaml#/components/schemas/HsOfficeRelationshipInsert' $ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelationInsert'
required: true required: true
responses: responses:
"201": "201":
@ -54,7 +54,7 @@ post:
content: content:
'application/json': 'application/json':
schema: schema:
$ref: './hs-office-relationship-schemas.yaml#/components/schemas/HsOfficeRelationship' $ref: './hs-office-relations-schemas.yaml#/components/schemas/HsOfficeRelation'
"401": "401":
$ref: './error-responses.yaml#/components/responses/Unauthorized' $ref: './error-responses.yaml#/components/responses/Unauthorized'
"403": "403":

View File

@ -35,13 +35,13 @@ paths:
$ref: "./hs-office-persons-with-uuid.yaml" $ref: "./hs-office-persons-with-uuid.yaml"
# Relationships # Relations
/api/hs/office/relationships: /api/hs/office/relations:
$ref: "./hs-office-relationships.yaml" $ref: "./hs-office-relations.yaml"
/api/hs/office/relationships/{relationshipUUID}: /api/hs/office/relations/{relationUUID}:
$ref: "./hs-office-relationships-with-uuid.yaml" $ref: "./hs-office-relations-with-uuid.yaml"
# BankAccounts # BankAccounts

View File

@ -1,6 +1,6 @@
### rbac customer ### rbac customer
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-11T11:29:11.571772062. This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:44:19.425403022.
```mermaid ```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%% %%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,5 +1,6 @@
--liquibase formatted sql --liquibase formatted sql
-- This code generated was by RbacViewPostgresGenerator at 2024-03-11T11:29:11.584886824. -- This code generated was by RbacViewPostgresGenerator at 2024-03-22T14:44:19.441879428.
-- ============================================================================ -- ============================================================================
--changeset test-customer-rbac-OBJECT:1 endDelimiter:--// --changeset test-customer-rbac-OBJECT:1 endDelimiter:--//
@ -36,8 +37,8 @@ begin
perform createRoleWithGrants( perform createRoleWithGrants(
testCustomerOwner(NEW), testCustomerOwner(NEW),
permissions => array['DELETE'], permissions => array['DELETE'],
userUuids => array[currentUserUuid()], incomingSuperRoles => array[globalAdmin(unassumed())],
incomingSuperRoles => array[globalAdmin(unassumed())] userUuids => array[currentUserUuid()]
); );
perform createRoleWithGrants( perform createRoleWithGrants(
@ -72,15 +73,16 @@ create trigger insertTriggerForTestCustomer_tg
after insert on test_customer after insert on test_customer
for each row for each row
execute procedure insertTriggerForTestCustomer_tf(); execute procedure insertTriggerForTestCustomer_tf();
--// --//
-- ============================================================================ -- ============================================================================
--changeset test-customer-rbac-INSERT:1 endDelimiter:--// --changeset test-customer-rbac-INSERT:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
/** /**
Checks if the user or assumed roles are allowed to insert a row to test_customer. Checks if the user or assumed roles are allowed to insert a row to test_customer,
where only global-admin has that permission.
*/ */
create or replace function test_customer_insert_permission_missing_tf() create or replace function test_customer_insert_permission_missing_tf()
returns trigger returns trigger
@ -93,12 +95,10 @@ end; $$;
create trigger test_customer_insert_permission_check_tg create trigger test_customer_insert_permission_check_tg
before insert on test_customer before insert on test_customer
for each row for each row
-- As there is no explicit INSERT grant specified for this table,
-- only global admins are allowed to insert any rows.
when ( not isGlobalAdmin() ) when ( not isGlobalAdmin() )
execute procedure test_customer_insert_permission_missing_tf(); execute procedure test_customer_insert_permission_missing_tf();
--// --//
-- ============================================================================ -- ============================================================================
--changeset test-customer-rbac-IDENTITY-VIEW:1 endDelimiter:--// --changeset test-customer-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
@ -106,13 +106,15 @@ create trigger test_customer_insert_permission_check_tg
call generateRbacIdentityViewFromProjection('test_customer', $idName$ call generateRbacIdentityViewFromProjection('test_customer', $idName$
prefix prefix
$idName$); $idName$);
--// --//
-- ============================================================================ -- ============================================================================
--changeset test-customer-rbac-RESTRICTED-VIEW:1 endDelimiter:--// --changeset test-customer-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
call generateRbacRestrictedView('test_customer', call generateRbacRestrictedView('test_customer',
'reference', $orderBy$
reference
$orderBy$,
$updates$ $updates$
reference = new.reference, reference = new.reference,
prefix = new.prefix, prefix = new.prefix,
@ -120,4 +122,3 @@ call generateRbacRestrictedView('test_customer',
$updates$); $updates$);
--// --//

View File

@ -1,6 +1,6 @@
### rbac package ### rbac package
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-11T11:29:11.624847792. This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:44:19.484173294.
```mermaid ```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%% %%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,6 +1,6 @@
### rbac domain ### rbac domain
This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-11T11:29:11.644658132. This code generated was by RbacViewMermaidFlowchartGenerator at 2024-03-22T14:44:19.510830235.
```mermaid ```mermaid
%%{init:{'flowchart':{'htmlLabels':false}}}%% %%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -0,0 +1,36 @@
--liquibase formatted sql
-- ============================================================================
--changeset hs-office-relation-MAIN-TABLE:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
CREATE TYPE HsOfficeRelationType AS ENUM (
'UNKNOWN',
'PARTNER',
'EX_PARTNER',
'REPRESENTATIVE',
'DEBITOR',
'VIP_CONTACT',
'OPERATIONS',
'SUBSCRIBER');
CREATE CAST (character varying as HsOfficeRelationType) WITH INOUT AS IMPLICIT;
create table if not exists hs_office_relation
(
uuid uuid unique references RbacObject (uuid) initially deferred, -- on delete cascade
anchorUuid uuid not null references hs_office_person(uuid),
holderUuid uuid not null references hs_office_person(uuid),
contactUuid uuid references hs_office_contact(uuid),
type HsOfficeRelationType not null,
mark varchar(24)
);
--//
-- ============================================================================
--changeset hs-office-relation-MAIN-TABLE-JOURNAL:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call create_journal('hs_office_relation');
--//

View File

@ -1,36 +0,0 @@
--liquibase formatted sql
-- ============================================================================
--changeset hs-office-relationship-MAIN-TABLE:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
CREATE TYPE HsOfficeRelationshipType AS ENUM (
'UNKNOWN',
'PARTNER',
'EX_PARTNER',
'REPRESENTATIVE',
'VIP_CONTACT',
'ACCOUNTING',
'OPERATIONS',
'SUBSCRIBER');
CREATE CAST (character varying as HsOfficeRelationshipType) WITH INOUT AS IMPLICIT;
create table if not exists hs_office_relationship
(
uuid uuid unique references RbacObject (uuid) initially deferred, -- on delete cascade
relAnchorUuid uuid not null references hs_office_person(uuid),
relHolderUuid uuid not null references hs_office_person(uuid),
contactUuid uuid references hs_office_contact(uuid),
relType HsOfficeRelationshipType not null,
relMark varchar(24)
);
--//
-- ============================================================================
--changeset hs-office-relationship-MAIN-TABLE-JOURNAL:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call create_journal('hs_office_relationship');
--//

View File

@ -70,11 +70,11 @@ databaseChangeLog:
- include: - include:
file: db/changelog/218-hs-office-person-test-data.sql file: db/changelog/218-hs-office-person-test-data.sql
- include: - include:
file: db/changelog/220-hs-office-relationship.sql file: db/changelog/220-hs-office-relation.sql
- include: - include:
file: db/changelog/223-hs-office-relationship-rbac.sql file: db/changelog/223-hs-office-relation-rbac.sql
- include: - include:
file: db/changelog/228-hs-office-relationship-test-data.sql file: db/changelog/228-hs-office-relation-test-data.sql
- include: - include:
file: db/changelog/230-hs-office-partner.sql file: db/changelog/230-hs-office-partner.sql
- include: - include:

View File

@ -41,7 +41,7 @@ public class ArchitectureTest {
"..hs.office.migration", "..hs.office.migration",
"..hs.office.partner", "..hs.office.partner",
"..hs.office.person", "..hs.office.person",
"..hs.office.relationship", "..hs.office.relation",
"..hs.office.sepamandate", "..hs.office.sepamandate",
"..errors", "..errors",
"..mapper", "..mapper",
@ -148,7 +148,7 @@ public class ArchitectureTest {
public static final ArchRule hsOfficeContactPackageRule = classes() public static final ArchRule hsOfficeContactPackageRule = classes()
.that().resideInAPackage("..hs.office.contact..") .that().resideInAPackage("..hs.office.contact..")
.should().onlyBeAccessed().byClassesThat() .should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage("..hs.office.contact..", "..hs.office.relationship..", .resideInAnyPackage("..hs.office.contact..", "..hs.office.relation..",
"..hs.office.partner..", "..hs.office.partner..",
"..hs.office.debitor..", "..hs.office.debitor..",
"..hs.office.membership..", "..hs.office.membership..",
@ -159,7 +159,7 @@ public class ArchitectureTest {
public static final ArchRule hsOfficePersonPackageRule = classes() public static final ArchRule hsOfficePersonPackageRule = classes()
.that().resideInAPackage("..hs.office.person..") .that().resideInAPackage("..hs.office.person..")
.should().onlyBeAccessed().byClassesThat() .should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage("..hs.office.person..", "..hs.office.relationship..", .resideInAnyPackage("..hs.office.person..", "..hs.office.relation..",
"..hs.office.partner..", "..hs.office.partner..",
"..hs.office.debitor..", "..hs.office.debitor..",
"..hs.office.membership..", "..hs.office.membership..",
@ -167,10 +167,10 @@ public class ArchitectureTest {
@ArchTest @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static final ArchRule hsOfficeRelationshipPackageRule = classes() public static final ArchRule hsOfficeRelationPackageRule = classes()
.that().resideInAPackage("..hs.office.relationship..") .that().resideInAPackage("..hs.office.relation..")
.should().onlyBeAccessed().byClassesThat() .should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage("..hs.office.relationship..", .resideInAnyPackage("..hs.office.relation..",
"..hs.office.partner..", "..hs.office.partner..",
"..hs.office.migration.."); "..hs.office.migration..");

View File

@ -18,8 +18,8 @@ import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerDetailsEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipType; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType;
import net.hostsharing.hsadminng.hs.office.sepamandate.HsOfficeSepaMandateEntity; import net.hostsharing.hsadminng.hs.office.sepamandate.HsOfficeSepaMandateEntity;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject; import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
import net.hostsharing.test.JpaAttempt; import net.hostsharing.test.JpaAttempt;
@ -127,7 +127,7 @@ public class ImportOfficeData extends ContextBasedTest {
new String[]{"partner", "vip-contact", "ex-partner", "billing", "contractual", "operation"}, new String[]{"partner", "vip-contact", "ex-partner", "billing", "contractual", "operation"},
SUBSCRIBER_ROLES); SUBSCRIBER_ROLES);
static int relationshipId = 2000000; static int relationId = 2000000;
@Value("${spring.datasource.url}") @Value("${spring.datasource.url}")
private String jdbcUrl; private String jdbcUrl;
@ -144,7 +144,7 @@ public class ImportOfficeData extends ContextBasedTest {
private static Map<Integer, HsOfficeDebitorEntity> debitors = new WriteOnceMap<>(); private static Map<Integer, HsOfficeDebitorEntity> debitors = new WriteOnceMap<>();
private static Map<Integer, HsOfficeMembershipEntity> memberships = new WriteOnceMap<>(); private static Map<Integer, HsOfficeMembershipEntity> memberships = new WriteOnceMap<>();
private static Map<Integer, HsOfficeRelationshipEntity> relationships = new WriteOnceMap<>(); private static Map<Integer, HsOfficeRelationEntity> relations = new WriteOnceMap<>();
private static Map<Integer, HsOfficeSepaMandateEntity> sepaMandates = new WriteOnceMap<>(); private static Map<Integer, HsOfficeSepaMandateEntity> sepaMandates = new WriteOnceMap<>();
private static Map<Integer, HsOfficeBankAccountEntity> bankAccounts = new WriteOnceMap<>(); private static Map<Integer, HsOfficeBankAccountEntity> bankAccounts = new WriteOnceMap<>();
private static Map<Integer, HsOfficeCoopSharesTransactionEntity> coopShares = new WriteOnceMap<>(); private static Map<Integer, HsOfficeCoopSharesTransactionEntity> coopShares = new WriteOnceMap<>();
@ -161,6 +161,7 @@ public class ImportOfficeData extends ContextBasedTest {
@MockBean @MockBean
HttpServletRequest request; HttpServletRequest request;
@Test @Test
@Order(1010) @Order(1010)
void importBusinessPartners() { void importBusinessPartners() {
@ -174,33 +175,33 @@ public class ImportOfficeData extends ContextBasedTest {
} }
@Test @Test
@Order(1011) @Order(1019)
void verifyBusinessPartners() { void verifyBusinessPartners() {
assumeThatWeAreImportingControlledTestData(); assumeThatWeAreImportingControlledTestData();
// no contacts yet => mostly null values // no contacts yet => mostly null values
assertThat(toFormattedString(partners)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(partners)).isEqualToIgnoringWhitespace("""
{ {
17=partner(P-10017: null null, null), 17=partner(null null, null),
20=partner(P-10020: null null, null), 20=partner(null null, null),
22=partner(P-11022: null null, null), 22=partner(null null, null),
99=partner(P-19999: null null, null) 99=partner(null null, null)
} }
"""); """);
assertThat(toFormattedString(contacts)).isEqualTo("{}"); assertThat(toFormattedString(contacts)).isEqualTo("{}");
assertThat(toFormattedString(debitors)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(debitors)).isEqualToIgnoringWhitespace("""
{ {
17=debitor(D-1001700: P-10017, mih), 17=debitor(D-1001700: null null, null: mih),
20=debitor(D-1002000: P-10020, xyz), 20=debitor(D-1002000: null null, null: xyz),
22=debitor(D-1102200: P-11022, xxx), 22=debitor(D-1102200: null null, null: xxx),
99=debitor(D-1999900: P-19999, zzz) 99=debitor(D-1999900: null null, null: zzz)
} }
"""); """);
assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace("""
{ {
17=Membership(M-1001700, P-10017, D-1001700, [2000-12-06,), NONE), 17=Membership(M-1001700, null null, null, D-1001700, [2000-12-06,), NONE),
20=Membership(M-1002000, P-10020, D-1002000, [2000-12-06,2016-01-01), UNKNOWN), 20=Membership(M-1002000, null null, null, D-1002000, [2000-12-06,2016-01-01), UNKNOWN),
22=Membership(M-1102200, P-11022, D-1102200, [2021-04-01,), NONE) 22=Membership(M-1102200, null null, null, D-1102200, [2021-04-01,), NONE)
} }
"""); """);
} }
@ -219,15 +220,32 @@ public class ImportOfficeData extends ContextBasedTest {
@Test @Test
@Order(1021) @Order(1021)
void buildDebitorRelations() {
debitors.forEach( (id, debitor) -> {
final var debitorRel = HsOfficeRelationEntity.builder()
.type(HsOfficeRelationType.DEBITOR)
.anchor(debitor.getPartner().getPartnerRel().getHolder())
.holder(debitor.getPartner().getPartnerRel().getHolder()) // just 1 debitor/partner in legacy hsadmin
.contact(debitor.getBillingContact())
.build();
if (debitorRel.getAnchor() != null && debitorRel.getHolder() != null &&
debitorRel.getContact() != null ) {
relations.put(relationId++, debitorRel);
}
});
}
@Test
@Order(1029)
void verifyContacts() { void verifyContacts() {
assumeThatWeAreImportingControlledTestData(); assumeThatWeAreImportingControlledTestData();
assertThat(toFormattedString(partners)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(partners)).isEqualToIgnoringWhitespace("""
{ {
17=partner(P-10017: NP Mellies, Michael, Herr Michael Mellies ), 17=partner(NP Mellies, Michael: Herr Michael Mellies ),
20=partner(P-10020: LP JM GmbH, Herr Philip Meyer-Contract , JM GmbH), 20=partner(LP JM GmbH: Herr Philip Meyer-Contract , JM GmbH),
22=partner(P-11022: ?? Test PS, Petra Schmidt , Test PS), 22=partner(?? Test PS: Petra Schmidt , Test PS),
99=partner(P-19999: null null, null) 99=partner(null null, null)
} }
"""); """);
assertThat(toFormattedString(contacts)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(contacts)).isEqualToIgnoringWhitespace("""
@ -257,38 +275,41 @@ public class ImportOfficeData extends ContextBasedTest {
"""); """);
assertThat(toFormattedString(debitors)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(debitors)).isEqualToIgnoringWhitespace("""
{ {
17=debitor(D-1001700: P-10017, mih), 17=debitor(D-1001700: NP Mellies, Michael: mih),
20=debitor(D-1002000: P-10020, xyz), 20=debitor(D-1002000: LP JM GmbH: xyz),
22=debitor(D-1102200: P-11022, xxx), 22=debitor(D-1102200: ?? Test PS: xxx),
99=debitor(D-1999900: P-19999, zzz) 99=debitor(D-1999900: null null, null: zzz)
} }
"""); """);
assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace("""
{ {
17=Membership(M-1001700, P-10017, D-1001700, [2000-12-06,), NONE), 17=Membership(M-1001700, NP Mellies, Michael, D-1001700, [2000-12-06,), NONE),
20=Membership(M-1002000, P-10020, D-1002000, [2000-12-06,2016-01-01), UNKNOWN), 20=Membership(M-1002000, LP JM GmbH, D-1002000, [2000-12-06,2016-01-01), UNKNOWN),
22=Membership(M-1102200, P-11022, D-1102200, [2021-04-01,), NONE) 22=Membership(M-1102200, ?? Test PS, D-1102200, [2021-04-01,), NONE)
} }
"""); """);
assertThat(toFormattedString(relationships)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(relations)).isEqualToIgnoringWhitespace("""
{ {
2000000=rel(relAnchor='LP Hostsharing eG', relType='PARTNER', relHolder='NP Mellies, Michael', contact='Herr Michael Mellies '), 2000000=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='NP Mellies, Michael', contact='Herr Michael Mellies '),
2000001=rel(relAnchor='LP Hostsharing eG', relType='PARTNER', relHolder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), 2000001=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'),
2000002=rel(relAnchor='LP Hostsharing eG', relType='PARTNER', relHolder='?? Test PS', contact='Petra Schmidt , Test PS'), 2000002=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000003=rel(relAnchor='LP Hostsharing eG', relType='PARTNER', relHolder='null null, null'), 2000003=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='null null, null'),
2000004=rel(relAnchor='NP Mellies, Michael', relType='OPERATIONS', relHolder='NP Mellies, Michael', contact='Herr Michael Mellies '), 2000004=rel(anchor='NP Mellies, Michael', type='OPERATIONS', holder='NP Mellies, Michael', contact='Herr Michael Mellies '),
2000005=rel(relAnchor='NP Mellies, Michael', relType='REPRESENTATIVE', relHolder='NP Mellies, Michael', contact='Herr Michael Mellies '), 2000005=rel(anchor='NP Mellies, Michael', type='REPRESENTATIVE', holder='NP Mellies, Michael', contact='Herr Michael Mellies '),
2000006=rel(relAnchor='LP JM GmbH', relType='EX_PARTNER', relHolder='LP JM e.K.', contact='JM e.K.'), 2000006=rel(anchor='LP JM GmbH', type='EX_PARTNER', holder='LP JM e.K.', contact='JM e.K.'),
2000007=rel(relAnchor='LP JM GmbH', relType='OPERATIONS', relHolder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), 2000007=rel(anchor='LP JM GmbH', type='OPERATIONS', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'),
2000008=rel(relAnchor='LP JM GmbH', relType='VIP_CONTACT', relHolder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), 2000008=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'),
2000009=rel(relAnchor='LP JM GmbH', relType='SUBSCRIBER', relMark='operations-announce', relHolder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), 2000009=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='operations-announce', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'),
2000010=rel(relAnchor='LP JM GmbH', relType='REPRESENTATIVE', relHolder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), 2000010=rel(anchor='LP JM GmbH', type='REPRESENTATIVE', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'),
2000011=rel(relAnchor='LP JM GmbH', relType='SUBSCRIBER', relMark='members-announce', relHolder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), 2000011=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='members-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'),
2000012=rel(relAnchor='LP JM GmbH', relType='SUBSCRIBER', relMark='customers-announce', relHolder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), 2000012=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='customers-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'),
2000013=rel(relAnchor='LP JM GmbH', relType='VIP_CONTACT', relHolder='LP JM GmbH', contact='Frau Tammy Meyer-VIP , JM GmbH'), 2000013=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Frau Tammy Meyer-VIP , JM GmbH'),
2000014=rel(relAnchor='?? Test PS', relType='OPERATIONS', relHolder='?? Test PS', contact='Petra Schmidt , Test PS'), 2000014=rel(anchor='?? Test PS', type='OPERATIONS', holder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000015=rel(relAnchor='?? Test PS', relType='REPRESENTATIVE', relHolder='?? Test PS', contact='Petra Schmidt , Test PS'), 2000015=rel(anchor='?? Test PS', type='REPRESENTATIVE', holder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000016=rel(relAnchor='NP Mellies, Michael', relType='SUBSCRIBER', relMark='operations-announce', relHolder='NP Fanninga, Frauke', contact='Frau Frauke Fanninga ') 2000016=rel(anchor='NP Mellies, Michael', type='SUBSCRIBER', mark='operations-announce', holder='NP Fanninga, Frauke', contact='Frau Frauke Fanninga '),
2000017=rel(anchor='NP Mellies, Michael', type='DEBITOR', holder='NP Mellies, Michael', contact='Herr Michael Mellies '),
2000018=rel(anchor='LP JM GmbH', type='DEBITOR', holder='LP JM GmbH', contact='Frau Dr. Jenny Meyer-Billing , JM GmbH'),
2000019=rel(anchor='?? Test PS', type='DEBITOR', holder='?? Test PS', contact='Petra Schmidt , Test PS')
} }
"""); """);
} }
@ -306,15 +327,15 @@ public class ImportOfficeData extends ContextBasedTest {
} }
@Test @Test
@Order(1031) @Order(1039)
void verifySepaMandates() { void verifySepaMandates() {
assumeThatWeAreImportingControlledTestData(); assumeThatWeAreImportingControlledTestData();
assertThat(toFormattedString(bankAccounts)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(bankAccounts)).isEqualToIgnoringWhitespace("""
{ {
234234=bankAccount(DE37500105177419788228: holder='Michael Mellies', bic='INGDDEFFXXX'), 234234=bankAccount(holder='Michael Mellies', iban='DE37500105177419788228', bic='INGDDEFFXXX'),
235600=bankAccount(DE02300209000106531065: holder='JM e.K.', bic='CMCIDEDD'), 235600=bankAccount(holder='JM e.K.', iban='DE02300209000106531065', bic='CMCIDEDD'),
235662=bankAccount(DE49500105174516484892: holder='JM GmbH', bic='INGDDEFFXXX') 235662=bankAccount(holder='JM GmbH', iban='DE49500105174516484892', bic='INGDDEFFXXX')
} }
"""); """);
assertThat(toFormattedString(sepaMandates)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(sepaMandates)).isEqualToIgnoringWhitespace("""
@ -338,7 +359,7 @@ public class ImportOfficeData extends ContextBasedTest {
} }
@Test @Test
@Order(1041) @Order(1049)
void verifyCoopShares() { void verifyCoopShares() {
assumeThatWeAreImportingControlledTestData(); assumeThatWeAreImportingControlledTestData();
@ -365,55 +386,53 @@ public class ImportOfficeData extends ContextBasedTest {
} }
@Test @Test
@Order(1051) @Order(1059)
void verifyCoopAssets() { void verifyCoopAssets() {
assumeThatWeAreImportingControlledTestData(); assumeThatWeAreImportingControlledTestData();
assertThat(toFormattedString(coopAssets)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(coopAssets)).isEqualToIgnoringWhitespace("""
{ {
30000=CoopAssetsTransaction(M-1001700: 2000-12-06, DEPOSIT, 1280.00, for subscription A), 30000=CoopAssetsTransaction(1001700, 2000-12-06, DEPOSIT, 1280.00, for subscription A),
31000=CoopAssetsTransaction(M-1002000: 2000-12-06, DEPOSIT, 128.00, for subscription B), 31000=CoopAssetsTransaction(1002000, 2000-12-06, DEPOSIT, 128.00, for subscription B),
32000=CoopAssetsTransaction(M-1001700: 2005-01-10, DEPOSIT, 2560.00, for subscription C), 32000=CoopAssetsTransaction(1001700, 2005-01-10, DEPOSIT, 2560.00, for subscription C),
33001=CoopAssetsTransaction(M-1001700: 2005-01-10, TRANSFER, -512.00, for transfer to 10), 33001=CoopAssetsTransaction(1001700, 2005-01-10, TRANSFER, -512.00, for transfer to 10),
33002=CoopAssetsTransaction(M-1002000: 2005-01-10, ADOPTION, 512.00, for transfer from 7), 33002=CoopAssetsTransaction(1002000, 2005-01-10, ADOPTION, 512.00, for transfer from 7),
34001=CoopAssetsTransaction(M-1002000: 2016-12-31, CLEARING, -8.00, for cancellation D), 34001=CoopAssetsTransaction(1002000, 2016-12-31, CLEARING, -8.00, for cancellation D),
34002=CoopAssetsTransaction(M-1002000: 2016-12-31, DISBURSAL, -100.00, for cancellation D), 34002=CoopAssetsTransaction(1002000, 2016-12-31, DISBURSAL, -100.00, for cancellation D),
34003=CoopAssetsTransaction(M-1002000: 2016-12-31, LOSS, -20.00, for cancellation D) 34003=CoopAssetsTransaction(1002000, 2016-12-31, LOSS, -20.00, for cancellation D)
} }
"""); """);
} }
@Test @Test
@Order(2000) @Order(2000)
void verifyAllPartnersHaveProperPartnerRoles() { void verifyAllPartnersHavePersons() {
partners.forEach((id, p) -> { partners.forEach((id, p) -> {
final var partnerRole = p.getPartnerRole();
assertThat(partnerRole).describedAs("partner " + id + " without partnerRole").isNotNull();
if ( id != 99 ) { if ( id != 99 ) {
assertThat(partnerRole.getContact()).describedAs("partner " + id + " without partnerRole.contact").isNotNull(); assertThat(p.getContact()).describedAs("partner " + id + " without contact").isNotNull();
assertThat(partnerRole.getContact().getLabel()).describedAs("partner " + id + " without valid partnerRole.contact").isNotNull(); assertThat(p.getContact().getLabel()).describedAs("partner " + id + " without valid contact").isNotNull();
assertThat(partnerRole.getRelHolder()).describedAs("partner " + id + " without partnerRole.relHolder").isNotNull(); assertThat(p.getPerson()).describedAs("partner " + id + " without person").isNotNull();
assertThat(partnerRole.getRelHolder().getPersonType()).describedAs("partner " + id + " without valid partnerRole.relHolder").isNotNull(); assertThat(p.getPerson().getPersonType()).describedAs("partner " + id + " without valid person").isNotNull();
} }
}); });
} }
@Test @Test
@Order(2001) @Order(2009)
void removeEmptyRelationships() { void removeEmptyRelations() {
assumeThatWeAreImportingControlledTestData(); assumeThatWeAreImportingControlledTestData();
// avoid a error when persisting the deliberetely invalid partner entry #99 // avoid a error when persisting the deliberetely invalid partner entry #99
final var idsToRemove = new HashSet<Integer>(); final var idsToRemove = new HashSet<Integer>();
relationships.forEach( (id, r) -> { relations.forEach( (id, r) -> {
// such a record // such a record
if (r.getContact() == null || r.getContact().getLabel() == null || if (r.getContact() == null || r.getContact().getLabel() == null ||
r.getRelHolder() == null | r.getRelHolder().getPersonType() == null ) { r.getHolder() == null | r.getHolder().getPersonType() == null ) {
idsToRemove.add(id); idsToRemove.add(id);
} }
}); });
assertThat(idsToRemove.size()).isEqualTo(1); // only from partner #99 (partner+contractual roles) assertThat(idsToRemove.size()).isEqualTo(1); // only from partner #99 (partner+contractual roles)
idsToRemove.forEach(id -> relationships.remove(id)); idsToRemove.forEach(id -> relations.remove(id));
} }
@Test @Test
@ -424,11 +443,9 @@ public class ImportOfficeData extends ContextBasedTest {
// avoid a error when persisting the deliberately invalid partner entry #99 // avoid a error when persisting the deliberately invalid partner entry #99
final var idsToRemove = new HashSet<Integer>(); final var idsToRemove = new HashSet<Integer>();
partners.forEach( (id, r) -> { partners.forEach( (id, r) -> {
final var partnerRole = r.getPartnerRole(); // such a record
if (r.getContact() == null || r.getContact().getLabel() == null ||
// such a record is in test data to test error messages r.getPerson() == null | r.getPerson().getPersonType() == null ) {
if (partnerRole.getContact() == null || partnerRole.getContact().getLabel() == null ||
partnerRole.getRelHolder() == null | partnerRole.getRelHolder().getPersonType() == null ) {
idsToRemove.add(id); idsToRemove.add(id);
} }
}); });
@ -443,9 +460,10 @@ public class ImportOfficeData extends ContextBasedTest {
// avoid a error when persisting the deliberately invalid partner entry #99 // avoid a error when persisting the deliberately invalid partner entry #99
final var idsToRemove = new HashSet<Integer>(); final var idsToRemove = new HashSet<Integer>();
debitors.forEach( (id, d) -> { debitors.forEach( (id, r) -> {
// such a record is in test data to test error messages // such a record
if (false) { // TODO: how can I now empty debitors? if (r.getBillingContact() == null || r.getBillingContact().getLabel() == null ||
r.getPartner().getPerson() == null | r.getPartner().getPerson().getPersonType() == null ) {
idsToRemove.add(id); idsToRemove.add(id);
} }
}); });
@ -454,7 +472,6 @@ public class ImportOfficeData extends ContextBasedTest {
} }
@Test @Test
@Disabled
@Order(3000) @Order(3000)
@Commit @Commit
void persistEntities() { void persistEntities() {
@ -478,7 +495,7 @@ public class ImportOfficeData extends ContextBasedTest {
jpaAttempt.transacted(() -> { jpaAttempt.transacted(() -> {
context(rbacSuperuser); context(rbacSuperuser);
relationships.forEach(this::persist); relations.forEach(this::persist);
}).assertSuccessful(); }).assertSuccessful();
jpaAttempt.transacted(() -> { jpaAttempt.transacted(() -> {
@ -555,7 +572,7 @@ public class ImportOfficeData extends ContextBasedTest {
em.createNativeQuery("delete from hs_office_bankaccount where true").executeUpdate(); em.createNativeQuery("delete from hs_office_bankaccount where true").executeUpdate();
em.createNativeQuery("delete from hs_office_partner where true").executeUpdate(); em.createNativeQuery("delete from hs_office_partner where true").executeUpdate();
em.createNativeQuery("delete from hs_office_partner_details where true").executeUpdate(); em.createNativeQuery("delete from hs_office_partner_details where true").executeUpdate();
em.createNativeQuery("delete from hs_office_relationship where true").executeUpdate(); em.createNativeQuery("delete from hs_office_relation where true").executeUpdate();
em.createNativeQuery("delete from hs_office_contact where true").executeUpdate(); em.createNativeQuery("delete from hs_office_contact where true").executeUpdate();
em.createNativeQuery("delete from hs_office_person where true").executeUpdate(); em.createNativeQuery("delete from hs_office_person where true").executeUpdate();
}).assertSuccessful(); }).assertSuccessful();
@ -659,31 +676,28 @@ public class ImportOfficeData extends ContextBasedTest {
.forEach(rec -> { .forEach(rec -> {
final var person = HsOfficePersonEntity.builder().build(); final var person = HsOfficePersonEntity.builder().build();
final var partnerRelationship = HsOfficeRelationshipEntity.builder() final var partnerRelation = HsOfficeRelationEntity.builder()
.relHolder(person) .holder(person)
.relType(HsOfficeRelationshipType.PARTNER) .type(HsOfficeRelationType.PARTNER)
.relAnchor(mandant) .anchor(mandant)
.contact(null) // is set during contacts import depending on assigned roles .contact(null) // is set during contacts import depending on assigned roles
.build(); .build();
relationships.put(relationshipId++, partnerRelationship); relations.put(relationId++, partnerRelation);
final var partner = HsOfficePartnerEntity.builder() final var partner = HsOfficePartnerEntity.builder()
.partnerNumber(rec.getInteger("member_id")) .partnerNumber(rec.getInteger("member_id"))
.details(HsOfficePartnerDetailsEntity.builder().build()) .details(HsOfficePartnerDetailsEntity.builder().build())
.partnerRole(partnerRelationship) .partnerRel(partnerRelation)
.contact(null) // is set during contacts import depending on assigned roles
.person(person)
.build(); .build();
partners.put(rec.getInteger("bp_id"), partner); partners.put(rec.getInteger("bp_id"), partner);
final var debitor = HsOfficeDebitorEntity.builder() final var debitor = HsOfficeDebitorEntity.builder()
.partner(partner)
.debitorNumberSuffix((byte) 0) .debitorNumberSuffix((byte) 0)
.debitorRel(
HsOfficeRelationshipEntity.builder()
.relType(HsOfficeRelationshipType.ACCOUNTING)
.relAnchor(partnerRelationship.getRelHolder())
.relHolder(null) // gets set later
.build()
)
.defaultPrefix(rec.getString("member_code").replace("hsh00-", "")) .defaultPrefix(rec.getString("member_code").replace("hsh00-", ""))
.partner(partner)
.billable(rec.isEmpty("free") || rec.getString("free").equals("f")) .billable(rec.isEmpty("free") || rec.getString("free").equals("f"))
.vatReverseCharge(rec.getBoolean("exempt_vat")) .vatReverseCharge(rec.getBoolean("exempt_vat"))
.vatBusiness("GROSS".equals(rec.getString("indicator_vat"))) // TODO: remove .vatBusiness("GROSS".equals(rec.getString("indicator_vat"))) // TODO: remove
@ -704,6 +718,7 @@ public class ImportOfficeData extends ContextBasedTest {
isBlank(rec.getString("member_until")) isBlank(rec.getString("member_until"))
? HsOfficeReasonForTermination.NONE ? HsOfficeReasonForTermination.NONE
: HsOfficeReasonForTermination.UNKNOWN) : HsOfficeReasonForTermination.UNKNOWN)
.mainDebitor(debitor)
.build(); .build();
memberships.put(rec.getInteger("bp_id"), membership); memberships.put(rec.getInteger("bp_id"), membership);
} }
@ -829,9 +844,9 @@ public class ImportOfficeData extends ContextBasedTest {
final var partner = partners.get(bpId); final var partner = partners.get(bpId);
final var debitor = debitors.get(bpId); final var debitor = debitors.get(bpId);
final var partnerPerson = partner.getPartnerRole().getRelHolder(); final var partnerPerson = partner.getPerson();
if (containsPartnerRole(rec)) { if (containsPartnerRel(rec)) {
initPerson(partnerPerson, rec); initPerson(partner.getPerson(), rec);
} }
HsOfficePersonEntity contactPerson = partnerPerson; HsOfficePersonEntity contactPerson = partnerPerson;
@ -844,71 +859,72 @@ public class ImportOfficeData extends ContextBasedTest {
final var contact = HsOfficeContactEntity.builder().build(); final var contact = HsOfficeContactEntity.builder().build();
initContact(contact, rec); initContact(contact, rec);
if (containsPartnerRole(rec)) { if (containsPartnerRel(rec)) {
assertThat(partner.getPartnerRole().getContact()).isNull(); assertThat(partner.getContact()).isNull();
partner.getPartnerRole().setContact(contact); partner.setContact(contact);
partner.getPartnerRel().setContact(contact);
} }
if (containsRole(rec, "billing")) { if (containsRole(rec, "billing")) {
assertThat(debitor.getDebitorRel().getContact()).isNull(); assertThat(debitor.getBillingContact()).isNull();
debitor.getDebitorRel().setContact(contact); debitor.setBillingContact(contact);
} }
if (containsRole(rec, "operation")) { if (containsRole(rec, "operation")) {
addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.OPERATIONS); addRelation(partnerPerson, contactPerson, contact, HsOfficeRelationType.OPERATIONS);
} }
if (containsRole(rec, "contractual")) { if (containsRole(rec, "contractual")) {
addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.REPRESENTATIVE); addRelation(partnerPerson, contactPerson, contact, HsOfficeRelationType.REPRESENTATIVE);
} }
if (containsRole(rec, "ex-partner")) { if (containsRole(rec, "ex-partner")) {
addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.EX_PARTNER); addRelation(partnerPerson, contactPerson, contact, HsOfficeRelationType.EX_PARTNER);
} }
if (containsRole(rec, "vip-contact")) { if (containsRole(rec, "vip-contact")) {
addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.VIP_CONTACT); addRelation(partnerPerson, contactPerson, contact, HsOfficeRelationType.VIP_CONTACT);
} }
for (String subscriberRole: SUBSCRIBER_ROLES) { for (String subscriberRole: SUBSCRIBER_ROLES) {
if (containsRole(rec, subscriberRole)) { if (containsRole(rec, subscriberRole)) {
addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.SUBSCRIBER) addRelation(partnerPerson, contactPerson, contact, HsOfficeRelationType.SUBSCRIBER)
.setRelMark(subscriberRole.split(":")[1]) .setMark(subscriberRole.split(":")[1])
; ;
} }
} }
verifyContainsOnlyKnownRoles(rec.getString("roles")); verifyContainsOnlyKnownRoles(rec.getString("roles"));
}); });
optionallyAddMissingContractualRelationships(); optionallyAddMissingContractualRelations();
} }
private static void optionallyAddMissingContractualRelationships() { private static void optionallyAddMissingContractualRelations() {
final var contractualMissing = new HashSet<Integer>(); final var contractualMissing = new HashSet<Integer>();
partners.forEach( (id, partner) -> { partners.forEach( (id, partner) -> {
final var partnerPerson = partner.getPartnerRole().getRelHolder(); final var partnerPerson = partner.getPerson();
if (relationships.values().stream() if (relations.values().stream()
.filter(rel -> rel.getRelAnchor() == partnerPerson && rel.getRelType() == HsOfficeRelationshipType.REPRESENTATIVE) .filter(rel -> rel.getAnchor() == partnerPerson && rel.getType() == HsOfficeRelationType.REPRESENTATIVE)
.findFirst().isEmpty()) { .findFirst().isEmpty()) {
contractualMissing.add(partner.getPartnerNumber()); contractualMissing.add(partner.getPartnerNumber());
} }
}); });
assertThat(contractualMissing).containsOnly(19999); // deliberately wrong partner entry
} }
private static boolean containsRole(final Record rec, final String role) { private static boolean containsRole(final Record rec, final String role) {
final var roles = rec.getString("roles"); final var roles = rec.getString("roles");
return ("," + roles + ",").contains("," + role + ","); return ("," + roles + ",").contains("," + role + ",");
} }
private static boolean containsPartnerRole(final Record rec) {
private static boolean containsPartnerRel(final Record rec) {
return containsRole(rec, "partner"); return containsRole(rec, "partner");
} }
private static HsOfficeRelationshipEntity addRelationship( private static HsOfficeRelationEntity addRelation(
final HsOfficePersonEntity partnerPerson, final HsOfficePersonEntity partnerPerson,
final HsOfficePersonEntity contactPerson, final HsOfficePersonEntity contactPerson,
final HsOfficeContactEntity contact, final HsOfficeContactEntity contact,
final HsOfficeRelationshipType representative) { final HsOfficeRelationType representative) {
final var rel = HsOfficeRelationshipEntity.builder() final var rel = HsOfficeRelationEntity.builder()
.relAnchor(partnerPerson) .anchor(partnerPerson)
.relHolder(contactPerson) .holder(contactPerson)
.contact(contact) .contact(contact)
.relType(representative) .type(representative)
.build(); .build();
relationships.put(relationshipId++, rel); relations.put(relationId++, rel);
return rel; return rel;
} }

View File

@ -1,7 +1,7 @@
package net.hostsharing.hsadminng.hs.office.relationship; package net.hostsharing.hsadminng.hs.office.relation;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationshipPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationPatchResource;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.test.PatchUnitTestBase; import net.hostsharing.test.PatchUnitTestBase;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -21,12 +21,12 @@ import static org.mockito.Mockito.lenient;
@TestInstance(PER_CLASS) @TestInstance(PER_CLASS)
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
class HsOfficeRelationshipEntityPatcherUnitTest extends PatchUnitTestBase< class HsOfficeRelationEntityPatcherUnitTest extends PatchUnitTestBase<
HsOfficeRelationshipPatchResource, HsOfficeRelationPatchResource,
HsOfficeRelationshipEntity HsOfficeRelationEntity
> { > {
static final UUID INITIAL_RELATIONSHIP_UUID = UUID.randomUUID(); static final UUID INITIAL_RELATION_UUID = UUID.randomUUID();
static final UUID PATCHED_CONTACT_UUID = UUID.randomUUID(); static final UUID PATCHED_CONTACT_UUID = UUID.randomUUID();
@Mock @Mock
@ -49,24 +49,24 @@ class HsOfficeRelationshipEntityPatcherUnitTest extends PatchUnitTestBase<
.build(); .build();
@Override @Override
protected HsOfficeRelationshipEntity newInitialEntity() { protected HsOfficeRelationEntity newInitialEntity() {
final var entity = new HsOfficeRelationshipEntity(); final var entity = new HsOfficeRelationEntity();
entity.setUuid(INITIAL_RELATIONSHIP_UUID); entity.setUuid(INITIAL_RELATION_UUID);
entity.setRelType(HsOfficeRelationshipType.REPRESENTATIVE); entity.setType(HsOfficeRelationType.REPRESENTATIVE);
entity.setRelAnchor(givenInitialAnchorPerson); entity.setAnchor(givenInitialAnchorPerson);
entity.setRelHolder(givenInitialHolderPerson); entity.setHolder(givenInitialHolderPerson);
entity.setContact(givenInitialContact); entity.setContact(givenInitialContact);
return entity; return entity;
} }
@Override @Override
protected HsOfficeRelationshipPatchResource newPatchResource() { protected HsOfficeRelationPatchResource newPatchResource() {
return new HsOfficeRelationshipPatchResource(); return new HsOfficeRelationPatchResource();
} }
@Override @Override
protected HsOfficeRelationshipEntityPatcher createPatcher(final HsOfficeRelationshipEntity relationship) { protected HsOfficeRelationEntityPatcher createPatcher(final HsOfficeRelationEntity relation) {
return new HsOfficeRelationshipEntityPatcher(em, relationship); return new HsOfficeRelationEntityPatcher(em, relation);
} }
@Override @Override
@ -74,9 +74,9 @@ class HsOfficeRelationshipEntityPatcherUnitTest extends PatchUnitTestBase<
return Stream.of( return Stream.of(
new JsonNullableProperty<>( new JsonNullableProperty<>(
"contact", "contact",
HsOfficeRelationshipPatchResource::setContactUuid, HsOfficeRelationPatchResource::setContactUuid,
PATCHED_CONTACT_UUID, PATCHED_CONTACT_UUID,
HsOfficeRelationshipEntity::setContact, HsOfficeRelationEntity::setContact,
newContact(PATCHED_CONTACT_UUID)) newContact(PATCHED_CONTACT_UUID))
.notNullable() .notNullable()
); );

View File

@ -0,0 +1,43 @@
package net.hostsharing.hsadminng.hs.office.relation;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class HsOfficeRelationEntityUnitTest {
private HsOfficePersonEntity anchor = HsOfficePersonEntity.builder()
.personType(HsOfficePersonType.LEGAL_PERSON)
.tradeName("some trade name")
.build();
private HsOfficePersonEntity holder = HsOfficePersonEntity.builder()
.personType(HsOfficePersonType.NATURAL_PERSON)
.familyName("Meier")
.givenName("Mellie")
.build();
@Test
void toStringReturnsAllProperties() {
final var given = HsOfficeRelationEntity.builder()
.type(HsOfficeRelationType.SUBSCRIBER)
.mark("members-announce")
.anchor(anchor)
.holder(holder)
.build();
assertThat(given.toString()).isEqualTo("rel(anchor='LP some trade name', type='SUBSCRIBER', mark='members-announce', holder='NP Meier, Mellie')");
}
@Test
void toShortString() {
final var given = HsOfficeRelationEntity.builder()
.type(HsOfficeRelationType.REPRESENTATIVE)
.anchor(anchor)
.holder(holder)
.build();
assertThat(given.toShortString()).isEqualTo("rel(anchor='LP some trade name', type='REPRESENTATIVE', holder='NP Meier, Mellie')");
}
}

View File

@ -1,44 +0,0 @@
package net.hostsharing.hsadminng.hs.office.relationship;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
class HsOfficeRelationshipEntityUnitTest {
private HsOfficePersonEntity anchor = HsOfficePersonEntity.builder()
.personType(HsOfficePersonType.LEGAL_PERSON)
.tradeName("some trade name")
.build();
private HsOfficePersonEntity holder = HsOfficePersonEntity.builder()
.personType(HsOfficePersonType.NATURAL_PERSON)
.familyName("Meier")
.givenName("Mellie")
.build();
@Test
void toStringReturnsAllProperties() {
final var given = HsOfficeRelationshipEntity.builder()
.relType(HsOfficeRelationshipType.SUBSCRIBER)
.relMark("members-announce")
.relAnchor(anchor)
.relHolder(holder)
.build();
assertThat(given.toString()).isEqualTo("rel(relAnchor='LP some trade name', relType='SUBSCRIBER', relMark='members-announce', relHolder='NP Meier, Mellie')");
}
@Test
void toShortString() {
final var given = HsOfficeRelationshipEntity.builder()
.relType(HsOfficeRelationshipType.REPRESENTATIVE)
.relAnchor(anchor)
.relHolder(holder)
.build();
assertThat(given.toShortString()).isEqualTo("rel(relAnchor='LP some trade name', relType='REPRESENTATIVE', relHolder='NP Meier, Mellie')");
}
}

View File

@ -1,41 +0,0 @@
#!/bin/bash
sourceLower=partner
targetLower=relationship
sourceStudly=Partner
targetStudly=Relationship
## for source in `find src -iname ""*$sourceLower*"" -type f \( -iname \*.yaml -o -iname \*.sql -o -iname \*.java \)`; do
for source in `find src -iname ""*$sourceLower*"" -type f \( -iname \*.yaml \)`; do
target=`echo $source | sed -e "s/$sourceStudly/$targetStudly/g" -e "s/$sourceLower/$targetLower/g"`
echo "Generating $target from $source:"
mkdir -p `dirname $target`
sed -e 's/hs-office-partner/hs-office-relationship/g' \
-e 's/hs_office_partner/hs_office_relationship/g' \
-e 's/HsOfficePartner/HsOfficeRelationship/g' \
-e 's/hsOfficePartner/hsOfficeRelationship/g' \
-e 's/partner/relationship/g' \
\
-e 's/addPartner/addRelationship/g' \
-e 's/listPartners/listRelationships/g' \
-e 's/getPartnerByUuid/getRelationshipByUuid/g' \
-e 's/patchPartner/patchRelationship/g' \
-e 's/person/relHolder/g' \
-e 's/registrationOffice/relType/g' \
<$source >$target
done
exit
cat >>src/main/resources/db/changelog/db.changelog-master.yaml <<EOF
- include:
file: db/changelog/2X0-hs-office-$sourceLower.sql
- include:
file: db/changelog/2X3-hs-office-$sourceLower-rbac.sql
- include:
file: db/changelog/2X8-hs-office-$sourceLower-test-data.sql
EOF