adds HsOfficePartner
This commit is contained in:
@ -0,0 +1,12 @@
|
||||
package net.hostsharing.hsadminng.errors;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface DisplayName {
|
||||
String value() default "";
|
||||
}
|
@ -6,15 +6,18 @@ import org.springframework.core.NestedExceptionUtils;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.orm.jpa.JpaObjectRetrievalFailureException;
|
||||
import org.springframework.orm.jpa.JpaSystemException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
||||
|
||||
import javax.persistence.EntityNotFoundException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ControllerAdvice
|
||||
public class RestResponseEntityExceptionHandler
|
||||
@ -42,6 +45,41 @@ public class RestResponseEntityExceptionHandler
|
||||
return errorResponse(request, HttpStatus.NOT_FOUND, message);
|
||||
}
|
||||
|
||||
@ExceptionHandler({ JpaObjectRetrievalFailureException.class, EntityNotFoundException.class })
|
||||
protected ResponseEntity<CustomErrorResponse> handleJpaObjectRetrievalFailureException(
|
||||
final RuntimeException exc, final WebRequest request) {
|
||||
final var message =
|
||||
userReadableEntityClassName(
|
||||
firstLine(NestedExceptionUtils.getMostSpecificCause(exc).getMessage()));
|
||||
return errorResponse(request, HttpStatus.BAD_REQUEST, message);
|
||||
}
|
||||
|
||||
private String userReadableEntityClassName(final String exceptionMessage) {
|
||||
final var regex = "(net.hostsharing.hsadminng.[a-z0-9_.]*.[A-Za-z0-9_$]*Entity) ";
|
||||
final var pattern = Pattern.compile(regex);
|
||||
final var matcher = pattern.matcher(exceptionMessage);
|
||||
if (matcher.find()) {
|
||||
final var entityName = matcher.group(1);
|
||||
final var entityClass = resolveClassOrNull(entityName);
|
||||
if (entityClass != null ) {
|
||||
return (entityClass.isAnnotationPresent(DisplayName.class)
|
||||
? exceptionMessage.replace(entityName, entityClass.getAnnotation(DisplayName.class).value())
|
||||
: exceptionMessage.replace(entityName, entityClass.getSimpleName()))
|
||||
.replace(" with id ", " with uuid ");
|
||||
}
|
||||
|
||||
}
|
||||
return exceptionMessage;
|
||||
}
|
||||
|
||||
private static Class<?> resolveClassOrNull(final String entityName) {
|
||||
try {
|
||||
return ClassLoader.getSystemClassLoader().loadClass(entityName);
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ExceptionHandler(Throwable.class)
|
||||
protected ResponseEntity<CustomErrorResponse> handleOtherExceptions(
|
||||
final Throwable exc, final WebRequest request) {
|
||||
|
@ -2,6 +2,7 @@ package net.hostsharing.hsadminng.hs.office.contact;
|
||||
|
||||
import lombok.*;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import net.hostsharing.hsadminng.errors.DisplayName;
|
||||
import net.hostsharing.hsadminng.Stringify;
|
||||
import net.hostsharing.hsadminng.Stringifyable;
|
||||
|
||||
@ -21,6 +22,7 @@ import static net.hostsharing.hsadminng.Stringify.stringify;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@FieldNameConstants
|
||||
@DisplayName("Contact")
|
||||
public class HsOfficeContactEntity implements Stringifyable {
|
||||
|
||||
private static Stringify<HsOfficeContactEntity> toString = stringify(HsOfficeContactEntity.class, "contact")
|
||||
|
@ -0,0 +1,150 @@
|
||||
package net.hostsharing.hsadminng.hs.office.debitor;
|
||||
|
||||
import net.hostsharing.hsadminng.Mapper;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeDebitorsApi;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*;
|
||||
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntityPatcher;
|
||||
import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorRepository;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static net.hostsharing.hsadminng.Mapper.map;
|
||||
|
||||
@RestController
|
||||
|
||||
public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
|
||||
|
||||
@Autowired
|
||||
private Context context;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeDebitorRepository debitorRepo;
|
||||
|
||||
@Autowired
|
||||
private HsOfficePartnerRepository partnerRepo;
|
||||
|
||||
@Autowired
|
||||
private HsOfficeContactRepository contactRepo;
|
||||
|
||||
@Autowired
|
||||
private EntityManager em;
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public ResponseEntity<List<HsOfficeDebitorResource>> listDebitors(
|
||||
final String currentUser,
|
||||
final String assumedRoles,
|
||||
final String name,
|
||||
final Integer debitorNumber) {
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
final var entities = debitorNumber != null
|
||||
? debitorRepo.findDebitorByDebitorNumber(debitorNumber)
|
||||
: debitorRepo.findDebitorByOptionalNameLike(name);
|
||||
|
||||
final var resources = Mapper.mapList(entities, HsOfficeDebitorResource.class,
|
||||
DEBITOR_ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||
return ResponseEntity.ok(resources);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ResponseEntity<HsOfficeDebitorResource> addDebitor(
|
||||
final String currentUser,
|
||||
final String assumedRoles,
|
||||
final HsOfficeDebitorInsertResource body) {
|
||||
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
final var entityToSave = map(body, HsOfficeDebitorEntity.class, DEBITOR_RESOURCE_TO_ENTITY_POSTMAPPER);
|
||||
entityToSave.setUuid(UUID.randomUUID());
|
||||
|
||||
final var saved = debitorRepo.save(entityToSave);
|
||||
|
||||
final var uri =
|
||||
MvcUriComponentsBuilder.fromController(getClass())
|
||||
.path("/api/hs/office/debitors/{id}")
|
||||
.buildAndExpand(entityToSave.getUuid())
|
||||
.toUri();
|
||||
final var mapped = map(saved, HsOfficeDebitorResource.class,
|
||||
DEBITOR_ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||
return ResponseEntity.created(uri).body(mapped);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public ResponseEntity<HsOfficeDebitorResource> getDebitorByUuid(
|
||||
final String currentUser,
|
||||
final String assumedRoles,
|
||||
final UUID debitorUuid) {
|
||||
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
final var result = debitorRepo.findByUuid(debitorUuid);
|
||||
if (result.isEmpty()) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
return ResponseEntity.ok(map(result.get(), HsOfficeDebitorResource.class, DEBITOR_ENTITY_TO_RESOURCE_POSTMAPPER));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ResponseEntity<Void> deleteDebitorByUuid(
|
||||
final String currentUser,
|
||||
final String assumedRoles,
|
||||
final UUID debitorUuid) {
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
final var result = debitorRepo.deleteByUuid(debitorUuid);
|
||||
if (result == 0) {
|
||||
return ResponseEntity.notFound().build();
|
||||
}
|
||||
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ResponseEntity<HsOfficeDebitorResource> patchDebitor(
|
||||
final String currentUser,
|
||||
final String assumedRoles,
|
||||
final UUID debitorUuid,
|
||||
final HsOfficeDebitorPatchResource body) {
|
||||
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
final var current = debitorRepo.findByUuid(debitorUuid).orElseThrow();
|
||||
|
||||
new HsOfficeDebitorEntityPatcher(em, current).apply(body);
|
||||
|
||||
final var saved = debitorRepo.save(current);
|
||||
final var mapped = map(saved, HsOfficeDebitorResource.class);
|
||||
return ResponseEntity.ok(mapped);
|
||||
}
|
||||
|
||||
|
||||
final BiConsumer<HsOfficeDebitorEntity, HsOfficeDebitorResource> DEBITOR_ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
|
||||
resource.setPartner(map(entity.getPartner(), HsOfficePartnerResource.class));
|
||||
resource.setBillingContact(map(entity.getBillingContact(), HsOfficeContactResource.class));
|
||||
};
|
||||
|
||||
final BiConsumer<HsOfficeDebitorInsertResource, HsOfficeDebitorEntity> DEBITOR_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||
entity.setPartner(em.getReference(HsOfficePartnerEntity.class, resource.getPartnerUuid()));
|
||||
entity.setBillingContact(em.getReference(HsOfficeContactEntity.class, resource.getBillingContactUuid()));
|
||||
};
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package net.hostsharing.hsadminng.hs.office.debitor;
|
||||
|
||||
import lombok.*;
|
||||
import net.hostsharing.hsadminng.errors.DisplayName;
|
||||
import net.hostsharing.hsadminng.Stringify;
|
||||
import net.hostsharing.hsadminng.Stringifyable;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.UUID;
|
||||
|
||||
import static net.hostsharing.hsadminng.Stringify.stringify;
|
||||
|
||||
@Entity
|
||||
@Table(name = "hs_office_debitor_rv")
|
||||
@Getter
|
||||
@Setter
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@DisplayName("Debitor")
|
||||
public class HsOfficeDebitorEntity implements Stringifyable {
|
||||
|
||||
private static Stringify<HsOfficeDebitorEntity> stringify =
|
||||
stringify(HsOfficeDebitorEntity.class, "debitor")
|
||||
.withProp(HsOfficeDebitorEntity::getDebitorNumber)
|
||||
.withProp(HsOfficeDebitorEntity::getPartner)
|
||||
.withSeparator(": ")
|
||||
.quotedValues(false);
|
||||
|
||||
private @Id UUID uuid;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "partneruuid")
|
||||
private HsOfficePartnerEntity partner;
|
||||
|
||||
private @Column(name = "debitornumber") Integer debitorNumber;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "billingcontactuuid")
|
||||
private HsOfficeContactEntity billingContact;
|
||||
|
||||
private @Column(name = "vatid") String vatId;
|
||||
private @Column(name = "vatcountrycode") String vatCountryCode;
|
||||
private @Column(name = "vatbusiness") boolean vatBusiness;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return stringify.apply(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toShortString() {
|
||||
return debitorNumber.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package net.hostsharing.hsadminng.hs.office.debitor;
|
||||
|
||||
import net.hostsharing.hsadminng.EntityPatcher;
|
||||
import net.hostsharing.hsadminng.OptionalFromJson;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeDebitorPatchResource;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
class HsOfficeDebitorEntityPatcher implements EntityPatcher<HsOfficeDebitorPatchResource> {
|
||||
|
||||
private final EntityManager em;
|
||||
private final HsOfficeDebitorEntity entity;
|
||||
|
||||
HsOfficeDebitorEntityPatcher(
|
||||
final EntityManager em,
|
||||
final HsOfficeDebitorEntity entity) {
|
||||
this.em = em;
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(final HsOfficeDebitorPatchResource resource) {
|
||||
OptionalFromJson.of(resource.getBillingContactUuid()).ifPresent(newValue -> {
|
||||
verifyNotNull(newValue, "billingContact");
|
||||
entity.setBillingContact(em.getReference(HsOfficeContactEntity.class, newValue));
|
||||
});
|
||||
OptionalFromJson.of(resource.getVatId()).ifPresent(entity::setVatId);
|
||||
OptionalFromJson.of(resource.getVatCountryCode()).ifPresent(entity::setVatCountryCode);
|
||||
OptionalFromJson.of(resource.getVatBusiness()).ifPresent(newValue -> {
|
||||
verifyNotNull(newValue, "vatBusiness");
|
||||
entity.setVatBusiness(newValue);
|
||||
});
|
||||
}
|
||||
|
||||
private void verifyNotNull(final Object newValue, final String propertyName) {
|
||||
if (newValue == null) {
|
||||
throw new IllegalArgumentException("property '" + propertyName + "' must not be null");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package net.hostsharing.hsadminng.hs.office.debitor;
|
||||
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEntity, UUID> {
|
||||
|
||||
Optional<HsOfficeDebitorEntity> findByUuid(UUID id);
|
||||
|
||||
@Query("""
|
||||
SELECT debitor FROM HsOfficeDebitorEntity debitor
|
||||
WHERE debitor.debitorNumber = :debitorNumber
|
||||
""")
|
||||
List<HsOfficeDebitorEntity> findDebitorByDebitorNumber(int debitorNumber);
|
||||
|
||||
@Query("""
|
||||
SELECT debitor FROM HsOfficeDebitorEntity debitor
|
||||
JOIN HsOfficePartnerEntity partner ON partner.uuid = debitor.partner
|
||||
JOIN HsOfficePersonEntity person ON person.uuid = partner.person
|
||||
JOIN HsOfficeContactEntity contact ON contact.uuid = debitor.billingContact
|
||||
WHERE :name is null
|
||||
OR partner.birthName like concat(:name, '%')
|
||||
OR person.tradeName like concat(:name, '%')
|
||||
OR person.familyName like concat(:name, '%')
|
||||
OR person.givenName like concat(:name, '%')
|
||||
OR contact.label like concat(:name, '%')
|
||||
""")
|
||||
List<HsOfficeDebitorEntity> findDebitorByOptionalNameLike(String name);
|
||||
|
||||
HsOfficeDebitorEntity save(final HsOfficeDebitorEntity entity);
|
||||
|
||||
long count();
|
||||
|
||||
int deleteByUuid(UUID uuid);
|
||||
}
|
@ -65,6 +65,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
||||
|
||||
final var entityToSave = mapToHsOfficePartnerEntity(body);
|
||||
entityToSave.setUuid(UUID.randomUUID());
|
||||
// TODO.impl: use getReference
|
||||
entityToSave.setContact(contactRepo.findByUuid(body.getContactUuid()).orElseThrow(
|
||||
() -> new NoSuchElementException("cannot find contact uuid " + body.getContactUuid())
|
||||
));
|
||||
@ -141,6 +142,7 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
|
||||
resource.setContact(map(entity.getContact(), HsOfficeContactResource.class));
|
||||
};
|
||||
|
||||
// TODO.impl: user postmapper + getReference
|
||||
private HsOfficePartnerEntity mapToHsOfficePartnerEntity(final HsOfficePartnerInsertResource resource) {
|
||||
final var entity = new HsOfficePartnerEntity();
|
||||
entity.setBirthday(resource.getBirthday());
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.hostsharing.hsadminng.hs.office.partner;
|
||||
|
||||
import lombok.*;
|
||||
import net.hostsharing.hsadminng.errors.DisplayName;
|
||||
import net.hostsharing.hsadminng.Stringify;
|
||||
import net.hostsharing.hsadminng.Stringifyable;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
|
||||
@ -19,6 +20,7 @@ import static net.hostsharing.hsadminng.Stringify.stringify;
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@DisplayName("Partner")
|
||||
public class HsOfficePartnerEntity implements Stringifyable {
|
||||
|
||||
private static Stringify<HsOfficePartnerEntity> stringify = stringify(HsOfficePartnerEntity.class, "partner")
|
||||
|
@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.office.person;
|
||||
import com.vladmihalcea.hibernate.type.basic.PostgreSQLEnumType;
|
||||
import lombok.*;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import net.hostsharing.hsadminng.errors.DisplayName;
|
||||
import net.hostsharing.hsadminng.Stringify;
|
||||
import net.hostsharing.hsadminng.Stringifyable;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@ -26,6 +27,7 @@ import static net.hostsharing.hsadminng.Stringify.stringify;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@FieldNameConstants
|
||||
@DisplayName("Person")
|
||||
public class HsOfficePersonEntity implements Stringifyable {
|
||||
|
||||
private static Stringify<HsOfficePersonEntity> toString = stringify(HsOfficePersonEntity.class, "person")
|
||||
@ -50,10 +52,6 @@ public class HsOfficePersonEntity implements Stringifyable {
|
||||
@Column(name = "givenname")
|
||||
private String givenName;
|
||||
|
||||
public String getDisplayName() {
|
||||
return toShortString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString.apply(this);
|
||||
|
Reference in New Issue
Block a user