rename partnerRole -> partnerRel, relationship -> relation and remove rel-Prefix (relAnchor etc.) (#23)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #23
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
This commit is contained in:
Michael Hoennig 2024-03-13 15:01:24 +01:00
parent 907e27ec19
commit 3faf2ea99e
39 changed files with 873 additions and 914 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

@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
@ -113,10 +113,10 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
SELECT debitor.uuid,
'D-' || (SELECT partner.partnerNumber
FROM hs_office_partner partner
JOIN hs_office_relationship partnerRel
ON partnerRel.uuid = partner.partnerRoleUUid AND partnerRel.relType = 'PARTNER'
JOIN hs_office_relationship debitorRel
ON debitorRel.relAnchorUuid = partnerRel.relHolderUuid AND partnerRel.relType = 'DEBITOR'
JOIN hs_office_relation partnerRel
ON partnerRel.uuid = partner.partnerRelUUid AND partnerRel.type = 'PARTNER'
JOIN hs_office_relation debitorRel
ON debitorRel.anchorUuid = partnerRel.holderUuid AND partnerRel.type = 'DEBITOR'
WHERE debitorRel.uuid = debitor.debitorRelUuid)
|| to_char(debitorNumberSuffix, 'fm00')
from hs_office_debitor as debitor
@ -133,11 +133,11 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
"defaultPrefix" /* TODO: do we want that updatable? */)
.createPermission(custom("new-debitor")).grantedTo("global", ADMIN)
.importRootEntityAliasProxy("debitorRel", HsOfficeRelationshipEntity.class,
.importRootEntityAliasProxy("debitorRel", HsOfficeRelationEntity.class,
fetchedBySql("""
SELECT *
FROM hs_office_relationship AS r
WHERE r.relType = 'DEBITOR' AND r.relHolderUuid = ${REF}.debitorRelUuid
FROM hs_office_relation AS r
WHERE r.type = 'DEBITOR' AND r.holderUuid = ${REF}.debitorRelUuid
"""),
dependsOnColumn("debitorRelUuid"))
.createPermission(DELETE).grantedTo("debitorRel", OWNER)
@ -147,18 +147,18 @@ public class HsOfficeDebitorEntity implements HasUuid, Stringifyable {
.importEntityAlias("refundBankAccount", HsOfficeBankAccountEntity.class,
dependsOnColumn("refundBankAccountUuid"), fetchedBySql("""
SELECT *
FROM hs_office_relationship AS r
WHERE r.relType = 'DEBITOR' AND r.relHolderUuid = ${REF}.debitorRelUuid
FROM hs_office_relation AS r
WHERE r.type = 'DEBITOR' AND r.holderUuid = ${REF}.debitorRelUuid
""")
)
.toRole("refundBankAccount", ADMIN).grantRole("debitorRel", AGENT)
.toRole("debitorRel", AGENT).grantRole("refundBankAccount", REFERRER)
.importEntityAlias("partnerRel", HsOfficeRelationshipEntity.class,
.importEntityAlias("partnerRel", HsOfficeRelationEntity.class,
dependsOnColumn("partnerRelUuid"), fetchedBySql("""
SELECT *
FROM hs_office_relationship AS partnerRel
WHERE ${debitorRel}.relAnchorUuid = partnerRel.relHolderUuid
FROM hs_office_relation AS partnerRel
WHERE ${debitorRel}.anchorUuid = partnerRel.holderUuid
""")
)
.toRole("partnerRel", ADMIN).grantRole("debitorRel", ADMIN)

View File

@ -7,11 +7,11 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficePartners
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerInsertResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerPatchResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerRoleInsertResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerRelInsertResource;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipRepository;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipType;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRepository;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType;
import net.hostsharing.hsadminng.mapper.Mapper;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
import org.springframework.beans.factory.annotation.Autowired;
@ -40,7 +40,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
private HsOfficePartnerRepository partnerRepo;
@Autowired
private HsOfficeRelationshipRepository relationshipRepo;
private HsOfficeRelationRepository relationRepo;
@PersistenceContext
private EntityManager em;
@ -112,7 +112,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
if (partnerRepo.deleteByUuid(partnerUuid) != 1 ||
// TODO: move to after delete trigger in partner
relationshipRepo.deleteByUuid(partnerToDelete.get().getPartnerRole().getUuid()) != 1 ) {
relationRepo.deleteByUuid(partnerToDelete.get().getPartnerRel().getUuid()) != 1 ) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
@ -141,18 +141,18 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
private HsOfficePartnerEntity createPartnerEntity(final HsOfficePartnerInsertResource body) {
final var entityToSave = new HsOfficePartnerEntity();
entityToSave.setPartnerNumber(body.getPartnerNumber());
entityToSave.setPartnerRole(persistPartnerRole(body.getPartnerRole()));
entityToSave.setPartnerRel(persistPartnerRel(body.getPartnerRel()));
entityToSave.setContact(ref(HsOfficeContactEntity.class, body.getContactUuid()));
entityToSave.setPerson(ref(HsOfficePersonEntity.class, body.getPersonUuid()));
entityToSave.setDetails(mapper.map(body.getDetails(), HsOfficePartnerDetailsEntity.class));
return entityToSave;
}
private HsOfficeRelationshipEntity persistPartnerRole(final HsOfficePartnerRoleInsertResource resource) {
final var entity = new HsOfficeRelationshipEntity();
entity.setRelType(HsOfficeRelationshipType.PARTNER);
entity.setRelAnchor(ref(HsOfficePersonEntity.class, resource.getRelAnchorUuid()));
entity.setRelHolder(ref(HsOfficePersonEntity.class, resource.getRelHolderUuid()));
private HsOfficeRelationEntity persistPartnerRel(final HsOfficePartnerRelInsertResource resource) {
final var entity = new HsOfficeRelationEntity();
entity.setType(HsOfficeRelationType.PARTNER);
entity.setAnchor(ref(HsOfficePersonEntity.class, resource.getAnchorUuid()));
entity.setHolder(ref(HsOfficePersonEntity.class, resource.getHolderUuid()));
entity.setContact(ref(HsOfficeContactEntity.class, resource.getContactUuid()));
em.persist(entity);
return entity;

View File

@ -2,7 +2,7 @@ package net.hostsharing.hsadminng.hs.office.partner;
import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
@ -86,15 +86,15 @@ public class HsOfficePartnerDetailsEntity implements HasUuid, Stringifyable {
"dateOfDeath")
.createPermission(custom("new-partner-details")).grantedTo("global", ADMIN)
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationshipEntity.class,
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class,
fetchedBySql("""
SELECT partnerRel.*
FROM hs_office_relationship AS partnerRel
FROM hs_office_relation AS partnerRel
JOIN hs_office_partner AS partner
ON partner.detailsUuid = ${ref}.uuid
WHERE partnerRel.uuid = partner.partnerRoleUuid
WHERE partnerRel.uuid = partner.partnerRelUuid
"""),
dependsOnColumn("partnerRoleUuid"))
dependsOnColumn("partnerRelUuid"))
// The grants are defined in HsOfficePartnerEntity.rbac()
// because they have to be changed when its partnerRel changes,

View File

@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.persistence.HasUuid;
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.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
import net.hostsharing.hsadminng.stringify.Stringify;
@ -50,15 +50,15 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
private Integer partnerNumber;
@ManyToOne
@JoinColumn(name = "partnerroleuuid", nullable = false)
private HsOfficeRelationshipEntity partnerRole;
@JoinColumn(name = "partnerreluuid", nullable = false)
private HsOfficeRelationEntity partnerRel;
// TODO: remove, is replaced by partnerRole
// TODO: remove, is replaced by partnerRel
@ManyToOne
@JoinColumn(name = "personuuid", nullable = false)
private HsOfficePersonEntity person;
// TODO: remove, is replaced by partnerRole
// TODO: remove, is replaced by partnerRel
@ManyToOne
@JoinColumn(name = "contactuuid", nullable = false)
private HsOfficeContactEntity contact;
@ -87,13 +87,13 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
FROM hs_office_partner AS partner
"""))
.withUpdatableColumns(
"partnerRoleUuid",
"partnerRelUuid",
"personUuid",
"contactUuid")
.createPermission(custom("new-partner")).grantedTo("global", ADMIN)
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationshipEntity.class,
fetchedBySql("SELECT * FROM hs_office_relationship AS r WHERE r.uuid = ${ref}.partnerRoleUuid"),
.importRootEntityAliasProxy("partnerRel", HsOfficeRelationEntity.class,
fetchedBySql("SELECT * FROM hs_office_relation AS r WHERE r.uuid = ${ref}.partnerRelUuid"),
dependsOnColumn("partnerRelUuid"))
.createPermission(DELETE).grantedTo("partnerRel", ADMIN)
.createPermission(UPDATE).grantedTo("partnerRel", AGENT)

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,4 +1,4 @@
package net.hostsharing.hsadminng.hs.office.relationship;
package net.hostsharing.hsadminng.hs.office.relation;
import lombok.*;
import lombok.experimental.FieldNameConstants;
@ -24,49 +24,49 @@ import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.rbacViewFor;
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity
@Table(name = "hs_office_relationship_rv")
@Table(name = "hs_office_relation_rv")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldNameConstants
public class HsOfficeRelationshipEntity implements HasUuid, Stringifyable {
public class HsOfficeRelationEntity implements HasUuid, Stringifyable {
private static Stringify<HsOfficeRelationshipEntity> toString = stringify(HsOfficeRelationshipEntity.class, "rel")
.withProp(Fields.relAnchor, HsOfficeRelationshipEntity::getRelAnchor)
.withProp(Fields.relType, HsOfficeRelationshipEntity::getRelType)
.withProp(Fields.relMark, HsOfficeRelationshipEntity::getRelMark)
.withProp(Fields.relHolder, HsOfficeRelationshipEntity::getRelHolder)
.withProp(Fields.contact, HsOfficeRelationshipEntity::getContact);
private static Stringify<HsOfficeRelationEntity> toString = stringify(HsOfficeRelationEntity.class, "rel")
.withProp(Fields.anchor, HsOfficeRelationEntity::getAnchor)
.withProp(Fields.type, HsOfficeRelationEntity::getType)
.withProp(Fields.mark, HsOfficeRelationEntity::getMark)
.withProp(Fields.holder, HsOfficeRelationEntity::getHolder)
.withProp(Fields.contact, HsOfficeRelationEntity::getContact);
private static Stringify<HsOfficeRelationshipEntity> toShortString = stringify(HsOfficeRelationshipEntity.class, "rel")
.withProp(Fields.relAnchor, HsOfficeRelationshipEntity::getRelAnchor)
.withProp(Fields.relType, HsOfficeRelationshipEntity::getRelType)
.withProp(Fields.relHolder, HsOfficeRelationshipEntity::getRelHolder);
private static Stringify<HsOfficeRelationEntity> toShortString = stringify(HsOfficeRelationEntity.class, "rel")
.withProp(Fields.anchor, HsOfficeRelationEntity::getAnchor)
.withProp(Fields.type, HsOfficeRelationEntity::getType)
.withProp(Fields.holder, HsOfficeRelationEntity::getHolder);
@Id
@GeneratedValue
private UUID uuid;
@ManyToOne
@JoinColumn(name = "relanchoruuid")
private HsOfficePersonEntity relAnchor;
@JoinColumn(name = "anchoruuid")
private HsOfficePersonEntity anchor;
@ManyToOne
@JoinColumn(name = "relholderuuid")
private HsOfficePersonEntity relHolder;
@JoinColumn(name = "holderuuid")
private HsOfficePersonEntity holder;
@ManyToOne
@JoinColumn(name = "contactuuid")
private HsOfficeContactEntity contact;
@Column(name = "reltype")
@Column(name = "type")
@Enumerated(EnumType.STRING)
private HsOfficeRelationshipType relType;
private HsOfficeRelationType type;
@Column(name = "relmark")
private String relMark;
@Column(name = "mark")
private String mark;
@Override
public String toString() {
@ -79,22 +79,22 @@ public class HsOfficeRelationshipEntity implements HasUuid, Stringifyable {
}
public static RbacView rbac() {
return rbacViewFor("relationship", HsOfficeRelationshipEntity.class)
return rbacViewFor("relation", HsOfficeRelationEntity.class)
.withIdentityView(SQL.projection("""
(select idName from hs_office_person_iv p where p.uuid = relAnchorUuid)
|| '-with-' || target.relType || '-'
|| (select idName from hs_office_person_iv p where p.uuid = relHolderUuid)
(select idName from hs_office_person_iv p where p.uuid = anchorUuid)
|| '-with-' || target.type || '-'
|| (select idName from hs_office_person_iv p where p.uuid = holderUuid)
"""))
.withRestrictedViewOrderBy(SQL.expression(
"(select idName from hs_office_person_iv p where p.uuid = target.relHolderUuid)"))
"(select idName from hs_office_person_iv p where p.uuid = target.holderUuid)"))
.withUpdatableColumns("contactUuid")
.importEntityAlias("anchorPerson", HsOfficePersonEntity.class,
dependsOnColumn("relAnchorUuid"),
fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.relAnchorUuid")
dependsOnColumn("anchorUuid"),
fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.anchorUuid")
)
.importEntityAlias("holderPerson", HsOfficePersonEntity.class,
dependsOnColumn("relHolderUuid"),
fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.relHolderUuid")
dependsOnColumn("holderUuid"),
fetchedBySql("select * from hs_office_person as p where p.uuid = ${REF}.holderUuid")
)
.importEntityAlias("contact", HsOfficeContactEntity.class,
dependsOnColumn("contactUuid"),
@ -123,6 +123,6 @@ public class HsOfficeRelationshipEntity implements HasUuid, Stringifyable {
}
public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("223-hs-office-relationship-rbac-generated");
rbac().generateWithBaseFileName("223-hs-office-relation-rbac-generated");
}
}

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,6 +1,6 @@
package net.hostsharing.hsadminng.hs.office.relationship;
package net.hostsharing.hsadminng.hs.office.relation;
public enum HsOfficeRelationshipType {
public enum HsOfficeRelationType {
UNKNOWN,
PARTNER,
EX_PARTNER,

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

@ -6,7 +6,7 @@ import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
import net.hostsharing.hsadminng.stringify.Stringify;
@ -99,7 +99,7 @@ public class HsOfficeSepaMandateEntity implements Stringifyable, HasUuid {
.withIdentityView(projection("concat(tradeName, familyName, givenName)"))
.withUpdatableColumns("reference", "agreement", "validity")
.importEntityAlias("debitorRel", HsOfficeRelationshipEntity.class, dependsOnColumn("debitorRelUuid"))
.importEntityAlias("debitorRel", HsOfficeRelationEntity.class, dependsOnColumn("debitorRelUuid"))
.importEntityAlias("bankAccount", HsOfficeBankAccountEntity.class, dependsOnColumn("bankAccountUuid"))
.createRole(OWNER, (with) -> {

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;
@ -804,7 +804,7 @@ public class RbacView {
HsOfficePartnerDetailsEntity.class,
HsOfficeBankAccountEntity.class,
HsOfficeDebitorEntity.class,
HsOfficeRelationshipEntity.class,
HsOfficeRelationEntity.class,
HsOfficeCoopAssetsTransactionEntity.class,
HsOfficeContactEntity.class,
HsOfficeSepaMandateEntity.class,

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

@ -96,8 +96,8 @@ components:
format: int8
minimum: 10000
maximum: 99999
partnerRole:
$ref: '#/components/schemas/HsOfficePartnerRoleInsert'
partnerRel:
$ref: '#/components/schemas/HsOfficePartnerRelInsert'
personUuid:
type: string
format: uuid
@ -112,22 +112,22 @@ components:
- contactUuid
- details
HsOfficePartnerRoleInsert:
HsOfficePartnerRelInsert:
type: object
nullable: false
properties:
relAnchorUuid:
anchorUuid:
type: string
format: uuid
relHolderUuid:
holderUuid:
type: string
format: uuid
contactUuid:
type: string
format: uuid
required:
- relAnchorUuid
- relHolderUuid
- anchorUuid
- holderUuid
- relContactUuid
HsOfficePartnerDetailsInsert:

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

@ -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',
'DEBITOR',
'VIP_CONTACT',
'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

@ -1,4 +1,4 @@
### hs_office_relationship RBAC
### hs_office_relation RBAC
```mermaid
@ -28,17 +28,17 @@ subgraph hsOfficePerson
--> role:hsOfficePerson.guest[person.guest]
end
subgraph hsOfficeRelationship
subgraph hsOfficeRelation
role:hsOfficePerson#relAnchor.admin[person#anchor.admin]
role:hsOfficePerson#anchor.admin[person#anchor.admin]
--- role:hsOfficePerson.admin
role:hsOfficeRelationship.owner[relationship.owner]
role:hsOfficeRelation.owner[relation.owner]
%% permissions
role:hsOfficeRelationship.owner --> perm:hsOfficeRelationship.*{{relationship.*}}
role:hsOfficeRelation.owner --> perm:hsOfficeRelation.*{{relation.*}}
%% incoming
role:global.admin ---> role:hsOfficeRelationship.owner
role:hsOfficePersonAdmin#relAnchor.admin
role:global.admin ---> role:hsOfficeRelation.owner
role:hsOfficePersonAdmin#anchor.admin
end
```

View File

@ -1,97 +1,97 @@
--liquibase formatted sql
-- ============================================================================
--changeset hs-office-relationship-rbac-OBJECT:1 endDelimiter:--//
--changeset hs-office-relation-rbac-OBJECT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRelatedRbacObject('hs_office_relationship');
call generateRelatedRbacObject('hs_office_relation');
--//
-- ============================================================================
--changeset hs-office-relationship-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--//
--changeset hs-office-relation-rbac-ROLE-DESCRIPTORS:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRoleDescriptors('hsOfficeRelationship', 'hs_office_relationship');
call generateRbacRoleDescriptors('hsOfficeRelation', 'hs_office_relation');
--//
-- ============================================================================
--changeset hs-office-relationship-rbac-ROLES-CREATION:1 endDelimiter:--//
--changeset hs-office-relation-rbac-ROLES-CREATION:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates and updates the roles and their assignments for relationship entities.
Creates and updates the roles and their assignments for relation entities.
*/
create or replace function hsOfficeRelationshipRbacRolesTrigger()
create or replace function hsOfficeRelationRbacRolesTrigger()
returns trigger
language plpgsql
strict as $$
declare
hsOfficeRelationshipTenant RbacRoleDescriptor;
newRelAnchor hs_office_person;
newRelHolder hs_office_person;
hsOfficeRelationTenant RbacRoleDescriptor;
newAnchor hs_office_person;
newHolder hs_office_person;
oldContact hs_office_contact;
newContact hs_office_contact;
begin
call enterTriggerForObjectUuid(NEW.uuid);
hsOfficeRelationshipTenant := hsOfficeRelationshipTenant(NEW);
hsOfficeRelationTenant := hsOfficeRelationTenant(NEW);
select * from hs_office_person as p where p.uuid = NEW.relAnchorUuid into newRelAnchor;
select * from hs_office_person as p where p.uuid = NEW.relHolderUuid into newRelHolder;
select * from hs_office_person as p where p.uuid = NEW.anchorUuid into newAnchor;
select * from hs_office_person as p where p.uuid = NEW.holderUuid into newHolder;
select * from hs_office_contact as c where c.uuid = NEW.contactUuid into newContact;
if TG_OP = 'INSERT' then
perform createRoleWithGrants(
hsOfficeRelationshipOwner(NEW),
hsOfficeRelationOwner(NEW),
permissions => array['DELETE'],
incomingSuperRoles => array[
globalAdmin(),
hsOfficePersonAdmin(newRelAnchor)]
hsOfficePersonAdmin(newAnchor)]
);
perform createRoleWithGrants(
hsOfficeRelationshipAdmin(NEW),
hsOfficeRelationAdmin(NEW),
permissions => array['UPDATE'],
incomingSuperRoles => array[hsOfficeRelationshipOwner(NEW)]
incomingSuperRoles => array[hsOfficeRelationOwner(NEW)]
);
-- the tenant role for those related users who can view the data
perform createRoleWithGrants(
hsOfficeRelationshipTenant,
hsOfficeRelationTenant,
permissions => array['SELECT'],
incomingSuperRoles => array[
hsOfficeRelationshipAdmin(NEW),
hsOfficePersonAdmin(newRelAnchor),
hsOfficePersonAdmin(newRelHolder),
hsOfficeRelationAdmin(NEW),
hsOfficePersonAdmin(newAnchor),
hsOfficePersonAdmin(newHolder),
hsOfficeContactAdmin(newContact)],
outgoingSubRoles => array[
hsOfficePersonTenant(newRelAnchor),
hsOfficePersonTenant(newRelHolder),
hsOfficePersonTenant(newAnchor),
hsOfficePersonTenant(newHolder),
hsOfficeContactTenant(newContact)]
);
-- anchor and holder admin roles need each others tenant role
-- to be able to see the joined relationship
-- to be able to see the joined relation
-- TODO: this can probably be avoided through agent+guest roles
call grantRoleToRole(hsOfficePersonTenant(newRelAnchor), hsOfficePersonAdmin(newRelHolder));
call grantRoleToRole(hsOfficePersonTenant(newRelHolder), hsOfficePersonAdmin(newRelAnchor));
call grantRoleToRoleIfNotNull(hsOfficePersonTenant(newRelHolder), hsOfficeContactAdmin(newContact));
call grantRoleToRole(hsOfficePersonTenant(newAnchor), hsOfficePersonAdmin(newHolder));
call grantRoleToRole(hsOfficePersonTenant(newHolder), hsOfficePersonAdmin(newAnchor));
call grantRoleToRoleIfNotNull(hsOfficePersonTenant(newHolder), hsOfficeContactAdmin(newContact));
elsif TG_OP = 'UPDATE' then
if OLD.contactUuid <> NEW.contactUuid then
-- nothing but the contact can be updated,
-- in other cases, a new relationship needs to be created and the old updated
-- in other cases, a new relation needs to be created and the old updated
select * from hs_office_contact as c where c.uuid = OLD.contactUuid into oldContact;
call revokeRoleFromRole( hsOfficeRelationshipTenant, hsOfficeContactAdmin(oldContact) );
call grantRoleToRole( hsOfficeRelationshipTenant, hsOfficeContactAdmin(newContact) );
call revokeRoleFromRole( hsOfficeRelationTenant, hsOfficeContactAdmin(oldContact) );
call grantRoleToRole( hsOfficeRelationTenant, hsOfficeContactAdmin(newContact) );
call revokeRoleFromRole( hsOfficeContactTenant(oldContact), hsOfficeRelationshipTenant );
call grantRoleToRole( hsOfficeContactTenant(newContact), hsOfficeRelationshipTenant );
call revokeRoleFromRole( hsOfficeContactTenant(oldContact), hsOfficeRelationTenant );
call grantRoleToRole( hsOfficeContactTenant(newContact), hsOfficeRelationTenant );
end if;
else
raise exception 'invalid usage of TRIGGER';
@ -104,39 +104,39 @@ end; $$;
/*
An AFTER INSERT TRIGGER which creates the role structure for a new customer.
*/
create trigger createRbacRolesForHsOfficeRelationship_Trigger
create trigger createRbacRolesForHsOfficeRelation_Trigger
after insert
on hs_office_relationship
on hs_office_relation
for each row
execute procedure hsOfficeRelationshipRbacRolesTrigger();
execute procedure hsOfficeRelationRbacRolesTrigger();
/*
An AFTER UPDATE TRIGGER which updates the role structure of a customer.
*/
create trigger updateRbacRolesForHsOfficeRelationship_Trigger
create trigger updateRbacRolesForHsOfficeRelation_Trigger
after update
on hs_office_relationship
on hs_office_relation
for each row
execute procedure hsOfficeRelationshipRbacRolesTrigger();
execute procedure hsOfficeRelationRbacRolesTrigger();
--//
-- ============================================================================
--changeset hs-office-relationship-rbac-IDENTITY-VIEW:1 endDelimiter:--//
--changeset hs-office-relation-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityViewFromProjection('hs_office_relationship', $idName$
(select idName from hs_office_person_iv p where p.uuid = target.relAnchorUuid)
|| '-with-' || target.relType || '-' ||
(select idName from hs_office_person_iv p where p.uuid = target.relHolderUuid)
call generateRbacIdentityViewFromProjection('hs_office_relation', $idName$
(select idName from hs_office_person_iv p where p.uuid = target.anchorUuid)
|| '-with-' || target.type || '-' ||
(select idName from hs_office_person_iv p where p.uuid = target.holderUuid)
$idName$);
--//
-- ============================================================================
--changeset hs-office-relationship-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
--changeset hs-office-relation-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_relationship',
'(select idName from hs_office_person_iv p where p.uuid = target.relHolderUuid)',
call generateRbacRestrictedView('hs_office_relation',
'(select idName from hs_office_person_iv p where p.uuid = target.holderUuid)',
$updates$
contactUuid = new.contactUuid
$updates$);
@ -146,10 +146,10 @@ call generateRbacRestrictedView('hs_office_relationship',
-- ============================================================================
--changeset hs-office-relationship-rbac-NEW-RELATHIONSHIP:1 endDelimiter:--//
--changeset hs-office-relation-rbac-NEW-RELATHIONSHIP:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates a global permission for new-relationship and assigns it to the hostsharing admins role.
Creates a global permission for new-relation and assigns it to the hostsharing admins role.
*/
do language plpgsql $$
declare
@ -157,11 +157,11 @@ do language plpgsql $$
globalObjectUuid uuid;
globalAdminRoleUuid uuid ;
begin
call defineContext('granting global new-relationship permission to global admin role', null, null, null);
call defineContext('granting global new-relation permission to global admin role', null, null, null);
globalAdminRoleUuid := findRoleId(globalAdmin());
globalObjectUuid := (select uuid from global);
addCustomerPermissions := createPermissions(globalObjectUuid, array ['new-relationship']);
addCustomerPermissions := createPermissions(globalObjectUuid, array ['new-relation']);
call grantPermissionsToRole(globalAdminRoleUuid, addCustomerPermissions);
end;
$$;
@ -169,24 +169,24 @@ $$;
/**
Used by the trigger to prevent the add-customer to current user respectively assumed roles.
*/
create or replace function addHsOfficeRelationshipNotAllowedForCurrentSubjects()
create or replace function addHsOfficeRelationNotAllowedForCurrentSubjects()
returns trigger
language PLPGSQL
as $$
begin
raise exception '[403] new-relationship not permitted for %',
raise exception '[403] new-relation not permitted for %',
array_to_string(currentSubjects(), ';', 'null');
end; $$;
/**
Checks if the user or assumed roles are allowed to create a new customer.
*/
create trigger hs_office_relationship_insert_trigger
create trigger hs_office_relation_insert_trigger
before insert
on hs_office_relationship
on hs_office_relation
for each row
-- TODO.spec: who is allowed to create new relationships
-- TODO.spec: who is allowed to create new relations
when ( not hasAssumedRole() )
execute procedure addHsOfficeRelationshipNotAllowedForCurrentSubjects();
execute procedure addHsOfficeRelationNotAllowedForCurrentSubjects();
--//

View File

@ -2,15 +2,15 @@
-- ============================================================================
--changeset hs-office-relationship-TEST-DATA-GENERATOR:1 endDelimiter:--//
--changeset hs-office-relation-TEST-DATA-GENERATOR:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates a single relationship test record.
Creates a single relation test record.
*/
create or replace procedure createHsOfficeRelationshipTestData(
create or replace procedure createHsOfficeRelationTestData(
holderPersonName varchar,
relationshipType HsOfficeRelationshipType,
relationType HsOfficeRelationType,
anchorPersonTradeName varchar,
contactLabel varchar,
mark varchar default null)
@ -24,7 +24,7 @@ declare
begin
idName := cleanIdentifier( anchorPersonTradeName || '-' || holderPersonName);
currentTask := 'creating relationship test-data ' || idName;
currentTask := 'creating relation test-data ' || idName;
call defineContext(currentTask, null, 'superuser-alex@hostsharing.net', 'global#global.admin');
execute format('set local hsadminng.currentTask to %L', currentTask);
@ -45,20 +45,20 @@ begin
raise exception 'contact "%" not found', contactLabel;
end if;
raise notice 'creating test relationship: %', idName;
raise notice 'creating test relation: %', idName;
raise notice '- using anchor person (%): %', anchorPerson.uuid, anchorPerson;
raise notice '- using holder person (%): %', holderPerson.uuid, holderPerson;
raise notice '- using contact (%): %', contact.uuid, contact;
insert
into hs_office_relationship (uuid, relanchoruuid, relholderuuid, reltype, relmark, contactUuid)
values (uuid_generate_v4(), anchorPerson.uuid, holderPerson.uuid, relationshipType, mark, contact.uuid);
into hs_office_relation (uuid, anchoruuid, holderuuid, type, mark, contactUuid)
values (uuid_generate_v4(), anchorPerson.uuid, holderPerson.uuid, relationType, mark, contact.uuid);
end; $$;
--//
/*
Creates a range of test relationship for mass data generation.
Creates a range of test relation for mass data generation.
*/
create or replace procedure createHsOfficeRelationshipTestData(
create or replace procedure createHsOfficeRelationTestData(
startCount integer, -- count of auto generated rows before the run
endCount integer -- count of auto generated rows after the run
)
@ -72,7 +72,7 @@ begin
select p.* from hs_office_person p where tradeName = intToVarChar(t, 4) into person;
select c.* from hs_office_contact c where c.label = intToVarChar(t, 4) || '#' || t into contact;
call createHsOfficeRelationshipTestData(person.uuid, contact.uuid, 'REPRESENTATIVE');
call createHsOfficeRelationTestData(person.uuid, contact.uuid, 'REPRESENTATIVE');
commit;
end loop;
end; $$;
@ -80,25 +80,25 @@ end; $$;
-- ============================================================================
--changeset hs-office-relationship-TEST-DATA-GENERATION:1 context=dev,tc endDelimiter:--//
--changeset hs-office-relation-TEST-DATA-GENERATION:1 context=dev,tc endDelimiter:--//
-- ----------------------------------------------------------------------------
do language plpgsql $$
begin
call createHsOfficeRelationshipTestData('First GmbH', 'PARTNER', 'Hostsharing eG', 'first contact');
call createHsOfficeRelationshipTestData('Firby', 'REPRESENTATIVE', 'First GmbH', 'first contact');
call createHsOfficeRelationTestData('First GmbH', 'PARTNER', 'Hostsharing eG', 'first contact');
call createHsOfficeRelationTestData('Firby', 'REPRESENTATIVE', 'First GmbH', 'first contact');
call createHsOfficeRelationshipTestData('Second e.K.', 'PARTNER', 'Hostsharing eG', 'second contact');
call createHsOfficeRelationshipTestData('Smith', 'REPRESENTATIVE', 'Second e.K.', 'second contact');
call createHsOfficeRelationTestData('Second e.K.', 'PARTNER', 'Hostsharing eG', 'second contact');
call createHsOfficeRelationTestData('Smith', 'REPRESENTATIVE', 'Second e.K.', 'second contact');
call createHsOfficeRelationshipTestData('Third OHG', 'PARTNER', 'Hostsharing eG', 'third contact');
call createHsOfficeRelationshipTestData('Tucker', 'REPRESENTATIVE', 'Third OHG', 'third contact');
call createHsOfficeRelationTestData('Third OHG', 'PARTNER', 'Hostsharing eG', 'third contact');
call createHsOfficeRelationTestData('Tucker', 'REPRESENTATIVE', 'Third OHG', 'third contact');
call createHsOfficeRelationshipTestData('Fourth eG', 'PARTNER', 'Hostsharing eG', 'fourth contact');
call createHsOfficeRelationshipTestData('Fouler', 'REPRESENTATIVE', 'Third OHG', 'third contact');
call createHsOfficeRelationTestData('Fourth eG', 'PARTNER', 'Hostsharing eG', 'fourth contact');
call createHsOfficeRelationTestData('Fouler', 'REPRESENTATIVE', 'Third OHG', 'third contact');
call createHsOfficeRelationshipTestData('Smith', 'PARTNER', 'Hostsharing eG', 'sixth contact');
call createHsOfficeRelationshipTestData('Smith', 'SUBSCRIBER', 'Third OHG', 'third contact', 'members-announce');
call createHsOfficeRelationTestData('Smith', 'PARTNER', 'Hostsharing eG', 'sixth contact');
call createHsOfficeRelationTestData('Smith', 'SUBSCRIBER', 'Third OHG', 'third contact', 'members-announce');
end;
$$;
--//

View File

@ -33,9 +33,9 @@ create table hs_office_partner
(
uuid uuid unique references RbacObject (uuid) initially deferred,
partnerNumber numeric(5) unique not null,
partnerRoleUuid uuid not null references hs_office_relationship(uuid), -- TODO: delete in after delete trigger
personUuid uuid not null references hs_office_person(uuid), -- TODO: remove, replaced by partnerRoleUuid
contactUuid uuid not null references hs_office_contact(uuid), -- TODO: remove, replaced by partnerRoleUuid
partnerRelUuid uuid not null references hs_office_relation(uuid), -- TODO: delete in after delete trigger
personUuid uuid not null references hs_office_person(uuid), -- TODO: remove, replaced by partnerRelUuid
contactUuid uuid not null references hs_office_contact(uuid), -- TODO: remove, replaced by partnerRelUuid
detailsUuid uuid not null references hs_office_partner_details(uuid) -- deleted in after delete trigger
);
--//

View File

@ -27,8 +27,8 @@ create or replace function hsOfficePartnerRbacRolesTrigger()
language plpgsql
strict as $$
declare
oldPartnerRole hs_office_relationship;
newPartnerRole hs_office_relationship;
oldPartnerRel hs_office_relation;
newPartnerRel hs_office_relation;
oldPerson hs_office_person;
newPerson hs_office_person;
@ -38,7 +38,7 @@ declare
begin
call enterTriggerForObjectUuid(NEW.uuid);
select * from hs_office_relationship as r where r.uuid = NEW.partnerroleuuid into newPartnerRole;
select * from hs_office_relation as r where r.uuid = NEW.partnerReluuid into newPartnerRel;
select * from hs_office_person as p where p.uuid = NEW.personUuid into newPerson;
select * from hs_office_contact as c where c.uuid = NEW.contactUuid into newContact;
@ -58,7 +58,7 @@ begin
incomingSuperRoles => array[
hsOfficePartnerOwner(NEW)],
outgoingSubRoles => array[
hsOfficeRelationshipTenant(newPartnerRole),
hsOfficeRelationTenant(newPartnerRel),
hsOfficePersonTenant(newPerson),
hsOfficeContactTenant(newContact)]
);
@ -67,7 +67,7 @@ begin
hsOfficePartnerAgent(NEW),
incomingSuperRoles => array[
hsOfficePartnerAdmin(NEW),
hsOfficeRelationshipAdmin(newPartnerRole),
hsOfficeRelationAdmin(newPartnerRel),
hsOfficePersonAdmin(newPerson),
hsOfficeContactAdmin(newContact)]
);
@ -77,7 +77,7 @@ begin
incomingSuperRoles => array[
hsOfficePartnerAgent(NEW)],
outgoingSubRoles => array[
hsOfficeRelationshipTenant(newPartnerRole),
hsOfficeRelationTenant(newPartnerRel),
hsOfficePersonGuest(newPerson),
hsOfficeContactGuest(newContact)]
);
@ -118,17 +118,17 @@ begin
elsif TG_OP = 'UPDATE' then
if OLD.partnerRoleUuid <> NEW.partnerRoleUuid then
select * from hs_office_relationship as r where r.uuid = OLD.partnerRoleUuid into oldPartnerRole;
if OLD.partnerRelUuid <> NEW.partnerRelUuid then
select * from hs_office_relation as r where r.uuid = OLD.partnerRelUuid into oldPartnerRel;
call revokeRoleFromRole(hsOfficeRelationshipTenant(oldPartnerRole), hsOfficePartnerAdmin(OLD));
call grantRoleToRole(hsOfficeRelationshipTenant(newPartnerRole), hsOfficePartnerAdmin(NEW));
call revokeRoleFromRole(hsOfficeRelationTenant(oldPartnerRel), hsOfficePartnerAdmin(OLD));
call grantRoleToRole(hsOfficeRelationTenant(newPartnerRel), hsOfficePartnerAdmin(NEW));
call revokeRoleFromRole(hsOfficePartnerAgent(OLD), hsOfficeRelationshipAdmin(oldPartnerRole));
call grantRoleToRole(hsOfficePartnerAgent(NEW), hsOfficeRelationshipAdmin(newPartnerRole));
call revokeRoleFromRole(hsOfficePartnerAgent(OLD), hsOfficeRelationAdmin(oldPartnerRel));
call grantRoleToRole(hsOfficePartnerAgent(NEW), hsOfficeRelationAdmin(newPartnerRel));
call revokeRoleFromRole(hsOfficeRelationshipGuest(oldPartnerRole), hsOfficePartnerTenant(OLD));
call grantRoleToRole(hsOfficeRelationshipGuest(newPartnerRole), hsOfficePartnerTenant(NEW));
call revokeRoleFromRole(hsOfficeRelationGuest(oldPartnerRel), hsOfficePartnerTenant(OLD));
call grantRoleToRole(hsOfficeRelationGuest(newPartnerRel), hsOfficePartnerTenant(NEW));
end if;
if OLD.personUuid <> NEW.personUuid then
@ -202,7 +202,7 @@ call generateRbacIdentityViewFromProjection('hs_office_partner', $idName$
call generateRbacRestrictedView('hs_office_partner',
'(select idName from hs_office_person_iv p where p.uuid = target.personUuid)',
$updates$
partnerRoleUuid = new.partnerRoleUuid,
partnerRelUuid = new.partnerRelUuid,
personUuid = new.personUuid,
contactUuid = new.contactUuid
$updates$);

View File

@ -18,7 +18,7 @@ declare
currentTask varchar;
idName varchar;
mandantPerson hs_office_person;
partnerRole hs_office_relationship;
partnerRel hs_office_relation;
relatedPerson hs_office_person;
relatedContact hs_office_contact;
relatedDetailsUuid uuid;
@ -42,16 +42,16 @@ begin
where c.label = contactLabel
into relatedContact;
select r.* from hs_office_relationship r
where r.reltype = 'PARTNER'
and r.relanchoruuid = mandantPerson.uuid and r.relholderuuid = relatedPerson.uuid
into partnerRole;
if partnerRole is null then
raise exception 'partnerRole "%"-"%" not found', mandantPerson.tradename, partnerPersonName;
select r.* from hs_office_relation r
where r.type = 'PARTNER'
and r.anchoruuid = mandantPerson.uuid and r.holderuuid = relatedPerson.uuid
into partnerRel;
if partnerRel is null then
raise exception 'partnerRel "%"-"%" not found', mandantPerson.tradename, partnerPersonName;
end if;
raise notice 'creating test partner: %', idName;
raise notice '- using partnerRole (%): %', partnerRole.uuid, partnerRole;
raise notice '- using partnerRel (%): %', partnerRel.uuid, partnerRel;
raise notice '- using person (%): %', relatedPerson.uuid, relatedPerson;
raise notice '- using contact (%): %', relatedContact.uuid, relatedContact;
@ -68,8 +68,8 @@ begin
end if;
insert
into hs_office_partner (uuid, partnerNumber, partnerRoleUuid, personuuid, contactuuid, detailsUuid)
values (uuid_generate_v4(), partnerNumber, partnerRole.uuid, relatedPerson.uuid, relatedContact.uuid, relatedDetailsUuid);
into hs_office_partner (uuid, partnerNumber, partnerRelUuid, personuuid, contactuuid, detailsUuid)
values (uuid_generate_v4(), partnerNumber, partnerRel.uuid, relatedPerson.uuid, relatedContact.uuid, relatedDetailsUuid);
end; $$;
--//

View File

@ -68,11 +68,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<>();
@ -220,17 +220,17 @@ public class ImportOfficeData extends ContextBasedTest {
@Test
@Order(1021)
void buildDebitorRelationships() {
void buildDebitorRelations() {
debitors.forEach( (id, debitor) -> {
final var debitorRel = HsOfficeRelationshipEntity.builder()
.relType(HsOfficeRelationshipType.DEBITOR)
.relAnchor(debitor.getPartner().getPartnerRole().getRelHolder())
.relHolder(debitor.getPartner().getPartnerRole().getRelHolder()) // just 1 debitor/partner in legacy hsadmin
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.getRelAnchor() != null && debitorRel.getRelHolder() != null &&
if (debitorRel.getAnchor() != null && debitorRel.getHolder() != null &&
debitorRel.getContact() != null ) {
relationships.put(relationshipId++, debitorRel);
relations.put(relationId++, debitorRel);
}
});
}
@ -288,28 +288,28 @@ public class ImportOfficeData extends ContextBasedTest {
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 '),
2000017=rel(relAnchor='NP Mellies, Michael', relType='DEBITOR', relHolder='NP Mellies, Michael', contact='Herr Michael Mellies '),
2000018=rel(relAnchor='LP JM GmbH', relType='DEBITOR', relHolder='LP JM GmbH', contact='Frau Dr. Jenny Meyer-Billing , JM GmbH'),
2000019=rel(relAnchor='?? Test PS', relType='DEBITOR', relHolder='?? Test PS', contact='Petra Schmidt , Test PS')
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')
}
""");
}
@ -419,20 +419,20 @@ public class ImportOfficeData extends ContextBasedTest {
@Test
@Order(2009)
void removeEmptyRelationships() {
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
@ -495,7 +495,7 @@ public class ImportOfficeData extends ContextBasedTest {
jpaAttempt.transacted(() -> {
context(rbacSuperuser);
relationships.forEach(this::persist);
relations.forEach(this::persist);
}).assertSuccessful();
jpaAttempt.transacted(() -> {
@ -572,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();
@ -676,18 +676,18 @@ 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();
@ -845,7 +845,7 @@ public class ImportOfficeData extends ContextBasedTest {
final var debitor = debitors.get(bpId);
final var partnerPerson = partner.getPerson();
if (containsPartnerRole(rec)) {
if (containsPartnerRel(rec)) {
initPerson(partner.getPerson(), rec);
}
@ -859,46 +859,46 @@ public class ImportOfficeData extends ContextBasedTest {
final var contact = HsOfficeContactEntity.builder().build();
initContact(contact, rec);
if (containsPartnerRole(rec)) {
if (containsPartnerRel(rec)) {
assertThat(partner.getContact()).isNull();
partner.setContact(contact);
partner.getPartnerRole().setContact(contact);
partner.getPartnerRel().setContact(contact);
}
if (containsRole(rec, "billing")) {
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.getPerson();
if (relationships.values().stream()
.filter(rel -> rel.getRelAnchor() == partnerPerson && rel.getRelType() == HsOfficeRelationshipType.REPRESENTATIVE)
if (relations.values().stream()
.filter(rel -> rel.getAnchor() == partnerPerson && rel.getType() == HsOfficeRelationType.REPRESENTATIVE)
.findFirst().isEmpty()) {
contractualMissing.add(partner.getPartnerNumber());
}
@ -909,22 +909,22 @@ public class ImportOfficeData extends ContextBasedTest {
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

@ -7,9 +7,9 @@ import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipRepository;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipType;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRepository;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType;
import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup;
import net.hostsharing.test.Accepts;
import net.hostsharing.test.JpaAttempt;
@ -41,7 +41,7 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu
HsOfficePartnerRepository partnerRepo;
@Autowired
HsOfficeRelationshipRepository relationshipRepository;
HsOfficeRelationRepository relationRepository;
@Autowired
HsOfficePersonRepository personRepo;
@ -102,9 +102,9 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu
.body("""
{
"partnerNumber": "20002",
"partnerRole": {
"relAnchorUuid": "%s",
"relHolderUuid": "%s",
"partnerRel": {
"anchorUuid": "%s",
"holderUuid": "%s",
"contactUuid": "%s"
},
"personUuid": "%s",
@ -155,9 +155,9 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu
.body("""
{
"partnerNumber": "20003",
"partnerRole": {
"relAnchorUuid": "%s",
"relHolderUuid": "%s",
"partnerRel": {
"anchorUuid": "%s",
"holderUuid": "%s",
"contactUuid": "%s"
},
"personUuid": "%s",
@ -193,9 +193,9 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu
.body("""
{
"partnerNumber": "20004",
"partnerRole": {
"relAnchorUuid": "%s",
"relHolderUuid": "%s",
"partnerRel": {
"anchorUuid": "%s",
"holderUuid": "%s",
"contactUuid": "%s"
},
"personUuid": "%s",
@ -413,7 +413,7 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu
// then the given partner is gone
assertThat(partnerRepo.findByUuid(givenPartner.getUuid())).isEmpty();
assertThat(relationshipRepository.findByUuid(givenPartner.getPartnerRole().getUuid())).isEmpty();
assertThat(relationRepository.findByUuid(givenPartner.getPartnerRel().getUuid())).isEmpty();
}
@Test
@ -465,15 +465,15 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu
final var givenPerson = personRepo.findPersonByOptionalNameLike("Erben Bessler").get(0);
final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth contact").get(0);
final var partnerRole = new HsOfficeRelationshipEntity();
partnerRole.setRelType(HsOfficeRelationshipType.PARTNER);
partnerRole.setRelAnchor(givenMandantPerson);
partnerRole.setRelHolder(givenPerson);
partnerRole.setContact(givenContact);
em.persist(partnerRole);
final var partnerRel = new HsOfficeRelationEntity();
partnerRel.setType(HsOfficeRelationType.PARTNER);
partnerRel.setAnchor(givenMandantPerson);
partnerRel.setHolder(givenPerson);
partnerRel.setContact(givenContact);
em.persist(partnerRel);
final var newPartner = HsOfficePartnerEntity.builder()
.partnerRole(partnerRole)
.partnerRel(partnerRel)
.partnerNumber(partnerNumber)
.person(givenPerson)
.contact(givenContact)
@ -492,6 +492,6 @@ class HsOfficePartnerControllerAcceptanceTest extends ContextBasedTestWithCleanu
cleanupAllNew(HsOfficePartnerEntity.class);
// TODO: should not be necessary anymore, once it's deleted via after delete trigger
cleanupAllNew(HsOfficeRelationshipEntity.class);
cleanupAllNew(HsOfficeRelationEntity.class);
}
}

View File

@ -3,8 +3,8 @@ package net.hostsharing.hsadminng.hs.office.partner;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipRepository;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRepository;
import net.hostsharing.hsadminng.mapper.Mapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
@ -54,7 +54,7 @@ class HsOfficePartnerControllerRestTest {
HsOfficePartnerRepository partnerRepo;
@MockBean
HsOfficeRelationshipRepository relationshipRepo;
HsOfficeRelationRepository relationRepo;
@MockBean
EntityManager em;
@ -100,9 +100,9 @@ class HsOfficePartnerControllerRestTest {
.content("""
{
"partnerNumber": "20002",
"partnerRole": {
"relAnchorUuid": "%s",
"relHolderUuid": "%s",
"partnerRel": {
"anchorUuid": "%s",
"holderUuid": "%s",
"contactUuid": "%s"
},
"personUuid": "%s",
@ -137,9 +137,9 @@ class HsOfficePartnerControllerRestTest {
.content("""
{
"partnerNumber": "20002",
"partnerRole": {
"relAnchorUuid": "%s",
"relHolderUuid": "%s",
"partnerRel": {
"anchorUuid": "%s",
"holderUuid": "%s",
"contactUuid": "%s"
},
"personUuid": "%s",
@ -175,11 +175,11 @@ class HsOfficePartnerControllerRestTest {
when(partnerRepo.findByUuid(givenPartnerUuid)).thenReturn(Optional.of(partnerMock));
when(partnerRepo.deleteByUuid(givenPartnerUuid)).thenReturn(0);
final UUID givenRelationshipUuid = UUID.randomUUID();
when(partnerMock.getPartnerRole()).thenReturn(HsOfficeRelationshipEntity.builder()
.uuid(givenRelationshipUuid)
final UUID givenRelationUuid = UUID.randomUUID();
when(partnerMock.getPartnerRel()).thenReturn(HsOfficeRelationEntity.builder()
.uuid(givenRelationUuid)
.build());
when(relationshipRepo.deleteByUuid(givenRelationshipUuid)).thenReturn(0);
when(relationRepo.deleteByUuid(givenRelationUuid)).thenReturn(0);
// when
mockMvc.perform(MockMvcRequestBuilders
@ -193,18 +193,18 @@ class HsOfficePartnerControllerRestTest {
}
@Test
void respondBadRequest_ifRelationshipCannotBeDeleted() throws Exception {
void respondBadRequest_ifRelationCannotBeDeleted() throws Exception {
// given
final UUID givenPartnerUuid = UUID.randomUUID();
when(partnerRepo.findByUuid(givenPartnerUuid)).thenReturn(Optional.of(partnerMock));
when(partnerRepo.deleteByUuid(givenPartnerUuid)).thenReturn(1);
when(relationshipRepo.deleteByUuid(any())).thenReturn(0);
when(relationRepo.deleteByUuid(any())).thenReturn(0);
final UUID givenRelationshipUuid = UUID.randomUUID();
when(partnerMock.getPartnerRole()).thenReturn(HsOfficeRelationshipEntity.builder()
.uuid(givenRelationshipUuid)
final UUID givenRelationUuid = UUID.randomUUID();
when(partnerMock.getPartnerRel()).thenReturn(HsOfficeRelationEntity.builder()
.uuid(givenRelationUuid)
.build());
when(relationshipRepo.deleteByUuid(givenRelationshipUuid)).thenReturn(0);
when(relationRepo.deleteByUuid(givenRelationUuid)).thenReturn(0);
// when
mockMvc.perform(MockMvcRequestBuilders

View File

@ -3,9 +3,9 @@ package net.hostsharing.hsadminng.hs.office.partner;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipRepository;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipType;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRepository;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType;
import net.hostsharing.hsadminng.hs.office.test.ContextBasedTestWithCleanup;
import net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantRepository;
import net.hostsharing.hsadminng.rbac.rbacrole.RawRbacRoleRepository;
@ -43,7 +43,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
HsOfficePartnerRepository partnerRepo;
@Autowired
HsOfficeRelationshipRepository relationshipRepo;
HsOfficeRelationRepository relationRepo;
@Autowired
HsOfficePersonRepository personRepo;
@ -80,19 +80,19 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
final var givenPartnerPerson = personRepo.findPersonByOptionalNameLike("First GmbH").get(0);
final var givenContact = contactRepo.findContactByOptionalLabelLike("first contact").get(0);
final var partnerRole = HsOfficeRelationshipEntity.builder()
.relHolder(givenPartnerPerson)
.relType(HsOfficeRelationshipType.PARTNER)
.relAnchor(givenMandantorPerson)
final var partnerRel = HsOfficeRelationEntity.builder()
.holder(givenPartnerPerson)
.type(HsOfficeRelationType.PARTNER)
.anchor(givenMandantorPerson)
.contact(givenContact)
.build();
relationshipRepo.save(partnerRole);
relationRepo.save(partnerRel);
// when
final var result = attempt(em, () -> {
final var newPartner = HsOfficePartnerEntity.builder()
.partnerNumber(20031)
.partnerRole(partnerRole)
.partnerRel(partnerRel)
.person(givenPartnerPerson)
.contact(givenContact)
.details(HsOfficePartnerDetailsEntity.builder()
@ -125,17 +125,17 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth contact").get(0);
final var givenMandantPerson = personRepo.findPersonByOptionalNameLike("Hostsharing eG").get(0);
final var newRelationship = HsOfficeRelationshipEntity.builder()
.relHolder(givenPartnerPerson)
.relType(HsOfficeRelationshipType.PARTNER)
.relAnchor(givenMandantPerson)
final var newRelation = HsOfficeRelationEntity.builder()
.holder(givenPartnerPerson)
.type(HsOfficeRelationType.PARTNER)
.anchor(givenMandantPerson)
.contact(givenContact)
.build();
relationshipRepo.save(newRelationship);
relationRepo.save(newRelation);
final var newPartner = HsOfficePartnerEntity.builder()
.partnerNumber(20032)
.partnerRole(newRelationship)
.partnerRel(newRelation)
.person(givenPartnerPerson)
.contact(givenContact)
.details(HsOfficePartnerDetailsEntity.builder().build())
@ -146,9 +146,9 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
// then
assertThat(distinctRoleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from(
initialRoleNames,
"hs_office_relationship#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler.admin",
"hs_office_relationship#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler.owner",
"hs_office_relationship#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler.tenant",
"hs_office_relation#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler.admin",
"hs_office_relation#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler.owner",
"hs_office_relation#HostsharingeG-with-PARTNER-ErbenBesslerMelBessler.tenant",
"hs_office_partner#20032:ErbenBesslerMelBessler-fourthcontact.admin",
"hs_office_partner#20032:ErbenBesslerMelBessler-fourthcontact.agent",
"hs_office_partner#20032:ErbenBesslerMelBessler-fourthcontact.owner",
@ -160,25 +160,25 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
.map(s -> s.replace("hs_office_", ""))
.containsExactlyInAnyOrder(distinct(fromFormatted(
initialGrantNames,
// relationship - TODO: check and cleanup
// relation - TODO: check and cleanup
"{ grant role person#HostsharingeG.tenant to role person#EBess.admin by system and assume }",
"{ grant role person#EBess.tenant to role person#HostsharingeG.admin by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role partner#20032:EBess-4th.admin by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role partner#20032:EBess-4th.tenant by system and assume }",
"{ grant role partner#20032:EBess-4th.agent to role relationship#HostsharingeG-with-PARTNER-EBess.admin by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.owner to role global#global.admin by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role contact#4th.admin by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role person#EBess.admin by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.owner to role person#HostsharingeG.admin by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role person#HostsharingeG.admin by system and assume }",
"{ grant perm UPDATE on relationship#HostsharingeG-with-PARTNER-EBess to role relationship#HostsharingeG-with-PARTNER-EBess.admin by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.tenant to role relationship#HostsharingeG-with-PARTNER-EBess.admin by system and assume }",
"{ grant perm DELETE on relationship#HostsharingeG-with-PARTNER-EBess to role relationship#HostsharingeG-with-PARTNER-EBess.owner by system and assume }",
"{ grant role relationship#HostsharingeG-with-PARTNER-EBess.admin to role relationship#HostsharingeG-with-PARTNER-EBess.owner by system and assume }",
"{ grant perm SELECT on relationship#HostsharingeG-with-PARTNER-EBess to role relationship#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }",
"{ grant role contact#4th.tenant to role relationship#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }",
"{ grant role person#EBess.tenant to role relationship#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }",
"{ grant role person#HostsharingeG.tenant to role relationship#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }",
"{ grant role relation#HostsharingeG-with-PARTNER-EBess.tenant to role partner#20032:EBess-4th.admin by system and assume }",
"{ grant role relation#HostsharingeG-with-PARTNER-EBess.tenant to role partner#20032:EBess-4th.tenant by system and assume }",
"{ grant role partner#20032:EBess-4th.agent to role relation#HostsharingeG-with-PARTNER-EBess.admin by system and assume }",
"{ grant role relation#HostsharingeG-with-PARTNER-EBess.owner to role global#global.admin by system and assume }",
"{ grant role relation#HostsharingeG-with-PARTNER-EBess.tenant to role contact#4th.admin by system and assume }",
"{ grant role relation#HostsharingeG-with-PARTNER-EBess.tenant to role person#EBess.admin by system and assume }",
"{ grant role relation#HostsharingeG-with-PARTNER-EBess.owner to role person#HostsharingeG.admin by system and assume }",
"{ grant role relation#HostsharingeG-with-PARTNER-EBess.tenant to role person#HostsharingeG.admin by system and assume }",
"{ grant perm UPDATE on relation#HostsharingeG-with-PARTNER-EBess to role relation#HostsharingeG-with-PARTNER-EBess.admin by system and assume }",
"{ grant role relation#HostsharingeG-with-PARTNER-EBess.tenant to role relation#HostsharingeG-with-PARTNER-EBess.admin by system and assume }",
"{ grant perm DELETE on relation#HostsharingeG-with-PARTNER-EBess to role relation#HostsharingeG-with-PARTNER-EBess.owner by system and assume }",
"{ grant role relation#HostsharingeG-with-PARTNER-EBess.admin to role relation#HostsharingeG-with-PARTNER-EBess.owner by system and assume }",
"{ grant perm SELECT on relation#HostsharingeG-with-PARTNER-EBess to role relation#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }",
"{ grant role contact#4th.tenant to role relation#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }",
"{ grant role person#EBess.tenant to role relation#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }",
"{ grant role person#HostsharingeG.tenant to role relation#HostsharingeG-with-PARTNER-EBess.tenant by system and assume }",
// owner
"{ grant perm DELETE on partner#20032:EBess-4th to role partner#20032:EBess-4th.owner by system and assume }",
@ -426,15 +426,15 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
// when
final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net");
// TODO: should deleting a partner automatically delete the PARTNER relationship? (same for debitor)
// TODO: should deleting a partner automatically delete the PARTNER relation? (same for debitor)
// TODO: why did the test cleanup check does not notice this, if missing?
return partnerRepo.deleteByUuid(givenPartner.getUuid()) +
relationshipRepo.deleteByUuid(givenPartner.getPartnerRole().getUuid());
relationRepo.deleteByUuid(givenPartner.getPartnerRel().getUuid());
});
// then
result.assertSuccessful();
assertThat(result.returnedValue()).isEqualTo(2); // partner+relationship
assertThat(result.returnedValue()).isEqualTo(2); // partner+relation
assertThat(distinctRoleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(initialRoleNames);
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(initialGrantNames);
}
@ -466,17 +466,17 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
final var givenPartnerPerson = personRepo.findPersonByOptionalNameLike(person).get(0);
final var givenContact = contactRepo.findContactByOptionalLabelLike(contact).get(0);
final var partnerRole = HsOfficeRelationshipEntity.builder()
.relHolder(givenPartnerPerson)
.relType(HsOfficeRelationshipType.PARTNER)
.relAnchor(givenMandantorPerson)
final var partnerRel = HsOfficeRelationEntity.builder()
.holder(givenPartnerPerson)
.type(HsOfficeRelationType.PARTNER)
.anchor(givenMandantorPerson)
.contact(givenContact)
.build();
relationshipRepo.save(partnerRole);
relationRepo.save(partnerRel);
final var newPartner = HsOfficePartnerEntity.builder()
.partnerNumber(partnerNumber)
.partnerRole(partnerRole)
.partnerRel(partnerRel)
.person(givenPartnerPerson)
.contact(givenContact)
.details(HsOfficePartnerDetailsEntity.builder().build())
@ -502,7 +502,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTestWithClean
void cleanup() {
cleanupAllNew(HsOfficePartnerDetailsEntity.class); // TODO: should not be necessary
cleanupAllNew(HsOfficePartnerEntity.class);
cleanupAllNew(HsOfficeRelationshipEntity.class);
cleanupAllNew(HsOfficeRelationEntity.class);
}
private String[] distinct(final String[] strings) {

View File

@ -1,4 +1,4 @@
package net.hostsharing.hsadminng.hs.office.relationship;
package net.hostsharing.hsadminng.hs.office.relation;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
@ -7,7 +7,7 @@ import net.hostsharing.test.Accepts;
import net.hostsharing.hsadminng.HsadminNgApplication;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationshipTypeResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeRelationTypeResource;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
import net.hostsharing.test.JpaAttempt;
import org.json.JSONException;
@ -31,7 +31,7 @@ import static org.hamcrest.Matchers.startsWith;
classes = { HsadminNgApplication.class, JpaAttempt.class }
)
@Transactional
class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithCleanup {
class HsOfficeRelationControllerAcceptanceTest extends ContextBasedTestWithCleanup {
public static final UUID GIVEN_NON_EXISTING_HOLDER_PERSON_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000");
@LocalServerPort
@ -44,7 +44,7 @@ class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithC
Context contextMock;
@Autowired
HsOfficeRelationshipRepository relationshipRepo;
HsOfficeRelationRepository relationRepo;
@Autowired
HsOfficePersonRepository personRepo;
@ -56,11 +56,11 @@ class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithC
JpaAttempt jpaAttempt;
@Nested
@Accepts({ "Relationship:F(Find)" })
class ListRelationships {
@Accepts({ "Relation:F(Find)" })
class ListRelations {
@Test
void globalAdmin_withoutAssumedRoles_canViewAllRelationshipsOfGivenPersonAndType_ifNoCriteriaGiven() throws JSONException {
void globalAdmin_withoutAssumedRoles_canViewAllRelationsOfGivenPersonAndType_ifNoCriteriaGiven() throws JSONException {
// given
context.define("superuser-alex@hostsharing.net");
@ -71,45 +71,45 @@ class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithC
.header("current-user", "superuser-alex@hostsharing.net")
.port(port)
.when()
.get("http://localhost/api/hs/office/relationships?personUuid=%s&relationshipType=%s"
.formatted(givenPerson.getUuid(), HsOfficeRelationshipTypeResource.PARTNER))
.get("http://localhost/api/hs/office/relations?personUuid=%s&relationType=%s"
.formatted(givenPerson.getUuid(), HsOfficeRelationTypeResource.PARTNER))
.then().log().all().assertThat()
.statusCode(200)
.contentType("application/json")
.body("", lenientlyEquals("""
[
{
"relAnchor": { "personType": "LEGAL_PERSON", "tradeName": "Hostsharing eG" },
"relHolder": { "personType": "LEGAL_PERSON", "tradeName": "First GmbH" },
"relType": "PARTNER",
"relMark": null,
"anchor": { "personType": "LEGAL_PERSON", "tradeName": "Hostsharing eG" },
"holder": { "personType": "LEGAL_PERSON", "tradeName": "First GmbH" },
"type": "PARTNER",
"mark": null,
"contact": { "label": "first contact" }
},
{
"relAnchor": { "personType": "LEGAL_PERSON", "tradeName": "Hostsharing eG" },
"relHolder": { "personType": "INCORPORATED_FIRM", "tradeName": "Fourth eG" },
"relType": "PARTNER",
"anchor": { "personType": "LEGAL_PERSON", "tradeName": "Hostsharing eG" },
"holder": { "personType": "INCORPORATED_FIRM", "tradeName": "Fourth eG" },
"type": "PARTNER",
"contact": { "label": "fourth contact" }
},
{
"relAnchor": { "personType": "LEGAL_PERSON", "tradeName": "Hostsharing eG" },
"relHolder": { "personType": "LEGAL_PERSON", "tradeName": "Second e.K.", "givenName": "Peter", "familyName": "Smith" },
"relType": "PARTNER",
"relMark": null,
"anchor": { "personType": "LEGAL_PERSON", "tradeName": "Hostsharing eG" },
"holder": { "personType": "LEGAL_PERSON", "tradeName": "Second e.K.", "givenName": "Peter", "familyName": "Smith" },
"type": "PARTNER",
"mark": null,
"contact": { "label": "second contact" }
},
{
"relAnchor": { "personType": "LEGAL_PERSON", "tradeName": "Hostsharing eG" },
"relHolder": { "personType": "NATURAL_PERSON", "givenName": "Peter", "familyName": "Smith" },
"relType": "PARTNER",
"relMark": null,
"anchor": { "personType": "LEGAL_PERSON", "tradeName": "Hostsharing eG" },
"holder": { "personType": "NATURAL_PERSON", "givenName": "Peter", "familyName": "Smith" },
"type": "PARTNER",
"mark": null,
"contact": { "label": "sixth contact" }
},
{
"relAnchor": { "personType": "LEGAL_PERSON", "tradeName": "Hostsharing eG" },
"relHolder": { "personType": "INCORPORATED_FIRM", "tradeName": "Third OHG" },
"relType": "PARTNER",
"relMark": null,
"anchor": { "personType": "LEGAL_PERSON", "tradeName": "Hostsharing eG" },
"holder": { "personType": "INCORPORATED_FIRM", "tradeName": "Third OHG" },
"type": "PARTNER",
"mark": null,
"contact": { "label": "third contact" }
}
]
@ -119,11 +119,11 @@ class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithC
}
@Nested
@Accepts({ "Relationship:C(Create)" })
class AddRelationship {
@Accepts({ "Relation:C(Create)" })
class AddRelation {
@Test
void globalAdmin_withoutAssumedRole_canAddRelationship() {
void globalAdmin_withoutAssumedRole_canAddRelation() {
context.define("superuser-alex@hostsharing.net");
final var givenAnchorPerson = personRepo.findPersonByOptionalNameLike("Third").get(0);
@ -136,38 +136,38 @@ class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithC
.contentType(ContentType.JSON)
.body("""
{
"relType": "%s",
"relAnchorUuid": "%s",
"relHolderUuid": "%s",
"type": "%s",
"anchorUuid": "%s",
"holderUuid": "%s",
"contactUuid": "%s"
}
""".formatted(
HsOfficeRelationshipTypeResource.ACCOUNTING,
HsOfficeRelationTypeResource.DEBITOR,
givenAnchorPerson.getUuid(),
givenHolderPerson.getUuid(),
givenContact.getUuid()))
.port(port)
.when()
.post("http://localhost/api/hs/office/relationships")
.post("http://localhost/api/hs/office/relations")
.then().log().all().assertThat()
.statusCode(201)
.contentType(ContentType.JSON)
.body("uuid", isUuidValid())
.body("relType", is("ACCOUNTING"))
.body("relAnchor.tradeName", is("Third OHG"))
.body("relHolder.givenName", is("Paul"))
.body("type", is("DEBITOR"))
.body("anchor.tradeName", is("Third OHG"))
.body("holder.givenName", is("Paul"))
.body("contact.label", is("second contact"))
.header("Location", startsWith("http://localhost"))
.extract().header("Location"); // @formatter:on
// finally, the new relationship can be accessed under the generated UUID
final var newUserUuid = toCleanup(HsOfficeRelationshipEntity.class, UUID.fromString(
// finally, the new relation can be accessed under the generated UUID
final var newUserUuid = toCleanup(HsOfficeRelationEntity.class, UUID.fromString(
location.substring(location.lastIndexOf('/') + 1)));
assertThat(newUserUuid).isNotNull();
}
@Test
void globalAdmin_canNotAddRelationship_ifAnchorPersonDoesNotExist() {
void globalAdmin_canNotAddRelation_ifAnchorPersonDoesNotExist() {
context.define("superuser-alex@hostsharing.net");
final var givenAnchorPersonUuid = GIVEN_NON_EXISTING_HOLDER_PERSON_UUID;
@ -180,27 +180,27 @@ class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithC
.contentType(ContentType.JSON)
.body("""
{
"relType": "%s",
"relAnchorUuid": "%s",
"relHolderUuid": "%s",
"type": "%s",
"anchorUuid": "%s",
"holderUuid": "%s",
"contactUuid": "%s"
}
""".formatted(
HsOfficeRelationshipTypeResource.ACCOUNTING,
HsOfficeRelationTypeResource.DEBITOR,
givenAnchorPersonUuid,
givenHolderPerson.getUuid(),
givenContact.getUuid()))
.port(port)
.when()
.post("http://localhost/api/hs/office/relationships")
.post("http://localhost/api/hs/office/relations")
.then().log().all().assertThat()
.statusCode(404)
.body("message", is("cannot find relAnchorUuid " + GIVEN_NON_EXISTING_HOLDER_PERSON_UUID));
.body("message", is("cannot find anchorUuid " + GIVEN_NON_EXISTING_HOLDER_PERSON_UUID));
// @formatter:on
}
@Test
void globalAdmin_canNotAddRelationship_ifHolderPersonDoesNotExist() {
void globalAdmin_canNotAddRelation_ifHolderPersonDoesNotExist() {
context.define("superuser-alex@hostsharing.net");
final var givenAnchorPerson = personRepo.findPersonByOptionalNameLike("Third").get(0);
@ -212,27 +212,27 @@ class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithC
.contentType(ContentType.JSON)
.body("""
{
"relType": "%s",
"relAnchorUuid": "%s",
"relHolderUuid": "%s",
"type": "%s",
"anchorUuid": "%s",
"holderUuid": "%s",
"contactUuid": "%s"
}
""".formatted(
HsOfficeRelationshipTypeResource.ACCOUNTING,
HsOfficeRelationTypeResource.DEBITOR,
givenAnchorPerson.getUuid(),
GIVEN_NON_EXISTING_HOLDER_PERSON_UUID,
givenContact.getUuid()))
.port(port)
.when()
.post("http://localhost/api/hs/office/relationships")
.post("http://localhost/api/hs/office/relations")
.then().log().all().assertThat()
.statusCode(404)
.body("message", is("cannot find relHolderUuid " + GIVEN_NON_EXISTING_HOLDER_PERSON_UUID));
.body("message", is("cannot find holderUuid " + GIVEN_NON_EXISTING_HOLDER_PERSON_UUID));
// @formatter:on
}
@Test
void globalAdmin_canNotAddRelationship_ifContactDoesNotExist() {
void globalAdmin_canNotAddRelation_ifContactDoesNotExist() {
context.define("superuser-alex@hostsharing.net");
final var givenAnchorPerson = personRepo.findPersonByOptionalNameLike("Third").get(0);
@ -245,19 +245,19 @@ class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithC
.contentType(ContentType.JSON)
.body("""
{
"relType": "%s",
"relAnchorUuid": "%s",
"relHolderUuid": "%s",
"type": "%s",
"anchorUuid": "%s",
"holderUuid": "%s",
"contactUuid": "%s"
}
""".formatted(
HsOfficeRelationshipTypeResource.ACCOUNTING,
HsOfficeRelationTypeResource.DEBITOR,
givenAnchorPerson.getUuid(),
givenHolderPerson.getUuid(),
givenContactUuid))
.port(port)
.when()
.post("http://localhost/api/hs/office/relationships")
.post("http://localhost/api/hs/office/relations")
.then().log().all().assertThat()
.statusCode(404)
.body("message", is("cannot find contactUuid 00000000-0000-0000-0000-000000000000"));
@ -266,97 +266,97 @@ class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithC
}
@Nested
@Accepts({ "Relationship:R(Read)" })
class GetRelationship {
@Accepts({ "Relation:R(Read)" })
class GetRelation {
@Test
void globalAdmin_withoutAssumedRole_canGetArbitraryRelationship() {
void globalAdmin_withoutAssumedRole_canGetArbitraryRelation() {
context.define("superuser-alex@hostsharing.net");
final UUID givenRelationshipUuid = findRelationship("First", "Firby").getUuid();
final UUID givenRelationUuid = findRelation("First", "Firby").getUuid();
RestAssured // @formatter:off
.given()
.header("current-user", "superuser-alex@hostsharing.net")
.port(port)
.when()
.get("http://localhost/api/hs/office/relationships/" + givenRelationshipUuid)
.get("http://localhost/api/hs/office/relations/" + givenRelationUuid)
.then().log().body().assertThat()
.statusCode(200)
.contentType("application/json")
.body("", lenientlyEquals("""
{
"relAnchor": { "tradeName": "First GmbH" },
"relHolder": { "familyName": "Firby" },
"anchor": { "tradeName": "First GmbH" },
"holder": { "familyName": "Firby" },
"contact": { "label": "first contact" }
}
""")); // @formatter:on
}
@Test
@Accepts({ "Relationship:X(Access Control)" })
void normalUser_canNotGetUnrelatedRelationship() {
@Accepts({ "Relation:X(Access Control)" })
void normalUser_canNotGetUnrelatedRelation() {
context.define("superuser-alex@hostsharing.net");
final UUID givenRelationshipUuid = findRelationship("First", "Firby").getUuid();
final UUID givenRelationUuid = findRelation("First", "Firby").getUuid();
RestAssured // @formatter:off
.given()
.header("current-user", "selfregistered-user-drew@hostsharing.org")
.port(port)
.when()
.get("http://localhost/api/hs/office/relationships/" + givenRelationshipUuid)
.get("http://localhost/api/hs/office/relations/" + givenRelationUuid)
.then().log().body().assertThat()
.statusCode(404); // @formatter:on
}
@Test
@Accepts({ "Relationship:X(Access Control)" })
void contactAdminUser_canGetRelatedRelationship() {
@Accepts({ "Relation:X(Access Control)" })
void contactAdminUser_canGetRelatedRelation() {
context.define("superuser-alex@hostsharing.net");
final var givenRelationship = findRelationship("First", "Firby");
assertThat(givenRelationship.getContact().getLabel()).isEqualTo("first contact");
final var givenRelation = findRelation("First", "Firby");
assertThat(givenRelation.getContact().getLabel()).isEqualTo("first contact");
RestAssured // @formatter:off
.given()
.header("current-user", "contact-admin@firstcontact.example.com")
.port(port)
.when()
.get("http://localhost/api/hs/office/relationships/" + givenRelationship.getUuid())
.get("http://localhost/api/hs/office/relations/" + givenRelation.getUuid())
.then().log().body().assertThat()
.statusCode(200)
.contentType("application/json")
.body("", lenientlyEquals("""
{
"relAnchor": { "tradeName": "First GmbH" },
"relHolder": { "familyName": "Firby" },
"anchor": { "tradeName": "First GmbH" },
"holder": { "familyName": "Firby" },
"contact": { "label": "first contact" }
}
""")); // @formatter:on
}
}
private HsOfficeRelationshipEntity findRelationship(
private HsOfficeRelationEntity findRelation(
final String anchorPersonName,
final String holderPersoneName) {
final var anchorPersonUuid = personRepo.findPersonByOptionalNameLike(anchorPersonName).get(0).getUuid();
final var holderPersonUuid = personRepo.findPersonByOptionalNameLike(holderPersoneName).get(0).getUuid();
final var givenRelationship = relationshipRepo
.findRelationshipRelatedToPersonUuid(anchorPersonUuid)
final var givenRelation = relationRepo
.findRelationRelatedToPersonUuid(anchorPersonUuid)
.stream()
.filter(r -> r.getRelHolder().getUuid().equals(holderPersonUuid))
.filter(r -> r.getHolder().getUuid().equals(holderPersonUuid))
.findFirst().orElseThrow();
return givenRelationship;
return givenRelation;
}
@Nested
@Accepts({ "Relationship:U(Update)" })
class PatchRelationship {
@Accepts({ "Relation:U(Update)" })
class PatchRelation {
@Test
void globalAdmin_withoutAssumedRole_canPatchContactOfArbitraryRelationship() {
void globalAdmin_withoutAssumedRole_canPatchContactOfArbitraryRelation() {
context.define("superuser-alex@hostsharing.net");
final var givenRelationship = givenSomeTemporaryRelationshipBessler();
assertThat(givenRelationship.getContact().getLabel()).isEqualTo("seventh contact");
final var givenRelation = givenSomeTemporaryRelationBessler();
assertThat(givenRelation.getContact().getLabel()).isEqualTo("seventh contact");
final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth").get(0);
final var location = RestAssured // @formatter:off
@ -370,109 +370,109 @@ class HsOfficeRelationshipControllerAcceptanceTest extends ContextBasedTestWithC
""".formatted(givenContact.getUuid()))
.port(port)
.when()
.patch("http://localhost/api/hs/office/relationships/" + givenRelationship.getUuid())
.patch("http://localhost/api/hs/office/relations/" + givenRelation.getUuid())
.then().log().all().assertThat()
.statusCode(200)
.contentType(ContentType.JSON)
.body("uuid", isUuidValid())
.body("relType", is("REPRESENTATIVE"))
.body("relAnchor.tradeName", is("Erben Bessler"))
.body("relHolder.familyName", is("Winkler"))
.body("type", is("REPRESENTATIVE"))
.body("anchor.tradeName", is("Erben Bessler"))
.body("holder.familyName", is("Winkler"))
.body("contact.label", is("fourth contact"));
// @formatter:on
// finally, the relationship is actually updated
// finally, the relation is actually updated
context.define("superuser-alex@hostsharing.net");
assertThat(relationshipRepo.findByUuid(givenRelationship.getUuid())).isPresent().get()
assertThat(relationRepo.findByUuid(givenRelation.getUuid())).isPresent().get()
.matches(rel -> {
assertThat(rel.getRelAnchor().getTradeName()).contains("Bessler");
assertThat(rel.getRelHolder().getFamilyName()).contains("Winkler");
assertThat(rel.getAnchor().getTradeName()).contains("Bessler");
assertThat(rel.getHolder().getFamilyName()).contains("Winkler");
assertThat(rel.getContact().getLabel()).isEqualTo("fourth contact");
assertThat(rel.getRelType()).isEqualTo(HsOfficeRelationshipType.REPRESENTATIVE);
assertThat(rel.getType()).isEqualTo(HsOfficeRelationType.REPRESENTATIVE);
return true;
});
}
}
@Nested
@Accepts({ "Relationship:D(Delete)" })
class DeleteRelationship {
@Accepts({ "Relation:D(Delete)" })
class DeleteRelation {
@Test
void globalAdmin_withoutAssumedRole_canDeleteArbitraryRelationship() {
void globalAdmin_withoutAssumedRole_canDeleteArbitraryRelation() {
context.define("superuser-alex@hostsharing.net");
final var givenRelationship = givenSomeTemporaryRelationshipBessler();
final var givenRelation = givenSomeTemporaryRelationBessler();
RestAssured // @formatter:off
.given()
.header("current-user", "superuser-alex@hostsharing.net")
.port(port)
.when()
.delete("http://localhost/api/hs/office/relationships/" + givenRelationship.getUuid())
.delete("http://localhost/api/hs/office/relations/" + givenRelation.getUuid())
.then().log().body().assertThat()
.statusCode(204); // @formatter:on
// then the given relationship is gone
assertThat(relationshipRepo.findByUuid(givenRelationship.getUuid())).isEmpty();
// then the given relation is gone
assertThat(relationRepo.findByUuid(givenRelation.getUuid())).isEmpty();
}
@Test
@Accepts({ "Relationship:X(Access Control)" })
void contactAdminUser_canNotDeleteRelatedRelationship() {
@Accepts({ "Relation:X(Access Control)" })
void contactAdminUser_canNotDeleteRelatedRelation() {
context.define("superuser-alex@hostsharing.net");
final var givenRelationship = givenSomeTemporaryRelationshipBessler();
assertThat(givenRelationship.getContact().getLabel()).isEqualTo("seventh contact");
final var givenRelation = givenSomeTemporaryRelationBessler();
assertThat(givenRelation.getContact().getLabel()).isEqualTo("seventh contact");
RestAssured // @formatter:off
.given()
.header("current-user", "contact-admin@seventhcontact.example.com")
.port(port)
.when()
.delete("http://localhost/api/hs/office/relationships/" + givenRelationship.getUuid())
.delete("http://localhost/api/hs/office/relations/" + givenRelation.getUuid())
.then().log().body().assertThat()
.statusCode(403); // @formatter:on
// then the given relationship is still there
assertThat(relationshipRepo.findByUuid(givenRelationship.getUuid())).isNotEmpty();
// then the given relation is still there
assertThat(relationRepo.findByUuid(givenRelation.getUuid())).isNotEmpty();
}
@Test
@Accepts({ "Relationship:X(Access Control)" })
void normalUser_canNotDeleteUnrelatedRelationship() {
@Accepts({ "Relation:X(Access Control)" })
void normalUser_canNotDeleteUnrelatedRelation() {
context.define("superuser-alex@hostsharing.net");
final var givenRelationship = givenSomeTemporaryRelationshipBessler();
assertThat(givenRelationship.getContact().getLabel()).isEqualTo("seventh contact");
final var givenRelation = givenSomeTemporaryRelationBessler();
assertThat(givenRelation.getContact().getLabel()).isEqualTo("seventh contact");
RestAssured // @formatter:off
.given()
.header("current-user", "selfregistered-user-drew@hostsharing.org")
.port(port)
.when()
.delete("http://localhost/api/hs/office/relationships/" + givenRelationship.getUuid())
.delete("http://localhost/api/hs/office/relations/" + givenRelation.getUuid())
.then().log().body().assertThat()
.statusCode(404); // @formatter:on
// then the given relationship is still there
assertThat(relationshipRepo.findByUuid(givenRelationship.getUuid())).isNotEmpty();
// then the given relation is still there
assertThat(relationRepo.findByUuid(givenRelation.getUuid())).isNotEmpty();
}
}
private HsOfficeRelationshipEntity givenSomeTemporaryRelationshipBessler() {
private HsOfficeRelationEntity givenSomeTemporaryRelationBessler() {
return jpaAttempt.transacted(() -> {
context.define("superuser-alex@hostsharing.net");
final var givenAnchorPerson = personRepo.findPersonByOptionalNameLike("Erben Bessler").get(0);
final var givenHolderPerson = personRepo.findPersonByOptionalNameLike("Winkler").get(0);
final var givenContact = contactRepo.findContactByOptionalLabelLike("seventh contact").get(0);
final var newRelationship = HsOfficeRelationshipEntity.builder()
.relType(HsOfficeRelationshipType.REPRESENTATIVE)
.relAnchor(givenAnchorPerson)
.relHolder(givenHolderPerson)
final var newRelation = HsOfficeRelationEntity.builder()
.type(HsOfficeRelationType.REPRESENTATIVE)
.anchor(givenAnchorPerson)
.holder(givenHolderPerson)
.contact(givenContact)
.build();
assertThat(toCleanup(relationshipRepo.save(newRelationship))).isEqualTo(newRelationship);
assertThat(toCleanup(relationRepo.save(newRelation))).isEqualTo(newRelation);
return newRelationship;
return newRelation;
}).assertSuccessful().returnedValue();
}

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,4 +1,4 @@
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;
@ -29,10 +29,10 @@ import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest
@Import( { Context.class, JpaAttempt.class })
class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTestWithCleanup {
class HsOfficeRelationRepositoryIntegrationTest extends ContextBasedTestWithCleanup {
@Autowired
HsOfficeRelationshipRepository relationshipRepo;
HsOfficeRelationRepository relationRepo;
@Autowired
HsOfficePersonRepository personRepo;
@ -56,33 +56,33 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTestWith
HttpServletRequest request;
@Nested
class CreateRelationship {
class CreateRelation {
@Test
public void testHostsharingAdmin_withoutAssumedRole_canCreateNewRelationship() {
public void testHostsharingAdmin_withoutAssumedRole_canCreateNewRelation() {
// given
context("superuser-alex@hostsharing.net");
final var count = relationshipRepo.count();
final var count = relationRepo.count();
final var givenAnchorPerson = personRepo.findPersonByOptionalNameLike("Bessler").get(0);
final var givenHolderPerson = personRepo.findPersonByOptionalNameLike("Anita").get(0);
final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth contact").get(0);
// when
final var result = attempt(em, () -> {
final var newRelationship = HsOfficeRelationshipEntity.builder()
.relAnchor(givenAnchorPerson)
.relHolder(givenHolderPerson)
.relType(HsOfficeRelationshipType.REPRESENTATIVE)
final var newRelation = HsOfficeRelationEntity.builder()
.anchor(givenAnchorPerson)
.holder(givenHolderPerson)
.type(HsOfficeRelationType.REPRESENTATIVE)
.contact(givenContact)
.build();
return toCleanup(relationshipRepo.save(newRelationship));
return toCleanup(relationRepo.save(newRelation));
});
// then
result.assertSuccessful();
assertThat(result.returnedValue()).isNotNull().extracting(HsOfficeRelationshipEntity::getUuid).isNotNull();
assertThatRelationshipIsPersisted(result.returnedValue());
assertThat(relationshipRepo.count()).isEqualTo(count + 1);
assertThat(result.returnedValue()).isNotNull().extracting(HsOfficeRelationEntity::getUuid).isNotNull();
assertThatRelationIsPersisted(result.returnedValue());
assertThat(relationRepo.count()).isEqualTo(count + 1);
}
@Test
@ -97,190 +97,190 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTestWith
final var givenAnchorPerson = personRepo.findPersonByOptionalNameLike("Bessler").get(0);
final var givenHolderPerson = personRepo.findPersonByOptionalNameLike("Anita").get(0);
final var givenContact = contactRepo.findContactByOptionalLabelLike("fourth contact").get(0);
final var newRelationship = HsOfficeRelationshipEntity.builder()
.relAnchor(givenAnchorPerson)
.relHolder(givenHolderPerson)
.relType(HsOfficeRelationshipType.REPRESENTATIVE)
final var newRelation = HsOfficeRelationEntity.builder()
.anchor(givenAnchorPerson)
.holder(givenHolderPerson)
.type(HsOfficeRelationType.REPRESENTATIVE)
.contact(givenContact)
.build();
return toCleanup(relationshipRepo.save(newRelationship));
return toCleanup(relationRepo.save(newRelation));
});
// then
assertThat(distinctRoleNamesOf(rawRoleRepo.findAll())).containsExactlyInAnyOrder(Array.from(
initialRoleNames,
"hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.admin",
"hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner",
"hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant"));
"hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.admin",
"hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner",
"hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant"));
assertThat(distinctGrantDisplaysOf(rawGrantRepo.findAll())).containsExactlyInAnyOrder(Array.fromFormatted(
initialGrantNames,
"{ grant perm DELETE on hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita to role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner by system and assume }",
"{ grant role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner to role global#global.admin by system and assume }",
"{ grant role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner to role hs_office_person#BesslerAnita.admin by system and assume }",
"{ grant perm DELETE on hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita to role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner by system and assume }",
"{ grant role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner to role global#global.admin by system and assume }",
"{ grant role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner to role hs_office_person#BesslerAnita.admin by system and assume }",
"{ grant perm UPDATE on hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita to role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.admin by system and assume }",
"{ grant role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.admin to role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner by system and assume }",
"{ grant perm UPDATE on hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita to role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.admin by system and assume }",
"{ grant role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.admin to role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.owner by system and assume }",
"{ grant perm SELECT on hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita to role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant by system and assume }",
"{ grant role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant to role hs_office_contact#fourthcontact.admin by system and assume }",
"{ grant role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant to role hs_office_person#BesslerAnita.admin by system and assume }",
"{ grant perm SELECT on hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita to role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant by system and assume }",
"{ grant role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant to role hs_office_contact#fourthcontact.admin by system and assume }",
"{ grant role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant to role hs_office_person#BesslerAnita.admin by system and assume }",
"{ grant role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant to role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.admin by system and assume }",
"{ grant role hs_office_contact#fourthcontact.tenant to role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant by system and assume }",
"{ grant role hs_office_person#BesslerAnita.tenant to role hs_office_relationship#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant by system and assume }",
"{ grant role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant to role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.admin by system and assume }",
"{ grant role hs_office_contact#fourthcontact.tenant to role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant by system and assume }",
"{ grant role hs_office_person#BesslerAnita.tenant to role hs_office_relation#BesslerAnita-with-REPRESENTATIVE-BesslerAnita.tenant by system and assume }",
null)
);
}
private void assertThatRelationshipIsPersisted(final HsOfficeRelationshipEntity saved) {
final var found = relationshipRepo.findByUuid(saved.getUuid());
private void assertThatRelationIsPersisted(final HsOfficeRelationEntity saved) {
final var found = relationRepo.findByUuid(saved.getUuid());
assertThat(found).isNotEmpty().get().usingRecursiveComparison().isEqualTo(saved);
}
}
@Nested
class FindAllRelationships {
class FindAllRelations {
@Test
public void globalAdmin_withoutAssumedRole_canViewAllRelationshipsOfArbitraryPerson() {
public void globalAdmin_withoutAssumedRole_canViewAllRelationsOfArbitraryPerson() {
// given
context("superuser-alex@hostsharing.net");
final var person = personRepo.findPersonByOptionalNameLike("Second e.K.").stream().findFirst().orElseThrow();
// when
final var result = relationshipRepo.findRelationshipRelatedToPersonUuid(person.getUuid());
final var result = relationRepo.findRelationRelatedToPersonUuid(person.getUuid());
// then
allTheseRelationshipsAreReturned(
allTheseRelationsAreReturned(
result,
"rel(relAnchor='LP Hostsharing eG', relType='PARTNER', relHolder='LP Second e.K.', contact='second contact')",
"rel(relAnchor='LP Second e.K.', relType='REPRESENTATIVE', relHolder='NP Smith, Peter', contact='second contact')");
"rel(anchor='LP Hostsharing eG', type='PARTNER', holder='LP Second e.K.', contact='second contact')",
"rel(anchor='LP Second e.K.', type='REPRESENTATIVE', holder='NP Smith, Peter', contact='second contact')");
}
@Test
public void normalUser_canViewRelationshipsOfOwnedPersons() {
public void normalUser_canViewRelationsOfOwnedPersons() {
// given:
context("person-FirstGmbH@example.com");
final var person = personRepo.findPersonByOptionalNameLike("First").stream().findFirst().orElseThrow();
// when:
final var result = relationshipRepo.findRelationshipRelatedToPersonUuid(person.getUuid());
final var result = relationRepo.findRelationRelatedToPersonUuid(person.getUuid());
// then:
exactlyTheseRelationshipsAreReturned(
exactlyTheseRelationsAreReturned(
result,
"rel(relAnchor='LP Hostsharing eG', relType='PARTNER', relHolder='LP First GmbH', contact='first contact')",
"rel(relAnchor='LP First GmbH', relType='REPRESENTATIVE', relHolder='NP Firby, Susan', contact='first contact')");
"rel(anchor='LP Hostsharing eG', type='PARTNER', holder='LP First GmbH', contact='first contact')",
"rel(anchor='LP First GmbH', type='REPRESENTATIVE', holder='NP Firby, Susan', contact='first contact')");
}
}
@Nested
class UpdateRelationship {
class UpdateRelation {
@Test
public void hostsharingAdmin_withoutAssumedRole_canUpdateContactOfArbitraryRelationship() {
public void hostsharingAdmin_withoutAssumedRole_canUpdateContactOfArbitraryRelation() {
// given
context("superuser-alex@hostsharing.net");
final var givenRelationship = givenSomeTemporaryRelationshipBessler(
final var givenRelation = givenSomeTemporaryRelationBessler(
"Anita", "fifth contact");
assertThatRelationshipIsVisibleForUserWithRole(
givenRelationship,
assertThatRelationIsVisibleForUserWithRole(
givenRelation,
"hs_office_person#ErbenBesslerMelBessler.admin");
assertThatRelationshipActuallyInDatabase(givenRelationship);
assertThatRelationActuallyInDatabase(givenRelation);
context("superuser-alex@hostsharing.net");
final var givenContact = contactRepo.findContactByOptionalLabelLike("sixth contact").get(0);
// when
final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net");
givenRelationship.setContact(givenContact);
return toCleanup(relationshipRepo.save(givenRelationship));
givenRelation.setContact(givenContact);
return toCleanup(relationRepo.save(givenRelation));
});
// then
result.assertSuccessful();
assertThat(result.returnedValue().getContact().getLabel()).isEqualTo("sixth contact");
assertThatRelationshipIsVisibleForUserWithRole(
assertThatRelationIsVisibleForUserWithRole(
result.returnedValue(),
"global#global.admin");
assertThatRelationshipIsVisibleForUserWithRole(
assertThatRelationIsVisibleForUserWithRole(
result.returnedValue(),
"hs_office_contact#sixthcontact.admin");
assertThatRelationshipIsNotVisibleForUserWithRole(
assertThatRelationIsNotVisibleForUserWithRole(
result.returnedValue(),
"hs_office_contact#fifthcontact.admin");
relationshipRepo.deleteByUuid(givenRelationship.getUuid());
relationRepo.deleteByUuid(givenRelation.getUuid());
}
@Test
public void relHolderAdmin_canNotUpdateRelatedRelationship() {
public void holderAdmin_canNotUpdateRelatedRelation() {
// given
context("superuser-alex@hostsharing.net");
final var givenRelationship = givenSomeTemporaryRelationshipBessler(
final var givenRelation = givenSomeTemporaryRelationBessler(
"Anita", "eighth");
assertThatRelationshipIsVisibleForUserWithRole(
givenRelationship,
assertThatRelationIsVisibleForUserWithRole(
givenRelation,
"hs_office_person#BesslerAnita.admin");
assertThatRelationshipActuallyInDatabase(givenRelationship);
assertThatRelationActuallyInDatabase(givenRelation);
// when
final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net", "hs_office_person#BesslerAnita.admin");
givenRelationship.setContact(null);
return relationshipRepo.save(givenRelationship);
givenRelation.setContact(null);
return relationRepo.save(givenRelation);
});
// then
result.assertExceptionWithRootCauseMessage(JpaSystemException.class,
"[403] Subject ", " is not allowed to update hs_office_relationship uuid");
"[403] Subject ", " is not allowed to update hs_office_relation uuid");
}
@Test
public void contactAdmin_canNotUpdateRelatedRelationship() {
public void contactAdmin_canNotUpdateRelatedRelation() {
// given
context("superuser-alex@hostsharing.net");
final var givenRelationship = givenSomeTemporaryRelationshipBessler(
final var givenRelation = givenSomeTemporaryRelationBessler(
"Anita", "ninth");
assertThatRelationshipIsVisibleForUserWithRole(
givenRelationship,
assertThatRelationIsVisibleForUserWithRole(
givenRelation,
"hs_office_contact#ninthcontact.admin");
assertThatRelationshipActuallyInDatabase(givenRelationship);
assertThatRelationActuallyInDatabase(givenRelation);
// when
final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net", "hs_office_contact#ninthcontact.admin");
givenRelationship.setContact(null); // TODO
return relationshipRepo.save(givenRelationship);
givenRelation.setContact(null); // TODO
return relationRepo.save(givenRelation);
});
// then
result.assertExceptionWithRootCauseMessage(JpaSystemException.class,
"[403] Subject ", " is not allowed to update hs_office_relationship uuid");
"[403] Subject ", " is not allowed to update hs_office_relation uuid");
}
private void assertThatRelationshipActuallyInDatabase(final HsOfficeRelationshipEntity saved) {
final var found = relationshipRepo.findByUuid(saved.getUuid());
private void assertThatRelationActuallyInDatabase(final HsOfficeRelationEntity saved) {
final var found = relationRepo.findByUuid(saved.getUuid());
assertThat(found).isNotEmpty().get().isNotSameAs(saved).usingRecursiveComparison().isEqualTo(saved);
}
private void assertThatRelationshipIsVisibleForUserWithRole(
final HsOfficeRelationshipEntity entity,
private void assertThatRelationIsVisibleForUserWithRole(
final HsOfficeRelationEntity entity,
final String assumedRoles) {
jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net", assumedRoles);
assertThatRelationshipActuallyInDatabase(entity);
assertThatRelationActuallyInDatabase(entity);
}).assertSuccessful();
}
private void assertThatRelationshipIsNotVisibleForUserWithRole(
final HsOfficeRelationshipEntity entity,
private void assertThatRelationIsNotVisibleForUserWithRole(
final HsOfficeRelationEntity entity,
final String assumedRoles) {
jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net", assumedRoles);
final var found = relationshipRepo.findByUuid(entity.getUuid());
final var found = relationRepo.findByUuid(entity.getUuid());
assertThat(found).isEmpty();
}).assertSuccessful();
}
@ -290,63 +290,63 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTestWith
class DeleteByUuid {
@Test
public void globalAdmin_withoutAssumedRole_canDeleteAnyRelationship() {
public void globalAdmin_withoutAssumedRole_canDeleteAnyRelation() {
// given
context("superuser-alex@hostsharing.net", null);
final var givenRelationship = givenSomeTemporaryRelationshipBessler(
final var givenRelation = givenSomeTemporaryRelationBessler(
"Anita", "tenth");
// when
final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net");
relationshipRepo.deleteByUuid(givenRelationship.getUuid());
relationRepo.deleteByUuid(givenRelation.getUuid());
});
// then
result.assertSuccessful();
assertThat(jpaAttempt.transacted(() -> {
context("superuser-fran@hostsharing.net", null);
return relationshipRepo.findByUuid(givenRelationship.getUuid());
return relationRepo.findByUuid(givenRelation.getUuid());
}).assertSuccessful().returnedValue()).isEmpty();
}
@Test
public void contactUser_canViewButNotDeleteTheirRelatedRelationship() {
public void contactUser_canViewButNotDeleteTheirRelatedRelation() {
// given
context("superuser-alex@hostsharing.net", null);
final var givenRelationship = givenSomeTemporaryRelationshipBessler(
final var givenRelation = givenSomeTemporaryRelationBessler(
"Anita", "eleventh");
// when
final var result = jpaAttempt.transacted(() -> {
context("contact-admin@eleventhcontact.example.com");
assertThat(relationshipRepo.findByUuid(givenRelationship.getUuid())).isPresent();
relationshipRepo.deleteByUuid(givenRelationship.getUuid());
assertThat(relationRepo.findByUuid(givenRelation.getUuid())).isPresent();
relationRepo.deleteByUuid(givenRelation.getUuid());
});
// then
result.assertExceptionWithRootCauseMessage(
JpaSystemException.class,
"[403] Subject ", " not allowed to delete hs_office_relationship");
"[403] Subject ", " not allowed to delete hs_office_relation");
assertThat(jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net");
return relationshipRepo.findByUuid(givenRelationship.getUuid());
return relationRepo.findByUuid(givenRelation.getUuid());
}).assertSuccessful().returnedValue()).isPresent(); // still there
}
@Test
public void deletingARelationshipAlsoDeletesRelatedRolesAndGrants() {
public void deletingARelationAlsoDeletesRelatedRolesAndGrants() {
// given
context("superuser-alex@hostsharing.net");
final var initialRoleNames = Array.from(distinctRoleNamesOf(rawRoleRepo.findAll()));
final var initialGrantNames = Array.from(distinctGrantDisplaysOf(rawGrantRepo.findAll()));
final var givenRelationship = givenSomeTemporaryRelationshipBessler(
final var givenRelation = givenSomeTemporaryRelationBessler(
"Anita", "twelfth");
// when
final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net");
return relationshipRepo.deleteByUuid(givenRelationship.getUuid());
return relationRepo.deleteByUuid(givenRelation.getUuid());
});
// then
@ -363,7 +363,7 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTestWith
final var query = em.createNativeQuery("""
select currentTask, targetTable, targetOp
from tx_journal_v
where targettable = 'hs_office_relationship';
where targettable = 'hs_office_relation';
""");
// when
@ -371,40 +371,40 @@ class HsOfficeRelationshipRepositoryIntegrationTest extends ContextBasedTestWith
// then
assertThat(customerLogEntries).map(Arrays::toString).contains(
"[creating relationship test-data HostsharingeG-FirstGmbH, hs_office_relationship, INSERT]",
"[creating relationship test-data FirstGmbH-Firby, hs_office_relationship, INSERT]");
"[creating relation test-data HostsharingeG-FirstGmbH, hs_office_relation, INSERT]",
"[creating relation test-data FirstGmbH-Firby, hs_office_relation, INSERT]");
}
private HsOfficeRelationshipEntity givenSomeTemporaryRelationshipBessler(final String holderPerson, final String contact) {
private HsOfficeRelationEntity givenSomeTemporaryRelationBessler(final String holderPerson, final String contact) {
return jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net");
final var givenAnchorPerson = personRepo.findPersonByOptionalNameLike("Erben Bessler").get(0);
final var givenHolderPerson = personRepo.findPersonByOptionalNameLike(holderPerson).get(0);
final var givenContact = contactRepo.findContactByOptionalLabelLike(contact).get(0);
final var newRelationship = HsOfficeRelationshipEntity.builder()
.relType(HsOfficeRelationshipType.REPRESENTATIVE)
.relAnchor(givenAnchorPerson)
.relHolder(givenHolderPerson)
final var newRelation = HsOfficeRelationEntity.builder()
.type(HsOfficeRelationType.REPRESENTATIVE)
.anchor(givenAnchorPerson)
.holder(givenHolderPerson)
.contact(givenContact)
.build();
return toCleanup(relationshipRepo.save(newRelationship));
return toCleanup(relationRepo.save(newRelation));
}).assertSuccessful().returnedValue();
}
void exactlyTheseRelationshipsAreReturned(
final List<HsOfficeRelationshipEntity> actualResult,
final String... relationshipNames) {
void exactlyTheseRelationsAreReturned(
final List<HsOfficeRelationEntity> actualResult,
final String... relationNames) {
assertThat(actualResult)
.extracting(HsOfficeRelationshipEntity::toString)
.containsExactlyInAnyOrder(relationshipNames);
.extracting(HsOfficeRelationEntity::toString)
.containsExactlyInAnyOrder(relationNames);
}
void allTheseRelationshipsAreReturned(
final List<HsOfficeRelationshipEntity> actualResult,
final String... relationshipNames) {
void allTheseRelationsAreReturned(
final List<HsOfficeRelationEntity> actualResult,
final String... relationNames) {
assertThat(actualResult)
.extracting(HsOfficeRelationshipEntity::toString)
.contains(relationshipNames);
.extracting(HsOfficeRelationEntity::toString)
.contains(relationNames);
}
}

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