hs-office-partner-details

This commit is contained in:
Michael Hoennig 2022-10-20 20:11:31 +02:00
parent dc0835fa25
commit e1895e3735
26 changed files with 573 additions and 212 deletions

View File

@ -23,7 +23,7 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
JOIN HsOfficePersonEntity person ON person.uuid = partner.person JOIN HsOfficePersonEntity person ON person.uuid = partner.person
JOIN HsOfficeContactEntity contact ON contact.uuid = debitor.billingContact JOIN HsOfficeContactEntity contact ON contact.uuid = debitor.billingContact
WHERE :name is null WHERE :name is null
OR partner.birthName like concat(:name, '%') OR partner.details.birthName like concat(:name, '%')
OR person.tradeName like concat(:name, '%') OR person.tradeName like concat(:name, '%')
OR person.familyName like concat(:name, '%') OR person.familyName like concat(:name, '%')
OR person.givenName like concat(:name, '%') OR person.givenName like concat(:name, '%')

View File

@ -72,6 +72,8 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
entityToSave.setPerson(personRepo.findByUuid(body.getPersonUuid()).orElseThrow( entityToSave.setPerson(personRepo.findByUuid(body.getPersonUuid()).orElseThrow(
() -> new NoSuchElementException("cannot find person uuid " + body.getPersonUuid()) () -> new NoSuchElementException("cannot find person uuid " + body.getPersonUuid())
)); ));
entityToSave.setDetails(map(body.getDetails(), HsOfficePartnerDetailsEntity.class));
entityToSave.getDetails().setUuid(UUID.randomUUID());
final var saved = partnerRepo.save(entityToSave); final var saved = partnerRepo.save(entityToSave);
@ -129,14 +131,13 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
final var current = partnerRepo.findByUuid(partnerUuid).orElseThrow(); final var current = partnerRepo.findByUuid(partnerUuid).orElseThrow();
new HsOfficePartnerEntityPatcher(em, current, contactRepo::findByUuid, personRepo::findByUuid).apply(body); new HsOfficePartnerEntityPatcher(em, current).apply(body);
final var saved = partnerRepo.save(current); final var saved = partnerRepo.save(current);
final var mapped = map(saved, HsOfficePartnerResource.class); final var mapped = map(saved, HsOfficePartnerResource.class);
return ResponseEntity.ok(mapped); return ResponseEntity.ok(mapped);
} }
final BiConsumer<HsOfficePartnerEntity, HsOfficePartnerResource> PARTNER_ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> { final BiConsumer<HsOfficePartnerEntity, HsOfficePartnerResource> PARTNER_ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
resource.setPerson(map(entity.getPerson(), HsOfficePersonResource.class)); resource.setPerson(map(entity.getPerson(), HsOfficePersonResource.class));
resource.setContact(map(entity.getContact(), HsOfficeContactResource.class)); resource.setContact(map(entity.getContact(), HsOfficeContactResource.class));
@ -145,11 +146,11 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
// TODO.impl: user postmapper + getReference // TODO.impl: user postmapper + getReference
private HsOfficePartnerEntity mapToHsOfficePartnerEntity(final HsOfficePartnerInsertResource resource) { private HsOfficePartnerEntity mapToHsOfficePartnerEntity(final HsOfficePartnerInsertResource resource) {
final var entity = new HsOfficePartnerEntity(); final var entity = new HsOfficePartnerEntity();
entity.setBirthday(resource.getBirthday()); // entity.setBirthday(resource.getBirthday());
entity.setBirthName(resource.getBirthName()); // entity.setBirthName(resource.getBirthName());
entity.setDateOfDeath(resource.getDateOfDeath()); // entity.setDateOfDeath(resource.getDateOfDeath());
entity.setRegistrationNumber(resource.getRegistrationNumber()); // entity.setRegistrationNumber(resource.getRegistrationNumber());
entity.setRegistrationOffice(resource.getRegistrationOffice()); // entity.setRegistrationOffice(resource.getRegistrationOffice());
return entity; return entity;
} }
} }

View File

@ -0,0 +1,58 @@
package net.hostsharing.hsadminng.hs.office.partner;
import lombok.*;
import net.hostsharing.hsadminng.Stringify;
import net.hostsharing.hsadminng.Stringifyable;
import net.hostsharing.hsadminng.errors.DisplayName;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.time.LocalDate;
import java.util.UUID;
import static net.hostsharing.hsadminng.Stringify.stringify;
@Entity
@Table(name = "hs_office_partner_details_rv")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@DisplayName("PartnerDetails")
public class HsOfficePartnerDetailsEntity implements Stringifyable {
private static Stringify<HsOfficePartnerDetailsEntity> stringify = stringify(
HsOfficePartnerDetailsEntity.class,
"partnerDetails")
.withProp(HsOfficePartnerDetailsEntity::getRegistrationOffice)
.withProp(HsOfficePartnerDetailsEntity::getRegistrationNumber)
.withProp(HsOfficePartnerDetailsEntity::getBirthday)
.withProp(HsOfficePartnerDetailsEntity::getBirthday)
.withProp(HsOfficePartnerDetailsEntity::getDateOfDeath)
.withSeparator(", ")
.quotedValues(false);
private @Id UUID uuid;
private @Column(name = "registrationoffice") String registrationOffice;
private @Column(name = "registrationnumber") String registrationNumber;
private @Column(name = "birthname") String birthName;
private @Column(name = "birthday") LocalDate birthday;
private @Column(name = "dateofdeath") LocalDate dateOfDeath;
@Override
public String toString() {
return stringify.apply(this);
}
@Override
public String toShortString() {
return registrationNumber != null ? registrationNumber
: birthName != null ? birthName
: birthday != null ? birthday.toString()
: dateOfDeath != null ? dateOfDeath.toString() : "<empty details>";
}
}

View File

@ -0,0 +1,32 @@
package net.hostsharing.hsadminng.hs.office.partner;
import net.hostsharing.hsadminng.EntityPatcher;
import net.hostsharing.hsadminng.OptionalFromJson;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerDetailsPatchResource;
import javax.persistence.EntityManager;
import java.util.UUID;
class HsOfficePartnerDetailsEntityPatcher implements EntityPatcher<HsOfficePartnerDetailsPatchResource> {
private final EntityManager em;
private final HsOfficePartnerDetailsEntity entity;
HsOfficePartnerDetailsEntityPatcher(
final EntityManager em,
final HsOfficePartnerDetailsEntity entity) {
this.em = em;
this.entity = entity;
}
@Override
public void apply(final HsOfficePartnerDetailsPatchResource resource) {
if (resource != null) {
OptionalFromJson.of(resource.getRegistrationOffice()).ifPresent(entity::setRegistrationOffice);
OptionalFromJson.of(resource.getRegistrationNumber()).ifPresent(entity::setRegistrationNumber);
OptionalFromJson.of(resource.getBirthday()).ifPresent(entity::setBirthday);
OptionalFromJson.of(resource.getBirthName()).ifPresent(entity::setBirthName);
OptionalFromJson.of(resource.getDateOfDeath()).ifPresent(entity::setDateOfDeath);
}
}
}

View File

@ -6,6 +6,8 @@ import net.hostsharing.hsadminng.Stringify;
import net.hostsharing.hsadminng.Stringifyable; import net.hostsharing.hsadminng.Stringifyable;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import javax.persistence.*; import javax.persistence.*;
import java.time.LocalDate; import java.time.LocalDate;
@ -32,18 +34,17 @@ public class HsOfficePartnerEntity implements Stringifyable {
private @Id UUID uuid; private @Id UUID uuid;
@ManyToOne @ManyToOne
@JoinColumn(name = "personuuid") @JoinColumn(name = "personuuid", nullable = false)
private HsOfficePersonEntity person; private HsOfficePersonEntity person;
@ManyToOne @ManyToOne
@JoinColumn(name = "contactuuid") @JoinColumn(name = "contactuuid", nullable = false)
private HsOfficeContactEntity contact; private HsOfficeContactEntity contact;
private @Column(name = "registrationoffice") String registrationOffice; @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH}, optional = true)
private @Column(name = "registrationnumber") String registrationNumber; @JoinColumn(name = "detailsuuid", nullable = true)
private @Column(name = "birthname") String birthName; @NotFound(action= NotFoundAction.IGNORE)
private @Column(name = "birthday") LocalDate birthday; private HsOfficePartnerDetailsEntity details;
private @Column(name = "dateofdeath") LocalDate dateOfDeath;
@Override @Override
public String toString() { public String toString() {

View File

@ -17,18 +17,11 @@ import java.util.function.Supplier;
class HsOfficePartnerEntityPatcher implements EntityPatcher<HsOfficePartnerPatchResource> { class HsOfficePartnerEntityPatcher implements EntityPatcher<HsOfficePartnerPatchResource> {
private final EntityManager em; private final EntityManager em;
private final HsOfficePartnerEntity entity; private final HsOfficePartnerEntity entity;
private final Function<UUID, Optional<HsOfficeContactEntity>> fetchContact;
private final Function<UUID, Optional<HsOfficePersonEntity>> fetchPerson;
HsOfficePartnerEntityPatcher( HsOfficePartnerEntityPatcher(
final EntityManager em, final EntityManager em,
final HsOfficePartnerEntity entity, final HsOfficePartnerEntity entity) {
final Function<UUID, Optional<HsOfficeContactEntity>> fetchContact,
final Function<UUID, Optional<HsOfficePersonEntity>> fetchPerson) {
this.em = em; this.em = em;
this.entity = entity; this.entity = entity;
this.fetchContact = fetchContact;
this.fetchPerson = fetchPerson;
} }
@Override @Override
@ -41,11 +34,8 @@ class HsOfficePartnerEntityPatcher implements EntityPatcher<HsOfficePartnerPatch
verifyNotNull(newValue, "person"); verifyNotNull(newValue, "person");
entity.setPerson(em.getReference(HsOfficePersonEntity.class, newValue)); entity.setPerson(em.getReference(HsOfficePersonEntity.class, newValue));
}); });
OptionalFromJson.of(resource.getRegistrationOffice()).ifPresent(entity::setRegistrationOffice);
OptionalFromJson.of(resource.getRegistrationNumber()).ifPresent(entity::setRegistrationNumber); new HsOfficePartnerDetailsEntityPatcher(em, entity.getDetails()).apply(resource.getDetails());
OptionalFromJson.of(resource.getBirthday()).ifPresent(entity::setBirthday);
OptionalFromJson.of(resource.getBirthName()).ifPresent(entity::setBirthName);
OptionalFromJson.of(resource.getDateOfDeath()).ifPresent(entity::setDateOfDeath);
} }
private void verifyNotNull(final UUID newValue, final String propertyName) { private void verifyNotNull(final UUID newValue, final String propertyName) {

View File

@ -16,7 +16,7 @@ public interface HsOfficePartnerRepository extends Repository<HsOfficePartnerEnt
JOIN HsOfficeContactEntity contact ON contact.uuid = partner.contact JOIN HsOfficeContactEntity contact ON contact.uuid = partner.contact
JOIN HsOfficePersonEntity person ON person.uuid = partner.person JOIN HsOfficePersonEntity person ON person.uuid = partner.person
WHERE :name is null WHERE :name is null
OR partner.birthName like concat(:name, '%') OR partner.details.birthName like concat(:name, '%')
OR contact.label like concat(:name, '%') OR contact.label like concat(:name, '%')
OR person.tradeName like concat(:name, '%') OR person.tradeName like concat(:name, '%')
OR person.givenName like concat(:name, '%') OR person.givenName like concat(:name, '%')

View File

@ -16,6 +16,8 @@ map:
paths: paths:
/api/hs/office/partners/{partnerUUID}: /api/hs/office/partners/{partnerUUID}:
null: org.openapitools.jackson.nullable.JsonNullable null: org.openapitools.jackson.nullable.JsonNullable
/api/hs/office/partners/{partnerUUID}/details:
null: org.openapitools.jackson.nullable.JsonNullable
/api/hs/office/contacts/{contactUUID}: /api/hs/office/contacts/{contactUUID}:
null: org.openapitools.jackson.nullable.JsonNullable null: org.openapitools.jackson.nullable.JsonNullable
/api/hs/office/persons/{personUUID}: /api/hs/office/persons/{personUUID}:

View File

@ -13,23 +13,32 @@ components:
$ref: './hs-office-person-schemas.yaml#/components/schemas/HsOfficePerson' $ref: './hs-office-person-schemas.yaml#/components/schemas/HsOfficePerson'
contact: contact:
$ref: './hs-office-contact-schemas.yaml#/components/schemas/HsOfficeContact' $ref: './hs-office-contact-schemas.yaml#/components/schemas/HsOfficeContact'
details:
$ref: '#/components/schemas/HsOfficePartnerDetails'
HsOfficePartnerDetails:
type: object
properties:
uuid:
type: string
format: uuid
registrationOffice: registrationOffice:
type: string type: string
nullable: true nullable: true
registrationNumber: registrationNumber:
type: string type: string
nullable: true nullable: true
birthName: birthName:
type: string type: string
nullable: true nullable: true
birthday: birthday:
type: string type: string
format: date format: date
nullable: true nullable: true
dateOfDeath: dateOfDeath:
type: string type: string
format: date format: date
nullable: true nullable: true
HsOfficePartnerPatch: HsOfficePartnerPatch:
type: object type: object
@ -42,6 +51,13 @@ components:
type: string type: string
format: uuid format: uuid
nullable: true nullable: true
details:
$ref: '#/components/schemas/HsOfficePartnerDetailsPatch'
HsOfficePartnerDetailsPatch:
type: object
nullable: true
properties:
registrationOffice: registrationOffice:
type: string type: string
nullable: true nullable: true
@ -69,6 +85,15 @@ components:
contactUuid: contactUuid:
type: string type: string
format: uuid format: uuid
details:
$ref: '#/components/schemas/HsOfficePartnerDetailsInsert'
required:
- personUuid
- contactUuid
HsOfficePartnerDetailsInsert:
type: object
properties:
registrationOffice: registrationOffice:
type: string type: string
nullable: true nullable: true
@ -86,6 +111,3 @@ components:
type: string type: string
format: date format: date
nullable: true nullable: true
required:
- personUuid
- contactUuid

View File

@ -372,7 +372,7 @@ create domain RbacOp as varchar(67)
or VALUE = 'view' or VALUE = 'view'
or VALUE = 'assume' or VALUE = 'assume'
or VALUE ~ '^add-[a-z]+$' or VALUE ~ '^add-[a-z]+$'
or VALUE ~ '^new-[a-z]+$' or VALUE ~ '^new-[a-z-]+$'
); );
create table RbacPermission create table RbacPermission

View File

@ -63,7 +63,7 @@ begin
and r.roleType = roleTypeToAssume and r.roleType = roleTypeToAssume
into roleUuidToAssume; into roleUuidToAssume;
if roleUuidToAssume is null then if roleUuidToAssume is null then
raise exception '[403] role % not accessible for user %', roleName, currentUser(); raise exception '[403] role % not accessible for user %', roleName, currentSubjects();
end if; end if;
if not isGranted(currentUserUuid, roleUuidToAssume) then if not isGranted(currentUserUuid, roleUuidToAssume) then
raise exception '[403] user % has no permission to assume role %', currentUser(), roleName; raise exception '[403] user % has no permission to assume role %', currentUser(), roleName;

View File

@ -1,14 +1,13 @@
--liquibase formatted sql --liquibase formatted sql
-- ============================================================================ -- ============================================================================
--changeset hs-office-partner-MAIN-TABLE:1 endDelimiter:--// --changeset hs-office-partner-DETAILS-TABLE:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
create table if not exists hs_office_partner create table hs_office_partner_details
( (
uuid uuid unique references RbacObject (uuid) initially deferred, uuid uuid unique references RbacObject (uuid) initially deferred,
personUuid uuid not null references hs_office_person(uuid),
contactUuid uuid not null references hs_office_contact(uuid),
registrationOffice varchar(96), registrationOffice varchar(96),
registrationNumber varchar(96), registrationNumber varchar(96),
birthName varchar(96), birthName varchar(96),
@ -18,6 +17,27 @@ create table if not exists hs_office_partner
--// --//
-- ============================================================================
--changeset hs-office-partner-DETAILS-TABLE-JOURNAL:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call create_journal('hs_office_partner_details');
--//
-- ============================================================================
--changeset hs-office-partner-MAIN-TABLE:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
create table hs_office_partner
(
uuid uuid unique references RbacObject (uuid) initially deferred,
personUuid uuid not null references hs_office_person(uuid),
contactUuid uuid not null references hs_office_contact(uuid),
detailsUuid uuid not null references hs_office_partner_details(uuid) on delete cascade
);
--//
-- ============================================================================ -- ============================================================================
--changeset hs-office-partner-MAIN-TABLE-JOURNAL:1 endDelimiter:--// --changeset hs-office-partner-MAIN-TABLE-JOURNAL:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------

View File

@ -27,17 +27,27 @@ subgraph hsOfficePerson
--> role:hsOfficePerson.guest[person.guest] --> role:hsOfficePerson.guest[person.guest]
end end
subgraph hsOfficePartnerDetails
direction TB
perm:hsOfficePartnerDetails.*{{partner.*}}
perm:hsOfficePartnerDetails.edit{{partner.edit}}
perm:hsOfficePartnerDetails.view{{partner.view}}
end
subgraph hsOfficePartner subgraph hsOfficePartner
role:hsOfficePartner.owner[partner.owner] role:hsOfficePartner.owner[partner.owner]
%% permissions %% permissions
role:hsOfficePartner.owner --> perm:hsOfficePartner.*{{partner.*}} role:hsOfficePartner.owner --> perm:hsOfficePartner.*{{partner.*}}
role:hsOfficePartner.owner --> perm:hsOfficePartnerDetails.*{{partner.*}}
%% incoming %% incoming
role:global.admin ---> role:hsOfficePartner.owner role:global.admin ---> role:hsOfficePartner.owner
role:hsOfficePartner.admin[partner.admin] role:hsOfficePartner.admin[partner.admin]
%% permissions %% permissions
role:hsOfficePartner.admin --> perm:hsOfficePartner.edit{{partner.edit}} role:hsOfficePartner.admin --> perm:hsOfficePartner.edit{{partner.edit}}
role:hsOfficePartner.admin --> perm:hsOfficePartnerDetails.edit{{partner.edit}}
%% incoming %% incoming
role:hsOfficePartner.owner ---> role:hsOfficePartner.admin role:hsOfficePartner.owner ---> role:hsOfficePartner.admin
%% outgoing %% outgoing
@ -45,6 +55,8 @@ subgraph hsOfficePartner
role:hsOfficePartner.admin --> role:hsOfficeContact.tenant role:hsOfficePartner.admin --> role:hsOfficeContact.tenant
role:hsOfficePartner.agent[partner.agent] role:hsOfficePartner.agent[partner.agent]
%% permissions
role:hsOfficePartner.agent --> perm:hsOfficePartnerDetails.view{{partner.view}}
%% incoming %% incoming
role:hsOfficePartner.admin ---> role:hsOfficePartner.agent role:hsOfficePartner.admin ---> role:hsOfficePartner.agent
role:hsOfficePerson.admin --> role:hsOfficePartner.agent role:hsOfficePerson.admin --> role:hsOfficePartner.agent

View File

@ -39,6 +39,8 @@ begin
if TG_OP = 'INSERT' then if TG_OP = 'INSERT' then
-- === ATTENTION: code generated from related Mermaid flowchart: ===
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficePartnerOwner(NEW), hsOfficePartnerOwner(NEW),
permissions => array['*'], permissions => array['*'],
@ -72,14 +74,40 @@ begin
hsOfficeContactGuest(newContact)] hsOfficeContactGuest(newContact)]
); );
perform createRoleWithGrants( perform createRoleWithGrants(
hsOfficePartnerGuest(NEW), hsOfficePartnerGuest(NEW),
permissions => array['view'], permissions => array['view'],
incomingSuperRoles => array[ incomingSuperRoles => array[hsOfficePartnerTenant(NEW)]
hsOfficePartnerTenant(NEW)]
); );
-- === END of code generated from Mermaid flowchart. ===
-- Each partner-details entity belong exactly to one partner entity
-- and it makes little sense just to delegate partner-details roles.
-- Therefore, we did not model partner-details roles,
-- but instead just assign extra permissions to existing partner-roles.
--Attention: Cannot be in partner-details because of insert order (partner is not in database yet)
call grantPermissionsToRole(
getRoleId(hsOfficePartnerOwner(NEW), 'fail'),
createPermissions(NEW.detailsUuid, array ['*'])
);
call grantPermissionsToRole(
getRoleId(hsOfficePartnerAdmin(NEW), 'fail'),
createPermissions(NEW.detailsUuid, array ['edit'])
);
call grantPermissionsToRole(
-- Yes, here hsOfficePartnerAGENT is used, not hsOfficePartnerTENANT.
-- Do NOT grant view permission on partner-details to hsOfficePartnerTENANT!
-- Otherwise package-admins etc. would be able to read the data.
getRoleId(hsOfficePartnerAgent(NEW), 'fail'),
createPermissions(NEW.detailsUuid, array ['view'])
);
elsif TG_OP = 'UPDATE' then elsif TG_OP = 'UPDATE' then
if OLD.personUuid <> NEW.personUuid then if OLD.personUuid <> NEW.personUuid then
@ -152,12 +180,7 @@ call generateRbacRestrictedView('hs_office_partner',
'(select idName from hs_office_person_iv p where p.uuid = target.personUuid)', '(select idName from hs_office_person_iv p where p.uuid = target.personUuid)',
$updates$ $updates$
personUuid = new.personUuid, personUuid = new.personUuid,
contactUuid = new.contactUuid, contactUuid = new.contactUuid
registrationOffice = new.registrationOffice,
registrationNumber = new.registrationNumber,
birthday = new.birthday,
birthName = new.birthName,
dateOfDeath = new.dateOfDeath
$updates$); $updates$);
--// --//

View File

@ -0,0 +1,85 @@
--liquibase formatted sql
-- ============================================================================
--changeset hs-office-partner-details-rbac-OBJECT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRelatedRbacObject('hs_office_partner_details');
--//
-- ============================================================================
--changeset hs-office-partner-details-rbac-IDENTITY-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacIdentityView('hs_office_partner_details', $idName$
(select idName || '-details' from hs_office_partner_iv partner_iv
join hs_office_partner partner on (partner_iv.uuid = partner.uuid)
where partner.detailsUuid = target.uuid)
$idName$);
--//
-- ============================================================================
--changeset hs-office-partner-details-rbac-RESTRICTED-VIEW:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
call generateRbacRestrictedView('hs_office_partner_details',
'target.uuid', -- no specific order required
$updates$
registrationOffice = new.registrationOffice,
registrationNumber = new.registrationNumber,
birthName = new.birthName,
birthday = new.birthday,
dateOfDeath = new.dateOfDeath
$updates$);
--//
-- ============================================================================
--changeset hs-office-partner-details-rbac-NEW-CONTACT:1 endDelimiter:--//
-- ----------------------------------------------------------------------------
/*
Creates a global permission for new-partner-details and assigns it to the hostsharing admins role.
*/
do language plpgsql $$
declare
addCustomerPermissions uuid[];
globalObjectUuid uuid;
globalAdminRoleUuid uuid ;
begin
call defineContext('granting global new-partner-details permission to global admin role', null, null, null);
globalAdminRoleUuid := findRoleId(globalAdmin());
globalObjectUuid := (select uuid from global);
addCustomerPermissions := createPermissions(globalObjectUuid, array ['new-partner-details']);
call grantPermissionsToRole(globalAdminRoleUuid, addCustomerPermissions);
end;
$$;
-- TODO.refa: the code below could be moved to a generator, maybe even the code above.
-- Additionally, the code below is not neccesary for all entities, specifiy when it is!
/**
Used by the trigger to prevent the add-partner-details to current user respectively assumed roles.
*/
create or replace function addHsOfficePartnerDetailsNotAllowedForCurrentSubjects()
returns trigger
language PLPGSQL
as $$
begin
raise exception '[403] new-partner-details not permitted for %',
array_to_string(currentSubjects(), ';', 'null');
end; $$;
/**
Checks if the user or assumed roles are allowed to create new partner-details.
*/
create trigger hs_office_partner_details_insert_trigger
before insert
on hs_office_partner_details
for each row
when ( not hasAssumedRole() )
execute procedure addHsOfficePartnerDetailsNotAllowedForCurrentSubjects();
--//

View File

@ -11,11 +11,12 @@
create or replace procedure createHsOfficePartnerTestData( personTradeOrFamilyName varchar, contactLabel varchar ) create or replace procedure createHsOfficePartnerTestData( personTradeOrFamilyName varchar, contactLabel varchar )
language plpgsql as $$ language plpgsql as $$
declare declare
currentTask varchar; currentTask varchar;
idName varchar; idName varchar;
relatedPerson hs_office_person; relatedPerson hs_office_person;
relatedContact hs_office_contact; relatedContact hs_office_contact;
birthday date; relatedDetailsUuid uuid;
birthday date;
begin begin
idName := cleanIdentifier( personTradeOrFamilyName|| '-' || contactLabel); idName := cleanIdentifier( personTradeOrFamilyName|| '-' || contactLabel);
currentTask := 'creating partner test-data ' || idName; currentTask := 'creating partner test-data ' || idName;
@ -36,34 +37,25 @@ begin
raise notice 'creating test partner: %', idName; raise notice 'creating test partner: %', idName;
raise notice '- using person (%): %', relatedPerson.uuid, relatedPerson; raise notice '- using person (%): %', relatedPerson.uuid, relatedPerson;
raise notice '- using contact (%): %', relatedContact.uuid, relatedContact; raise notice '- using contact (%): %', relatedContact.uuid, relatedContact;
if relatedPerson.persontype = 'NATURAL' then
insert
into hs_office_partner_details (uuid, birthName, birthday)
values (uuid_generate_v4(), 'Meyer', '1987-10-31')
returning uuid into relatedDetailsUuid;
else
insert
into hs_office_partner_details (uuid, registrationOffice, registrationNumber)
values (uuid_generate_v4(), 'Hamburg', '12345')
returning uuid into relatedDetailsUuid;
end if;
insert insert
into hs_office_partner (uuid, personuuid, contactuuid, birthday) into hs_office_partner (uuid, personuuid, contactuuid, detailsUuid)
values (uuid_generate_v4(), relatedPerson.uuid, relatedContact.uuid, birthDay); values (uuid_generate_v4(), relatedPerson.uuid, relatedContact.uuid, relatedDetailsUuid);
end; $$; end; $$;
--// --//
/*
Creates a range of test partner for mass data generation.
*/
create or replace procedure createHsOfficePartnerTestData(
startCount integer, -- count of auto generated rows before the run
endCount integer -- count of auto generated rows after the run
)
language plpgsql as $$
declare
person hs_office_person;
contact hs_office_contact;
begin
for t in startCount..endCount
loop
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 createHsOfficePartnerTestData(person.uuid, contact.uuid);
commit;
end loop;
end; $$;
--//
-- ============================================================================ -- ============================================================================

View File

@ -65,6 +65,8 @@ databaseChangeLog:
file: db/changelog/220-hs-office-partner.sql file: db/changelog/220-hs-office-partner.sql
- include: - include:
file: db/changelog/223-hs-office-partner-rbac.sql file: db/changelog/223-hs-office-partner-rbac.sql
- include:
file: db/changelog/224-hs-office-partner-details-rbac.sql
- include: - include:
file: db/changelog/228-hs-office-partner-test-data.sql file: db/changelog/228-hs-office-partner-test-data.sql
- include: - include:

View File

@ -1,6 +1,7 @@
package net.hostsharing.hsadminng.hs.office.debitor; package net.hostsharing.hsadminng.hs.office.debitor;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerDetailsEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -17,7 +18,8 @@ class HsOfficeDebitorEntityTest {
.person(HsOfficePersonEntity.builder() .person(HsOfficePersonEntity.builder()
.tradeName("some trade name") .tradeName("some trade name")
.build()) .build())
.birthName("some birth name") .details(HsOfficePartnerDetailsEntity.builder().birthName("some birth name").build())
.build()) .build())
.billingContact(HsOfficeContactEntity.builder().label("some label").build()) .billingContact(HsOfficeContactEntity.builder().label("some label").build())
.build(); .build();

View File

@ -66,7 +66,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
class CreateDebitor { class CreateDebitor {
@Test @Test
public void testHostsharingAdmin_withoutAssumedRole_canCreateNewDebitor() { public void globalAdmin_canCreateNewDebitor() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var count = debitorRepo.count(); final var count = debitorRepo.count();
@ -170,7 +170,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
class FindByOptionalName { class FindByOptionalName {
@Test @Test
public void globalAdmin_withoutAssumedRole_canViewAllDebitors() { public void globalAdmin_canViewAllDebitors() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
@ -219,7 +219,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
class FindByDebitorNumberLike { class FindByDebitorNumberLike {
@Test @Test
public void globalAdmin_withoutAssumedRole_canViewAllDebitors() { public void globalAdmin_canViewAllDebitors() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
@ -235,7 +235,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
class FindByNameLike { class FindByNameLike {
@Test @Test
public void globalAdmin_withoutAssumedRole_canViewAllDebitors() { public void globalAdmin_canViewAllDebitors() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
@ -251,7 +251,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
class UpdateDebitor { class UpdateDebitor {
@Test @Test
public void hostsharingAdmin_withoutAssumedRole_canUpdateArbitraryDebitor() { public void globalAdmin_canUpdateArbitraryDebitor() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact"); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "fifth contact");
@ -336,7 +336,8 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
private void assertThatDebitorActuallyInDatabase(final HsOfficeDebitorEntity saved) { private void assertThatDebitorActuallyInDatabase(final HsOfficeDebitorEntity saved) {
final var found = debitorRepo.findByUuid(saved.getUuid()); final var found = debitorRepo.findByUuid(saved.getUuid());
assertThat(found).isNotEmpty().get().isNotSameAs(saved).usingRecursiveComparison().isEqualTo(saved); assertThat(found).isNotEmpty().get().isNotSameAs(saved)
.extracting(Object::toString).isEqualTo(saved.toString());
} }
private void assertThatDebitorIsVisibleForUserWithRole( private void assertThatDebitorIsVisibleForUserWithRole(
@ -363,7 +364,7 @@ class HsOfficeDebitorRepositoryIntegrationTest extends ContextBasedTest {
class DeleteByUuid { class DeleteByUuid {
@Test @Test
public void globalAdmin_withoutAssumedRole_canDeleteAnyDebitor() { public void globalAdmin_canDeleteAnyDebitor() {
// given // given
context("superuser-alex@hostsharing.net", null); context("superuser-alex@hostsharing.net", null);
final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "tenth"); final var givenDebitor = givenSomeTemporaryDebitor("Fourth", "tenth");

View File

@ -271,7 +271,8 @@ class HsOfficeMembershipRepositoryIntegrationTest extends ContextBasedTest {
private void assertThatMembershipExistsAndIsAccessibleToCurrentContext(final HsOfficeMembershipEntity saved) { private void assertThatMembershipExistsAndIsAccessibleToCurrentContext(final HsOfficeMembershipEntity saved) {
final var found = membershipRepo.findByUuid(saved.getUuid()); final var found = membershipRepo.findByUuid(saved.getUuid());
assertThat(found).isNotEmpty().get().isNotSameAs(saved).usingRecursiveComparison().isEqualTo(saved); assertThat(found).isNotEmpty().get().isNotSameAs(saved)
.extracting(Object::toString).isEqualTo(saved.toString());
} }
private void assertThatMembershipIsVisibleForUserWithRole( private void assertThatMembershipIsVisibleForUserWithRole(

View File

@ -79,23 +79,27 @@ class HsOfficePartnerControllerAcceptanceTest {
{ {
"person": { "familyName": "Smith" }, "person": { "familyName": "Smith" },
"contact": { "label": "fifth contact" }, "contact": { "label": "fifth contact" },
"birthday": "1987-10-31" "details": { "birthday": "1987-10-31" }
}, },
{ {
"person": { "tradeName": "First GmbH" }, "person": { "tradeName": "First GmbH" },
"contact": { "label": "first contact" } "contact": { "label": "first contact" },
"details": { "registrationOffice": "Hamburg" }
}, },
{ {
"person": { "tradeName": "Third OHG" }, "person": { "tradeName": "Third OHG" },
"contact": { "label": "third contact" } "contact": { "label": "third contact" },
"details": { "registrationOffice": "Hamburg" }
}, },
{ {
"person": { "tradeName": "Second e.K." }, "person": { "tradeName": "Second e.K." },
"contact": { "label": "second contact" } "contact": { "label": "second contact" },
"details": { "registrationOffice": "Hamburg" }
}, },
{ {
"person": { "personType": "SOLE_REPRESENTATION" }, "person": { "personType": "SOLE_REPRESENTATION" },
"contact": { "label": "forth contact" } "contact": { "label": "forth contact" },
"details": { "registrationOffice": "Hamburg" }
} }
] ]
""")); """));
@ -122,8 +126,10 @@ class HsOfficePartnerControllerAcceptanceTest {
{ {
"contactUuid": "%s", "contactUuid": "%s",
"personUuid": "%s", "personUuid": "%s",
"registrationOffice": "Registergericht Hamburg", "details": {
"registrationNumber": "123456" "registrationOffice": "Registergericht Aurich",
"registrationNumber": "123456"
}
} }
""".formatted(givenContact.getUuid(), givenPerson.getUuid())) """.formatted(givenContact.getUuid(), givenPerson.getUuid()))
.port(port) .port(port)
@ -133,7 +139,8 @@ class HsOfficePartnerControllerAcceptanceTest {
.statusCode(201) .statusCode(201)
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body("uuid", isUuidValid()) .body("uuid", isUuidValid())
.body("registrationNumber", is("123456")) .body("details.registrationOffice", is("Registergericht Aurich"))
.body("details.registrationNumber", is("123456"))
.body("contact.label", is(givenContact.getLabel())) .body("contact.label", is(givenContact.getLabel()))
.body("person.tradeName", is(givenPerson.getTradeName())) .body("person.tradeName", is(givenPerson.getTradeName()))
.header("Location", startsWith("http://localhost")) .header("Location", startsWith("http://localhost"))
@ -289,11 +296,13 @@ class HsOfficePartnerControllerAcceptanceTest {
{ {
"contactUuid": "%s", "contactUuid": "%s",
"personUuid": "%s", "personUuid": "%s",
"registrationOffice": "Registergericht Hamburg", "details": {
"registrationNumber": "222222", "registrationOffice": "Registergericht Hamburg",
"birthName": "Maja Schmidt", "registrationNumber": "222222",
"birthday": "1938-04-08", "birthName": "Maja Schmidt",
"dateOfDeath": "2022-01-12" "birthday": "1938-04-08",
"dateOfDeath": "2022-01-12"
}
} }
""".formatted(givenContact.getUuid(), givenPerson.getUuid())) """.formatted(givenContact.getUuid(), givenPerson.getUuid()))
.port(port) .port(port)
@ -303,7 +312,7 @@ class HsOfficePartnerControllerAcceptanceTest {
.statusCode(200) .statusCode(200)
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body("uuid", isUuidValid()) .body("uuid", isUuidValid())
.body("registrationNumber", is("222222")) .body("details.registrationNumber", is("222222"))
.body("contact.label", is(givenContact.getLabel())) .body("contact.label", is(givenContact.getLabel()))
.body("person.tradeName", is(givenPerson.getTradeName())); .body("person.tradeName", is(givenPerson.getTradeName()));
// @formatter:on // @formatter:on
@ -314,11 +323,11 @@ class HsOfficePartnerControllerAcceptanceTest {
.matches(person -> { .matches(person -> {
assertThat(person.getPerson().getTradeName()).isEqualTo("Third OHG"); assertThat(person.getPerson().getTradeName()).isEqualTo("Third OHG");
assertThat(person.getContact().getLabel()).isEqualTo("forth contact"); assertThat(person.getContact().getLabel()).isEqualTo("forth contact");
assertThat(person.getRegistrationOffice()).isEqualTo("Registergericht Hamburg"); assertThat(person.getDetails().getRegistrationOffice()).isEqualTo("Registergericht Hamburg");
assertThat(person.getRegistrationNumber()).isEqualTo("222222"); assertThat(person.getDetails().getRegistrationNumber()).isEqualTo("222222");
assertThat(person.getBirthName()).isEqualTo("Maja Schmidt"); assertThat(person.getDetails().getBirthName()).isEqualTo("Maja Schmidt");
assertThat(person.getBirthday()).isEqualTo("1938-04-08"); assertThat(person.getDetails().getBirthday()).isEqualTo("1938-04-08");
assertThat(person.getDateOfDeath()).isEqualTo("2022-01-12"); assertThat(person.getDetails().getDateOfDeath()).isEqualTo("2022-01-12");
return true; return true;
}); });
} }
@ -335,9 +344,11 @@ class HsOfficePartnerControllerAcceptanceTest {
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"birthName": "Maja Schmidt", "details": {
"birthday": "1938-04-08", "birthName": "Maja Schmidt",
"dateOfDeath": "2022-01-12" "birthday": "1938-04-08",
"dateOfDeath": "2022-01-12"
}
} }
""") """)
.port(port) .port(port)
@ -347,7 +358,7 @@ class HsOfficePartnerControllerAcceptanceTest {
.statusCode(200) .statusCode(200)
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body("uuid", isUuidValid()) .body("uuid", isUuidValid())
.body("birthName", is("Maja Schmidt")) .body("details.birthName", is("Maja Schmidt"))
.body("contact.label", is(givenPartner.getContact().getLabel())) .body("contact.label", is(givenPartner.getContact().getLabel()))
.body("person.tradeName", is(givenPartner.getPerson().getTradeName())); .body("person.tradeName", is(givenPartner.getPerson().getTradeName()));
// @formatter:on // @formatter:on
@ -357,11 +368,11 @@ class HsOfficePartnerControllerAcceptanceTest {
.matches(person -> { .matches(person -> {
assertThat(person.getPerson().getTradeName()).isEqualTo(givenPartner.getPerson().getTradeName()); assertThat(person.getPerson().getTradeName()).isEqualTo(givenPartner.getPerson().getTradeName());
assertThat(person.getContact().getLabel()).isEqualTo(givenPartner.getContact().getLabel()); assertThat(person.getContact().getLabel()).isEqualTo(givenPartner.getContact().getLabel());
assertThat(person.getRegistrationOffice()).isEqualTo(null); assertThat(person.getDetails().getRegistrationOffice()).isEqualTo(null);
assertThat(person.getRegistrationNumber()).isEqualTo(null); assertThat(person.getDetails().getRegistrationNumber()).isEqualTo(null);
assertThat(person.getBirthName()).isEqualTo("Maja Schmidt"); assertThat(person.getDetails().getBirthName()).isEqualTo("Maja Schmidt");
assertThat(person.getBirthday()).isEqualTo("1938-04-08"); assertThat(person.getDetails().getBirthday()).isEqualTo("1938-04-08");
assertThat(person.getDateOfDeath()).isEqualTo("2022-01-12"); assertThat(person.getDetails().getDateOfDeath()).isEqualTo("2022-01-12");
return true; return true;
}); });
} }
@ -440,6 +451,9 @@ class HsOfficePartnerControllerAcceptanceTest {
.uuid(UUID.randomUUID()) .uuid(UUID.randomUUID())
.person(givenPerson) .person(givenPerson)
.contact(givenContact) .contact(givenContact)
.details(HsOfficePartnerDetailsEntity.builder()
.uuid((UUID.randomUUID()))
.build())
.build(); .build();
toCleanup(newPartner.getUuid()); toCleanup(newPartner.getUuid());

View File

@ -0,0 +1,93 @@
package net.hostsharing.hsadminng.hs.office.partner;
import net.hostsharing.hsadminng.PatchUnitTestBase;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerDetailsPatchResource;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import javax.persistence.EntityManager;
import java.time.LocalDate;
import java.util.UUID;
import java.util.stream.Stream;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.lenient;
@TestInstance(PER_CLASS)
@ExtendWith(MockitoExtension.class)
class HsOfficePartnerDetailsEntityPatcherUnitTest extends PatchUnitTestBase<
HsOfficePartnerDetailsPatchResource,
HsOfficePartnerDetailsEntity
> {
private static final UUID INITIAL_PARTNER_UUID = UUID.randomUUID();
private static final UUID INITIAL_CONTACT_UUID = UUID.randomUUID();
private static final UUID INITIAL_PERSON_UUID = UUID.randomUUID();
private static final LocalDate INITIAL_BIRTHDAY = LocalDate.parse("1900-01-01");
private static final LocalDate PATCHED_BIRTHDAY = LocalDate.parse("1990-12-31");
private static final LocalDate INITIAL_DAY_OF_DEATH = LocalDate.parse("2000-01-01");
private static final LocalDate PATCHED_DATE_OF_DEATH = LocalDate.parse("2022-08-31");
@Mock
private EntityManager em;
@BeforeEach
void initMocks() {
lenient().when(em.getReference(eq(HsOfficeContactEntity.class), any())).thenAnswer(invocation ->
HsOfficeContactEntity.builder().uuid(invocation.getArgument(1)).build());
lenient().when(em.getReference(eq(HsOfficePersonEntity.class), any())).thenAnswer(invocation ->
HsOfficePersonEntity.builder().uuid(invocation.getArgument(1)).build());
}
@Override
protected HsOfficePartnerDetailsEntity newInitialEntity() {
final var entity = new HsOfficePartnerDetailsEntity();
entity.setUuid(INITIAL_PARTNER_UUID);
entity.setRegistrationOffice("initial Reg-Office");
entity.setRegistrationNumber("initial Reg-Number");
entity.setBirthday(INITIAL_BIRTHDAY);
entity.setBirthName("initial birth name");
entity.setDateOfDeath(INITIAL_DAY_OF_DEATH);
return entity;
}
@Override
protected HsOfficePartnerDetailsPatchResource newPatchResource() {
return new HsOfficePartnerDetailsPatchResource();
}
@Override
protected HsOfficePartnerDetailsEntityPatcher createPatcher(final HsOfficePartnerDetailsEntity details) {
return new HsOfficePartnerDetailsEntityPatcher(em, details);
}
@Override
protected Stream<Property> propertyTestDescriptors() {
return Stream.of(
new JsonNullableProperty<>(
"registrationOffice",
HsOfficePartnerDetailsPatchResource::setRegistrationOffice,
"patched Reg-Office",
HsOfficePartnerDetailsEntity::setRegistrationOffice),
new JsonNullableProperty<>(
"birthday",
HsOfficePartnerDetailsPatchResource::setBirthday,
PATCHED_BIRTHDAY,
HsOfficePartnerDetailsEntity::setBirthday),
new JsonNullableProperty<>(
"dayOfDeath",
HsOfficePartnerDetailsPatchResource::setDateOfDeath,
PATCHED_DATE_OF_DEATH,
HsOfficePartnerDetailsEntity::setDateOfDeath)
);
}
}

View File

@ -0,0 +1,51 @@
package net.hostsharing.hsadminng.hs.office.partner;
import org.junit.jupiter.api.Test;
import java.time.LocalDate;
import static org.assertj.core.api.Assertions.assertThat;
class HsOfficePartnerDetailsEntityTest {
final HsOfficePartnerDetailsEntity given = HsOfficePartnerDetailsEntity.builder()
.registrationOffice("Hamburg")
.registrationNumber("12345")
.birthday(LocalDate.parse("2002-01-15"))
.birthName("Melly Miller")
.dateOfDeath(LocalDate.parse("2081-12-21"))
.build();
@Test
void toStringContainsAllProperties() {
final var result = given.toString();
assertThat(result).isEqualTo("partnerDetails(Hamburg, 12345, 2002-01-15, 2002-01-15, 2081-12-21)");
}
@Test
void toShortStringContainsFirstNonNullValue() {
assertThat(given.toShortString()).isEqualTo("12345");
assertThat(HsOfficePartnerDetailsEntity.builder()
.birthName("Melly Miller")
.birthday(LocalDate.parse("2002-01-15"))
.dateOfDeath(LocalDate.parse("2081-12-21"))
.build().toShortString()).isEqualTo("Melly Miller");
assertThat(HsOfficePartnerDetailsEntity.builder()
.birthday(LocalDate.parse("2002-01-15"))
.dateOfDeath(LocalDate.parse("2081-12-21"))
.build().toShortString()).isEqualTo("2002-01-15");
assertThat(HsOfficePartnerDetailsEntity.builder()
.dateOfDeath(LocalDate.parse("2081-12-21"))
.build().toShortString()).isEqualTo("2081-12-21");
assertThat(HsOfficePartnerDetailsEntity.builder()
.build().toShortString()).isEqualTo("<empty details>");
}
}

View File

@ -1,9 +1,9 @@
package net.hostsharing.hsadminng.hs.office.partner; package net.hostsharing.hsadminng.hs.office.partner;
import net.hostsharing.hsadminng.PatchUnitTestBase;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerPatchResource; import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerPatchResource;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.PatchUnitTestBase;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@ -11,14 +11,12 @@ import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import java.time.LocalDate;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Stream; import java.util.stream.Stream;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.lenient;
@TestInstance(PER_CLASS) @TestInstance(PER_CLASS)
@ -31,28 +29,27 @@ class HsOfficePartnerEntityPatcherUnitTest extends PatchUnitTestBase<
private static final UUID INITIAL_PARTNER_UUID = UUID.randomUUID(); private static final UUID INITIAL_PARTNER_UUID = UUID.randomUUID();
private static final UUID INITIAL_CONTACT_UUID = UUID.randomUUID(); private static final UUID INITIAL_CONTACT_UUID = UUID.randomUUID();
private static final UUID INITIAL_PERSON_UUID = UUID.randomUUID(); private static final UUID INITIAL_PERSON_UUID = UUID.randomUUID();
private static final UUID INITIAL_DETAILS_UUID = UUID.randomUUID();
private static final UUID PATCHED_CONTACT_UUID = UUID.randomUUID(); private static final UUID PATCHED_CONTACT_UUID = UUID.randomUUID();
private static final UUID PATCHED_PERSON_UUID = UUID.randomUUID(); private static final UUID PATCHED_PERSON_UUID = UUID.randomUUID();
private static final LocalDate INITIAL_BIRTHDAY = LocalDate.parse("1900-01-01");
private static final LocalDate PATCHED_BIRTHDAY = LocalDate.parse("1990-12-31");
private static final LocalDate INITIAL_DAY_OF_DEATH = LocalDate.parse("2000-01-01");
private static final LocalDate PATCHED_DATE_OF_DEATH = LocalDate.parse("2022-08-31");
private final HsOfficePersonEntity givenInitialPerson = HsOfficePersonEntity.builder() private final HsOfficePersonEntity givenInitialPerson = HsOfficePersonEntity.builder()
.uuid(INITIAL_PERSON_UUID) .uuid(INITIAL_PERSON_UUID)
.build(); .build();
private final HsOfficeContactEntity givenInitialContact = HsOfficeContactEntity.builder() private final HsOfficeContactEntity givenInitialContact = HsOfficeContactEntity.builder()
.uuid(INITIAL_CONTACT_UUID) .uuid(INITIAL_CONTACT_UUID)
.build(); .build();
private final HsOfficePartnerDetailsEntity givenInitialDetails = HsOfficePartnerDetailsEntity.builder()
.uuid(INITIAL_DETAILS_UUID)
.build();
@Mock @Mock
private EntityManager em; private EntityManager em;
@BeforeEach @BeforeEach
void initMocks() { void initMocks() {
lenient().when(em.getReference(eq(HsOfficeContactEntity.class), any())).thenAnswer(invocation -> lenient().when(em.getReference(eq(HsOfficeContactEntity.class), any())).thenAnswer(invocation ->
HsOfficeContactEntity.builder().uuid(invocation.getArgument(1)).build()); HsOfficeContactEntity.builder().uuid(invocation.getArgument(1)).build());
lenient().when(em.getReference(eq(HsOfficePersonEntity.class), any())).thenAnswer(invocation -> lenient().when(em.getReference(eq(HsOfficePersonEntity.class), any())).thenAnswer(invocation ->
HsOfficePersonEntity.builder().uuid(invocation.getArgument(1)).build()); HsOfficePersonEntity.builder().uuid(invocation.getArgument(1)).build());
} }
@ -63,11 +60,7 @@ class HsOfficePartnerEntityPatcherUnitTest extends PatchUnitTestBase<
entity.setUuid(INITIAL_PARTNER_UUID); entity.setUuid(INITIAL_PARTNER_UUID);
entity.setPerson(givenInitialPerson); entity.setPerson(givenInitialPerson);
entity.setContact(givenInitialContact); entity.setContact(givenInitialContact);
entity.setRegistrationOffice("initial Reg-Office"); entity.setDetails(givenInitialDetails);
entity.setRegistrationNumber("initial Reg-Number");
entity.setBirthday(INITIAL_BIRTHDAY);
entity.setBirthName("initial birth name");
entity.setDateOfDeath(INITIAL_DAY_OF_DEATH);
return entity; return entity;
} }
@ -78,15 +71,7 @@ class HsOfficePartnerEntityPatcherUnitTest extends PatchUnitTestBase<
@Override @Override
protected HsOfficePartnerEntityPatcher createPatcher(final HsOfficePartnerEntity partner) { protected HsOfficePartnerEntityPatcher createPatcher(final HsOfficePartnerEntity partner) {
return new HsOfficePartnerEntityPatcher( return new HsOfficePartnerEntityPatcher(em, partner);
em,
partner,
uuid -> uuid == PATCHED_CONTACT_UUID
? Optional.of(newContact(uuid))
: Optional.empty(),
uuid -> uuid == PATCHED_PERSON_UUID
? Optional.of(newPerson(uuid))
: Optional.empty());
} }
@Override @Override
@ -105,22 +90,7 @@ class HsOfficePartnerEntityPatcherUnitTest extends PatchUnitTestBase<
PATCHED_PERSON_UUID, PATCHED_PERSON_UUID,
HsOfficePartnerEntity::setPerson, HsOfficePartnerEntity::setPerson,
newPerson(PATCHED_PERSON_UUID)) newPerson(PATCHED_PERSON_UUID))
.notNullable(), .notNullable()
new JsonNullableProperty<>(
"registrationOffice",
HsOfficePartnerPatchResource::setRegistrationOffice,
"patched Reg-Office",
HsOfficePartnerEntity::setRegistrationOffice),
new JsonNullableProperty<>(
"birthday",
HsOfficePartnerPatchResource::setBirthday,
PATCHED_BIRTHDAY,
HsOfficePartnerEntity::setBirthday),
new JsonNullableProperty<>(
"dayOfDeath",
HsOfficePartnerPatchResource::setDateOfDeath,
PATCHED_DATE_OF_DEATH,
HsOfficePartnerEntity::setDateOfDeath)
); );
} }

View File

@ -20,7 +20,6 @@ import org.springframework.test.annotation.DirtiesContext;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.time.LocalDate;
import java.util.*; import java.util.*;
import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.grantDisplaysOf; import static net.hostsharing.hsadminng.rbac.rbacgrant.RawRbacGrantEntity.grantDisplaysOf;
@ -77,6 +76,9 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
.uuid(UUID.randomUUID()) .uuid(UUID.randomUUID())
.person(givenPerson) .person(givenPerson)
.contact(givenContact) .contact(givenContact)
.details(HsOfficePartnerDetailsEntity.builder()
.uuid(UUID.randomUUID())
.build())
.build()); .build());
return partnerRepo.save(newPartner); return partnerRepo.save(newPartner);
}); });
@ -107,6 +109,7 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
.uuid(UUID.randomUUID()) .uuid(UUID.randomUUID())
.person(givenPerson) .person(givenPerson)
.contact(givenContact) .contact(givenContact)
.details(HsOfficePartnerDetailsEntity.builder().uuid(UUID.randomUUID()).build())
.build()); .build());
return partnerRepo.save(newPartner); return partnerRepo.save(newPartner);
}); });
@ -126,28 +129,32 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
.containsExactlyInAnyOrder(Array.fromFormatted( .containsExactlyInAnyOrder(Array.fromFormatted(
initialGrantNames, initialGrantNames,
// owner // owner
"{ grant perm * on partner#EBess-4th to role partner#EBess-4th.owner by system and assume }", "{ grant perm * on partner#EBess-4th to role partner#EBess-4th.owner by system and assume }",
"{ grant role partner#EBess-4th.owner to role global#global.admin by system and assume }", "{ grant perm * on partner_details#EBess-4th-details to role partner#EBess-4th.owner by system and assume }",
"{ grant role partner#EBess-4th.owner to role global#global.admin by system and assume }",
// admin // admin
"{ grant perm edit on partner#EBess-4th to role partner#EBess-4th.admin by system and assume }", "{ grant perm edit on partner#EBess-4th to role partner#EBess-4th.admin by system and assume }",
"{ grant role partner#EBess-4th.admin to role partner#EBess-4th.owner by system and assume }", "{ grant perm edit on partner_details#EBess-4th-details to role partner#EBess-4th.admin by system and assume }",
"{ grant role person#EBess.tenant to role partner#EBess-4th.admin by system and assume }", "{ grant role partner#EBess-4th.admin to role partner#EBess-4th.owner by system and assume }",
"{ grant role contact#4th.tenant to role partner#EBess-4th.admin by system and assume }", "{ grant role person#EBess.tenant to role partner#EBess-4th.admin by system and assume }",
"{ grant role contact#4th.tenant to role partner#EBess-4th.admin by system and assume }",
// agent // agent
"{ grant role partner#EBess-4th.agent to role partner#EBess-4th.admin by system and assume }", "{ grant perm view on partner_details#EBess-4th-details to role partner#EBess-4th.agent by system and assume }",
"{ grant role partner#EBess-4th.agent to role person#EBess.admin by system and assume }", "{ grant role partner#EBess-4th.agent to role partner#EBess-4th.admin by system and assume }",
"{ grant role partner#EBess-4th.agent to role contact#4th.admin by system and assume }", "{ grant role partner#EBess-4th.agent to role person#EBess.admin by system and assume }",
"{ grant role partner#EBess-4th.agent to role contact#4th.admin by system and assume }",
// tenant // tenant
"{ grant role partner#EBess-4th.tenant to role partner#EBess-4th.agent by system and assume }", "{ grant role partner#EBess-4th.tenant to role partner#EBess-4th.agent by system and assume }",
"{ grant role person#EBess.guest to role partner#EBess-4th.tenant by system and assume }", "{ grant role person#EBess.guest to role partner#EBess-4th.tenant by system and assume }",
"{ grant role contact#4th.guest to role partner#EBess-4th.tenant by system and assume }", "{ grant role contact#4th.guest to role partner#EBess-4th.tenant by system and assume }",
// guest // guest
"{ grant perm view on partner#EBess-4th to role partner#EBess-4th.guest by system and assume }", "{ grant perm view on partner#EBess-4th to role partner#EBess-4th.guest by system and assume }",
"{ grant role partner#EBess-4th.guest to role partner#EBess-4th.tenant by system and assume }", "{ grant role partner#EBess-4th.guest to role partner#EBess-4th.tenant by system and assume }",
null)); null));
} }
@ -226,7 +233,6 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
givenPartner.setContact(givenNewContact); givenPartner.setContact(givenNewContact);
givenPartner.setPerson(givenNewPerson); givenPartner.setPerson(givenNewPerson);
givenPartner.setDateOfDeath(LocalDate.parse("2022-09-15"));
return toCleanup(partnerRepo.save(givenPartner)); return toCleanup(partnerRepo.save(givenPartner));
}); });
@ -246,47 +252,26 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
} }
@Test @Test
public void personAdmin_canNotUpdateRelatedPartner() { public void partnerAgent_canNotUpdateRelatedPartner() {
// given
context("superuser-alex@hostsharing.net");
final var givenPartner = givenSomeTemporaryPartnerBessler("eighth");
assertThatPartnerIsVisibleForUserWithRole(
givenPartner,
"hs_office_person#ErbenBesslerMelBessler.admin");
assertThatPartnerActuallyInDatabase(givenPartner);
// when
final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net", "hs_office_person#ErbenBesslerMelBessler.admin");
givenPartner.setDateOfDeath(LocalDate.parse("2022-09-15"));
return partnerRepo.save(givenPartner);
});
// then
result.assertExceptionWithRootCauseMessage(JpaSystemException.class,
"[403] Subject ", " is not allowed to update hs_office_partner uuid");
}
@Test
public void contactAdmin_canNotUpdateRelatedPartner() {
// given // given
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var givenPartner = givenSomeTemporaryPartnerBessler("ninth"); final var givenPartner = givenSomeTemporaryPartnerBessler("ninth");
assertThatPartnerIsVisibleForUserWithRole( assertThatPartnerIsVisibleForUserWithRole(
givenPartner, givenPartner,
"hs_office_contact#ninthcontact.admin"); "hs_office_partner#ErbenBesslerMelBessler-ninthcontact.agent");
assertThatPartnerActuallyInDatabase(givenPartner); assertThatPartnerActuallyInDatabase(givenPartner);
final var givenNewContact = contactRepo.findContactByOptionalLabelLike("tenth").get(0);
// when // when
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net", "hs_office_contact#ninthcontact.admin"); context("superuser-alex@hostsharing.net", "hs_office_partner#ErbenBesslerMelBessler-ninthcontact.agent");
givenPartner.setDateOfDeath(LocalDate.parse("2022-09-15")); givenPartner.getDetails().setBirthName("new birthname");
return partnerRepo.save(givenPartner); return partnerRepo.save(givenPartner);
}); });
// then // then
result.assertExceptionWithRootCauseMessage(JpaSystemException.class, result.assertExceptionWithRootCauseMessage(JpaSystemException.class,
"[403] Subject ", " is not allowed to update hs_office_partner uuid"); "[403] Subject ", " is not allowed to update hs_office_partner_details uuid");
} }
private void assertThatPartnerActuallyInDatabase(final HsOfficePartnerEntity saved) { private void assertThatPartnerActuallyInDatabase(final HsOfficePartnerEntity saved) {
@ -424,6 +409,9 @@ class HsOfficePartnerRepositoryIntegrationTest extends ContextBasedTest {
.uuid(UUID.randomUUID()) .uuid(UUID.randomUUID())
.person(givenPerson) .person(givenPerson)
.contact(givenContact) .contact(givenContact)
.details(HsOfficePartnerDetailsEntity.builder()
.uuid(UUID.randomUUID())
.build())
.build(); .build();
toCleanup(newPartner); toCleanup(newPartner);

View File

@ -290,7 +290,8 @@ class HsOfficeSepaMandateRepositoryIntegrationTest extends ContextBasedTest {
private void assertThatSepaMandateActuallyInDatabase(final HsOfficeSepaMandateEntity saved) { private void assertThatSepaMandateActuallyInDatabase(final HsOfficeSepaMandateEntity saved) {
final var found = sepaMandateRepo.findByUuid(saved.getUuid()); final var found = sepaMandateRepo.findByUuid(saved.getUuid());
assertThat(found).isNotEmpty().get().isNotSameAs(saved).usingRecursiveComparison().isEqualTo(saved); assertThat(found).isNotEmpty().get().isNotSameAs(saved)
.extracting(Object::toString).isEqualTo(saved.toString());
} }
private void assertThatSepaMandateIsVisibleForUserWithRole( private void assertThatSepaMandateIsVisibleForUserWithRole(