Compare commits

...

3 Commits

Author SHA1 Message Date
Michael Hoennig
1e5bc03407 make contacts.csv easier readable by empty lines, comments and better numbering 2024-01-16 15:49:53 +01:00
Michael Hoennig
2e609884bf remove _CONTACT from relationship types 2024-01-16 15:46:56 +01:00
Michael Hoennig
683c2f0ce4 enable import of a single contact for multiple roles 2024-01-16 14:44:17 +01:00
6 changed files with 90 additions and 43 deletions

View File

@ -3,6 +3,6 @@ package net.hostsharing.hsadminng.hs.office.relationship;
public enum HsOfficeRelationshipType { public enum HsOfficeRelationshipType {
UNKNOWN, UNKNOWN,
REPRESENTATIVE, REPRESENTATIVE,
ACCOUNTING_CONTACT, ACCOUNTING,
TECHNICAL_CONTACT OPERATIONS
} }

View File

@ -7,8 +7,8 @@ components:
type: string type: string
enum: enum:
- REPRESENTATIVE - REPRESENTATIVE
- ACCOUNTING_CONTACT - ACCOUNTING
- TECHNICAL_CONTACT - TECHNICAL
HsOfficeRelationship: HsOfficeRelationship:
type: object type: object

View File

@ -4,7 +4,7 @@
--changeset hs-office-relationship-MAIN-TABLE:1 endDelimiter:--// --changeset hs-office-relationship-MAIN-TABLE:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
CREATE TYPE HsOfficeRelationshipType AS ENUM ('UNKNOWN', 'REPRESENTATIVE', 'ACCOUNTING_CONTACT', 'TECHNICAL_CONTACT'); CREATE TYPE HsOfficeRelationshipType AS ENUM ('UNKNOWN', 'REPRESENTATIVE', 'ACCOUNTING', 'OPERATIONS');
CREATE CAST (character varying as HsOfficeRelationshipType) WITH INOUT AS IMPLICIT; CREATE CAST (character varying as HsOfficeRelationshipType) WITH INOUT AS IMPLICIT;

View File

@ -35,9 +35,7 @@ import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext; import jakarta.persistence.PersistenceContext;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import java.io.IOException; import java.io.*;
import java.io.InputStreamReader;
import java.io.Reader;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -45,6 +43,7 @@ import java.time.LocalDate;
import java.util.*; import java.util.*;
import static java.util.Arrays.stream; import static java.util.Arrays.stream;
import static java.util.Objects.requireNonNull;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -102,8 +101,8 @@ import static org.assertj.core.api.Fail.fail;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class ImportOfficeTables extends ContextBasedTest { public class ImportOfficeTables extends ContextBasedTest {
@Value("${spring.datasource.url}") @Value("${spring.datasource.username}")
private String jdbcUrl; private String postgresAdminUser;
// TODO: use real rbacSuperuser for actual import // TODO: use real rbacSuperuser for actual import
private static final String rbacSuperuser = "superuser-alex@hostsharing.net"; private static final String rbacSuperuser = "superuser-alex@hostsharing.net";
@ -145,16 +144,16 @@ public class ImportOfficeTables extends ContextBasedTest {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
if ( !"admin".equals(System.getenv("ADMIN_USER") )) { if ( !"admin".equals(postgresAdminUser) ) {
return; return;
} }
// no contacts yet => mostly null values // no contacts yet => mostly null values
assertThat(partners.toString()).isEqualToIgnoringWhitespace(""" assertThat(partners.toString()).isEqualToIgnoringWhitespace("""
{ {
7=partner(null, null: null), 7=partner(null, null),
10=partner(null, null: null), 10=partner(null, null),
12=partner(null, null: null) 12=partner(null, null)
} }
"""); """);
assertThat(contacts.toString()).isEqualTo("{}"); assertThat(contacts.toString()).isEqualTo("{}");
@ -184,7 +183,7 @@ public class ImportOfficeTables extends ContextBasedTest {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
if ( !"admin".equals(System.getenv("HSADMINNG_POSTGRES_ADMIN_USERNAME") )) { if ( !"admin".equals(postgresAdminUser) ) {
return; return;
} }
@ -197,11 +196,11 @@ public class ImportOfficeTables extends ContextBasedTest {
"""); """);
assertThat(contacts.toString()).isEqualToIgnoringWhitespace(""" assertThat(contacts.toString()).isEqualToIgnoringWhitespace("""
{ {
71=contact(label='Herr Michael Mellies ', emailAddresses='mih@example.org'), 1101=contact(label='Herr Michael Mellies ', emailAddresses='mih@example.org'),
101=contact(label='Frau Dr. Jenny Meyer-Billing , JM e.K.', emailAddresses='jm-billing@example.org'), 1201=contact(label='Frau Dr. Jenny Meyer-Billing , JM e.K.', emailAddresses='jm-billing@example.org'),
102=contact(label='Herr Andrew Meyer-Operation , JM e.K.', emailAddresses='am-operation@example.org'), 1202=contact(label='Herr Andrew Meyer-Operation , JM e.K.', emailAddresses='am-operation@example.org'),
103=contact(label='Herr Philip Meyer-Contract , JM e.K.', emailAddresses='pm-contractual@example.org'), 1203=contact(label='Herr Philip Meyer-Contract , JM e.K.', emailAddresses='pm-partner@example.org'),
121=contact(label='Petra Schmidt , Test PS', emailAddresses='ps@example.com') 1301=contact(label='Petra Schmidt , Test PS', emailAddresses='ps@example.com')
} }
"""); """);
assertThat(persons.toString()).isEqualToIgnoringWhitespace(""" assertThat(persons.toString()).isEqualToIgnoringWhitespace("""
@ -227,9 +226,10 @@ public class ImportOfficeTables extends ContextBasedTest {
"""); """);
assertThat(relationships.toString()).isEqualToIgnoringWhitespace(""" assertThat(relationships.toString()).isEqualToIgnoringWhitespace("""
{ {
71=rel(relAnchor='Mellies, Michael', relType='TECHNICAL_CONTACT', relHolder='Mellies, Michael', contact='Herr Michael Mellies '), 1101=rel(relAnchor='Mellies, Michael', relType='OPERATIONS', relHolder='Mellies, Michael', contact='Herr Michael Mellies '),
102=rel(relAnchor='JM e.K.', relType='TECHNICAL_CONTACT', relHolder='JM e.K.', contact='Herr Andrew Meyer-Operation , JM e.K.'), 1202=rel(relAnchor='JM e.K.', relType='OPERATIONS', relHolder='JM e.K.', contact='Herr Andrew Meyer-Operation , JM e.K.'),
121=rel(relAnchor='Test PS', relType='TECHNICAL_CONTACT', relHolder='Test PS', contact='Petra Schmidt , Test PS') 1203=rel(relAnchor='JM e.K.', relType='REPRESENTATIVE', relHolder='JM e.K.', contact='Herr Philip Meyer-Contract , JM e.K.'),
1301=rel(relAnchor='Test PS', relType='REPRESENTATIVE', relHolder='Test PS', contact='Petra Schmidt , Test PS')
} }
"""); """);
} }
@ -245,7 +245,7 @@ public class ImportOfficeTables extends ContextBasedTest {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
if ( !"admin".equals(System.getenv("HSADMINNG_POSTGRES_ADMIN_USERNAME") )) { if ( !"admin".equals(postgresAdminUser) ) {
return; return;
} }
@ -274,7 +274,7 @@ public class ImportOfficeTables extends ContextBasedTest {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
if ( !"admin".equals(System.getenv("HSADMINNG_POSTGRES_ADMIN_USERNAME") )) { if ( !"admin".equals(postgresAdminUser) ) {
return; return;
} }
@ -299,7 +299,7 @@ public class ImportOfficeTables extends ContextBasedTest {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
if ( !"admin".equals(System.getenv("ADMIN_USER") )) { if ( !"admin".equals(postgresAdminUser) ) {
return; return;
} }
@ -486,12 +486,28 @@ public class ImportOfficeTables extends ContextBasedTest {
.withQuoteChar('"') .withQuoteChar('"')
.build(); .build();
try (CSVReader csvReader = new CSVReaderBuilder(reader) final var filteredReader = skippingEmptyAndCommentLines(reader);
try (CSVReader csvReader = new CSVReaderBuilder(filteredReader)
.withCSVParser(parser) .withCSVParser(parser)
.build()) { .build()) {
return csvReader.readAll(); return csvReader.readAll();
} }
} }
public static Reader skippingEmptyAndCommentLines(Reader reader) throws IOException {
try (var bufferedReader = new BufferedReader(reader);
StringWriter writer = new StringWriter()) {
String line;
while ((line = bufferedReader.readLine()) != null) {
if (!line.isBlank() && !line.startsWith("#")) {
writer.write(line);
writer.write("\n");
}
}
return new StringReader(writer.toString());
}
}
private void importBusinessPartners(final String[] header, final List<String[]> records) { private void importBusinessPartners(final String[] header, final List<String[]> records) {
@ -657,9 +673,12 @@ public class ImportOfficeTables extends ContextBasedTest {
.map(this::trimAll) .map(this::trimAll)
.map(row -> new Record(columns, row)) .map(row -> new Record(columns, row))
.forEach(rec -> { .forEach(rec -> {
if (isNotBlank(rec.getString("roles"))) {
final var contactId = rec.getInteger("contact_id"); final var contactId = rec.getInteger("contact_id");
if (rec.getString("roles").isBlank()) {
fail("empty roles assignment not allowed for contact_id: " + contactId);
}
final var partner = partners.get(rec.getInteger("bp_id")); final var partner = partners.get(rec.getInteger("bp_id"));
final var debitor = debitors.get(rec.getInteger("bp_id")); final var debitor = debitors.get(rec.getInteger("bp_id"));
@ -672,23 +691,45 @@ public class ImportOfficeTables extends ContextBasedTest {
final var contact = HsOfficeContactEntity.builder().build(); final var contact = HsOfficeContactEntity.builder().build();
contacts.put(contactId, initContact(contact, rec)); contacts.put(contactId, initContact(contact, rec));
if (rec.getString("roles").contains("contractual")) { var imported = false;
if (rec.getString("roles").contains("partner")) {
assertThat(partner.getContact()).isNull();
partner.setContact(contact); partner.setContact(contact);
imported = true;
} }
if (rec.getString("roles").contains("billing")) { if (rec.getString("roles").contains("billing")) {
assertThat(debitor.getBillingContact()).isNull();
debitor.setBillingContact(contact); debitor.setBillingContact(contact);
imported = true;
} }
if (rec.getString("roles").contains("operation")) { if (rec.getString("roles").contains("operation")) {
final var rel = HsOfficeRelationshipEntity.builder() final var rel = HsOfficeRelationshipEntity.builder()
.relAnchor(partner.getPerson()) .relAnchor(partner.getPerson())
.relHolder(person) .relHolder(person)
.contact(contact) .contact(contact)
.relType(HsOfficeRelationshipType.TECHNICAL_CONTACT) .relType(HsOfficeRelationshipType.OPERATIONS)
.build(); .build();
relationships.put(contactId, rel); relationships.put(contactId, rel);
imported = true;
} }
} else { if (rec.getString("roles").contains("contractual")) {
fail("contact without role: " + rec); final var rel = HsOfficeRelationshipEntity.builder()
.relAnchor(partner.getPerson())
.relHolder(person)
.contact(contact)
.relType(HsOfficeRelationshipType.REPRESENTATIVE)
.build();
relationships.put(contactId, rel);
imported = true;
}
if (!imported) {
final var rel = HsOfficeRelationshipEntity.builder()
.relAnchor(partner.getPerson())
.relHolder(person)
.contact(contact)
.relType(HsOfficeRelationshipType.UNKNOWN)
.build();
relationships.put(contactId, rel);
} }
}); });
} }
@ -788,7 +829,7 @@ public class ImportOfficeTables extends ContextBasedTest {
} }
private Reader resourceReader(@NotNull final String resourcePath) { private Reader resourceReader(@NotNull final String resourcePath) {
return new InputStreamReader(getClass().getClassLoader().getResourceAsStream(resourcePath)); return new InputStreamReader(requireNonNull(getClass().getClassLoader().getResourceAsStream(resourcePath)));
} }
private Reader fileReader(@NotNull final Path filePath) throws IOException { private Reader fileReader(@NotNull final Path filePath) throws IOException {

View File

@ -153,7 +153,7 @@ class HsOfficeRelationshipControllerAcceptanceTest {
"contactUuid": "%s" "contactUuid": "%s"
} }
""".formatted( """.formatted(
HsOfficeRelationshipTypeResource.ACCOUNTING_CONTACT, HsOfficeRelationshipTypeResource.ACCOUNTING,
givenAnchorPerson.getUuid(), givenAnchorPerson.getUuid(),
givenHolderPerson.getUuid(), givenHolderPerson.getUuid(),
givenContact.getUuid())) givenContact.getUuid()))
@ -164,7 +164,7 @@ class HsOfficeRelationshipControllerAcceptanceTest {
.statusCode(201) .statusCode(201)
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body("uuid", isUuidValid()) .body("uuid", isUuidValid())
.body("relType", is("ACCOUNTING_CONTACT")) .body("relType", is("ACCOUNTING"))
.body("relAnchor.tradeName", is("Third OHG")) .body("relAnchor.tradeName", is("Third OHG"))
.body("relHolder.givenName", is("Paul")) .body("relHolder.givenName", is("Paul"))
.body("contact.label", is("forth contact")) .body("contact.label", is("forth contact"))
@ -197,7 +197,7 @@ class HsOfficeRelationshipControllerAcceptanceTest {
"contactUuid": "%s" "contactUuid": "%s"
} }
""".formatted( """.formatted(
HsOfficeRelationshipTypeResource.ACCOUNTING_CONTACT, HsOfficeRelationshipTypeResource.ACCOUNTING,
givenAnchorPersonUuid, givenAnchorPersonUuid,
givenHolderPerson.getUuid(), givenHolderPerson.getUuid(),
givenContact.getUuid())) givenContact.getUuid()))
@ -230,7 +230,7 @@ class HsOfficeRelationshipControllerAcceptanceTest {
"contactUuid": "%s" "contactUuid": "%s"
} }
""".formatted( """.formatted(
HsOfficeRelationshipTypeResource.ACCOUNTING_CONTACT, HsOfficeRelationshipTypeResource.ACCOUNTING,
givenAnchorPerson.getUuid(), givenAnchorPerson.getUuid(),
givenHolderPersonUuid, givenHolderPersonUuid,
givenContact.getUuid())) givenContact.getUuid()))
@ -263,7 +263,7 @@ class HsOfficeRelationshipControllerAcceptanceTest {
"contactUuid": "%s" "contactUuid": "%s"
} }
""".formatted( """.formatted(
HsOfficeRelationshipTypeResource.ACCOUNTING_CONTACT, HsOfficeRelationshipTypeResource.ACCOUNTING,
givenAnchorPerson.getUuid(), givenAnchorPerson.getUuid(),
givenHolderPerson.getUuid(), givenHolderPerson.getUuid(),
givenContactUuid)) givenContactUuid))

View File

@ -1,6 +1,12 @@
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 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
71; 7; Herr; Michael; Mellies; ; ; ; Kleine Freiheit 50; 26524; Hage; DE; ; +49 4931 123456; +49 1522 123456;; mih@example.org; contractual,billing,operation
101; 10; 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 # eine natürliche Person
102; 10; 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 1101; 7; Herr; Michael; Mellies; ; ; ; Kleine Freiheit 50; 26524; Hage; DE; ; +49 4931 123456; +49 1522 123456;; mih@example.org; partner,billing,operation
103; 10; Herr; Philip; Meyer-Contract; ; JM e.K.;; Waldweg 5; 11001; Berlin; DE; +49 30 6666666; +49 30 5555555; ; +49 30 6666666; pm-contractual@example.org; contractual
121; 12; ; Petra; Schmidt; ; Test PS;; ; ; ; ; ; ; ; ; ps@example.com; billing,contractual,operation # eine juristische Person mit drei separaten Ansprechpartnern
1201; 10; 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; 10; 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; 10; 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 nur einem Ansprechpartner
1301; 12; ; Petra; Schmidt; ; Test PS;; ; ; ; ; ; ; ; ; ps@example.com; partner,billing,contractual,operation

1 contact_id 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 bp_id salut first_name last_name title firma co street zipcode city country phone_private phone_office phone_mobile fax email roles
2 71 # eine natürliche Person 7 Herr Michael Mellies Kleine Freiheit 50 26524 Hage DE +49 4931 123456 +49 1522 123456 mih@example.org contractual,billing,operation
3 101 1101; 7; Herr; Michael; Mellies; ; ; ; Kleine Freiheit 50; 26524; Hage; DE; ; +49 4931 123456; +49 1522 123456;; mih@example.org; partner,billing,operation 10 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
4 102 # eine juristische Person mit drei separaten Ansprechpartnern 10 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
5 103 1201; 10; 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 10 Herr Philip Meyer-Contract JM e.K. Waldweg 5 11001 Berlin DE +49 30 6666666 +49 30 5555555 +49 30 6666666 pm-contractual@example.org contractual
6 121 1202; 10; 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 12 Petra Schmidt Test PS ps@example.com billing,contractual,operation
7 1203; 10; 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
8 # eine juristische Person mit nur einem Ansprechpartner
9 1301; 12; ; Petra; Schmidt; ; Test PS;; ; ; ; ; ; ; ; ; ps@example.com; partner,billing,contractual,operation
10
11
12