Merge branch 'improved-rbac-generator' into remove-direct-partner-person-and-contact

# Conflicts:
#	src/main/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorEntity.java
#	src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerController.java
#	src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerDetailsEntity.java
#	src/main/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerEntity.java
#	src/main/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationEntity.java
#	src/main/java/net/hostsharing/hsadminng/hs/office/sepamandate/HsOfficeSepaMandateEntity.java
#	src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/InsertTriggerGenerator.java
#	src/main/java/net/hostsharing/hsadminng/rbac/rbacdef/RbacRestrictedViewGenerator.java
#	src/main/resources/api-definition/hs-office/hs-office-partner-schemas.yaml
#	src/main/resources/db/changelog/123-test-package-rbac.sql
#	src/main/resources/db/changelog/133-test-domain-rbac.sql
#	src/main/resources/db/changelog/223-hs-office-relation-rbac.md
#	src/main/resources/db/changelog/223-hs-office-relation-rbac.sql
#	src/main/resources/db/changelog/228-hs-office-relation-test-data.sql
#	src/main/resources/db/changelog/230-hs-office-partner.sql
#	src/main/resources/db/changelog/233-hs-office-partner-rbac.sql
#	src/main/resources/db/changelog/238-hs-office-partner-test-data.sql
#	src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java
#	src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerAcceptanceTest.java
#	src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerControllerRestTest.java
#	src/test/java/net/hostsharing/hsadminng/hs/office/partner/HsOfficePartnerRepositoryIntegrationTest.java
#	src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationControllerAcceptanceTest.java
#	src/test/java/net/hostsharing/hsadminng/hs/office/relation/HsOfficeRelationRepositoryIntegrationTest.java
This commit is contained in:
Michael Hoennig 2024-03-22 15:16:34 +01:00
commit 6e663cf525
33 changed files with 463 additions and 539 deletions

View File

@ -10,7 +10,7 @@ classDiagram
namespace Partner {
class partner-MeierGmbH
class role-MeierGmbH
class rel-MeierGmbH
class personDetails-MeierGmbH
class contactData-MeierGmbH
class person-MeierGmbH
@ -19,28 +19,29 @@ classDiagram
namespace Representatives {
class person-FrankMeier
class contactData-FrankMeier
class role-MeierGmbH-FrankMeier
class rel-MeierGmbH-FrankMeier
}
namespace Debitors {
class debitor-MeierGmbH
class contactData-MeierGmbH-Buha
class role-MeierGmbH-Buha
class rel-MeierGmbH-Buha
}
namespace Operations {
class person-SabineMeier
class contactData-SabineMeier
class role-MeierGmbH-SabineMeier
class rel-MeierGmbH-SabineMeier
}
namespace Enums {
class RoleType {
class RelationType {
<<enumeration>>
UNKNOWN
PARTNER
DEBITOR
REPRESENTATIVE
ACCOUNTING
OPERATIONS
}
@ -64,9 +65,9 @@ classDiagram
class partner-MeierGmbH {
+Numeric partnerNumber: 12345
+Role partnerRole
+Relation partnerRel
}
partner-MeierGmbH *-- role-MeierGmbH
partner-MeierGmbH *-- rel-MeierGmbH
class person-MeierGmbH {
+personType: LEGAL
@ -90,32 +91,32 @@ classDiagram
+emailAddresses: office@meier-gmbh.de
}
class role-MeierGmbH {
+RoleType RoleType PARTNER
class rel-MeierGmbH {
+RelationType type PARTNER
+Person anchor
+Person holder
+Contact roleContact
+Contact contact
}
role-MeierGmbH o-- person-HostsharingEG : anchor
role-MeierGmbH o-- person-MeierGmbH : holder
role-MeierGmbH o-- contactData-MeierGmbH
rel-MeierGmbH o-- person-HostsharingEG : anchor
rel-MeierGmbH o-- person-MeierGmbH : holder
rel-MeierGmbH o-- contactData-MeierGmbH
%% --- Debitors ---
class debitor-MeierGmbH {
+Partner partner
+Numeric[2] debitorNumberSuffix: 00
+Role billingRole
+boolean billable: true
+String vatId: ID123456789
+String vatCountryCode: DE
+boolean vatBusiness: true
+boolean vatReverseCharge: false
+Partner partner
+Numeric[2] debitorNumberSuffix: 00
+Relation debitorRel
+boolean billable: true
+String vatId: ID123456789
+String vatCountryCode: DE
+boolean vatBusiness: true
+boolean vatReverseCharge: false
+BankAccount refundBankAccount
+String defaultPrefix: mei
+String defaultPrefix: mei
}
debitor-MeierGmbH o-- partner-MeierGmbH
debitor-MeierGmbH *-- role-MeierGmbH-Buha
debitor-MeierGmbH *-- rel-MeierGmbH-Buha
class contactData-MeierGmbH-Buha {
+postalAddress: Hauptstraße 5, 22345 Hamburg
@ -123,15 +124,15 @@ classDiagram
+emailAddresses: buha@meier-gmbh.de
}
class role-MeierGmbH-Buha {
+RoleType RoleType ACCOUNTING
class rel-MeierGmbH-Buha {
+RelationType type DEBITOR
+Person anchor
+Person holder
+Contact roleContact
+Contact contact
}
role-MeierGmbH-Buha o-- person-MeierGmbH : anchor
role-MeierGmbH-Buha o-- person-MeierGmbH : holder
role-MeierGmbH-Buha o-- contactData-MeierGmbH-Buha
rel-MeierGmbH-Buha o-- person-MeierGmbH : anchor
rel-MeierGmbH-Buha o-- person-MeierGmbH : holder
rel-MeierGmbH-Buha o-- contactData-MeierGmbH-Buha
%% --- Representatives ---
@ -148,15 +149,15 @@ classDiagram
+emailAddresses: frank.meier@meier-gmbh.de
}
class role-MeierGmbH-FrankMeier {
+RoleType RoleType REPRESENTATIVE
class rel-MeierGmbH-FrankMeier {
+RelationType type REPRESENTATIVE
+Person anchor
+Person holder
+Contact roleContact
+Contact contact
}
role-MeierGmbH-FrankMeier o-- person-MeierGmbH : anchor
role-MeierGmbH-FrankMeier o-- person-FrankMeier : holder
role-MeierGmbH-FrankMeier o-- contactData-FrankMeier
rel-MeierGmbH-FrankMeier o-- person-MeierGmbH : anchor
rel-MeierGmbH-FrankMeier o-- person-FrankMeier : holder
rel-MeierGmbH-FrankMeier o-- contactData-FrankMeier
%% --- Operations ---
@ -173,14 +174,14 @@ classDiagram
+emailAddresses: sabine.meier@meier-gmbh.de
}
class role-MeierGmbH-SabineMeier {
+RoleType RoleType OPERATIONAL
class rel-MeierGmbH-SabineMeier {
+RelationType type OPERATIONAL
+Person anchor
+Person holder
+Contact roleContact
+Contact contact
}
role-MeierGmbH-SabineMeier o-- person-MeierGmbH : anchor
role-MeierGmbH-SabineMeier o-- person-SabineMeier : holder
role-MeierGmbH-SabineMeier o-- contactData-SabineMeier
rel-MeierGmbH-SabineMeier o-- person-MeierGmbH : anchor
rel-MeierGmbH-SabineMeier o-- person-SabineMeier : holder
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.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.person.HsOfficePersonRepository;
import net.hostsharing.hsadminng.mapper.Mapper;
@ -22,7 +22,7 @@ import java.util.function.BiConsumer;
@RestController
public class HsOfficeRelationshipController implements HsOfficeRelationshipsApi {
public class HsOfficeRelationController implements HsOfficeRelationsApi {
@Autowired
private Context context;
@ -31,10 +31,10 @@ public class HsOfficeRelationshipController implements HsOfficeRelationshipsApi
private Mapper mapper;
@Autowired
private HsOfficeRelationshipRepository relationshipRepo;
private HsOfficeRelationRepository relationRepo;
@Autowired
private HsOfficePersonRepository relHolderRepo;
private HsOfficePersonRepository holderRepo;
@Autowired
private HsOfficeContactRepository contactRepo;
@ -44,79 +44,79 @@ public class HsOfficeRelationshipController implements HsOfficeRelationshipsApi
@Override
@Transactional(readOnly = true)
public ResponseEntity<List<HsOfficeRelationshipResource>> listRelationships(
public ResponseEntity<List<HsOfficeRelationResource>> listRelations(
final String currentUser,
final String assumedRoles,
final UUID personUuid,
final HsOfficeRelationshipTypeResource relationshipType) {
final HsOfficeRelationTypeResource relationType) {
context.define(currentUser, assumedRoles);
final var entities = relationshipRepo.findRelationshipRelatedToPersonUuidAndRelationshipType(personUuid,
mapper.map(relationshipType, HsOfficeRelationshipType.class));
final var entities = relationRepo.findRelationRelatedToPersonUuidAndRelationType(personUuid,
mapper.map(relationType, HsOfficeRelationType.class));
final var resources = mapper.mapList(entities, HsOfficeRelationshipResource.class,
RELATIONSHIP_ENTITY_TO_RESOURCE_POSTMAPPER);
final var resources = mapper.mapList(entities, HsOfficeRelationResource.class,
RELATION_ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.ok(resources);
}
@Override
@Transactional
public ResponseEntity<HsOfficeRelationshipResource> addRelationship(
public ResponseEntity<HsOfficeRelationResource> addRelation(
final String currentUser,
final String assumedRoles,
final HsOfficeRelationshipInsertResource body) {
final HsOfficeRelationInsertResource body) {
context.define(currentUser, assumedRoles);
final var entityToSave = new HsOfficeRelationshipEntity();
entityToSave.setRelType(HsOfficeRelationshipType.valueOf(body.getRelType()));
entityToSave.setRelAnchor(relHolderRepo.findByUuid(body.getRelAnchorUuid()).orElseThrow(
() -> new NoSuchElementException("cannot find relAnchorUuid " + body.getRelAnchorUuid())
final var entityToSave = new HsOfficeRelationEntity();
entityToSave.setType(HsOfficeRelationType.valueOf(body.getType()));
entityToSave.setAnchor(holderRepo.findByUuid(body.getAnchorUuid()).orElseThrow(
() -> new NoSuchElementException("cannot find anchorUuid " + body.getAnchorUuid())
));
entityToSave.setRelHolder(relHolderRepo.findByUuid(body.getRelHolderUuid()).orElseThrow(
() -> new NoSuchElementException("cannot find relHolderUuid " + body.getRelHolderUuid())
entityToSave.setHolder(holderRepo.findByUuid(body.getHolderUuid()).orElseThrow(
() -> new NoSuchElementException("cannot find holderUuid " + body.getHolderUuid())
));
entityToSave.setContact(contactRepo.findByUuid(body.getContactUuid()).orElseThrow(
() -> new NoSuchElementException("cannot find contactUuid " + body.getContactUuid())
));
final var saved = relationshipRepo.save(entityToSave);
final var saved = relationRepo.save(entityToSave);
final var uri =
MvcUriComponentsBuilder.fromController(getClass())
.path("/api/hs/office/relationships/{id}")
.path("/api/hs/office/relations/{id}")
.buildAndExpand(saved.getUuid())
.toUri();
final var mapped = mapper.map(saved, HsOfficeRelationshipResource.class,
RELATIONSHIP_ENTITY_TO_RESOURCE_POSTMAPPER);
final var mapped = mapper.map(saved, HsOfficeRelationResource.class,
RELATION_ENTITY_TO_RESOURCE_POSTMAPPER);
return ResponseEntity.created(uri).body(mapped);
}
@Override
@Transactional(readOnly = true)
public ResponseEntity<HsOfficeRelationshipResource> getRelationshipByUuid(
public ResponseEntity<HsOfficeRelationResource> getRelationByUuid(
final String currentUser,
final String assumedRoles,
final UUID relationshipUuid) {
final UUID relationUuid) {
context.define(currentUser, assumedRoles);
final var result = relationshipRepo.findByUuid(relationshipUuid);
final var result = relationRepo.findByUuid(relationUuid);
if (result.isEmpty()) {
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
@Transactional
public ResponseEntity<Void> deleteRelationshipByUuid(
public ResponseEntity<Void> deleteRelationByUuid(
final String currentUser,
final String assumedRoles,
final UUID relationshipUuid) {
final UUID relationUuid) {
context.define(currentUser, assumedRoles);
final var result = relationshipRepo.deleteByUuid(relationshipUuid);
final var result = relationRepo.deleteByUuid(relationUuid);
if (result == 0) {
return ResponseEntity.notFound().build();
}
@ -126,27 +126,27 @@ public class HsOfficeRelationshipController implements HsOfficeRelationshipsApi
@Override
@Transactional
public ResponseEntity<HsOfficeRelationshipResource> patchRelationship(
public ResponseEntity<HsOfficeRelationResource> patchRelation(
final String currentUser,
final String assumedRoles,
final UUID relationshipUuid,
final HsOfficeRelationshipPatchResource body) {
final UUID relationUuid,
final HsOfficeRelationPatchResource body) {
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 mapped = mapper.map(saved, HsOfficeRelationshipResource.class);
final var saved = relationRepo.save(current);
final var mapped = mapper.map(saved, HsOfficeRelationResource.class);
return ResponseEntity.ok(mapped);
}
final BiConsumer<HsOfficeRelationshipEntity, HsOfficeRelationshipResource> RELATIONSHIP_ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
resource.setRelAnchor(mapper.map(entity.getRelAnchor(), HsOfficePersonResource.class));
resource.setRelHolder(mapper.map(entity.getRelHolder(), HsOfficePersonResource.class));
final BiConsumer<HsOfficeRelationEntity, HsOfficeRelationResource> RELATION_ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
resource.setAnchor(mapper.map(entity.getAnchor(), HsOfficePersonResource.class));
resource.setHolder(mapper.map(entity.getHolder(), HsOfficePersonResource.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.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.OptionalFromJson;
import jakarta.persistence.EntityManager;
import java.util.UUID;
class HsOfficeRelationshipEntityPatcher implements EntityPatcher<HsOfficeRelationshipPatchResource> {
class HsOfficeRelationEntityPatcher implements EntityPatcher<HsOfficeRelationPatchResource> {
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.entity = entity;
}
@Override
public void apply(final HsOfficeRelationshipPatchResource resource) {
public void apply(final HsOfficeRelationPatchResource resource) {
OptionalFromJson.of(resource.getContactUuid()).ifPresent(newValue -> {
verifyNotNull(newValue, "contact");
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,
PARTNER,
EX_PARTNER,
REPRESENTATIVE,
VIP_CONTACT,
ACCOUNTING,
DEBITOR,
OPERATIONS,
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) {
generateLiquibaseChangesetHeader(plPgSql);
generateGrantInsertRoleToExistingCustomers(plPgSql);
generateGrantInsertRoleToExistingObjects(plPgSql);
generateInsertPermissionGrantTrigger(plPgSql);
generateInsertCheckTrigger(plPgSql);
plPgSql.writeLn("--//");
@ -38,7 +38,7 @@ public class InsertTriggerGenerator {
with("liquibaseTagPrefix", liquibaseTagPrefix));
}
private void generateGrantInsertRoleToExistingCustomers(final StringWriter plPgSql) {
private void generateGrantInsertRoleToExistingObjects(final StringWriter plPgSql) {
getOptionalInsertSuperRole().ifPresent( superRoleDef -> {
plPgSql.writeLn("""
/*
@ -100,13 +100,7 @@ public class InsertTriggerGenerator {
private void generateInsertCheckTrigger(final StringWriter plPgSql) {
getOptionalInsertGrant().ifPresentOrElse(g -> {
if (!g.getSuperRoleDef().getEntityAlias().isGlobal()) {
if (rbacDef.isRootEntityAlias(g.getSuperRoleDef().getEntityAlias())) {
generateInsertPermissionTriggerAllowByDirectRole(plPgSql, g);
} else {
generateInsertPermissionTriggerAllowByIndirectRole(plPgSql, g);
}
} else {
if (g.getSuperRoleDef().getEntityAlias().isGlobal()) {
switch (g.getSuperRoleDef().getRole()) {
case ADMIN -> {
generateInsertPermissionTriggerAllowOnlyGlobalAdmin(plPgSql);
@ -119,27 +113,23 @@ public class InsertTriggerGenerator {
"invalid global role for INSERT permission: " + g.getSuperRoleDef().getRole());
}
}
} else {
generateInsertPermissionTriggerAllowByRoleOfDirectForeignKey(plPgSql, g);
}
},
() -> {
plPgSql.writeLn("""
-- FIXME: Where is this case necessary?
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()));
System.err.println("WARNING: no explicit INSERT grant for " + rbacDef.getRootEntityAlias().simpleName() + " => implicitly grant INSERT to global.admin");
generateInsertPermissionTriggerAllowOnlyGlobalAdmin(plPgSql);
});
}
private void generateInsertPermissionTriggerAllowByDirectRole(final StringWriter plPgSql, final RbacView.RbacGrantDefinition g) {
private void generateInsertPermissionTriggerAllowByRoleOfDirectForeignKey(final StringWriter plPgSql, final RbacView.RbacGrantDefinition g) {
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()
returns trigger
@ -159,51 +149,11 @@ public class InsertTriggerGenerator {
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) {
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()
returns trigger

View File

@ -8,13 +8,11 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.StringWriter.with;
public class RbacRestrictedViewGenerator {
private final RbacView rbacDef;
private final String liquibaseTagPrefix;
private final String simpleEntityVarName;
private final String rawTableName;
public RbacRestrictedViewGenerator(final RbacView rbacDef, final String liquibaseTagPrefix) {
this.rbacDef = rbacDef;
this.liquibaseTagPrefix = liquibaseTagPrefix;
this.simpleEntityVarName = rbacDef.getRootEntityAlias().simpleName();
this.rawTableName = rbacDef.getRootEntityAlias().getRawTableName();
}
@ -25,16 +23,16 @@ public class RbacRestrictedViewGenerator {
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('${rawTableName}',
$orderBy$
${orderBy}
${orderBy}
$orderBy$,
$updates$
${updates}
${updates}
$updates$);
--//
""",
with("liquibaseTagPrefix", liquibaseTagPrefix),
with("orderBy", rbacDef.getOrderBySqlExpression().sql),
with("orderBy", indented(rbacDef.getOrderBySqlExpression().sql, 2)),
with("updates", indented(rbacDef.getUpdatableColumns().stream()
.map(c -> c + " = new." + c)
.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.HsOfficePartnerEntity;
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.persistence.HasUuid;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
@ -812,7 +812,7 @@ public class RbacView {
HsOfficePartnerDetailsEntity.class,
HsOfficeBankAccountEntity.class,
HsOfficeDebitorEntity.class,
HsOfficeRelationshipEntity.class,
HsOfficeRelationEntity.class,
HsOfficeCoopAssetsTransactionEntity.class,
HsOfficeContactEntity.class,
HsOfficeSepaMandateEntity.class,
@ -831,7 +831,7 @@ public class RbacView {
throw new RuntimeException(e);
}
} 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
/api/hs/office/persons/{personUUID}:
null: org.openapitools.jackson.nullable.JsonNullable
/api/hs/office/relationships/{relationshipUUID}:
/api/hs/office/relations/{relationUUID}:
null: org.openapitools.jackson.nullable.JsonNullable
/api/hs/office/bankaccounts/{bankAccountUUID}:
null: org.openapitools.jackson.nullable.JsonNullable

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
### 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
%%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,5 +1,6 @@
--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:--//
@ -36,8 +37,8 @@ begin
perform createRoleWithGrants(
testCustomerOwner(NEW),
permissions => array['DELETE'],
userUuids => array[currentUserUuid()],
incomingSuperRoles => array[globalAdmin(unassumed())]
incomingSuperRoles => array[globalAdmin(unassumed())],
userUuids => array[currentUserUuid()]
);
perform createRoleWithGrants(
@ -72,15 +73,16 @@ create trigger insertTriggerForTestCustomer_tg
after insert on test_customer
for each row
execute procedure insertTriggerForTestCustomer_tf();
--//
-- ============================================================================
--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()
returns trigger
@ -93,12 +95,10 @@ end; $$;
create trigger test_customer_insert_permission_check_tg
before insert on test_customer
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 test_customer_insert_permission_missing_tf();
--//
-- ============================================================================
--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$
prefix
$idName$);
--//
-- ============================================================================
--changeset test-customer-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('test_customer',
'reference',
$orderBy$
reference
$orderBy$,
$updates$
reference = new.reference,
prefix = new.prefix,
@ -120,4 +122,3 @@ call generateRbacRestrictedView('test_customer',
$updates$);
--//

View File

@ -1,6 +1,6 @@
### 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
%%{init:{'flowchart':{'htmlLabels':false}}}%%

View File

@ -1,6 +1,6 @@
### 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
%%{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:
file: db/changelog/218-hs-office-person-test-data.sql
- include:
file: db/changelog/220-hs-office-relationship.sql
file: db/changelog/220-hs-office-relation.sql
- include:
file: db/changelog/223-hs-office-relationship-rbac.sql
file: db/changelog/223-hs-office-relation-rbac.sql
- include:
file: db/changelog/228-hs-office-relationship-test-data.sql
file: db/changelog/228-hs-office-relation-test-data.sql
- include:
file: db/changelog/230-hs-office-partner.sql
- include:

View File

@ -41,7 +41,7 @@ public class ArchitectureTest {
"..hs.office.migration",
"..hs.office.partner",
"..hs.office.person",
"..hs.office.relationship",
"..hs.office.relation",
"..hs.office.sepamandate",
"..errors",
"..mapper",
@ -148,7 +148,7 @@ public class ArchitectureTest {
public static final ArchRule hsOfficeContactPackageRule = classes()
.that().resideInAPackage("..hs.office.contact..")
.should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage("..hs.office.contact..", "..hs.office.relationship..",
.resideInAnyPackage("..hs.office.contact..", "..hs.office.relation..",
"..hs.office.partner..",
"..hs.office.debitor..",
"..hs.office.membership..",
@ -159,7 +159,7 @@ public class ArchitectureTest {
public static final ArchRule hsOfficePersonPackageRule = classes()
.that().resideInAPackage("..hs.office.person..")
.should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage("..hs.office.person..", "..hs.office.relationship..",
.resideInAnyPackage("..hs.office.person..", "..hs.office.relation..",
"..hs.office.partner..",
"..hs.office.debitor..",
"..hs.office.membership..",
@ -167,10 +167,10 @@ public class ArchitectureTest {
@ArchTest
@SuppressWarnings("unused")
public static final ArchRule hsOfficeRelationshipPackageRule = classes()
.that().resideInAPackage("..hs.office.relationship..")
public static final ArchRule hsOfficeRelationPackageRule = classes()
.that().resideInAPackage("..hs.office.relation..")
.should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage("..hs.office.relationship..",
.resideInAnyPackage("..hs.office.relation..",
"..hs.office.partner..",
"..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.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonType;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipType;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType;
import net.hostsharing.hsadminng.hs.office.sepamandate.HsOfficeSepaMandateEntity;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
import net.hostsharing.test.JpaAttempt;
@ -127,7 +127,7 @@ public class ImportOfficeData extends ContextBasedTest {
new String[]{"partner", "vip-contact", "ex-partner", "billing", "contractual", "operation"},
SUBSCRIBER_ROLES);
static int relationshipId = 2000000;
static int relationId = 2000000;
@Value("${spring.datasource.url}")
private String jdbcUrl;
@ -144,7 +144,7 @@ public class ImportOfficeData extends ContextBasedTest {
private static Map<Integer, HsOfficeDebitorEntity> debitors = 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, HsOfficeBankAccountEntity> bankAccounts = new WriteOnceMap<>();
private static Map<Integer, HsOfficeCoopSharesTransactionEntity> coopShares = new WriteOnceMap<>();
@ -161,6 +161,7 @@ public class ImportOfficeData extends ContextBasedTest {
@MockBean
HttpServletRequest request;
@Test
@Order(1010)
void importBusinessPartners() {
@ -174,33 +175,33 @@ public class ImportOfficeData extends ContextBasedTest {
}
@Test
@Order(1011)
@Order(1019)
void verifyBusinessPartners() {
assumeThatWeAreImportingControlledTestData();
// no contacts yet => mostly null values
assertThat(toFormattedString(partners)).isEqualToIgnoringWhitespace("""
{
17=partner(P-10017: null null, null),
20=partner(P-10020: null null, null),
22=partner(P-11022: null null, null),
99=partner(P-19999: null null, null)
17=partner(null null, null),
20=partner(null null, null),
22=partner(null null, null),
99=partner(null null, null)
}
""");
assertThat(toFormattedString(contacts)).isEqualTo("{}");
assertThat(toFormattedString(debitors)).isEqualToIgnoringWhitespace("""
{
17=debitor(D-1001700: P-10017, mih),
20=debitor(D-1002000: P-10020, xyz),
22=debitor(D-1102200: P-11022, xxx),
99=debitor(D-1999900: P-19999, zzz)
17=debitor(D-1001700: null null, null: mih),
20=debitor(D-1002000: null null, null: xyz),
22=debitor(D-1102200: null null, null: xxx),
99=debitor(D-1999900: null null, null: zzz)
}
""");
assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace("""
{
17=Membership(M-1001700, P-10017, D-1001700, [2000-12-06,), NONE),
20=Membership(M-1002000, P-10020, D-1002000, [2000-12-06,2016-01-01), UNKNOWN),
22=Membership(M-1102200, P-11022, D-1102200, [2021-04-01,), NONE)
17=Membership(M-1001700, null null, null, D-1001700, [2000-12-06,), NONE),
20=Membership(M-1002000, null null, null, D-1002000, [2000-12-06,2016-01-01), UNKNOWN),
22=Membership(M-1102200, null null, null, D-1102200, [2021-04-01,), NONE)
}
""");
}
@ -219,15 +220,32 @@ public class ImportOfficeData extends ContextBasedTest {
@Test
@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() {
assumeThatWeAreImportingControlledTestData();
assertThat(toFormattedString(partners)).isEqualToIgnoringWhitespace("""
{
17=partner(P-10017: NP Mellies, Michael, Herr Michael Mellies ),
20=partner(P-10020: LP JM GmbH, Herr Philip Meyer-Contract , JM GmbH),
22=partner(P-11022: ?? Test PS, Petra Schmidt , Test PS),
99=partner(P-19999: null null, null)
17=partner(NP Mellies, Michael: Herr Michael Mellies ),
20=partner(LP JM GmbH: Herr Philip Meyer-Contract , JM GmbH),
22=partner(?? Test PS: Petra Schmidt , Test PS),
99=partner(null null, null)
}
""");
assertThat(toFormattedString(contacts)).isEqualToIgnoringWhitespace("""
@ -257,38 +275,41 @@ public class ImportOfficeData extends ContextBasedTest {
""");
assertThat(toFormattedString(debitors)).isEqualToIgnoringWhitespace("""
{
17=debitor(D-1001700: P-10017, mih),
20=debitor(D-1002000: P-10020, xyz),
22=debitor(D-1102200: P-11022, xxx),
99=debitor(D-1999900: P-19999, zzz)
17=debitor(D-1001700: NP Mellies, Michael: mih),
20=debitor(D-1002000: LP JM GmbH: xyz),
22=debitor(D-1102200: ?? Test PS: xxx),
99=debitor(D-1999900: null null, null: zzz)
}
""");
assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace("""
{
17=Membership(M-1001700, P-10017, D-1001700, [2000-12-06,), NONE),
20=Membership(M-1002000, P-10020, D-1002000, [2000-12-06,2016-01-01), UNKNOWN),
22=Membership(M-1102200, P-11022, D-1102200, [2021-04-01,), NONE)
17=Membership(M-1001700, NP Mellies, Michael, D-1001700, [2000-12-06,), NONE),
20=Membership(M-1002000, LP JM GmbH, D-1002000, [2000-12-06,2016-01-01), UNKNOWN),
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 '),
2000001=rel(relAnchor='LP Hostsharing eG', relType='PARTNER', relHolder='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'),
2000003=rel(relAnchor='LP Hostsharing eG', relType='PARTNER', relHolder='null null, null'),
2000004=rel(relAnchor='NP Mellies, Michael', relType='OPERATIONS', relHolder='NP Mellies, Michael', contact='Herr Michael Mellies '),
2000005=rel(relAnchor='NP Mellies, Michael', relType='REPRESENTATIVE', relHolder='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.'),
2000007=rel(relAnchor='LP JM GmbH', relType='OPERATIONS', relHolder='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'),
2000009=rel(relAnchor='LP JM GmbH', relType='SUBSCRIBER', relMark='operations-announce', relHolder='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'),
2000011=rel(relAnchor='LP JM GmbH', relType='SUBSCRIBER', relMark='members-announce', relHolder='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'),
2000013=rel(relAnchor='LP JM GmbH', relType='VIP_CONTACT', relHolder='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'),
2000015=rel(relAnchor='?? Test PS', relType='REPRESENTATIVE', relHolder='?? 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 ')
2000000=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='NP Mellies, Michael', contact='Herr Michael Mellies '),
2000001=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'),
2000002=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000003=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='null null, null'),
2000004=rel(anchor='NP Mellies, Michael', type='OPERATIONS', holder='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(anchor='LP JM GmbH', type='EX_PARTNER', holder='LP JM e.K.', contact='JM e.K.'),
2000007=rel(anchor='LP JM GmbH', type='OPERATIONS', holder='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(anchor='LP JM GmbH', type='SUBSCRIBER', mark='operations-announce', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'),
2000010=rel(anchor='LP JM GmbH', type='REPRESENTATIVE', holder='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(anchor='LP JM GmbH', type='SUBSCRIBER', mark='customers-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'),
2000013=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Frau Tammy Meyer-VIP , JM GmbH'),
2000014=rel(anchor='?? Test PS', type='OPERATIONS', holder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000015=rel(anchor='?? Test PS', type='REPRESENTATIVE', holder='?? Test PS', contact='Petra Schmidt , Test PS'),
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
@Order(1031)
@Order(1039)
void verifySepaMandates() {
assumeThatWeAreImportingControlledTestData();
assertThat(toFormattedString(bankAccounts)).isEqualToIgnoringWhitespace("""
{
234234=bankAccount(DE37500105177419788228: holder='Michael Mellies', bic='INGDDEFFXXX'),
235600=bankAccount(DE02300209000106531065: holder='JM e.K.', bic='CMCIDEDD'),
235662=bankAccount(DE49500105174516484892: holder='JM GmbH', bic='INGDDEFFXXX')
234234=bankAccount(holder='Michael Mellies', iban='DE37500105177419788228', bic='INGDDEFFXXX'),
235600=bankAccount(holder='JM e.K.', iban='DE02300209000106531065', bic='CMCIDEDD'),
235662=bankAccount(holder='JM GmbH', iban='DE49500105174516484892', bic='INGDDEFFXXX')
}
""");
assertThat(toFormattedString(sepaMandates)).isEqualToIgnoringWhitespace("""
@ -338,7 +359,7 @@ public class ImportOfficeData extends ContextBasedTest {
}
@Test
@Order(1041)
@Order(1049)
void verifyCoopShares() {
assumeThatWeAreImportingControlledTestData();
@ -365,55 +386,53 @@ public class ImportOfficeData extends ContextBasedTest {
}
@Test
@Order(1051)
@Order(1059)
void verifyCoopAssets() {
assumeThatWeAreImportingControlledTestData();
assertThat(toFormattedString(coopAssets)).isEqualToIgnoringWhitespace("""
{
30000=CoopAssetsTransaction(M-1001700: 2000-12-06, DEPOSIT, 1280.00, for subscription A),
31000=CoopAssetsTransaction(M-1002000: 2000-12-06, DEPOSIT, 128.00, for subscription B),
32000=CoopAssetsTransaction(M-1001700: 2005-01-10, DEPOSIT, 2560.00, for subscription C),
33001=CoopAssetsTransaction(M-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),
34001=CoopAssetsTransaction(M-1002000: 2016-12-31, CLEARING, -8.00, for cancellation D),
34002=CoopAssetsTransaction(M-1002000: 2016-12-31, DISBURSAL, -100.00, for cancellation D),
34003=CoopAssetsTransaction(M-1002000: 2016-12-31, LOSS, -20.00, for cancellation D)
30000=CoopAssetsTransaction(1001700, 2000-12-06, DEPOSIT, 1280.00, for subscription A),
31000=CoopAssetsTransaction(1002000, 2000-12-06, DEPOSIT, 128.00, for subscription B),
32000=CoopAssetsTransaction(1001700, 2005-01-10, DEPOSIT, 2560.00, for subscription C),
33001=CoopAssetsTransaction(1001700, 2005-01-10, TRANSFER, -512.00, for transfer to 10),
33002=CoopAssetsTransaction(1002000, 2005-01-10, ADOPTION, 512.00, for transfer from 7),
34001=CoopAssetsTransaction(1002000, 2016-12-31, CLEARING, -8.00, for cancellation D),
34002=CoopAssetsTransaction(1002000, 2016-12-31, DISBURSAL, -100.00, for cancellation D),
34003=CoopAssetsTransaction(1002000, 2016-12-31, LOSS, -20.00, for cancellation D)
}
""");
}
@Test
@Order(2000)
void verifyAllPartnersHaveProperPartnerRoles() {
void verifyAllPartnersHavePersons() {
partners.forEach((id, p) -> {
final var partnerRole = p.getPartnerRole();
assertThat(partnerRole).describedAs("partner " + id + " without partnerRole").isNotNull();
if ( id != 99 ) {
assertThat(partnerRole.getContact()).describedAs("partner " + id + " without partnerRole.contact").isNotNull();
assertThat(partnerRole.getContact().getLabel()).describedAs("partner " + id + " without valid partnerRole.contact").isNotNull();
assertThat(partnerRole.getRelHolder()).describedAs("partner " + id + " without partnerRole.relHolder").isNotNull();
assertThat(partnerRole.getRelHolder().getPersonType()).describedAs("partner " + id + " without valid partnerRole.relHolder").isNotNull();
assertThat(p.getContact()).describedAs("partner " + id + " without contact").isNotNull();
assertThat(p.getContact().getLabel()).describedAs("partner " + id + " without valid contact").isNotNull();
assertThat(p.getPerson()).describedAs("partner " + id + " without person").isNotNull();
assertThat(p.getPerson().getPersonType()).describedAs("partner " + id + " without valid person").isNotNull();
}
});
}
@Test
@Order(2001)
void removeEmptyRelationships() {
@Order(2009)
void removeEmptyRelations() {
assumeThatWeAreImportingControlledTestData();
// avoid a error when persisting the deliberetely invalid partner entry #99
final var idsToRemove = new HashSet<Integer>();
relationships.forEach( (id, r) -> {
relations.forEach( (id, r) -> {
// such a record
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);
}
});
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
@ -424,11 +443,9 @@ public class ImportOfficeData extends ContextBasedTest {
// avoid a error when persisting the deliberately invalid partner entry #99
final var idsToRemove = new HashSet<Integer>();
partners.forEach( (id, r) -> {
final var partnerRole = r.getPartnerRole();
// such a record is in test data to test error messages
if (partnerRole.getContact() == null || partnerRole.getContact().getLabel() == null ||
partnerRole.getRelHolder() == null | partnerRole.getRelHolder().getPersonType() == null ) {
// such a record
if (r.getContact() == null || r.getContact().getLabel() == null ||
r.getPerson() == null | r.getPerson().getPersonType() == null ) {
idsToRemove.add(id);
}
});
@ -443,9 +460,10 @@ public class ImportOfficeData extends ContextBasedTest {
// avoid a error when persisting the deliberately invalid partner entry #99
final var idsToRemove = new HashSet<Integer>();
debitors.forEach( (id, d) -> {
// such a record is in test data to test error messages
if (false) { // TODO: how can I now empty debitors?
debitors.forEach( (id, r) -> {
// such a record
if (r.getBillingContact() == null || r.getBillingContact().getLabel() == null ||
r.getPartner().getPerson() == null | r.getPartner().getPerson().getPersonType() == null ) {
idsToRemove.add(id);
}
});
@ -454,7 +472,6 @@ public class ImportOfficeData extends ContextBasedTest {
}
@Test
@Disabled
@Order(3000)
@Commit
void persistEntities() {
@ -478,7 +495,7 @@ public class ImportOfficeData extends ContextBasedTest {
jpaAttempt.transacted(() -> {
context(rbacSuperuser);
relationships.forEach(this::persist);
relations.forEach(this::persist);
}).assertSuccessful();
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_partner 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_person where true").executeUpdate();
}).assertSuccessful();
@ -659,31 +676,28 @@ public class ImportOfficeData extends ContextBasedTest {
.forEach(rec -> {
final var person = HsOfficePersonEntity.builder().build();
final var partnerRelationship = HsOfficeRelationshipEntity.builder()
.relHolder(person)
.relType(HsOfficeRelationshipType.PARTNER)
.relAnchor(mandant)
final var partnerRelation = HsOfficeRelationEntity.builder()
.holder(person)
.type(HsOfficeRelationType.PARTNER)
.anchor(mandant)
.contact(null) // is set during contacts import depending on assigned roles
.build();
relationships.put(relationshipId++, partnerRelationship);
relations.put(relationId++, partnerRelation);
final var partner = HsOfficePartnerEntity.builder()
.partnerNumber(rec.getInteger("member_id"))
.details(HsOfficePartnerDetailsEntity.builder().build())
.partnerRole(partnerRelationship)
.partnerRel(partnerRelation)
.contact(null) // is set during contacts import depending on assigned roles
.person(person)
.build();
partners.put(rec.getInteger("bp_id"), partner);
final var debitor = HsOfficeDebitorEntity.builder()
.partner(partner)
.debitorNumberSuffix((byte) 0)
.debitorRel(
HsOfficeRelationshipEntity.builder()
.relType(HsOfficeRelationshipType.ACCOUNTING)
.relAnchor(partnerRelationship.getRelHolder())
.relHolder(null) // gets set later
.build()
)
.defaultPrefix(rec.getString("member_code").replace("hsh00-", ""))
.partner(partner)
.billable(rec.isEmpty("free") || rec.getString("free").equals("f"))
.vatReverseCharge(rec.getBoolean("exempt_vat"))
.vatBusiness("GROSS".equals(rec.getString("indicator_vat"))) // TODO: remove
@ -704,6 +718,7 @@ public class ImportOfficeData extends ContextBasedTest {
isBlank(rec.getString("member_until"))
? HsOfficeReasonForTermination.NONE
: HsOfficeReasonForTermination.UNKNOWN)
.mainDebitor(debitor)
.build();
memberships.put(rec.getInteger("bp_id"), membership);
}
@ -829,9 +844,9 @@ public class ImportOfficeData extends ContextBasedTest {
final var partner = partners.get(bpId);
final var debitor = debitors.get(bpId);
final var partnerPerson = partner.getPartnerRole().getRelHolder();
if (containsPartnerRole(rec)) {
initPerson(partnerPerson, rec);
final var partnerPerson = partner.getPerson();
if (containsPartnerRel(rec)) {
initPerson(partner.getPerson(), rec);
}
HsOfficePersonEntity contactPerson = partnerPerson;
@ -844,71 +859,72 @@ public class ImportOfficeData extends ContextBasedTest {
final var contact = HsOfficeContactEntity.builder().build();
initContact(contact, rec);
if (containsPartnerRole(rec)) {
assertThat(partner.getPartnerRole().getContact()).isNull();
partner.getPartnerRole().setContact(contact);
if (containsPartnerRel(rec)) {
assertThat(partner.getContact()).isNull();
partner.setContact(contact);
partner.getPartnerRel().setContact(contact);
}
if (containsRole(rec, "billing")) {
assertThat(debitor.getDebitorRel().getContact()).isNull();
debitor.getDebitorRel().setContact(contact);
assertThat(debitor.getBillingContact()).isNull();
debitor.setBillingContact(contact);
}
if (containsRole(rec, "operation")) {
addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.OPERATIONS);
addRelation(partnerPerson, contactPerson, contact, HsOfficeRelationType.OPERATIONS);
}
if (containsRole(rec, "contractual")) {
addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.REPRESENTATIVE);
addRelation(partnerPerson, contactPerson, contact, HsOfficeRelationType.REPRESENTATIVE);
}
if (containsRole(rec, "ex-partner")) {
addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.EX_PARTNER);
addRelation(partnerPerson, contactPerson, contact, HsOfficeRelationType.EX_PARTNER);
}
if (containsRole(rec, "vip-contact")) {
addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.VIP_CONTACT);
addRelation(partnerPerson, contactPerson, contact, HsOfficeRelationType.VIP_CONTACT);
}
for (String subscriberRole: SUBSCRIBER_ROLES) {
if (containsRole(rec, subscriberRole)) {
addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.SUBSCRIBER)
.setRelMark(subscriberRole.split(":")[1])
addRelation(partnerPerson, contactPerson, contact, HsOfficeRelationType.SUBSCRIBER)
.setMark(subscriberRole.split(":")[1])
;
}
}
verifyContainsOnlyKnownRoles(rec.getString("roles"));
});
optionallyAddMissingContractualRelationships();
optionallyAddMissingContractualRelations();
}
private static void optionallyAddMissingContractualRelationships() {
private static void optionallyAddMissingContractualRelations() {
final var contractualMissing = new HashSet<Integer>();
partners.forEach( (id, partner) -> {
final var partnerPerson = partner.getPartnerRole().getRelHolder();
if (relationships.values().stream()
.filter(rel -> rel.getRelAnchor() == partnerPerson && rel.getRelType() == HsOfficeRelationshipType.REPRESENTATIVE)
final var partnerPerson = partner.getPerson();
if (relations.values().stream()
.filter(rel -> rel.getAnchor() == partnerPerson && rel.getType() == HsOfficeRelationType.REPRESENTATIVE)
.findFirst().isEmpty()) {
contractualMissing.add(partner.getPartnerNumber());
}
});
assertThat(contractualMissing).containsOnly(19999); // deliberately wrong partner entry
}
private static boolean containsRole(final Record rec, final String role) {
final var roles = rec.getString("roles");
return ("," + roles + ",").contains("," + role + ",");
}
private static boolean containsPartnerRole(final Record rec) {
private static boolean containsPartnerRel(final Record rec) {
return containsRole(rec, "partner");
}
private static HsOfficeRelationshipEntity addRelationship(
private static HsOfficeRelationEntity addRelation(
final HsOfficePersonEntity partnerPerson,
final HsOfficePersonEntity contactPerson,
final HsOfficeContactEntity contact,
final HsOfficeRelationshipType representative) {
final var rel = HsOfficeRelationshipEntity.builder()
.relAnchor(partnerPerson)
.relHolder(contactPerson)
final HsOfficeRelationType representative) {
final var rel = HsOfficeRelationEntity.builder()
.anchor(partnerPerson)
.holder(contactPerson)
.contact(contact)
.relType(representative)
.type(representative)
.build();
relationships.put(relationshipId++, rel);
relations.put(relationId++, 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.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.test.PatchUnitTestBase;
import org.junit.jupiter.api.BeforeEach;
@ -21,12 +21,12 @@ import static org.mockito.Mockito.lenient;
@TestInstance(PER_CLASS)
@ExtendWith(MockitoExtension.class)
class HsOfficeRelationshipEntityPatcherUnitTest extends PatchUnitTestBase<
HsOfficeRelationshipPatchResource,
HsOfficeRelationshipEntity
class HsOfficeRelationEntityPatcherUnitTest extends PatchUnitTestBase<
HsOfficeRelationPatchResource,
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();
@Mock
@ -49,24 +49,24 @@ class HsOfficeRelationshipEntityPatcherUnitTest extends PatchUnitTestBase<
.build();
@Override
protected HsOfficeRelationshipEntity newInitialEntity() {
final var entity = new HsOfficeRelationshipEntity();
entity.setUuid(INITIAL_RELATIONSHIP_UUID);
entity.setRelType(HsOfficeRelationshipType.REPRESENTATIVE);
entity.setRelAnchor(givenInitialAnchorPerson);
entity.setRelHolder(givenInitialHolderPerson);
protected HsOfficeRelationEntity newInitialEntity() {
final var entity = new HsOfficeRelationEntity();
entity.setUuid(INITIAL_RELATION_UUID);
entity.setType(HsOfficeRelationType.REPRESENTATIVE);
entity.setAnchor(givenInitialAnchorPerson);
entity.setHolder(givenInitialHolderPerson);
entity.setContact(givenInitialContact);
return entity;
}
@Override
protected HsOfficeRelationshipPatchResource newPatchResource() {
return new HsOfficeRelationshipPatchResource();
protected HsOfficeRelationPatchResource newPatchResource() {
return new HsOfficeRelationPatchResource();
}
@Override
protected HsOfficeRelationshipEntityPatcher createPatcher(final HsOfficeRelationshipEntity relationship) {
return new HsOfficeRelationshipEntityPatcher(em, relationship);
protected HsOfficeRelationEntityPatcher createPatcher(final HsOfficeRelationEntity relation) {
return new HsOfficeRelationEntityPatcher(em, relation);
}
@Override
@ -74,9 +74,9 @@ class HsOfficeRelationshipEntityPatcherUnitTest extends PatchUnitTestBase<
return Stream.of(
new JsonNullableProperty<>(
"contact",
HsOfficeRelationshipPatchResource::setContactUuid,
HsOfficeRelationPatchResource::setContactUuid,
PATCHED_CONTACT_UUID,
HsOfficeRelationshipEntity::setContact,
HsOfficeRelationEntity::setContact,
newContact(PATCHED_CONTACT_UUID))
.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