office-related spec-clarifications and -amendmends (contact.emailaddresses+.phonenumbers JSON) #50
@ -98,7 +98,15 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject {
|
|||||||
private Map<String, Object> resources = new HashMap<>();
|
private Map<String, Object> resources = new HashMap<>();
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private PatchableMapWrapper resourcesWrapper;
|
private PatchableMapWrapper<Object> resourcesWrapper;
|
||||||
|
|
||||||
|
public PatchableMapWrapper<Object> getResources() {
|
||||||
|
return PatchableMapWrapper.of(resourcesWrapper, (newWrapper) -> {resourcesWrapper = newWrapper; }, resources );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putResources(Map<String, Object> newResources) {
|
||||||
|
getResources().assign(newResources);
|
||||||
|
}
|
||||||
|
|
||||||
public void setValidFrom(final LocalDate validFrom) {
|
public void setValidFrom(final LocalDate validFrom) {
|
||||||
setValidity(toPostgresDateRange(validFrom, getValidTo()));
|
setValidity(toPostgresDateRange(validFrom, getValidTo()));
|
||||||
@ -116,20 +124,6 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject {
|
|||||||
return upperInclusiveFromPostgresDateRange(getValidity());
|
return upperInclusiveFromPostgresDateRange(getValidity());
|
||||||
}
|
}
|
||||||
|
|
||||||
public PatchableMapWrapper getResources() {
|
|
||||||
if ( resourcesWrapper == null ) {
|
|
||||||
resourcesWrapper = new PatchableMapWrapper(resources);
|
|
||||||
}
|
|
||||||
return resourcesWrapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void putResources(Map<String, Object> entries) {
|
|
||||||
if ( resourcesWrapper == null ) {
|
|
||||||
resourcesWrapper = new PatchableMapWrapper(resources);
|
|
||||||
}
|
|
||||||
resourcesWrapper.assign(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return stringify.apply(this);
|
return stringify.apply(this);
|
||||||
|
@ -105,20 +105,14 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject {
|
|||||||
private Map<String, Object> config = new HashMap<>();
|
private Map<String, Object> config = new HashMap<>();
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
private PatchableMapWrapper configWrapper;
|
private PatchableMapWrapper<Object> configWrapper;
|
||||||
|
|
||||||
public PatchableMapWrapper getConfig() {
|
public PatchableMapWrapper<Object> getConfig() {
|
||||||
if ( configWrapper == null ) {
|
return PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config );
|
||||||
configWrapper = new PatchableMapWrapper(config);
|
|
||||||
}
|
|
||||||
return configWrapper;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void putConfig(Map<String, Object> entries) {
|
public void putConfig(Map<String, Object> newConfg) {
|
||||||
if ( configWrapper == null ) {
|
PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config).assign(newConfg);
|
||||||
configWrapper = new PatchableMapWrapper(config);
|
|
||||||
}
|
|
||||||
configWrapper.assign(entries);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -14,6 +14,9 @@ import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBui
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
import static net.hostsharing.hsadminng.mapper.KeyValueMap.from;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
|
||||||
@ -51,7 +54,7 @@ public class HsOfficeContactController implements HsOfficeContactsApi {
|
|||||||
|
|
||||||
context.define(currentUser, assumedRoles);
|
context.define(currentUser, assumedRoles);
|
||||||
|
|
||||||
final var entityToSave = mapper.map(body, HsOfficeContactEntity.class);
|
final var entityToSave = mapper.map(body, HsOfficeContactEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
|
||||||
|
|
||||||
final var saved = contactRepo.save(entityToSave);
|
final var saved = contactRepo.save(entityToSave);
|
||||||
|
|
||||||
@ -108,10 +111,16 @@ public class HsOfficeContactController implements HsOfficeContactsApi {
|
|||||||
|
|
||||||
final var current = contactRepo.findByUuid(contactUuid).orElseThrow();
|
final var current = contactRepo.findByUuid(contactUuid).orElseThrow();
|
||||||
|
|
||||||
new HsOfficeContactEntityPatch(current).apply(body);
|
new HsOfficeContactEntityPatcher(current).apply(body);
|
||||||
|
|
||||||
final var saved = contactRepo.save(current);
|
final var saved = contactRepo.save(current);
|
||||||
final var mapped = mapper.map(saved, HsOfficeContactResource.class);
|
final var mapped = mapper.map(saved, HsOfficeContactResource.class);
|
||||||
return ResponseEntity.ok(mapped);
|
return ResponseEntity.ok(mapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final BiConsumer<HsOfficeContactInsertResource, HsOfficeContactEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
|
||||||
|
entity.putEmailAddresses(from(resource.getEmailAddresses()));
|
||||||
|
entity.putPhoneNumbers(from(resource.getPhoneNumbers()));
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,22 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.contact;
|
package net.hostsharing.hsadminng.hs.office.contact;
|
||||||
|
|
||||||
|
import io.hypersistence.utils.hibernate.type.json.JsonType;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import lombok.experimental.FieldNameConstants;
|
import lombok.experimental.FieldNameConstants;
|
||||||
import net.hostsharing.hsadminng.errors.DisplayName;
|
import net.hostsharing.hsadminng.errors.DisplayName;
|
||||||
|
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
|
||||||
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
||||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
|
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
|
||||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
|
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
|
||||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||||
import org.hibernate.annotations.GenericGenerator;
|
import org.hibernate.annotations.GenericGenerator;
|
||||||
|
import org.hibernate.annotations.Type;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL;
|
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.GLOBAL;
|
||||||
@ -44,17 +49,45 @@ public class HsOfficeContactEntity implements Stringifyable, RbacObject {
|
|||||||
@Version
|
@Version
|
||||||
private int version;
|
private int version;
|
||||||
|
|
||||||
@Column(name = "label")
|
@Column(name = "label") // TODO.impl: rename to caption
|
||||||
private String label;
|
private String label;
|
||||||
|
|
||||||
@Column(name = "postaladdress")
|
@Column(name = "postaladdress")
|
||||||
private String postalAddress; // TODO.spec: check if we really want multiple, if so: JSON-Array or Postgres-Array?
|
private String postalAddress; // multiline free-format text
|
||||||
|
|
||||||
@Column(name = "emailaddresses", columnDefinition = "json")
|
@Builder.Default
|
||||||
private String emailAddresses; // TODO.spec: check if we can really add multiple. format: ["eins@...", "zwei@..."]
|
@Setter(AccessLevel.NONE)
|
||||||
|
@Type(JsonType.class)
|
||||||
|
@Column(name = "emailaddresses")
|
||||||
|
private Map<String, String> emailAddresses = new HashMap<>();
|
||||||
|
|
||||||
@Column(name = "phonenumbers", columnDefinition = "json")
|
@Transient
|
||||||
private String phoneNumbers; // TODO.spec: check if we can really add multiple. format: { "office": "+49 40 12345-10", "fax": "+49 40 12345-05" }
|
private PatchableMapWrapper<String> emailAddressesWrapper;
|
||||||
|
|
||||||
|
@Builder.Default
|
||||||
|
@Setter(AccessLevel.NONE)
|
||||||
|
@Type(JsonType.class)
|
||||||
|
@Column(name = "phonenumbers")
|
||||||
|
private Map<String, String> phoneNumbers = new HashMap<>();
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private PatchableMapWrapper<String> phoneNumbersWrapper;
|
||||||
|
|
||||||
|
public PatchableMapWrapper<String> getEmailAddresses() {
|
||||||
|
return PatchableMapWrapper.of(emailAddressesWrapper, (newWrapper) -> {emailAddressesWrapper = newWrapper; }, emailAddresses );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putEmailAddresses(Map<String, String> newEmailAddresses) {
|
||||||
|
getEmailAddresses().assign(newEmailAddresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PatchableMapWrapper<String> getPhoneNumbers() {
|
||||||
|
return PatchableMapWrapper.of(phoneNumbersWrapper, (newWrapper) -> {phoneNumbersWrapper = newWrapper; }, phoneNumbers );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void putPhoneNumbers(Map<String, String> newPhoneNumbers) {
|
||||||
|
getPhoneNumbers().assign(newPhoneNumbers);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.contact;
|
package net.hostsharing.hsadminng.hs.office.contact;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.mapper.EntityPatcher;
|
import net.hostsharing.hsadminng.mapper.EntityPatcher;
|
||||||
|
import net.hostsharing.hsadminng.mapper.KeyValueMap;
|
||||||
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
|
import net.hostsharing.hsadminng.mapper.OptionalFromJson;
|
||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeContactPatchResource;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeContactPatchResource;
|
||||||
|
|
||||||
class HsOfficeContactEntityPatch implements EntityPatcher<HsOfficeContactPatchResource> {
|
import java.util.Optional;
|
||||||
|
|
||||||
|
class HsOfficeContactEntityPatcher implements EntityPatcher<HsOfficeContactPatchResource> {
|
||||||
|
|
||||||
private final HsOfficeContactEntity entity;
|
private final HsOfficeContactEntity entity;
|
||||||
|
|
||||||
HsOfficeContactEntityPatch(final HsOfficeContactEntity entity) {
|
HsOfficeContactEntityPatcher(final HsOfficeContactEntity entity) {
|
||||||
this.entity = entity;
|
this.entity = entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,7 +19,9 @@ class HsOfficeContactEntityPatch implements EntityPatcher<HsOfficeContactPatchRe
|
|||||||
public void apply(final HsOfficeContactPatchResource resource) {
|
public void apply(final HsOfficeContactPatchResource resource) {
|
||||||
OptionalFromJson.of(resource.getLabel()).ifPresent(entity::setLabel);
|
OptionalFromJson.of(resource.getLabel()).ifPresent(entity::setLabel);
|
||||||
OptionalFromJson.of(resource.getPostalAddress()).ifPresent(entity::setPostalAddress);
|
OptionalFromJson.of(resource.getPostalAddress()).ifPresent(entity::setPostalAddress);
|
||||||
OptionalFromJson.of(resource.getEmailAddresses()).ifPresent(entity::setEmailAddresses);
|
Optional.ofNullable(resource.getEmailAddresses())
|
||||||
OptionalFromJson.of(resource.getPhoneNumbers()).ifPresent(entity::setPhoneNumbers);
|
.ifPresent(r -> entity.getEmailAddresses().patch(KeyValueMap.from(resource.getEmailAddresses())));
|
||||||
|
Optional.ofNullable(resource.getPhoneNumbers())
|
||||||
|
.ifPresent(r -> entity.getPhoneNumbers().patch(KeyValueMap.from(resource.getPhoneNumbers())));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -170,7 +170,7 @@ public class HsOfficeDebitorEntity implements RbacObject, Stringifyable {
|
|||||||
"vatCountryCode",
|
"vatCountryCode",
|
||||||
"vatBusiness",
|
"vatBusiness",
|
||||||
"vatReverseCharge",
|
"vatReverseCharge",
|
||||||
"defaultPrefix" /* TODO.spec: do we want that updatable? */)
|
"defaultPrefix")
|
||||||
.toRole("global", ADMIN).grantPermission(INSERT)
|
.toRole("global", ADMIN).grantPermission(INSERT)
|
||||||
|
|
||||||
.importRootEntityAliasProxy("debitorRel", HsOfficeRelationEntity.class, usingCase(DEBITOR),
|
.importRootEntityAliasProxy("debitorRel", HsOfficeRelationEntity.class, usingCase(DEBITOR),
|
||||||
|
@ -142,7 +142,7 @@ public class HsOfficeRelationEntity implements RbacObject, Stringifyable {
|
|||||||
with.permission(UPDATE);
|
with.permission(UPDATE);
|
||||||
})
|
})
|
||||||
.createSubRole(AGENT, (with) -> {
|
.createSubRole(AGENT, (with) -> {
|
||||||
// TODO.spec: we need relation:PROXY, to allow changing the relation contact.
|
// TODO.rbac: we need relation:PROXY, to allow changing the relation contact.
|
||||||
// the alternative would be to move this to the relation:ADMIN role,
|
// the alternative would be to move this to the relation:ADMIN role,
|
||||||
// but then the partner holder person could update the partner relation itself,
|
// but then the partner holder person could update the partner relation itself,
|
||||||
// see partner entity.
|
// see partner entity.
|
||||||
|
@ -5,9 +5,9 @@ import java.util.Map;
|
|||||||
public class KeyValueMap {
|
public class KeyValueMap {
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static Map<String, Object> from(final Object obj) {
|
public static <T> Map<String, T> from(final Object obj) {
|
||||||
if (obj instanceof Map<?, ?>) {
|
if (obj == null || obj instanceof Map<?, ?>) {
|
||||||
return (Map<String, Object>) obj;
|
return (Map<String, T>) obj;
|
||||||
}
|
}
|
||||||
throw new ClassCastException("Map expected, but got: " + obj);
|
throw new ClassCastException("Map expected, but got: " + obj);
|
||||||
}
|
}
|
||||||
|
@ -12,19 +12,19 @@ import static java.util.Arrays.stream;
|
|||||||
* This is a map which can take key-value-pairs where the value can be null
|
* This is a map which can take key-value-pairs where the value can be null
|
||||||
* thus JSON nullable object structures from HTTP PATCH can be represented.
|
* thus JSON nullable object structures from HTTP PATCH can be represented.
|
||||||
*/
|
*/
|
||||||
public class PatchMap extends TreeMap<String, Object> {
|
public class PatchMap<T> extends TreeMap<String, T> {
|
||||||
|
|
||||||
public PatchMap(final ImmutablePair<String, Object>[] entries) {
|
public PatchMap(final ImmutablePair<String, T>[] entries) {
|
||||||
stream(entries).forEach(r -> put(r.getKey(), r.getValue()));
|
stream(entries).forEach(r -> put(r.getKey(), r.getValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
public static Map<String, Object> patchMap(final ImmutablePair<String, Object>... entries) {
|
public static <T> Map<String, T> patchMap(final ImmutablePair<String, Object>... entries) {
|
||||||
return new PatchMap(entries);
|
return new PatchMap(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static ImmutablePair<String, Object> entry(final String key, final Object value) {
|
public static <T> ImmutablePair<String, T> entry(final String key, final T value) {
|
||||||
return new ImmutablePair<>(key, value);
|
return new ImmutablePair<>(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,31 +6,43 @@ import jakarta.validation.constraints.NotNull;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static java.util.Optional.ofNullable;
|
||||||
import static java.util.stream.Collectors.joining;
|
import static java.util.stream.Collectors.joining;
|
||||||
|
|
||||||
/** This class wraps another (usually persistent) map and
|
/** This class wraps another (usually persistent) map and
|
||||||
* supports applying `PatchMap` as well as a toString method with stable entry order.
|
* supports applying `PatchMap` as well as a toString method with stable entry order.
|
||||||
*/
|
*/
|
||||||
public class PatchableMapWrapper implements Map<String, Object> {
|
public class PatchableMapWrapper<T> implements Map<String, T> {
|
||||||
|
|
||||||
private final Map<String, Object> delegate;
|
private final Map<String, T> delegate;
|
||||||
|
|
||||||
public PatchableMapWrapper(final Map<String, Object> map) {
|
private PatchableMapWrapper(final Map<String, T> map) {
|
||||||
delegate = map;
|
delegate = map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> PatchableMapWrapper<T> of(final PatchableMapWrapper<T> currentWrapper, final Consumer<PatchableMapWrapper<T>> setWrapper, final Map<String, T> target) {
|
||||||
|
return ofNullable(currentWrapper).orElseGet(() -> {
|
||||||
|
final var newWrapper = new PatchableMapWrapper<T>(target);
|
||||||
|
setWrapper.accept(newWrapper);
|
||||||
|
return newWrapper;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static ImmutablePair<String, Object> entry(final String key, final Object value) {
|
public static <E> ImmutablePair<String, E> entry(final String key, final E value) {
|
||||||
return new ImmutablePair<>(key, value);
|
return new ImmutablePair<>(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void assign(final Map<String, Object> entries) {
|
public void assign(final Map<String, T> entries) {
|
||||||
|
if (entries != null ) {
|
||||||
delegate.clear();
|
delegate.clear();
|
||||||
delegate.putAll(entries);
|
delegate.putAll(entries);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void patch(final Map<String, Object> patch) {
|
public void patch(final Map<String, T> patch) {
|
||||||
patch.forEach((key, value) -> {
|
patch.forEach((key, value) -> {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
remove(key);
|
remove(key);
|
||||||
@ -73,22 +85,22 @@ public class PatchableMapWrapper implements Map<String, Object> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object get(final Object key) {
|
public T get(final Object key) {
|
||||||
return delegate.get(key);
|
return delegate.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object put(final String key, final Object value) {
|
public T put(final String key, final T value) {
|
||||||
return delegate.put(key, value);
|
return delegate.put(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object remove(final Object key) {
|
public T remove(final Object key) {
|
||||||
return delegate.remove(key);
|
return delegate.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void putAll(final Map<? extends String, ?> m) {
|
public void putAll(final @NotNull Map<? extends String, ? extends T> m) {
|
||||||
delegate.putAll(m);
|
delegate.putAll(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,12 +115,12 @@ public class PatchableMapWrapper implements Map<String, Object> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<Object> values() {
|
public Collection<T> values() {
|
||||||
return delegate.values();
|
return delegate.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<Entry<String, Object>> entrySet() {
|
public Set<Entry<String, T>> entrySet() {
|
||||||
return delegate.entrySet();
|
return delegate.entrySet();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,9 @@ import net.hostsharing.hsadminng.errors.DisplayName;
|
|||||||
|
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
@ -62,6 +64,7 @@ public final class Stringify<B> {
|
|||||||
final var propValues = props.stream()
|
final var propValues = props.stream()
|
||||||
.map(prop -> PropertyValue.of(prop, prop.getter.apply(object)))
|
.map(prop -> PropertyValue.of(prop, prop.getter.apply(object)))
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
|
.filter(PropertyValue::nonEmpty)
|
||||||
.map(propVal -> {
|
.map(propVal -> {
|
||||||
if (propVal.rawValue instanceof Stringifyable stringifyable) {
|
if (propVal.rawValue instanceof Stringifyable stringifyable) {
|
||||||
return new PropertyValue<>(propVal.prop, propVal.rawValue, stringifyable.toShortString());
|
return new PropertyValue<>(propVal.prop, propVal.rawValue, stringifyable.toShortString());
|
||||||
@ -110,5 +113,12 @@ public final class Stringify<B> {
|
|||||||
static <B> PropertyValue<B> of(Property<B> prop, Object rawValue) {
|
static <B> PropertyValue<B> of(Property<B> prop, Object rawValue) {
|
||||||
return rawValue != null ? new PropertyValue<>(prop, rawValue, rawValue.toString()) : null;
|
return rawValue != null ? new PropertyValue<>(prop, rawValue, rawValue.toString()) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean nonEmpty() {
|
||||||
|
return rawValue != null &&
|
||||||
|
(!(rawValue instanceof Collection<?> c) || !c.isEmpty()) &&
|
||||||
|
(!(rawValue instanceof Map<?,?> m) || !m.isEmpty()) &&
|
||||||
|
(!(rawValue instanceof String s) || !s.isEmpty());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,9 +14,9 @@ components:
|
|||||||
postalAddress:
|
postalAddress:
|
||||||
type: string
|
type: string
|
||||||
emailAddresses:
|
emailAddresses:
|
||||||
type: string
|
$ref: '#/components/schemas/HsOfficeContactEmailAddresses'
|
||||||
phoneNumbers:
|
phoneNumbers:
|
||||||
type: string
|
$ref: '#/components/schemas/HsOfficeContactPhoneNumbers'
|
||||||
|
|
||||||
HsOfficeContactInsert:
|
HsOfficeContactInsert:
|
||||||
type: object
|
type: object
|
||||||
@ -26,9 +26,9 @@ components:
|
|||||||
postalAddress:
|
postalAddress:
|
||||||
type: string
|
type: string
|
||||||
emailAddresses:
|
emailAddresses:
|
||||||
type: string
|
$ref: '#/components/schemas/HsOfficeContactEmailAddresses'
|
||||||
phoneNumbers:
|
phoneNumbers:
|
||||||
type: string
|
$ref: '#/components/schemas/HsOfficeContactPhoneNumbers'
|
||||||
required:
|
required:
|
||||||
- label
|
- label
|
||||||
|
|
||||||
@ -42,8 +42,31 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
nullable: true
|
nullable: true
|
||||||
emailAddresses:
|
emailAddresses:
|
||||||
type: string
|
$ref: '#/components/schemas/HsOfficeContactEmailAddresses'
|
||||||
nullable: true
|
|
||||||
phoneNumbers:
|
phoneNumbers:
|
||||||
|
$ref: '#/components/schemas/HsOfficeContactPhoneNumbers'
|
||||||
|
|
||||||
|
HsOfficeContactEmailAddresses:
|
||||||
|
# forces generating a java.lang.Object containing a Map, instead of class HsOfficeContactEmailAddresses
|
||||||
|
anyOf:
|
||||||
|
- type: object
|
||||||
|
additionalProperties: true
|
||||||
|
|
||||||
|
HsOfficeContactPhoneNumbers:
|
||||||
hsh-michaelhoennig marked this conversation as resolved
|
|||||||
|
# forces generating a java.lang.Object containing a Map, instead of class HsOfficeContactEmailAddresses
|
||||||
|
anyOf:
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
phone_office:
|
||||||
type: string
|
type: string
|
||||||
nullable: true
|
nullable: true
|
||||||
|
phone_private:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
phone_mobile:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
fax:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
additionalProperties: false
|
||||||
|
@ -63,7 +63,7 @@ begin
|
|||||||
insert
|
insert
|
||||||
into RbacGrants (grantedByRoleUuid, ascendantUuid, descendantUuid, assumed)
|
into RbacGrants (grantedByRoleUuid, ascendantUuid, descendantUuid, assumed)
|
||||||
values (grantedByRoleUuid, userUuid, grantedRoleUuid, doAssume);
|
values (grantedByRoleUuid, userUuid, grantedRoleUuid, doAssume);
|
||||||
-- TODO.spec: What should happen on mupltiple grants? What if options (doAssume) are not the same?
|
-- TODO.impl: What should happen on mupltiple grants? What if options (doAssume) are not the same?
|
||||||
-- Most powerful or latest grant wins? What about managed?
|
-- Most powerful or latest grant wins? What about managed?
|
||||||
-- on conflict do nothing; -- allow granting multiple times
|
-- on conflict do nothing; -- allow granting multiple times
|
||||||
end; $$;
|
end; $$;
|
||||||
|
@ -52,7 +52,7 @@ begin
|
|||||||
if cardinality(userUuids) > 0 then
|
if cardinality(userUuids) > 0 then
|
||||||
-- direct grants to users need a grantedByRole which can revoke the grant
|
-- direct grants to users need a grantedByRole which can revoke the grant
|
||||||
if grantedByRole is null then
|
if grantedByRole is null then
|
||||||
userGrantsByRoleUuid := roleUuid; -- TODO.spec: or do we want to require an explicit userGrantsByRoleUuid?
|
userGrantsByRoleUuid := roleUuid; -- TODO.impl: or do we want to require an explicit userGrantsByRoleUuid?
|
||||||
else
|
else
|
||||||
userGrantsByRoleUuid := getRoleId(grantedByRole);
|
userGrantsByRoleUuid := getRoleId(grantedByRole);
|
||||||
end if;
|
end if;
|
||||||
|
@ -10,8 +10,8 @@ create table if not exists hs_office_contact
|
|||||||
version int not null default 0,
|
version int not null default 0,
|
||||||
label varchar(128) not null,
|
label varchar(128) not null,
|
||||||
postalAddress text,
|
postalAddress text,
|
||||||
emailAddresses text, -- TODO.feat: change to json
|
emailAddresses jsonb not null,
|
||||||
phoneNumbers text -- TODO.feat: change to json
|
phoneNumbers jsonb not null
|
||||||
hsh-michaelhoennig marked this conversation as resolved
Outdated
hsh-marcsandlus
commented
jsonb jsonb
|
|||||||
);
|
);
|
||||||
--//
|
--//
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ create or replace procedure createHsOfficeContactTestData(contLabel varchar)
|
|||||||
language plpgsql as $$
|
language plpgsql as $$
|
||||||
declare
|
declare
|
||||||
currentTask varchar;
|
currentTask varchar;
|
||||||
|
postalAddr varchar;
|
||||||
emailAddr varchar;
|
emailAddr varchar;
|
||||||
begin
|
begin
|
||||||
currentTask = 'creating contact test-data ' || contLabel;
|
currentTask = 'creating contact test-data ' || contLabel;
|
||||||
@ -22,14 +23,17 @@ begin
|
|||||||
perform createRbacUser(emailAddr);
|
perform createRbacUser(emailAddr);
|
||||||
call defineContext(currentTask, null, emailAddr);
|
call defineContext(currentTask, null, emailAddr);
|
||||||
|
|
||||||
|
postalAddr := E'Vorname Nachname\nStraße Hnr\nPLZ Stadt';
|
||||||
|
|
||||||
raise notice 'creating test contact: %', contLabel;
|
raise notice 'creating test contact: %', contLabel;
|
||||||
insert
|
insert
|
||||||
into hs_office_contact (label, postaladdress, emailaddresses, phonenumbers)
|
into hs_office_contact (label, postaladdress, emailaddresses, phonenumbers)
|
||||||
values (contLabel, $address$
|
values (
|
||||||
Vorname Nachname
|
contLabel,
|
||||||
Straße Hnr
|
postalAddr,
|
||||||
PLZ Stadt
|
('{ "main": "' || emailAddr || '" }')::jsonb,
|
||||||
$address$, emailAddr, '+49 123 1234567');
|
('{ "phone_office": "+49 123 1234567" }')::jsonb
|
||||||
|
);
|
||||||
end; $$;
|
end; $$;
|
||||||
--//
|
--//
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ create table hs_office_debitor
|
|||||||
debitorNumberSuffix char(2) not null check (debitorNumberSuffix::text ~ '^[0-9][0-9]$'),
|
debitorNumberSuffix char(2) not null check (debitorNumberSuffix::text ~ '^[0-9][0-9]$'),
|
||||||
debitorRelUuid uuid not null references hs_office_relation(uuid),
|
debitorRelUuid uuid not null references hs_office_relation(uuid),
|
||||||
billable boolean not null default true,
|
billable boolean not null default true,
|
||||||
vatId varchar(24), -- TODO.spec: here or in person?
|
vatId varchar(24),
|
||||||
vatCountryCode varchar(2),
|
vatCountryCode varchar(2),
|
||||||
vatBusiness boolean not null,
|
vatBusiness boolean not null,
|
||||||
vatReverseCharge boolean not null,
|
vatReverseCharge boolean not null,
|
||||||
|
@ -19,6 +19,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
|
|
||||||
import jakarta.persistence.EntityManager;
|
import jakarta.persistence.EntityManager;
|
||||||
import jakarta.persistence.PersistenceContext;
|
import jakarta.persistence.PersistenceContext;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.rbac.test.IsValidUuidMatcher.isUuidValid;
|
import static net.hostsharing.hsadminng.rbac.test.IsValidUuidMatcher.isUuidValid;
|
||||||
@ -103,7 +104,9 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
|||||||
.body("""
|
.body("""
|
||||||
{
|
{
|
||||||
"label": "Temp Contact",
|
"label": "Temp Contact",
|
||||||
"emailAddresses": "test@example.org"
|
"emailAddresses": {
|
||||||
|
"main": "test@example.org"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
.port(port)
|
.port(port)
|
||||||
@ -114,7 +117,7 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
|||||||
.contentType(ContentType.JSON)
|
.contentType(ContentType.JSON)
|
||||||
.body("uuid", isUuidValid())
|
.body("uuid", isUuidValid())
|
||||||
.body("label", is("Temp Contact"))
|
.body("label", is("Temp Contact"))
|
||||||
.body("emailAddresses", is("test@example.org"))
|
.body("emailAddresses", is(Map.of("main", "test@example.org")))
|
||||||
.header("Location", startsWith("http://localhost"))
|
.header("Location", startsWith("http://localhost"))
|
||||||
.extract().header("Location"); // @formatter:on
|
.extract().header("Location"); // @formatter:on
|
||||||
|
|
||||||
@ -181,8 +184,12 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
|||||||
.body("", lenientlyEquals("""
|
.body("", lenientlyEquals("""
|
||||||
{
|
{
|
||||||
"label": "first contact",
|
"label": "first contact",
|
||||||
"emailAddresses": "contact-admin@firstcontact.example.com",
|
"emailAddresses": {
|
||||||
"phoneNumbers": "+49 123 1234567"
|
"main": "contact-admin@firstcontact.example.com"
|
||||||
|
},
|
||||||
|
"phoneNumbers": {
|
||||||
|
"phone_office": "+49 123 1234567"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
""")); // @formatter:on
|
""")); // @formatter:on
|
||||||
}
|
}
|
||||||
@ -204,9 +211,13 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
|||||||
.body("""
|
.body("""
|
||||||
{
|
{
|
||||||
"label": "Temp patched contact",
|
"label": "Temp patched contact",
|
||||||
"emailAddresses": "patched@example.org",
|
"emailAddresses": {
|
||||||
|
"main": "patched@example.org"
|
||||||
|
},
|
||||||
"postalAddress": "Patched Address",
|
"postalAddress": "Patched Address",
|
||||||
"phoneNumbers": "+01 100 123456"
|
"phoneNumbers": {
|
||||||
|
"phone_office": "+01 100 123456"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
.port(port)
|
.port(port)
|
||||||
@ -217,9 +228,9 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
|||||||
.contentType(ContentType.JSON)
|
.contentType(ContentType.JSON)
|
||||||
.body("uuid", isUuidValid())
|
.body("uuid", isUuidValid())
|
||||||
.body("label", is("Temp patched contact"))
|
.body("label", is("Temp patched contact"))
|
||||||
.body("emailAddresses", is("patched@example.org"))
|
.body("emailAddresses", is(Map.of("main", "patched@example.org")))
|
||||||
.body("postalAddress", is("Patched Address"))
|
.body("postalAddress", is("Patched Address"))
|
||||||
.body("phoneNumbers", is("+01 100 123456"));
|
.body("phoneNumbers", is(Map.of("phone_office", "+01 100 123456")));
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
// finally, the contact is actually updated
|
// finally, the contact is actually updated
|
||||||
@ -227,9 +238,9 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
|||||||
assertThat(contactRepo.findByUuid(givenContact.getUuid())).isPresent().get()
|
assertThat(contactRepo.findByUuid(givenContact.getUuid())).isPresent().get()
|
||||||
.matches(person -> {
|
.matches(person -> {
|
||||||
assertThat(person.getLabel()).isEqualTo("Temp patched contact");
|
assertThat(person.getLabel()).isEqualTo("Temp patched contact");
|
||||||
assertThat(person.getEmailAddresses()).isEqualTo("patched@example.org");
|
assertThat(person.getEmailAddresses()).containsExactlyEntriesOf(Map.of("main", "patched@example.org"));
|
||||||
assertThat(person.getPostalAddress()).isEqualTo("Patched Address");
|
assertThat(person.getPostalAddress()).isEqualTo("Patched Address");
|
||||||
assertThat(person.getPhoneNumbers()).isEqualTo("+01 100 123456");
|
assertThat(person.getPhoneNumbers()).containsExactlyEntriesOf(Map.of("phone_office", "+01 100 123456"));
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -246,8 +257,12 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
|||||||
.contentType(ContentType.JSON)
|
.contentType(ContentType.JSON)
|
||||||
.body("""
|
.body("""
|
||||||
{
|
{
|
||||||
"emailAddresses": "patched@example.org",
|
"emailAddresses": {
|
||||||
"phoneNumbers": "+01 100 123456"
|
"main": "patched@example.org"
|
||||||
|
},
|
||||||
|
"phoneNumbers": {
|
||||||
|
"phone_office": "+01 100 123456"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
.port(port)
|
.port(port)
|
||||||
@ -258,18 +273,18 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
|||||||
.contentType(ContentType.JSON)
|
.contentType(ContentType.JSON)
|
||||||
.body("uuid", isUuidValid())
|
.body("uuid", isUuidValid())
|
||||||
.body("label", is(givenContact.getLabel()))
|
.body("label", is(givenContact.getLabel()))
|
||||||
.body("emailAddresses", is("patched@example.org"))
|
.body("emailAddresses", is(Map.of("main", "patched@example.org")))
|
||||||
.body("postalAddress", is(givenContact.getPostalAddress()))
|
.body("postalAddress", is(givenContact.getPostalAddress()))
|
||||||
.body("phoneNumbers", is("+01 100 123456"));
|
.body("phoneNumbers", is(Map.of("phone_office", "+01 100 123456")));
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
|
|
||||||
// finally, the contact is actually updated
|
// finally, the contact is actually updated
|
||||||
assertThat(contactRepo.findByUuid(givenContact.getUuid())).isPresent().get()
|
assertThat(contactRepo.findByUuid(givenContact.getUuid())).isPresent().get()
|
||||||
.matches(person -> {
|
.matches(person -> {
|
||||||
assertThat(person.getLabel()).isEqualTo(givenContact.getLabel());
|
assertThat(person.getLabel()).isEqualTo(givenContact.getLabel());
|
||||||
assertThat(person.getEmailAddresses()).isEqualTo("patched@example.org");
|
assertThat(person.getEmailAddresses()).containsExactlyEntriesOf(Map.of("main", "patched@example.org"));
|
||||||
assertThat(person.getPostalAddress()).isEqualTo(givenContact.getPostalAddress());
|
assertThat(person.getPostalAddress()).isEqualTo(givenContact.getPostalAddress());
|
||||||
assertThat(person.getPhoneNumbers()).isEqualTo("+01 100 123456");
|
assertThat(person.getPhoneNumbers()).containsExactlyEntriesOf(Map.of("phone_office", "+01 100 123456"));
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -340,9 +355,9 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
|||||||
final var newContact = HsOfficeContactEntity.builder()
|
final var newContact = HsOfficeContactEntity.builder()
|
||||||
.uuid(UUID.randomUUID())
|
.uuid(UUID.randomUUID())
|
||||||
.label("Temp from " + Context.getCallerMethodNameFromStackFrame(1) )
|
.label("Temp from " + Context.getCallerMethodNameFromStackFrame(1) )
|
||||||
.emailAddresses(RandomStringUtils.randomAlphabetic(10) + "@example.org")
|
.emailAddresses(Map.of("main", RandomStringUtils.randomAlphabetic(10) + "@example.org"))
|
||||||
.postalAddress("Postal Address " + RandomStringUtils.randomAlphabetic(10))
|
.postalAddress("Postal Address " + RandomStringUtils.randomAlphabetic(10))
|
||||||
.phoneNumbers("+01 200 " + RandomStringUtils.randomNumeric(8))
|
.phoneNumbers(Map.of("phone_office", "+01 200 " + RandomStringUtils.randomNumeric(8)))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
return contactRepo.save(newContact);
|
return contactRepo.save(newContact);
|
||||||
|
@ -4,9 +4,12 @@ import net.hostsharing.hsadminng.rbac.test.PatchUnitTestBase;
|
|||||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeContactPatchResource;
|
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeContactPatchResource;
|
||||||
import org.junit.jupiter.api.TestInstance;
|
import org.junit.jupiter.api.TestInstance;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static net.hostsharing.hsadminng.mapper.PatchMap.entry;
|
||||||
|
import static net.hostsharing.hsadminng.mapper.PatchMap.patchMap;
|
||||||
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
|
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
|
||||||
|
|
||||||
@TestInstance(PER_CLASS)
|
@TestInstance(PER_CLASS)
|
||||||
@ -16,15 +19,42 @@ class HsOfficeContactEntityPatcherUnitTest extends PatchUnitTestBase<
|
|||||||
> {
|
> {
|
||||||
|
|
||||||
private static final UUID INITIAL_CONTACT_UUID = UUID.randomUUID();
|
private static final UUID INITIAL_CONTACT_UUID = UUID.randomUUID();
|
||||||
|
private static final Map<String, String> PATCH_EMAIL_ADDRESSES = patchMap(
|
||||||
|
entry("main", "patched@example.com"),
|
||||||
|
entry("paul", null),
|
||||||
|
entry("suse", "suse@example.com")
|
||||||
|
);
|
||||||
|
private static final Map<String, String> PATCHED_EMAIL_ADDRESSES = patchMap(
|
||||||
|
entry("main", "patched@example.com"),
|
||||||
|
entry("suse", "suse@example.com"),
|
||||||
|
entry("mila", "mila@example.com")
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final Map<String, String> PATCH_PHONE_NUMBERS = patchMap(
|
||||||
|
entry("phone_mobile", null),
|
||||||
|
entry("phone_private", "+49 40 987654321"),
|
||||||
|
entry("fax", "+49 40 12345-99")
|
||||||
|
);
|
||||||
|
private static final Map<String, String> PATCHED_PHONE_NUMBERS = patchMap(
|
||||||
|
entry("phone_office", "+49 40 12345-00"),
|
||||||
|
entry("phone_private", "+49 40 987654321"),
|
||||||
|
entry("fax", "+49 40 12345-99")
|
||||||
|
);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected HsOfficeContactEntity newInitialEntity() {
|
protected HsOfficeContactEntity newInitialEntity() {
|
||||||
final var entity = new HsOfficeContactEntity();
|
final var entity = new HsOfficeContactEntity();
|
||||||
entity.setUuid(INITIAL_CONTACT_UUID);
|
entity.setUuid(INITIAL_CONTACT_UUID);
|
||||||
entity.setLabel("initial label");
|
entity.setLabel("initial label");
|
||||||
entity.setEmailAddresses("initial@example.org");
|
entity.putEmailAddresses(Map.ofEntries(
|
||||||
entity.setPhoneNumbers("initial postal address");
|
entry("main", "initial@example.org"),
|
||||||
entity.setPostalAddress("+01 100 123456789");
|
entry("paul", "paul@example.com"),
|
||||||
|
entry("mila", "mila@example.com")));
|
||||||
|
entity.putPhoneNumbers(Map.ofEntries(
|
||||||
|
entry("phone_office", "+49 40 12345-00"),
|
||||||
|
entry("phone_mobile", "+49 1555 1234567"),
|
||||||
|
entry("fax", "+49 40 12345-90")));
|
||||||
|
entity.setPostalAddress("Initialstraße 50\n20000 Hamburg");
|
||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,8 +64,8 @@ class HsOfficeContactEntityPatcherUnitTest extends PatchUnitTestBase<
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected HsOfficeContactEntityPatch createPatcher(final HsOfficeContactEntity entity) {
|
protected HsOfficeContactEntityPatcher createPatcher(final HsOfficeContactEntity entity) {
|
||||||
return new HsOfficeContactEntityPatch(entity);
|
return new HsOfficeContactEntityPatcher(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -46,16 +76,20 @@ class HsOfficeContactEntityPatcherUnitTest extends PatchUnitTestBase<
|
|||||||
HsOfficeContactPatchResource::setLabel,
|
HsOfficeContactPatchResource::setLabel,
|
||||||
"patched label",
|
"patched label",
|
||||||
HsOfficeContactEntity::setLabel),
|
HsOfficeContactEntity::setLabel),
|
||||||
new JsonNullableProperty<>(
|
new SimpleProperty<>(
|
||||||
"emailAddresses",
|
"resources",
|
||||||
HsOfficeContactPatchResource::setEmailAddresses,
|
HsOfficeContactPatchResource::setEmailAddresses,
|
||||||
"patched trade name",
|
PATCH_EMAIL_ADDRESSES,
|
||||||
HsOfficeContactEntity::setEmailAddresses),
|
HsOfficeContactEntity::putEmailAddresses,
|
||||||
new JsonNullableProperty<>(
|
PATCHED_EMAIL_ADDRESSES)
|
||||||
"phoneNumbers",
|
.notNullable(),
|
||||||
|
new SimpleProperty<>(
|
||||||
|
"resources",
|
||||||
HsOfficeContactPatchResource::setPhoneNumbers,
|
HsOfficeContactPatchResource::setPhoneNumbers,
|
||||||
"patched family name",
|
PATCH_PHONE_NUMBERS,
|
||||||
HsOfficeContactEntity::setPhoneNumbers),
|
HsOfficeContactEntity::putPhoneNumbers,
|
||||||
|
PATCHED_PHONE_NUMBERS)
|
||||||
|
.notNullable(),
|
||||||
new JsonNullableProperty<>(
|
new JsonNullableProperty<>(
|
||||||
"patched given name",
|
"patched given name",
|
||||||
HsOfficeContactPatchResource::setPostalAddress,
|
HsOfficeContactPatchResource::setPostalAddress,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.hostsharing.hsadminng.hs.office.contact;
|
package net.hostsharing.hsadminng.hs.office.contact;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class TestHsOfficeContact {
|
public class TestHsOfficeContact {
|
||||||
|
|
||||||
@ -9,7 +10,7 @@ public class TestHsOfficeContact {
|
|||||||
return HsOfficeContactEntity.builder()
|
return HsOfficeContactEntity.builder()
|
||||||
.label(label)
|
.label(label)
|
||||||
.postalAddress("address of " + label)
|
.postalAddress("address of " + label)
|
||||||
.emailAddresses(emailAddr)
|
.emailAddresses(Map.of("main", emailAddr))
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,8 +107,8 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
|||||||
"mark": null,
|
"mark": null,
|
||||||
"contact": {
|
"contact": {
|
||||||
"label": "first contact",
|
"label": "first contact",
|
||||||
"emailAddresses": "contact-admin@firstcontact.example.com",
|
"emailAddresses": { "main": "contact-admin@firstcontact.example.com" },
|
||||||
"phoneNumbers": "+49 123 1234567"
|
"phoneNumbers": { "phone_office": "+49 123 1234567" }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"debitorNumber": 1000111,
|
"debitorNumber": 1000111,
|
||||||
@ -132,8 +132,8 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
|||||||
"mark": null,
|
"mark": null,
|
||||||
"contact": {
|
"contact": {
|
||||||
"label": "first contact",
|
"label": "first contact",
|
||||||
"emailAddresses": "contact-admin@firstcontact.example.com",
|
"emailAddresses": { "main": "contact-admin@firstcontact.example.com" },
|
||||||
"phoneNumbers": "+49 123 1234567"
|
"phoneNumbers": { "phone_office": "+49 123 1234567" }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"details": {
|
"details": {
|
||||||
@ -162,7 +162,9 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
|||||||
"anchor": {"tradeName": "Second e.K."},
|
"anchor": {"tradeName": "Second e.K."},
|
||||||
"holder": {"tradeName": "Second e.K."},
|
"holder": {"tradeName": "Second e.K."},
|
||||||
"type": "DEBITOR",
|
"type": "DEBITOR",
|
||||||
"contact": {"emailAddresses": "contact-admin@secondcontact.example.com"}
|
"contact": {
|
||||||
|
"emailAddresses": { "main": "contact-admin@secondcontact.example.com" }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"debitorNumber": 1000212,
|
"debitorNumber": 1000212,
|
||||||
"debitorNumberSuffix": 12,
|
"debitorNumberSuffix": 12,
|
||||||
@ -172,7 +174,9 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
|||||||
"anchor": {"tradeName": "Hostsharing eG"},
|
"anchor": {"tradeName": "Hostsharing eG"},
|
||||||
"holder": {"tradeName": "Second e.K."},
|
"holder": {"tradeName": "Second e.K."},
|
||||||
"type": "PARTNER",
|
"type": "PARTNER",
|
||||||
"contact": {"emailAddresses": "contact-admin@secondcontact.example.com"}
|
"contact": {
|
||||||
|
"emailAddresses": { "main": "contact-admin@secondcontact.example.com" }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"details": {
|
"details": {
|
||||||
"registrationOffice": "Hamburg",
|
"registrationOffice": "Hamburg",
|
||||||
@ -192,7 +196,9 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
|||||||
"anchor": {"tradeName": "Third OHG"},
|
"anchor": {"tradeName": "Third OHG"},
|
||||||
"holder": {"tradeName": "Third OHG"},
|
"holder": {"tradeName": "Third OHG"},
|
||||||
"type": "DEBITOR",
|
"type": "DEBITOR",
|
||||||
"contact": {"emailAddresses": "contact-admin@thirdcontact.example.com"}
|
"contact": {
|
||||||
|
"emailAddresses": { "main": "contact-admin@thirdcontact.example.com" }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"debitorNumber": 1000313,
|
"debitorNumber": 1000313,
|
||||||
"debitorNumberSuffix": 13,
|
"debitorNumberSuffix": 13,
|
||||||
@ -202,7 +208,9 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
|||||||
"anchor": {"tradeName": "Hostsharing eG"},
|
"anchor": {"tradeName": "Hostsharing eG"},
|
||||||
"holder": {"tradeName": "Third OHG"},
|
"holder": {"tradeName": "Third OHG"},
|
||||||
"type": "PARTNER",
|
"type": "PARTNER",
|
||||||
"contact": {"emailAddresses": "contact-admin@thirdcontact.example.com"}
|
"contact": {
|
||||||
|
"emailAddresses": { "main": "contact-admin@thirdcontact.example.com" }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"details": {
|
"details": {
|
||||||
"registrationOffice": "Hamburg",
|
"registrationOffice": "Hamburg",
|
||||||
@ -223,7 +231,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void globalAdmin_withoutAssumedRoles_canFindDebitorDebitorByDebitorNumber() throws JSONException {
|
void globalAdmin_withoutAssumedRoles_canFindDebitorDebitorByDebitorNumber() {
|
||||||
|
|
||||||
RestAssured // @formatter:off
|
RestAssured // @formatter:off
|
||||||
.given()
|
.given()
|
||||||
@ -456,9 +464,9 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
|||||||
"type": "DEBITOR",
|
"type": "DEBITOR",
|
||||||
"contact": {
|
"contact": {
|
||||||
"label": "first contact",
|
"label": "first contact",
|
||||||
"postalAddress": "\\nVorname Nachname\\nStraße Hnr\\nPLZ Stadt\\n",
|
"postalAddress": "Vorname Nachname\\nStraße Hnr\\nPLZ Stadt",
|
||||||
"emailAddresses": "contact-admin@firstcontact.example.com",
|
"emailAddresses": { "main": "contact-admin@firstcontact.example.com" },
|
||||||
"phoneNumbers": "+49 123 1234567"
|
"phoneNumbers": { "phone_office": "+49 123 1234567" }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"debitorNumber": 1000111,
|
"debitorNumber": 1000111,
|
||||||
@ -472,9 +480,9 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
|
|||||||
"mark": null,
|
"mark": null,
|
||||||
"contact": {
|
"contact": {
|
||||||
"label": "first contact",
|
"label": "first contact",
|
||||||
"postalAddress": "\\nVorname Nachname\\nStraße Hnr\\nPLZ Stadt\\n",
|
"postalAddress": "Vorname Nachname\\nStraße Hnr\\nPLZ Stadt",
|
||||||
"emailAddresses": "contact-admin@firstcontact.example.com",
|
"emailAddresses": { "main": "contact-admin@firstcontact.example.com" },
|
||||||
"phoneNumbers": "+49 123 1234567"
|
"phoneNumbers": { "phone_office": "+49 123 1234567" }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"details": {
|
"details": {
|
||||||
|
@ -240,29 +240,29 @@ public class ImportOfficeData extends ContextBasedTest {
|
|||||||
""");
|
""");
|
||||||
assertThat(toFormattedString(contacts)).isEqualToIgnoringWhitespace("""
|
assertThat(toFormattedString(contacts)).isEqualToIgnoringWhitespace("""
|
||||||
{
|
{
|
||||||
1101=contact(label='Herr Michael Mellies ', emailAddresses='mih@example.org'),
|
1101=contact(label='Herr Michael Mellies ', emailAddresses='{ main: mih@example.org }'),
|
||||||
1200=contact(label='JM e.K.', emailAddresses='jm-ex-partner@example.org'),
|
1200=contact(label='JM e.K.', emailAddresses='{ main: jm-ex-partner@example.org }'),
|
||||||
1201=contact(label='Frau Dr. Jenny Meyer-Billing , JM GmbH', emailAddresses='jm-billing@example.org'),
|
1201=contact(label='Frau Dr. Jenny Meyer-Billing , JM GmbH', emailAddresses='{ main: jm-billing@example.org }'),
|
||||||
1202=contact(label='Herr Andrew Meyer-Operation , JM GmbH', emailAddresses='am-operation@example.org'),
|
1202=contact(label='Herr Andrew Meyer-Operation , JM GmbH', emailAddresses='{ main: am-operation@example.org }'),
|
||||||
1203=contact(label='Herr Philip Meyer-Contract , JM GmbH', emailAddresses='pm-partner@example.org'),
|
1203=contact(label='Herr Philip Meyer-Contract , JM GmbH', emailAddresses='{ main: pm-partner@example.org }'),
|
||||||
1204=contact(label='Frau Tammy Meyer-VIP , JM GmbH', emailAddresses='tm-vip@example.org'),
|
1204=contact(label='Frau Tammy Meyer-VIP , JM GmbH', emailAddresses='{ main: tm-vip@example.org }'),
|
||||||
1301=contact(label='Petra Schmidt , Test PS', emailAddresses='ps@example.com'),
|
1301=contact(label='Petra Schmidt , Test PS', emailAddresses='{ main: ps@example.com }'),
|
||||||
1401=contact(label='Frau Frauke Fanninga ', emailAddresses='ff@example.org'),
|
1401=contact(label='Frau Frauke Fanninga ', emailAddresses='{ main: ff@example.org }'),
|
||||||
1501=contact(label='Frau Cecilia Camus ', emailAddresses='cc@example.org')
|
1501=contact(label='Frau Cecilia Camus ', emailAddresses='{ main: cc@example.org }')
|
||||||
}
|
}
|
||||||
""");
|
""");
|
||||||
assertThat(toFormattedString(persons)).isEqualToIgnoringWhitespace("""
|
assertThat(toFormattedString(persons)).isEqualToIgnoringWhitespace("""
|
||||||
{
|
{
|
||||||
1=person(personType='LP', tradeName='Hostsharing eG'),
|
1=person(personType='LP', tradeName='Hostsharing eG'),
|
||||||
1101=person(personType='NP', tradeName='', familyName='Mellies', givenName='Michael'),
|
1101=person(personType='NP', familyName='Mellies', givenName='Michael'),
|
||||||
1200=person(personType='LP', tradeName='JM e.K.', familyName='', givenName=''),
|
1200=person(personType='LP', tradeName='JM e.K.'),
|
||||||
1201=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-Billing', givenName='Jenny'),
|
1201=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-Billing', givenName='Jenny'),
|
||||||
1202=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-Operation', givenName='Andrew'),
|
1202=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-Operation', givenName='Andrew'),
|
||||||
1203=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-Contract', givenName='Philip'),
|
1203=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-Contract', givenName='Philip'),
|
||||||
1204=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-VIP', givenName='Tammy'),
|
1204=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-VIP', givenName='Tammy'),
|
||||||
1301=person(personType='??', tradeName='Test PS', familyName='Schmidt', givenName='Petra'),
|
1301=person(personType='??', tradeName='Test PS', familyName='Schmidt', givenName='Petra'),
|
||||||
1401=person(personType='NP', tradeName='', familyName='Fanninga', givenName='Frauke'),
|
1401=person(personType='NP', familyName='Fanninga', givenName='Frauke'),
|
||||||
1501=person(personType='NP', tradeName='', familyName='Camus', givenName='Cecilia')
|
1501=person(personType='NP', familyName='Camus', givenName='Cecilia')
|
||||||
}
|
}
|
||||||
""");
|
""");
|
||||||
assertThat(toFormattedString(debitors)).isEqualToIgnoringWhitespace("""
|
assertThat(toFormattedString(debitors)).isEqualToIgnoringWhitespace("""
|
||||||
@ -363,10 +363,10 @@ public class ImportOfficeData extends ContextBasedTest {
|
|||||||
|
|
||||||
assertThat(toFormattedString(coopShares)).isEqualToIgnoringWhitespace("""
|
assertThat(toFormattedString(coopShares)).isEqualToIgnoringWhitespace("""
|
||||||
{
|
{
|
||||||
33443=CoopShareTransaction(M-1001700: 2000-12-06, SUBSCRIPTION, 20, legacy data import, initial share subscription),
|
33443=CoopShareTransaction(M-1001700: 2000-12-06, SUBSCRIPTION, 20, 1001700, initial share subscription),
|
||||||
hsh-michaelhoennig marked this conversation as resolved
hsh-marcsandlus
commented
auch bei CoopAssets memberNo nehmen auch bei CoopAssets memberNo nehmen
|
|||||||
33451=CoopShareTransaction(M-1002000: 2000-12-06, SUBSCRIPTION, 2, legacy data import, initial share subscription),
|
33451=CoopShareTransaction(M-1002000: 2000-12-06, SUBSCRIPTION, 2, 1002000, initial share subscription),
|
||||||
33701=CoopShareTransaction(M-1001700: 2005-01-10, SUBSCRIPTION, 40, legacy data import, increase),
|
33701=CoopShareTransaction(M-1001700: 2005-01-10, SUBSCRIPTION, 40, 1001700, increase),
|
||||||
33810=CoopShareTransaction(M-1002000: 2016-12-31, CANCELLATION, 22, legacy data import, membership ended)
|
33810=CoopShareTransaction(M-1002000: 2016-12-31, CANCELLATION, 22, 1002000, membership ended)
|
||||||
}
|
}
|
||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
@ -390,16 +390,16 @@ public class ImportOfficeData extends ContextBasedTest {
|
|||||||
|
|
||||||
assertThat(toFormattedString(coopAssets)).isEqualToIgnoringWhitespace("""
|
assertThat(toFormattedString(coopAssets)).isEqualToIgnoringWhitespace("""
|
||||||
{
|
{
|
||||||
30000=CoopAssetsTransaction(M-1001700: 2000-12-06, DEPOSIT, 1280.00, legacy data import, for subscription A),
|
30000=CoopAssetsTransaction(M-1001700: 2000-12-06, DEPOSIT, 1280.00, 1001700, for subscription A),
|
||||||
31000=CoopAssetsTransaction(M-1002000: 2000-12-06, DEPOSIT, 128.00, legacy data import, for subscription B),
|
31000=CoopAssetsTransaction(M-1002000: 2000-12-06, DEPOSIT, 128.00, 1002000, for subscription B),
|
||||||
32000=CoopAssetsTransaction(M-1001700: 2005-01-10, DEPOSIT, 2560.00, legacy data import, for subscription C),
|
32000=CoopAssetsTransaction(M-1001700: 2005-01-10, DEPOSIT, 2560.00, 1001700, for subscription C),
|
||||||
33001=CoopAssetsTransaction(M-1001700: 2005-01-10, TRANSFER, -512.00, legacy data import, for transfer to 10),
|
33001=CoopAssetsTransaction(M-1001700: 2005-01-10, TRANSFER, -512.00, 1001700, for transfer to 10),
|
||||||
33002=CoopAssetsTransaction(M-1002000: 2005-01-10, ADOPTION, 512.00, legacy data import, for transfer from 7),
|
33002=CoopAssetsTransaction(M-1002000: 2005-01-10, ADOPTION, 512.00, 1002000, for transfer from 7),
|
||||||
34001=CoopAssetsTransaction(M-1002000: 2016-12-31, CLEARING, -8.00, legacy data import, for cancellation D),
|
34001=CoopAssetsTransaction(M-1002000: 2016-12-31, CLEARING, -8.00, 1002000, for cancellation D),
|
||||||
34002=CoopAssetsTransaction(M-1002000: 2016-12-31, DISBURSAL, -100.00, legacy data import, for cancellation D),
|
34002=CoopAssetsTransaction(M-1002000: 2016-12-31, DISBURSAL, -100.00, 1002000, for cancellation D),
|
||||||
34003=CoopAssetsTransaction(M-1002000: 2016-12-31, LOSS, -20.00, legacy data import, for cancellation D),
|
34003=CoopAssetsTransaction(M-1002000: 2016-12-31, LOSS, -20.00, 1002000, for cancellation D),
|
||||||
35001=CoopAssetsTransaction(M-1909000: 2024-01-15, DEPOSIT, 128.00, legacy data import, for subscription E),
|
35001=CoopAssetsTransaction(M-1909000: 2024-01-15, DEPOSIT, 128.00, 1909000, for subscription E),
|
||||||
35002=CoopAssetsTransaction(M-1909000: 2024-01-20, ADJUSTMENT, -128.00, legacy data import, chargeback for subscription E, M-1909000:DEP:+128.00)
|
35002=CoopAssetsTransaction(M-1909000: 2024-01-20, ADJUSTMENT, -128.00, 1909000, chargeback for subscription E, M-1909000:DEP:+128.00)
|
||||||
}
|
}
|
||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
@ -810,7 +810,7 @@ public class ImportOfficeData extends ContextBasedTest {
|
|||||||
)
|
)
|
||||||
.shareCount(rec.getInteger("quantity"))
|
.shareCount(rec.getInteger("quantity"))
|
||||||
.comment( rec.getString("comment"))
|
.comment( rec.getString("comment"))
|
||||||
.reference("legacy data import") // TODO.spec: or use value from comment column?
|
.reference(member.getMemberNumber().toString())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
if (shareTransaction.getTransactionType() == HsOfficeCoopSharesTransactionType.ADJUSTMENT) {
|
if (shareTransaction.getTransactionType() == HsOfficeCoopSharesTransactionType.ADJUSTMENT) {
|
||||||
@ -867,7 +867,7 @@ public class ImportOfficeData extends ContextBasedTest {
|
|||||||
.transactionType(assetTypeMapping.get(rec.getString("action")))
|
.transactionType(assetTypeMapping.get(rec.getString("action")))
|
||||||
.assetValue(rec.getBigDecimal("amount"))
|
.assetValue(rec.getBigDecimal("amount"))
|
||||||
.comment(rec.getString("comment"))
|
.comment(rec.getString("comment"))
|
||||||
.reference("legacy data import") // TODO.spec: or use value from comment column?
|
.reference(member.getMemberNumber().toString())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
if (assetTransaction.getTransactionType() == HsOfficeCoopAssetsTransactionType.ADJUSTMENT) {
|
if (assetTransaction.getTransactionType() == HsOfficeCoopAssetsTransactionType.ADJUSTMENT) {
|
||||||
@ -1092,9 +1092,9 @@ public class ImportOfficeData extends ContextBasedTest {
|
|||||||
contactRecord.getString("first_name"),
|
contactRecord.getString("first_name"),
|
||||||
contactRecord.getString("last_name"),
|
contactRecord.getString("last_name"),
|
||||||
contactRecord.getString("firma")));
|
contactRecord.getString("firma")));
|
||||||
contact.setEmailAddresses(contactRecord.getString("email"));
|
contact.putEmailAddresses( Map.of("main", contactRecord.getString("email")));
|
||||||
contact.setPostalAddress(toAddress(contactRecord));
|
contact.setPostalAddress(toAddress(contactRecord));
|
||||||
contact.setPhoneNumbers(toPhoneNumbers(contactRecord));
|
contact.putPhoneNumbers(toPhoneNumbers(contactRecord));
|
||||||
|
|
||||||
contacts.put(contactRecord.getInteger("contact_id"), contact);
|
contacts.put(contactRecord.getInteger("contact_id"), contact);
|
||||||
return contact;
|
return contact;
|
||||||
@ -1120,17 +1120,17 @@ public class ImportOfficeData extends ContextBasedTest {
|
|||||||
return record;
|
return record;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String toPhoneNumbers(final Record rec) {
|
private Map<String, String> toPhoneNumbers(final Record rec) {
|
||||||
final var result = new StringBuilder("{\n");
|
final var phoneNumbers = new LinkedHashMap<String, String>();
|
||||||
if (isNotBlank(rec.getString("phone_private")))
|
if (isNotBlank(rec.getString("phone_private")))
|
||||||
result.append(" \"private\": " + "\"" + rec.getString("phone_private") + "\",\n");
|
phoneNumbers.put("phone_private", rec.getString("phone_private"));
|
||||||
if (isNotBlank(rec.getString("phone_office")))
|
if (isNotBlank(rec.getString("phone_office")))
|
||||||
result.append(" \"office\": " + "\"" + rec.getString("phone_office") + "\",\n");
|
phoneNumbers.put("phone_office", rec.getString("phone_office"));
|
||||||
if (isNotBlank(rec.getString("phone_mobile")))
|
if (isNotBlank(rec.getString("phone_mobile")))
|
||||||
result.append(" \"mobile\": " + "\"" + rec.getString("phone_mobile") + "\",\n");
|
phoneNumbers.put("phone_mobile", rec.getString("phone_mobile"));
|
||||||
if (isNotBlank(rec.getString("fax")))
|
if (isNotBlank(rec.getString("fax")))
|
||||||
result.append(" \"fax\": " + "\"" + rec.getString("fax") + "\",\n");
|
phoneNumbers.put("fax", rec.getString("fax"));
|
||||||
return (result + "}").replace("\",\n}", "\"\n}");
|
return phoneNumbers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String toAddress(final Record rec) {
|
private String toAddress(final Record rec) {
|
||||||
|
@ -14,6 +14,19 @@ class TestPackageEntityUnitTest {
|
|||||||
%%{init:{'flowchart':{'htmlLabels':false}}}%%
|
%%{init:{'flowchart':{'htmlLabels':false}}}%%
|
||||||
flowchart TB
|
flowchart TB
|
||||||
|
|
||||||
|
subgraph customer["`**customer**`"]
|
||||||
|
direction TB
|
||||||
|
style customer fill:#99bcdb,stroke:#274d6e,stroke-width:8px
|
||||||
|
|
||||||
|
subgraph customer:roles[ ]
|
||||||
|
style customer:roles fill:#99bcdb,stroke:white
|
||||||
|
|
||||||
|
role:customer:OWNER[[customer:OWNER]]
|
||||||
|
role:customer:ADMIN[[customer:ADMIN]]
|
||||||
|
role:customer:TENANT[[customer:TENANT]]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
subgraph package["`**package**`"]
|
subgraph package["`**package**`"]
|
||||||
direction TB
|
direction TB
|
||||||
style package fill:#dd4901,stroke:#274d6e,stroke-width:8px
|
style package fill:#dd4901,stroke:#274d6e,stroke-width:8px
|
||||||
@ -36,19 +49,6 @@ class TestPackageEntityUnitTest {
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph customer["`**customer**`"]
|
|
||||||
direction TB
|
|
||||||
style customer fill:#99bcdb,stroke:#274d6e,stroke-width:8px
|
|
||||||
|
|
||||||
subgraph customer:roles[ ]
|
|
||||||
style customer:roles fill:#99bcdb,stroke:white
|
|
||||||
|
|
||||||
role:customer:OWNER[[customer:OWNER]]
|
|
||||||
role:customer:ADMIN[[customer:ADMIN]]
|
|
||||||
role:customer:TENANT[[customer:TENANT]]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
%% granting roles to roles
|
%% granting roles to roles
|
||||||
role:global:ADMIN -.->|XX| role:customer:OWNER
|
role:global:ADMIN -.->|XX| role:customer:OWNER
|
||||||
role:customer:OWNER -.-> role:customer:ADMIN
|
role:customer:OWNER -.-> role:customer:ADMIN
|
||||||
|
Loading…
Reference in New Issue
Block a user
feste Werte für Kompatibiltät (phone_private, phone_office, phone_mobile)