From 90c21e7ec13dea8925df9d756c3e7e9147b160e8 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Tue, 30 Apr 2024 11:14:34 +0200 Subject: [PATCH] contact.phonenumbers from TEXT to JSON --- .../contact/HsOfficeContactController.java | 1 + .../office/contact/HsOfficeContactEntity.java | 24 ++++++++++--- .../contact/HsOfficeContactEntityPatcher.java | 5 +-- .../hsadminng/mapper/KeyValueMap.java | 2 +- .../hsadminng/mapper/PatchableMapWrapper.java | 6 ++-- .../hs-office/hs-office-contact-schemas.yaml | 13 ++++--- .../5018-hs-office-contact-test-data.sql | 2 +- ...OfficeContactControllerAcceptanceTest.java | 26 ++++++++------ .../HsOfficeContactEntityPatcherUnitTest.java | 28 +++++++++++---- ...OfficeDebitorControllerAcceptanceTest.java | 8 ++--- .../hs/office/migration/ImportOfficeData.java | 36 +++++++++---------- 11 files changed, 98 insertions(+), 53 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactController.java b/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactController.java index 25695c92..028e8398 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactController.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactController.java @@ -121,5 +121,6 @@ public class HsOfficeContactController implements HsOfficeContactsApi { @SuppressWarnings("unchecked") final BiConsumer RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { entity.putEmailAdresses(from(resource.getEmailAddresses())); + entity.putPhoneNumbers(from(resource.getPhoneNumbers())); }; } diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntity.java b/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntity.java index dabcba8a..a33ddf5d 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntity.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntity.java @@ -49,7 +49,7 @@ public class HsOfficeContactEntity implements Stringifyable, RbacObject { @Version private int version; - @Column(name = "label") + @Column(name = "label") // TODO.impl: rename to caption private String label; @Column(name = "postaladdress") @@ -64,16 +64,30 @@ public class HsOfficeContactEntity implements Stringifyable, RbacObject { @Transient private PatchableMapWrapper emailAddressesWrapper; - public PatchableMapWrapper getEmailAdresses() { + @Builder.Default + @Setter(AccessLevel.NONE) + @Type(JsonType.class) + @Column(name = "phonenumbers") + private Map phoneNumbers = new HashMap<>(); + + @Transient + private PatchableMapWrapper phoneNumbersWrapper; + + public PatchableMapWrapper getEmailAddresses() { return PatchableMapWrapper.of(emailAddressesWrapper, (newWrapper) -> {emailAddressesWrapper = newWrapper; }, emailAddresses ); } public void putEmailAdresses(Map newEmailAddresses) { - getEmailAdresses().assign(newEmailAddresses); + getEmailAddresses().assign(newEmailAddresses); } - @Column(name = "phonenumbers", columnDefinition = "json") - private String phoneNumbers; + public PatchableMapWrapper getPhoneNumbers() { + return PatchableMapWrapper.of(phoneNumbersWrapper, (newWrapper) -> {phoneNumbersWrapper = newWrapper; }, phoneNumbers ); + } + + public void putPhoneNumbers(Map newPhoneNumbers) { + getPhoneNumbers().assign(newPhoneNumbers); + } @Override public String toString() { diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntityPatcher.java b/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntityPatcher.java index 4e13ede9..edefb8f3 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntityPatcher.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntityPatcher.java @@ -20,7 +20,8 @@ class HsOfficeContactEntityPatcher implements EntityPatcher entity.getEmailAdresses().patch(KeyValueMap.from(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()))); } } diff --git a/src/main/java/net/hostsharing/hsadminng/mapper/KeyValueMap.java b/src/main/java/net/hostsharing/hsadminng/mapper/KeyValueMap.java index a17eab25..7fded816 100644 --- a/src/main/java/net/hostsharing/hsadminng/mapper/KeyValueMap.java +++ b/src/main/java/net/hostsharing/hsadminng/mapper/KeyValueMap.java @@ -6,7 +6,7 @@ public class KeyValueMap { @SuppressWarnings("unchecked") public static Map from(final Object obj) { - if (obj instanceof Map) { + if (obj == null || obj instanceof Map) { return (Map) obj; } throw new ClassCastException("Map expected, but got: " + obj); diff --git a/src/main/java/net/hostsharing/hsadminng/mapper/PatchableMapWrapper.java b/src/main/java/net/hostsharing/hsadminng/mapper/PatchableMapWrapper.java index a3d2687e..4962ac8d 100644 --- a/src/main/java/net/hostsharing/hsadminng/mapper/PatchableMapWrapper.java +++ b/src/main/java/net/hostsharing/hsadminng/mapper/PatchableMapWrapper.java @@ -36,8 +36,10 @@ public class PatchableMapWrapper implements Map { } public void assign(final Map entries) { - delegate.clear(); - delegate.putAll(entries); + if (entries != null ) { + delegate.clear(); + delegate.putAll(entries); + } } public void patch(final Map patch) { diff --git a/src/main/resources/api-definition/hs-office/hs-office-contact-schemas.yaml b/src/main/resources/api-definition/hs-office/hs-office-contact-schemas.yaml index 507211b6..6d9f2a46 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-contact-schemas.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-contact-schemas.yaml @@ -16,7 +16,7 @@ components: emailAddresses: $ref: '#/components/schemas/HsOfficeContactEmailAddresses' phoneNumbers: - type: string + $ref: '#/components/schemas/HsOfficeContactPhoneNumbers' HsOfficeContactInsert: type: object @@ -28,7 +28,7 @@ components: emailAddresses: $ref: '#/components/schemas/HsOfficeContactEmailAddresses' phoneNumbers: - type: string + $ref: '#/components/schemas/HsOfficeContactPhoneNumbers' required: - label @@ -44,11 +44,16 @@ components: emailAddresses: $ref: '#/components/schemas/HsOfficeContactEmailAddresses' phoneNumbers: - type: string - nullable: true + $ref: '#/components/schemas/HsOfficeContactPhoneNumbers' HsOfficeContactEmailAddresses: # forces generating a java.lang.Object containing a Map, instead of class HsOfficeContactEmailAddresses anyOf: - type: object additionalProperties: true + + HsOfficeContactPhoneNumbers: + # forces generating a java.lang.Object containing a Map, instead of class HsOfficeContactEmailAddresses + anyOf: + - type: object + additionalProperties: true diff --git a/src/main/resources/db/changelog/5-hs-office/501-contact/5018-hs-office-contact-test-data.sql b/src/main/resources/db/changelog/5-hs-office/501-contact/5018-hs-office-contact-test-data.sql index 996f97a0..2a989593 100644 --- a/src/main/resources/db/changelog/5-hs-office/501-contact/5018-hs-office-contact-test-data.sql +++ b/src/main/resources/db/changelog/5-hs-office/501-contact/5018-hs-office-contact-test-data.sql @@ -32,7 +32,7 @@ begin contLabel, postalAddr, ('{ "main": "' || emailAddr || '" }')::jsonb, - '+49 123 1234567' + ('{ "office": "+49 123 1234567" }')::jsonb ); end; $$; --// diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactControllerAcceptanceTest.java index 328148a6..0b53d0b1 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactControllerAcceptanceTest.java @@ -187,7 +187,9 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu "emailAddresses": { "main": "contact-admin@firstcontact.example.com" }, - "phoneNumbers": "+49 123 1234567" + "phoneNumbers": { + "office": "+49 123 1234567" + } } """)); // @formatter:on } @@ -213,7 +215,9 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu "main": "patched@example.org" }, "postalAddress": "Patched Address", - "phoneNumbers": "+01 100 123456" + "phoneNumbers": { + "office": "+01 100 123456" + } } """) .port(port) @@ -226,7 +230,7 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu .body("label", is("Temp patched contact")) .body("emailAddresses", is(Map.of("main", "patched@example.org"))) .body("postalAddress", is("Patched Address")) - .body("phoneNumbers", is("+01 100 123456")); + .body("phoneNumbers", is(Map.of("office", "+01 100 123456"))); // @formatter:on // finally, the contact is actually updated @@ -234,9 +238,9 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu assertThat(contactRepo.findByUuid(givenContact.getUuid())).isPresent().get() .matches(person -> { 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.getPhoneNumbers()).isEqualTo("+01 100 123456"); + assertThat(person.getPhoneNumbers()).containsExactlyEntriesOf(Map.of("office", "+01 100 123456")); return true; }); } @@ -256,7 +260,9 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu "emailAddresses": { "main": "patched@example.org" }, - "phoneNumbers": "+01 100 123456" + "phoneNumbers": { + "office": "+01 100 123456" + } } """) .port(port) @@ -269,16 +275,16 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu .body("label", is(givenContact.getLabel())) .body("emailAddresses", is(Map.of("main", "patched@example.org"))) .body("postalAddress", is(givenContact.getPostalAddress())) - .body("phoneNumbers", is("+01 100 123456")); + .body("phoneNumbers", is(Map.of("office", "+01 100 123456"))); // @formatter:on // finally, the contact is actually updated assertThat(contactRepo.findByUuid(givenContact.getUuid())).isPresent().get() .matches(person -> { 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.getPhoneNumbers()).isEqualTo("+01 100 123456"); + assertThat(person.getPhoneNumbers()).containsExactlyEntriesOf(Map.of("office", "+01 100 123456")); return true; }); } @@ -351,7 +357,7 @@ class HsOfficeContactControllerAcceptanceTest extends ContextBasedTestWithCleanu .label("Temp from " + Context.getCallerMethodNameFromStackFrame(1) ) .emailAddresses(Map.of("main", RandomStringUtils.randomAlphabetic(10) + "@example.org")) .postalAddress("Postal Address " + RandomStringUtils.randomAlphabetic(10)) - .phoneNumbers("+01 200 " + RandomStringUtils.randomNumeric(8)) + .phoneNumbers(Map.of("office", "+01 200 " + RandomStringUtils.randomNumeric(8))) .build(); return contactRepo.save(newContact); diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntityPatcherUnitTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntityPatcherUnitTest.java index 9e2d6d8f..ffd5dd4b 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntityPatcherUnitTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/contact/HsOfficeContactEntityPatcherUnitTest.java @@ -30,6 +30,17 @@ class HsOfficeContactEntityPatcherUnitTest extends PatchUnitTestBase< entry("mila", "mila@example.com") ); + private static final Map PATCH_PHONE_NUMBERS = patchMap( + entry("mobile", null), + entry("private", "+49 40 987654321"), + entry("fax", "+49 40 12345-99") + ); + private static final Map PATCHED_PHONE_NUMBERS = patchMap( + entry("office", "+49 40 12345-00"), + entry("private", "+49 40 987654321"), + entry("fax", "+49 40 12345-99") + ); + @Override protected HsOfficeContactEntity newInitialEntity() { final var entity = new HsOfficeContactEntity(); @@ -39,8 +50,11 @@ class HsOfficeContactEntityPatcherUnitTest extends PatchUnitTestBase< entry("main", "initial@example.org"), entry("paul", "paul@example.com"), entry("mila", "mila@example.com"))); - entity.setPhoneNumbers("initial postal address"); - entity.setPostalAddress("+01 100 123456789"); + entity.putPhoneNumbers(Map.ofEntries( + 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; } @@ -69,11 +83,13 @@ class HsOfficeContactEntityPatcherUnitTest extends PatchUnitTestBase< HsOfficeContactEntity::putEmailAdresses, PATCHED_EMAIL_ADDRESSES) .notNullable(), - new JsonNullableProperty<>( - "phoneNumbers", + new SimpleProperty<>( + "resources", HsOfficeContactPatchResource::setPhoneNumbers, - "patched family name", - HsOfficeContactEntity::setPhoneNumbers), + PATCH_PHONE_NUMBERS, + HsOfficeContactEntity::putPhoneNumbers, + PATCHED_PHONE_NUMBERS) + .notNullable(), new JsonNullableProperty<>( "patched given name", HsOfficeContactPatchResource::setPostalAddress, diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java index a18f23ec..71505f38 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/debitor/HsOfficeDebitorControllerAcceptanceTest.java @@ -108,7 +108,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu "contact": { "label": "first contact", "emailAddresses": { "main": "contact-admin@firstcontact.example.com" }, - "phoneNumbers": "+49 123 1234567" + "phoneNumbers": { "office": "+49 123 1234567" } } }, "debitorNumber": 1000111, @@ -133,7 +133,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu "contact": { "label": "first contact", "emailAddresses": { "main": "contact-admin@firstcontact.example.com" }, - "phoneNumbers": "+49 123 1234567" + "phoneNumbers": { "office": "+49 123 1234567" } } }, "details": { @@ -466,7 +466,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu "label": "first contact", "postalAddress": "Vorname Nachname\\nStraße Hnr\\nPLZ Stadt", "emailAddresses": { "main": "contact-admin@firstcontact.example.com" }, - "phoneNumbers": "+49 123 1234567" + "phoneNumbers": { "office": "+49 123 1234567" } } }, "debitorNumber": 1000111, @@ -482,7 +482,7 @@ class HsOfficeDebitorControllerAcceptanceTest extends ContextBasedTestWithCleanu "label": "first contact", "postalAddress": "Vorname Nachname\\nStraße Hnr\\nPLZ Stadt", "emailAddresses": { "main": "contact-admin@firstcontact.example.com" }, - "phoneNumbers": "+49 123 1234567" + "phoneNumbers": { "office": "+49 123 1234567" } } }, "details": { diff --git a/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java b/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java index 08d5cbcb..c42e0629 100644 --- a/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java +++ b/src/test/java/net/hostsharing/hsadminng/hs/office/migration/ImportOfficeData.java @@ -240,16 +240,16 @@ public class ImportOfficeData extends ContextBasedTest { """); assertThat(toFormattedString(contacts)).isEqualToIgnoringWhitespace(""" { - 1101=contact(label='Herr Michael Mellies ', emailAddresses='{main=mih@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}'), - 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}'), - 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}'), - 1401=contact(label='Frau Frauke Fanninga ', emailAddresses='{main=ff@example.org}'), - 1501=contact(label='Frau Cecilia Camus ', emailAddresses='{main=cc@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 }'), + 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 }'), + 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 }'), + 1301=contact(label='Petra Schmidt , Test PS', emailAddresses='{ main: ps@example.com }'), + 1401=contact(label='Frau Frauke Fanninga ', emailAddresses='{ main: ff@example.org }'), + 1501=contact(label='Frau Cecilia Camus ', emailAddresses='{ main: cc@example.org }') + } """); assertThat(toFormattedString(persons)).isEqualToIgnoringWhitespace(""" { @@ -1094,7 +1094,7 @@ public class ImportOfficeData extends ContextBasedTest { contactRecord.getString("firma"))); contact.putEmailAdresses( Map.of("main", contactRecord.getString("email"))); contact.setPostalAddress(toAddress(contactRecord)); - contact.setPhoneNumbers(toPhoneNumbers(contactRecord)); + contact.putPhoneNumbers(toPhoneNumbers(contactRecord)); contacts.put(contactRecord.getInteger("contact_id"), contact); return contact; @@ -1120,17 +1120,17 @@ public class ImportOfficeData extends ContextBasedTest { return record; } - private String toPhoneNumbers(final Record rec) { - final var result = new StringBuilder("{\n"); + private Map toPhoneNumbers(final Record rec) { + final var phoneNumbers = new LinkedHashMap(); 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"))) - result.append(" \"office\": " + "\"" + rec.getString("phone_office") + "\",\n"); + phoneNumbers.put("geschäftlich", rec.getString("phone_office")); 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"))) - result.append(" \"fax\": " + "\"" + rec.getString("fax") + "\",\n"); - return (result + "}").replace("\",\n}", "\"\n}"); + phoneNumbers.put("Fax", rec.getString("fax")); + return phoneNumbers; } private String toAddress(final Record rec) {