1
0

introduce-partner-business-role (#16)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/16
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
This commit is contained in:
Michael Hoennig
2024-02-01 14:48:15 +01:00
parent 188cb9733e
commit 2c0101b46d
72 changed files with 1666 additions and 930 deletions

View File

@ -0,0 +1,21 @@
package net.hostsharing.hsadminng.errors;
import net.hostsharing.hsadminng.persistence.HasUuid;
import java.util.UUID;
public class ReferenceNotFoundException extends RuntimeException {
private final Class<?> entityClass;
private final UUID uuid;
public <E extends HasUuid> ReferenceNotFoundException(final Class<E> entityClass, final UUID uuid, final Throwable exc) {
super(exc);
this.entityClass = entityClass;
this.uuid = uuid;
}
@Override
public String getMessage() {
return "Cannot resolve " + entityClass.getSimpleName() +" with uuid " + uuid;
}
}

View File

@ -45,7 +45,7 @@ public class RestResponseEntityExceptionHandler
protected ResponseEntity<CustomErrorResponse> handleJpaExceptions(
final RuntimeException exc, final WebRequest request) {
final var message = line(NestedExceptionUtils.getMostSpecificCause(exc).getMessage(), 0);
return errorResponse(request, httpStatus(message).orElse(HttpStatus.INTERNAL_SERVER_ERROR), message);
return errorResponse(request, httpStatus(exc, message).orElse(HttpStatus.INTERNAL_SERVER_ERROR), message);
}
@ExceptionHandler(NoSuchElementException.class)
@ -55,6 +55,12 @@ public class RestResponseEntityExceptionHandler
return errorResponse(request, HttpStatus.NOT_FOUND, message);
}
@ExceptionHandler(ReferenceNotFoundException.class)
protected ResponseEntity<CustomErrorResponse> handleReferenceNotFoundException(
final ReferenceNotFoundException exc, final WebRequest request) {
return errorResponse(request, HttpStatus.BAD_REQUEST, exc.getMessage());
}
@ExceptionHandler({ JpaObjectRetrievalFailureException.class, EntityNotFoundException.class })
protected ResponseEntity<CustomErrorResponse> handleJpaObjectRetrievalFailureException(
final RuntimeException exc, final WebRequest request) {
@ -74,8 +80,9 @@ public class RestResponseEntityExceptionHandler
@ExceptionHandler(Throwable.class)
protected ResponseEntity<CustomErrorResponse> handleOtherExceptions(
final Throwable exc, final WebRequest request) {
final var message = firstMessageLine(NestedExceptionUtils.getMostSpecificCause(exc));
return errorResponse(request, httpStatus(message).orElse(HttpStatus.INTERNAL_SERVER_ERROR), message);
final var causingException = NestedExceptionUtils.getMostSpecificCause(exc);
final var message = firstMessageLine(causingException);
return errorResponse(request, httpStatus(causingException, message).orElse(HttpStatus.INTERNAL_SERVER_ERROR), message);
}
@Override
@ -138,7 +145,10 @@ public class RestResponseEntityExceptionHandler
}
}
private Optional<HttpStatus> httpStatus(final String message) {
private Optional<HttpStatus> httpStatus(final Throwable causingException, final String message) {
if ( EntityNotFoundException.class.isInstance(causingException) ) {
return Optional.of(HttpStatus.BAD_REQUEST);
}
if (message.startsWith("ERROR: [")) {
for (HttpStatus status : HttpStatus.values()) {
if (message.startsWith("ERROR: [" + status.value() + "]")) {

View File

@ -3,7 +3,7 @@ package net.hostsharing.hsadminng.hs.office.bankaccount;
import lombok.*;
import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.migration.HasUuid;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;

View File

@ -13,13 +13,15 @@ public interface HsOfficeBankAccountRepository extends Repository<HsOfficeBankAc
@Query("""
SELECT c FROM HsOfficeBankAccountEntity c
WHERE :holder is null
OR lower(c.holder) like lower(concat(:holder, '%'))
WHERE lower(c.holder) like lower(concat(:holder, '%'))
ORDER BY c.holder
""")
List<HsOfficeBankAccountEntity> findByOptionalHolderLike(String holder);
List<HsOfficeBankAccountEntity> findByOptionalHolderLikeImpl(String holder);
default List<HsOfficeBankAccountEntity> findByOptionalHolderLike(String holder) {
return findByOptionalHolderLikeImpl(holder == null ? "" : holder);
}
List<HsOfficeBankAccountEntity> findByIbanOrderByIban(String iban);
List<HsOfficeBankAccountEntity> findByIbanOrderByIbanAsc(String iban);
<S extends HsOfficeBankAccountEntity> S save(S entity);

View File

@ -3,7 +3,7 @@ package net.hostsharing.hsadminng.hs.office.contact;
import lombok.*;
import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.migration.HasUuid;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator;
@ -36,7 +36,7 @@ public class HsOfficeContactEntity implements Stringifyable, HasUuid {
private String label;
@Column(name = "postaladdress")
private String postalAddress;
private String postalAddress; // TODO: check if we really want multiple, if so: JSON-Array or Postgres-Array?
@Column(name = "emailaddresses", columnDefinition = "json")
private String emailAddresses; // TODO: check if we can really add multiple. format: ["eins@...", "zwei@..."]

View File

@ -4,7 +4,7 @@ package net.hostsharing.hsadminng.hs.office.coopassets;
import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
import net.hostsharing.hsadminng.hs.office.migration.HasUuid;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator;

View File

@ -3,7 +3,7 @@ package net.hostsharing.hsadminng.hs.office.coopshares;
import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
import net.hostsharing.hsadminng.hs.office.migration.HasUuid;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
@ -25,7 +25,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, HasUuid {
private static Stringify<HsOfficeCoopSharesTransactionEntity> stringify = stringify(HsOfficeCoopSharesTransactionEntity.class)
.withProp(HsOfficeCoopSharesTransactionEntity::getMemberNumber)
.withProp(HsOfficeCoopSharesTransactionEntity::getMemberNumberTagged)
.withProp(HsOfficeCoopSharesTransactionEntity::getValueDate)
.withProp(HsOfficeCoopSharesTransactionEntity::getTransactionType)
.withProp(HsOfficeCoopSharesTransactionEntity::getShareCount)
@ -76,12 +76,12 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, HasUu
return stringify.apply(this);
}
public Integer getMemberNumber() {
return ofNullable(membership).map(HsOfficeMembershipEntity::getMemberNumber).orElse(null);
private String getMemberNumberTagged() {
return ofNullable(membership).map(HsOfficeMembershipEntity::toShortString).orElse(null);
}
@Override
public String toShortString() {
return "M-%s%+d".formatted(getMemberNumber(), shareCount);
return "%s%+d".formatted(getMemberNumberTagged(), shareCount);
}
}

View File

@ -4,7 +4,7 @@ import lombok.*;
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.migration.HasUuid;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;

View File

@ -5,7 +5,7 @@ import com.vladmihalcea.hibernate.type.range.Range;
import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.migration.HasUuid;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;

View File

@ -1,12 +1,21 @@
package net.hostsharing.hsadminng.hs.office.partner;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.errors.ReferenceNotFoundException;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficePartnersApi;
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.persistence.HasUuid;
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.mapper.Mapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;
@ -30,6 +39,9 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
@Autowired
private HsOfficePartnerRepository partnerRepo;
@Autowired
private HsOfficeRelationshipRepository relationshipRepo;
@PersistenceContext
private EntityManager em;
@ -56,7 +68,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
context.define(currentUser, assumedRoles);
final var entityToSave = mapper.map(body, HsOfficePartnerEntity.class);
final var entityToSave = createPartnerEntity(body);
final var saved = partnerRepo.save(entityToSave);
@ -93,11 +105,17 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
final UUID partnerUuid) {
context.define(currentUser, assumedRoles);
final var result = partnerRepo.deleteByUuid(partnerUuid);
if (result == 0) {
final var partnerToDelete = partnerRepo.findByUuid(partnerUuid);
if (partnerToDelete.isEmpty()) {
return ResponseEntity.notFound().build();
}
if (partnerRepo.deleteByUuid(partnerUuid) != 1 ||
// TODO: move to after delete trigger in partner
relationshipRepo.deleteByUuid(partnerToDelete.get().getPartnerRole().getUuid()) != 1 ) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
return ResponseEntity.noContent().build();
}
@ -119,4 +137,32 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
final var mapped = mapper.map(saved, HsOfficePartnerResource.class);
return ResponseEntity.ok(mapped);
}
private HsOfficePartnerEntity createPartnerEntity(final HsOfficePartnerInsertResource body) {
final var entityToSave = new HsOfficePartnerEntity();
entityToSave.setPartnerNumber(body.getPartnerNumber());
entityToSave.setPartnerRole(persistPartnerRole(body.getPartnerRole()));
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()));
entity.setContact(ref(HsOfficeContactEntity.class, resource.getContactUuid()));
em.persist(entity);
return entity;
}
private <E extends HasUuid> E ref(final Class<E> entityClass, final UUID uuid) {
try {
return em.getReference(entityClass, uuid);
} catch (final Throwable exc) {
throw new ReferenceNotFoundException(entityClass, uuid, exc);
}
}
}

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.migration.HasUuid;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;

View File

@ -3,8 +3,9 @@ package net.hostsharing.hsadminng.hs.office.partner;
import lombok.*;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.migration.HasUuid;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.relationship.HsOfficeRelationshipEntity;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.NotFound;
@ -39,10 +40,16 @@ public class HsOfficePartnerEntity implements Stringifyable, HasUuid {
@Column(name = "partnernumber", columnDefinition = "numeric(5) not null")
private Integer partnerNumber;
@ManyToOne
@JoinColumn(name = "partnerroleuuid", nullable = false)
private HsOfficeRelationshipEntity partnerRole;
// TODO: remove, is replaced by partnerRole
@ManyToOne
@JoinColumn(name = "personuuid", nullable = false)
private HsOfficePersonEntity person;
// TODO: remove, is replaced by partnerRole
@ManyToOne
@JoinColumn(name = "contactuuid", nullable = false)
private HsOfficeContactEntity contact;

View File

@ -3,7 +3,7 @@ package net.hostsharing.hsadminng.hs.office.person;
import lombok.*;
import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.errors.DisplayName;
import net.hostsharing.hsadminng.hs.office.migration.HasUuid;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.apache.commons.lang3.StringUtils;

View File

@ -3,7 +3,7 @@ package net.hostsharing.hsadminng.hs.office.relationship;
import lombok.*;
import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.migration.HasUuid;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;

View File

@ -2,6 +2,7 @@ package net.hostsharing.hsadminng.hs.office.relationship;
public enum HsOfficeRelationshipType {
UNKNOWN,
PARTNER,
EX_PARTNER,
REPRESENTATIVE,
VIP_CONTACT,

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.migration.HasUuid;
import net.hostsharing.hsadminng.persistence.HasUuid;
import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.Type;

View File

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

View File

@ -15,6 +15,8 @@ public interface RbacGrantRepository extends Repository<RbacGrantEntity, RbacGra
""")
RbacGrantEntity findById(RbacGrantId rbacGrantId);
long count();
List<RbacGrantEntity> findAll();
RbacGrantEntity save(final RbacGrantEntity grant);

View File

@ -8,9 +8,12 @@ import java.util.UUID;
public interface RbacRoleRepository extends Repository<RbacRoleEntity, UUID> {
/**
* Returns all instances of the type.
*
* @return all entities
* @return the number of persistent RbacRoleEntity instances, mostly for testing purposes.
*/
long count(); // TODO: move to test sources
/**
* @return all persistent RbacRoleEntity instances, assigned to the current subject (user or assumed roles)
*/
List<RbacRoleEntity> findAll();