contact.phonenumbers from TEXT to JSON

This commit is contained in:
Michael Hoennig 2024-04-30 11:14:34 +02:00
parent 5954a87b36
commit 90c21e7ec1
11 changed files with 98 additions and 53 deletions

View File

@ -121,5 +121,6 @@ public class HsOfficeContactController implements HsOfficeContactsApi {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final BiConsumer<HsOfficeContactInsertResource, HsOfficeContactEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { final BiConsumer<HsOfficeContactInsertResource, HsOfficeContactEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
entity.putEmailAdresses(from(resource.getEmailAddresses())); entity.putEmailAdresses(from(resource.getEmailAddresses()));
entity.putPhoneNumbers(from(resource.getPhoneNumbers()));
}; };
} }

View File

@ -49,7 +49,7 @@ 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")
@ -64,16 +64,30 @@ public class HsOfficeContactEntity implements Stringifyable, RbacObject {
@Transient @Transient
private PatchableMapWrapper<String> emailAddressesWrapper; private PatchableMapWrapper<String> emailAddressesWrapper;
public PatchableMapWrapper<String> getEmailAdresses() { @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 ); return PatchableMapWrapper.of(emailAddressesWrapper, (newWrapper) -> {emailAddressesWrapper = newWrapper; }, emailAddresses );
} }
public void putEmailAdresses(Map<String, String> newEmailAddresses) { public void putEmailAdresses(Map<String, String> newEmailAddresses) {
getEmailAdresses().assign(newEmailAddresses); getEmailAddresses().assign(newEmailAddresses);
} }
@Column(name = "phonenumbers", columnDefinition = "json") public PatchableMapWrapper<String> getPhoneNumbers() {
private String phoneNumbers; 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() {

View File

@ -20,7 +20,8 @@ class HsOfficeContactEntityPatcher implements EntityPatcher<HsOfficeContactPatch
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);
Optional.ofNullable(resource.getEmailAddresses()) Optional.ofNullable(resource.getEmailAddresses())
.ifPresent(r -> entity.getEmailAdresses().patch(KeyValueMap.from(resource.getEmailAddresses()))); .ifPresent(r -> entity.getEmailAddresses().patch(KeyValueMap.from(resource.getEmailAddresses())));
OptionalFromJson.of(resource.getPhoneNumbers()).ifPresent(entity::setPhoneNumbers); Optional.ofNullable(resource.getPhoneNumbers())
.ifPresent(r -> entity.getPhoneNumbers().patch(KeyValueMap.from(resource.getPhoneNumbers())));
} }
} }

View File

@ -6,7 +6,7 @@ public class KeyValueMap {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T> Map<String, T> 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, T>) obj; return (Map<String, T>) obj;
} }
throw new ClassCastException("Map expected, but got: " + obj); throw new ClassCastException("Map expected, but got: " + obj);

View File

@ -36,8 +36,10 @@ public class PatchableMapWrapper<T> implements Map<String, T> {
} }
public void assign(final Map<String, T> entries) { public void assign(final Map<String, T> entries) {
delegate.clear(); if (entries != null ) {
delegate.putAll(entries); delegate.clear();
delegate.putAll(entries);
}
} }
public void patch(final Map<String, T> patch) { public void patch(final Map<String, T> patch) {

View File

@ -16,7 +16,7 @@ components:
emailAddresses: emailAddresses:
$ref: '#/components/schemas/HsOfficeContactEmailAddresses' $ref: '#/components/schemas/HsOfficeContactEmailAddresses'
phoneNumbers: phoneNumbers:
type: string $ref: '#/components/schemas/HsOfficeContactPhoneNumbers'
HsOfficeContactInsert: HsOfficeContactInsert:
type: object type: object
@ -28,7 +28,7 @@ components:
emailAddresses: emailAddresses:
$ref: '#/components/schemas/HsOfficeContactEmailAddresses' $ref: '#/components/schemas/HsOfficeContactEmailAddresses'
phoneNumbers: phoneNumbers:
type: string $ref: '#/components/schemas/HsOfficeContactPhoneNumbers'
required: required:
- label - label
@ -44,11 +44,16 @@ components:
emailAddresses: emailAddresses:
$ref: '#/components/schemas/HsOfficeContactEmailAddresses' $ref: '#/components/schemas/HsOfficeContactEmailAddresses'
phoneNumbers: phoneNumbers:
type: string $ref: '#/components/schemas/HsOfficeContactPhoneNumbers'
nullable: true
HsOfficeContactEmailAddresses: HsOfficeContactEmailAddresses:
# forces generating a java.lang.Object containing a Map, instead of class HsOfficeContactEmailAddresses # forces generating a java.lang.Object containing a Map, instead of class HsOfficeContactEmailAddresses
anyOf: anyOf:
- type: object - type: object
additionalProperties: true additionalProperties: true
HsOfficeContactPhoneNumbers:
# forces generating a java.lang.Object containing a Map, instead of class HsOfficeContactEmailAddresses
anyOf:
- type: object
additionalProperties: true

View File

@ -32,7 +32,7 @@ begin
contLabel, contLabel,
postalAddr, postalAddr,
('{ "main": "' || emailAddr || '" }')::jsonb, ('{ "main": "' || emailAddr || '" }')::jsonb,
'+49 123 1234567' ('{ "office": "+49 123 1234567" }')::jsonb
); );
end; $$; end; $$;
--// --//

View File

@ -187,7 +187,9 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
"emailAddresses": { "emailAddresses": {
"main": "contact-admin@firstcontact.example.com" "main": "contact-admin@firstcontact.example.com"
}, },
"phoneNumbers": "+49 123 1234567" "phoneNumbers": {
"office": "+49 123 1234567"
}
} }
""")); // @formatter:on """)); // @formatter:on
} }
@ -213,7 +215,9 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
"main": "patched@example.org" "main": "patched@example.org"
}, },
"postalAddress": "Patched Address", "postalAddress": "Patched Address",
"phoneNumbers": "+01 100 123456" "phoneNumbers": {
"office": "+01 100 123456"
}
} }
""") """)
.port(port) .port(port)
@ -226,7 +230,7 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
.body("label", is("Temp patched contact")) .body("label", is("Temp patched contact"))
.body("emailAddresses", is(Map.of("main", "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("office", "+01 100 123456")));
// @formatter:on // @formatter:on
// finally, the contact is actually updated // finally, the contact is actually updated
@ -234,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(Map.of("main", "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("office", "+01 100 123456"));
return true; return true;
}); });
} }
@ -256,7 +260,9 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
"emailAddresses": { "emailAddresses": {
"main": "patched@example.org" "main": "patched@example.org"
}, },
"phoneNumbers": "+01 100 123456" "phoneNumbers": {
"office": "+01 100 123456"
}
} }
""") """)
.port(port) .port(port)
@ -269,16 +275,16 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
.body("label", is(givenContact.getLabel())) .body("label", is(givenContact.getLabel()))
.body("emailAddresses", is(Map.of("main", "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("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(Map.of("main", "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("office", "+01 100 123456"));
return true; return true;
}); });
} }
@ -351,7 +357,7 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu
.label("Temp from " + Context.getCallerMethodNameFromStackFrame(1) ) .label("Temp from " + Context.getCallerMethodNameFromStackFrame(1) )
.emailAddresses(Map.of("main", 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("office", "+01 200 " + RandomStringUtils.randomNumeric(8)))
.build(); .build();
return contactRepo.save(newContact); return contactRepo.save(newContact);

View File

@ -30,6 +30,17 @@ class HsOfficeContactEntityPatcherUnitTest extends PatchUnitTestBase<
entry("mila", "mila@example.com") entry("mila", "mila@example.com")
); );
private static final Map<String, String> PATCH_PHONE_NUMBERS = patchMap(
entry("mobile", null),
entry("private", "+49 40 987654321"),
entry("fax", "+49 40 12345-99")
);
private static final Map<String, String> PATCHED_PHONE_NUMBERS = patchMap(
entry("office", "+49 40 12345-00"),
entry("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();
@ -39,8 +50,11 @@ class HsOfficeContactEntityPatcherUnitTest extends PatchUnitTestBase<
entry("main", "initial@example.org"), entry("main", "initial@example.org"),
entry("paul", "paul@example.com"), entry("paul", "paul@example.com"),
entry("mila", "mila@example.com"))); entry("mila", "mila@example.com")));
entity.setPhoneNumbers("initial postal address"); entity.putPhoneNumbers(Map.ofEntries(
entity.setPostalAddress("+01 100 123456789"); entry("office", "+49 40 12345-00"),
entry("mobile", "+49 1555 1234567"),
entry("fax", "+49 40 12345-90")));
entity.setPostalAddress("Initialstraße 50\n20000 Hamburg");
return entity; return entity;
} }
@ -69,11 +83,13 @@ class HsOfficeContactEntityPatcherUnitTest extends PatchUnitTestBase<
HsOfficeContactEntity::putEmailAdresses, HsOfficeContactEntity::putEmailAdresses,
PATCHED_EMAIL_ADDRESSES) PATCHED_EMAIL_ADDRESSES)
.notNullable(), .notNullable(),
new JsonNullableProperty<>( new SimpleProperty<>(
"phoneNumbers", "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,

View File

@ -108,7 +108,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
"contact": { "contact": {
"label": "first contact", "label": "first contact",
"emailAddresses": { "main": "contact-admin@firstcontact.example.com" }, "emailAddresses": { "main": "contact-admin@firstcontact.example.com" },
"phoneNumbers": "+49 123 1234567" "phoneNumbers": { "office": "+49 123 1234567" }
} }
}, },
"debitorNumber": 1000111, "debitorNumber": 1000111,
@ -133,7 +133,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
"contact": { "contact": {
"label": "first contact", "label": "first contact",
"emailAddresses": { "main": "contact-admin@firstcontact.example.com" }, "emailAddresses": { "main": "contact-admin@firstcontact.example.com" },
"phoneNumbers": "+49 123 1234567" "phoneNumbers": { "office": "+49 123 1234567" }
} }
}, },
"details": { "details": {
@ -466,7 +466,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
"label": "first contact", "label": "first contact",
"postalAddress": "Vorname Nachname\\nStraße Hnr\\nPLZ Stadt", "postalAddress": "Vorname Nachname\\nStraße Hnr\\nPLZ Stadt",
"emailAddresses": { "main": "contact-admin@firstcontact.example.com" }, "emailAddresses": { "main": "contact-admin@firstcontact.example.com" },
"phoneNumbers": "+49 123 1234567" "phoneNumbers": { "office": "+49 123 1234567" }
} }
}, },
"debitorNumber": 1000111, "debitorNumber": 1000111,
@ -482,7 +482,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu
"label": "first contact", "label": "first contact",
"postalAddress": "Vorname Nachname\\nStraße Hnr\\nPLZ Stadt", "postalAddress": "Vorname Nachname\\nStraße Hnr\\nPLZ Stadt",
"emailAddresses": { "main": "contact-admin@firstcontact.example.com" }, "emailAddresses": { "main": "contact-admin@firstcontact.example.com" },
"phoneNumbers": "+49 123 1234567" "phoneNumbers": { "office": "+49 123 1234567" }
} }
}, },
"details": { "details": {

View File

@ -240,16 +240,16 @@ public class ImportOfficeData extends ContextBasedTest {
"""); """);
assertThat(toFormattedString(contacts)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(contacts)).isEqualToIgnoringWhitespace("""
{ {
1101=contact(label='Herr Michael Mellies ', emailAddresses='{main=mih@example.org}'), 1101=contact(label='Herr Michael Mellies ', emailAddresses='{ main: mih@example.org }'),
1200=contact(label='JM e.K.', emailAddresses='{main=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='{main=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='{main=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='{main=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='{main=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='{main=ps@example.com}'), 1301=contact(label='Petra Schmidt , Test PS', emailAddresses='{ main: ps@example.com }'),
1401=contact(label='Frau Frauke Fanninga ', emailAddresses='{main=ff@example.org}'), 1401=contact(label='Frau Frauke Fanninga ', emailAddresses='{ main: ff@example.org }'),
1501=contact(label='Frau Cecilia Camus ', emailAddresses='{main=cc@example.org}') 1501=contact(label='Frau Cecilia Camus ', emailAddresses='{ main: cc@example.org }')
} }
"""); """);
assertThat(toFormattedString(persons)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(persons)).isEqualToIgnoringWhitespace("""
{ {
@ -1094,7 +1094,7 @@ public class ImportOfficeData extends ContextBasedTest {
contactRecord.getString("firma"))); contactRecord.getString("firma")));
contact.putEmailAdresses( Map.of("main", contactRecord.getString("email"))); contact.putEmailAdresses( 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("privat", 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("geschäftlich", 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("mobil", 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) {