From 5d298a498f773d51fbdedfce160f08375add15ba Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Fri, 19 Jan 2024 12:39:03 +0100 Subject: [PATCH] auto generate representative relationship if no explicit contractor set, add vip-contact and fix db enum value for ex-partner --- .../HsOfficeRelationshipType.java | 1 + .../hs-office-relationship-schemas.yaml | 9 +- .../changelog/230-hs-office-relationship.sql | 8 +- .../hs/office/migration/ImportOfficeData.java | 141 +++++++++++------- src/test/resources/migration/contacts.csv | 13 +- .../resources/migration/sepa-mandates.csv | 5 +- 6 files changed, 109 insertions(+), 68 deletions(-) diff --git a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipType.java b/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipType.java index 6d4efc84..e3955c7e 100644 --- a/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipType.java +++ b/src/main/java/net/hostsharing/hsadminng/hs/office/relationship/HsOfficeRelationshipType.java @@ -4,6 +4,7 @@ public enum HsOfficeRelationshipType { UNKNOWN, EX_PARTNER, REPRESENTATIVE, + VIP_CONTACT, ACCOUNTING, OPERATIONS } diff --git a/src/main/resources/api-definition/hs-office/hs-office-relationship-schemas.yaml b/src/main/resources/api-definition/hs-office/hs-office-relationship-schemas.yaml index cfda18c0..cb865205 100644 --- a/src/main/resources/api-definition/hs-office/hs-office-relationship-schemas.yaml +++ b/src/main/resources/api-definition/hs-office/hs-office-relationship-schemas.yaml @@ -6,9 +6,12 @@ components: HsOfficeRelationshipType: type: string enum: - - REPRESENTATIVE - - ACCOUNTING - - TECHNICAL + - UNKNOWN + - EX_PARTNER + - REPRESENTATIVE, + - VIP_CONTACT + - ACCOUNTING, + - OPERATIONS HsOfficeRelationship: type: object diff --git a/src/main/resources/db/changelog/230-hs-office-relationship.sql b/src/main/resources/db/changelog/230-hs-office-relationship.sql index d1d4a366..942813d9 100644 --- a/src/main/resources/db/changelog/230-hs-office-relationship.sql +++ b/src/main/resources/db/changelog/230-hs-office-relationship.sql @@ -4,7 +4,13 @@ --changeset hs-office-relationship-MAIN-TABLE:1 endDelimiter:--// -- ---------------------------------------------------------------------------- -CREATE TYPE HsOfficeRelationshipType AS ENUM ('UNKNOWN', 'EX-PARTNER', 'REPRESENTATIVE', 'ACCOUNTING', 'OPERATIONS'); +CREATE TYPE HsOfficeRelationshipType AS ENUM ( + 'UNKNOWN', + 'EX_PARTNER', + 'REPRESENTATIVE', + 'VIP_CONTACT', + 'ACCOUNTING', + 'OPERATIONS'); CREATE CAST (character varying as HsOfficeRelationshipType) WITH INOUT AS IMPLICIT; 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 3a1bf345..7bdf815b 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 @@ -111,6 +111,8 @@ import static org.assertj.core.api.Fail.fail; @ExtendWith(OrderedDependedTestsExtension.class) public class ImportOfficeData extends ContextBasedTest { + static int relationshipId = 2000000; + @Value("${spring.datasource.username}") private String postgresAdminUser; @@ -172,7 +174,7 @@ public class ImportOfficeData extends ContextBasedTest { assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace(""" { 17=Membership(10017, null null, null, 1001700, [2000-12-06,), NONE), - 20=Membership(10020, null null, null, 1002000, [2000-12-06,2016-01-01), UNKNOWN), + 20=Membership(10020, null null, null, 1002000, [2000-12-06,2016-01-01), UNKNOWN), 22=Membership(11022, null null, null, 1102200, [2021-04-01,), NONE) } """); @@ -194,48 +196,54 @@ public class ImportOfficeData extends ContextBasedTest { assertThat(toFormattedString(partners)).isEqualToIgnoringWhitespace(""" { 17=partner(NATURAL Mellies, Michael: Herr Michael Mellies ), - 20=partner(LEGAL JM e.K.: Herr Philip Meyer-Contract , JM e.K.), + 20=partner(LEGAL JM GmbH: Herr Philip Meyer-Contract , JM GmbH), 22=partner(LEGAL Test PS: Petra Schmidt , Test PS) } """); assertThat(toFormattedString(contacts)).isEqualToIgnoringWhitespace(""" { 1101=contact(label='Herr Michael Mellies ', emailAddresses='mih@example.org'), - 1201=contact(label='Frau Dr. Jenny Meyer-Billing , JM e.K.', emailAddresses='jm-billing@example.org'), - 1202=contact(label='Herr Andrew Meyer-Operation , JM e.K.', emailAddresses='am-operation@example.org'), - 1203=contact(label='Herr Philip Meyer-Contract , JM e.K.', emailAddresses='pm-partner@example.org'), + 1200=contact(label='JM e.K.', emailAddresses='jm-ex-partner@example.org'), + 1201=contact(label='Frau Dr. Jenny Meyer-Billing , JM GmbH', emailAddresses='jm-billing@example.org'), + 1202=contact(label='Herr Andrew Meyer-Operation , JM GmbH', emailAddresses='am-operation@example.org'), + 1203=contact(label='Herr Philip Meyer-Contract , JM GmbH', emailAddresses='pm-partner@example.org'), 1301=contact(label='Petra Schmidt , Test PS', emailAddresses='ps@example.com') } """); assertThat(toFormattedString(persons)).isEqualToIgnoringWhitespace(""" { 1101=person(personType='NATURAL', tradeName='', familyName='Mellies', givenName='Michael'), - 1201=person(personType='LEGAL', tradeName='JM e.K.', familyName='Meyer-Billing', givenName='Jenny'), - 1202=person(personType='LEGAL', tradeName='JM e.K.', familyName='Meyer-Operation', givenName='Andrew'), - 1203=person(personType='LEGAL', tradeName='JM e.K.', familyName='Meyer-Contract', givenName='Philip'), + 1200=person(personType='LEGAL', tradeName='JM e.K.', familyName='', givenName=''), + 1201=person(personType='LEGAL', tradeName='JM GmbH', familyName='Meyer-Billing', givenName='Jenny'), + 1202=person(personType='LEGAL', tradeName='JM GmbH', familyName='Meyer-Operation', givenName='Andrew'), + 1203=person(personType='LEGAL', tradeName='JM GmbH', familyName='Meyer-Contract', givenName='Philip'), 1301=person(personType='LEGAL', tradeName='Test PS', familyName='Schmidt', givenName='Petra') } """); assertThat(toFormattedString(debitors)).isEqualToIgnoringWhitespace(""" { 17=debitor(1001700: NATURAL Mellies, Michael: mih), - 20=debitor(1002000: LEGAL JM e.K.: xyz), + 20=debitor(1002000: LEGAL JM GmbH: xyz), 22=debitor(1102200: LEGAL Test PS: xxx) } """); assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace(""" { 17=Membership(10017, NATURAL Mellies, Michael, 1001700, [2000-12-06,), NONE), - 20=Membership(10020, LEGAL JM e.K., 1002000, [2000-12-06,2016-01-01), UNKNOWN), + 20=Membership(10020, LEGAL JM GmbH, 1002000, [2000-12-06,2016-01-01), UNKNOWN), 22=Membership(11022, LEGAL Test PS, 1102200, [2021-04-01,), NONE) } """); assertThat(toFormattedString(relationships)).isEqualToIgnoringWhitespace(""" { - 1101=rel(relAnchor='NATURAL Mellies, Michael', relType='OPERATIONS', relHolder='NATURAL Mellies, Michael', contact='Herr Michael Mellies '), - 1202=rel(relAnchor='LEGAL JM e.K.', relType='OPERATIONS', relHolder='LEGAL JM e.K.', contact='Herr Andrew Meyer-Operation , JM e.K.'), - 1203=rel(relAnchor='LEGAL JM e.K.', relType='REPRESENTATIVE', relHolder='LEGAL JM e.K.', contact='Herr Philip Meyer-Contract , JM e.K.'), - 1301=rel(relAnchor='LEGAL Test PS', relType='REPRESENTATIVE', relHolder='LEGAL Test PS', contact='Petra Schmidt , Test PS') + 2000000=rel(relAnchor='NATURAL Mellies, Michael', relType='OPERATIONS', relHolder='NATURAL Mellies, Michael', contact='Herr Michael Mellies '), + 2000001=rel(relAnchor='LEGAL JM GmbH', relType='EX_PARTNER', relHolder='LEGAL JM e.K.', contact='JM e.K.'), + 2000002=rel(relAnchor='LEGAL JM GmbH', relType='OPERATIONS', relHolder='LEGAL JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), + 2000003=rel(relAnchor='LEGAL JM GmbH', relType='VIP_CONTACT', relHolder='LEGAL JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), + 2000004=rel(relAnchor='LEGAL JM GmbH', relType='REPRESENTATIVE', relHolder='LEGAL JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), + 2000005=rel(relAnchor='LEGAL Test PS', relType='OPERATIONS', relHolder='LEGAL Test PS', contact='Petra Schmidt , Test PS'), + 2000006=rel(relAnchor='LEGAL Test PS', relType='REPRESENTATIVE', relHolder='LEGAL Test PS', contact='Petra Schmidt , Test PS'), + 2000007=rel(relAnchor='NATURAL Mellies, Michael', relType='REPRESENTATIVE', relHolder='NATURAL Mellies, Michael', contact='Herr Michael Mellies ') } """); } @@ -253,18 +261,20 @@ public class ImportOfficeData extends ContextBasedTest { assumeThat(postgresAdminUser).isEqualTo("admin"); - assertThat(bankAccounts.toString()).isEqualToIgnoringWhitespace(""" - { - 234234=bankAccount(holder='Michael Mellies', iban='DE37500105177419788228', bic='INGDDEFFXXX'), - 235662=bankAccount(holder='JM e.K.', iban='DE49500105174516484892', bic='INGDDEFFXXX') - } - """); - assertThat(sepaMandates.toString()).isEqualToIgnoringWhitespace(""" - { - 234234=SEPA-Mandate(DE37500105177419788228, MH12345, 2004-06-12, [2004-06-15,)), - 235662=SEPA-Mandate(DE49500105174516484892, JM33344, 2005-06-28, [2005-07-01,)) - } - """); + assertThat(toFormattedString(bankAccounts)).isEqualToIgnoringWhitespace(""" + { + 234234=bankAccount(holder='Michael Mellies', iban='DE37500105177419788228', bic='INGDDEFFXXX'), + 235600=bankAccount(holder='JM e.K.', iban='DE02300209000106531065', bic='CMCIDEDD'), + 235662=bankAccount(holder='JM GmbH', iban='DE49500105174516484892', bic='INGDDEFFXXX') + } + """); + assertThat(toFormattedString(sepaMandates)).isEqualToIgnoringWhitespace(""" + { + 234234=SEPA-Mandate(DE37500105177419788228, MH12345, 2004-06-12, [2004-06-15,)), + 235600=SEPA-Mandate(DE02300209000106531065, JM33344, 2004-01-15, [2004-01-20,2005-06-28)), + 235662=SEPA-Mandate(DE49500105174516484892, JM33344, 2005-06-28, [2005-07-01,)) + } + """); } @Test @@ -686,7 +696,7 @@ public class ImportOfficeData extends ContextBasedTest { final var debitor = debitors.get(rec.getInteger("bp_id")); final var partnerPerson = partner.getPerson(); - if (rec.getString("roles").contains("partner")) { + if (containsRole(rec)) { initPerson(partner.getPerson(), rec); } @@ -700,43 +710,62 @@ public class ImportOfficeData extends ContextBasedTest { final var contact = HsOfficeContactEntity.builder().build(); initContact(contact, rec); - if (rec.getString("roles").contains("partner")) { + if (containsRole(rec, "partner")) { assertThat(partner.getContact()).isNull(); partner.setContact(contact); } - if (rec.getString("roles").contains("billing")) { + if (containsRole(rec, "billing")) { assertThat(debitor.getBillingContact()).isNull(); debitor.setBillingContact(contact); } - if (rec.getString("roles").contains("operation")) { - final var rel = HsOfficeRelationshipEntity.builder() - .relAnchor(partnerPerson) - .relHolder(contactPerson) - .contact(contact) - .relType(HsOfficeRelationshipType.OPERATIONS) - .build(); - relationships.put(contactId, rel); + if (containsRole(rec, "operation")) { + addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.OPERATIONS); } - if (rec.getString("roles").contains("contractual")) { - final var rel = HsOfficeRelationshipEntity.builder() - .relAnchor(partnerPerson) - .relHolder(contactPerson) - .contact(contact) - .relType(HsOfficeRelationshipType.REPRESENTATIVE) - .build(); - relationships.put(contactId, rel); + if (containsRole(rec, "contractual")) { + addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.REPRESENTATIVE); } - if (rec.getString("roles").contains("ex-partner")) { - final var rel = HsOfficeRelationshipEntity.builder() - .relAnchor(partnerPerson) - .relHolder(contactPerson) - .contact(contact) - .relType(HsOfficeRelationshipType.EX_PARTNER) - .build(); - relationships.put(contactId, rel); + if (containsRole(rec, "ex-partner")) { + addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.EX_PARTNER); } - verifyContainsOnly(rec.getString("roles"), "partner", "ex-partner", "billing", "contractual", "operation"); + if (containsRole(rec, "vip-contact")) { + addRelationship(partnerPerson, contactPerson, contact, HsOfficeRelationshipType.VIP_CONTACT); + } + verifyContainsOnly(rec.getString("roles"), "partner", "vip-contact", "ex-partner", "billing", "contractual", "operation"); }); + + optionallyAddMissingContractualRelationships(); + } + + private static void optionallyAddMissingContractualRelationships() { + partners.forEach( (id, partner) -> { + final var partnerPerson = partner.getPerson(); + if (relationships.values().stream().filter(rel -> rel.getRelHolder() == partnerPerson && rel.getRelType() == HsOfficeRelationshipType.REPRESENTATIVE).findFirst().isEmpty()) { + addRelationship(partnerPerson, partnerPerson, partner.getContact(), HsOfficeRelationshipType.REPRESENTATIVE); + } + }); + } + + private static boolean containsRole(final Record rec, final String role) { + final var roles = rec.getString("roles"); + return ("," + roles + ",").contains("," + role + ","); + } + + private static boolean containsRole(final Record rec) { + return containsRole(rec, "partner"); + } + + private static void addRelationship( + final HsOfficePersonEntity partnerPerson, + final HsOfficePersonEntity contactPerson, + final HsOfficeContactEntity contact, + final HsOfficeRelationshipType representative) { + final var rel = HsOfficeRelationshipEntity.builder() + .relAnchor(partnerPerson) + .relHolder(contactPerson) + .contact(contact) + .relType(representative) + .build(); + relationships.put(relationshipId++, rel); } private HsOfficePersonEntity initPerson(final HsOfficePersonEntity person, final Record contactRecord) { @@ -880,8 +909,8 @@ public class ImportOfficeData extends ContextBasedTest { result.append(firstname + " "); if (isNotBlank(lastname)) result.append(lastname + " "); - if (result.length() > 0 && isNotBlank(firm)) { - result.append(", " + firm); + if (isNotBlank(firm)) { + result.append( (isBlank(result) ? "" : ", ") + firm); } return result.toString(); } diff --git a/src/test/resources/migration/contacts.csv b/src/test/resources/migration/contacts.csv index d357b137..af67bce3 100644 --- a/src/test/resources/migration/contacts.csv +++ b/src/test/resources/migration/contacts.csv @@ -1,12 +1,13 @@ contact_id; bp_id; salut; first_name; last_name; title; firma; co; street; zipcode;city; country; phone_private; phone_office; phone_mobile; fax; email; roles -# eine natürliche Person +# eine natürliche Person, implizites contractual 1101; 17; Herr; Michael; Mellies; ; ; ; Kleine Freiheit 50; 26524; Hage; DE; ; +49 4931 123456; +49 1522 123456;; mih@example.org; partner,billing,operation -# eine juristische Person mit drei separaten Ansprechpartnern -1201; 20; Frau; Jenny; Meyer-Billing; Dr.; JM e.K.;; Waldweg 5; 11001; Berlin; DE; +49 30 7777777; +49 30 1111111; ; +49 30 2222222; jm-billing@example.org; billing -1202; 20; Herr; Andrew; Meyer-Operation; ; JM e.K.;; Waldweg 5; 11001; Berlin; DE; +49 30 6666666; +49 30 3333333; ; +49 30 4444444; am-operation@example.org; operation -1203; 20; Herr; Philip; Meyer-Contract; ; JM e.K.;; Waldweg 5; 11001; Berlin; DE; +49 30 6666666; +49 30 5555555; ; +49 30 6666666; pm-partner@example.org; partner,contractual +# eine juristische Person mit drei separaten Ansprechpartnern, vip-contact und ex-partner +1200; 20;; ; ; ; JM e.K.;; Wiesenweg 15; 12335; Berlin; DE; +49 30 6666666; +49 30 5555555; ; +49 30 6666666; jm-ex-partner@example.org; ex-partner +1201; 20; Frau; Jenny; Meyer-Billing; Dr.; JM GmbH;; Waldweg 5; 11001; Berlin; DE; +49 30 7777777; +49 30 1111111; ; +49 30 2222222; jm-billing@example.org; billing +1202; 20; Herr; Andrew; Meyer-Operation; ; JM GmbH;; Waldweg 5; 11001; Berlin; DE; +49 30 6666666; +49 30 3333333; ; +49 30 4444444; am-operation@example.org; operation,vip-contact +1203; 20; Herr; Philip; Meyer-Contract; ; JM GmbH;; Waldweg 5; 11001; Berlin; DE; +49 30 6666666; +49 30 5555555; ; +49 30 6666666; pm-partner@example.org; partner,contractual -# eine juristische Person mit nur einem Ansprechpartner +# eine juristische Person mit nur einem Ansprechpartner und explizitem contractual 1301; 22; ; Petra; Schmidt; ; Test PS;; ; ; ; ; ; ; ; ; ps@example.com; partner,billing,contractual,operation diff --git a/src/test/resources/migration/sepa-mandates.csv b/src/test/resources/migration/sepa-mandates.csv index d8dde8be..a76adc16 100644 --- a/src/test/resources/migration/sepa-mandates.csv +++ b/src/test/resources/migration/sepa-mandates.csv @@ -1,3 +1,4 @@ sepa_mandat_id; bp_id; bank_customer; bank_name; bank_iban; bank_bic; mandat_ref; mandat_signed; mandat_since; mandat_until; mandat_used -234234; 17; Michael Mellies; ING Bank AG; DE37500105177419788228; INGDDEFFXXX; MH12345; 2004-06-12; 2004-06-15; ; 2022-10-20 -235662; 20; JM e.K.; ING Bank AG; DE49500105174516484892; INGDDEFFXXX; JM33344; 2005-06-28; 2005-07-01; ; 2016-01-18 +234234; 17; Michael Mellies; ING Bank AG; DE37500105177419788228; INGDDEFFXXX; MH12345; 2004-06-12; 2004-06-15; ; 2022-10-20 +235600; 20; JM e.K.; Targobank AG; DE02300209000106531065; CMCIDEDD; JM33344; 2004-01-15; 2004-01-20;2005-06-27 ;2016-01-18 +235662; 20; JM GmbH; ING Bank AG; DE49500105174516484892; INGDDEFFXXX; JM33344; 2005-06-28; 2005-07-01; ; 2016-01-18