Compare commits

..

No commits in common. "master" and "feature/introduce-global-agent-for-non-hostmaster-admins" have entirely different histories.

168 changed files with 763 additions and 4552 deletions

View File

@ -95,6 +95,3 @@ if [ ! -f .environment ]; then
cp .tc-environment .environment cp .tc-environment .environment
fi fi
source .environment source .environment
alias scenario-reports-upload='./gradlew scenarioTests convertMarkdownToHtml && ssh hsh03-hsngdev@h50.hostsharing.net "rm -f doms/hsngdev.hs-example.de/htdocs-ssl/scenarios/office/*.html" && scp build/doc/scenarios/*.html hsh03-hsngdev@h50.hostsharing.net:doms/hsngdev.hs-example.de/htdocs-ssl/scenarios/office'
alias scenario-reports-open='open https://hsngdev.hs-example.de/scenarios/office'

36
Jenkinsfile vendored
View File

@ -26,35 +26,9 @@ pipeline {
} }
} }
stage ('Compile') { stage ('Compile & Test') {
steps { steps {
sh './gradlew clean processSpring compileJava compileTestJava --no-daemon' sh './gradlew clean check --no-daemon -x pitest -x dependencyCheckAnalyze'
}
}
stage ('Tests') {
parallel {
stage('Unit-/Integration/Acceptance-Tests') {
steps {
sh './gradlew check --no-daemon -x pitest -x dependencyCheckAnalyze -x importOfficeData -x importHostingAssets'
}
}
stage('Import-Tests') {
steps {
sh './gradlew importOfficeData importHostingAssets --no-daemon'
}
}
stage ('Scenario-Tests') {
steps {
sh './gradlew scenarioTests --no-daemon'
}
}
}
}
stage ('Check') {
steps {
sh './gradlew check -x pitest -x dependencyCheckAnalyze --no-daemon'
} }
} }
} }
@ -71,12 +45,6 @@ pipeline {
sourcePattern: 'src/main/java' sourcePattern: 'src/main/java'
) )
// archive scenario-test reports in HTML format
sh '''
./gradlew convertMarkdownToHtml
'''
archiveArtifacts artifacts: 'doc/scenarios/*.html', allowEmptyArchive: true
// cleanup workspace // cleanup workspace
cleanWs() cleanWs()
} }

View File

@ -118,8 +118,8 @@ openapiProcessor {
springRoot { springRoot {
processorName 'spring' processorName 'spring'
processor 'io.openapiprocessor:openapi-processor-spring:2022.5' processor 'io.openapiprocessor:openapi-processor-spring:2022.5'
apiPath "$projectDir/src/main/resources/api-definition/api-definition.yaml" apiPath "$projectDir/src/main/resources/api-definition.yaml"
mapping "$projectDir/src/main/resources/api-definition/api-mappings.yaml" mapping "$projectDir/src/main/resources/api-mappings.yaml"
targetDir "$buildDir/generated/sources/openapi-javax" targetDir "$buildDir/generated/sources/openapi-javax"
showWarnings true showWarnings true
openApiNullable true openApiNullable true
@ -255,7 +255,7 @@ test {
'net.hostsharing.hsadminng.**.generated.**', 'net.hostsharing.hsadminng.**.generated.**',
] ]
useJUnitPlatform { useJUnitPlatform {
excludeTags 'importOfficeData', 'importHostingData', 'scenarioTest' excludeTags 'import'
} }
} }
jacocoTestReport { jacocoTestReport {
@ -344,17 +344,6 @@ tasks.register('importHostingAssets', Test) {
mustRunAfter spotlessJava mustRunAfter spotlessJava
} }
tasks.register('scenarioTests', Test) {
useJUnitPlatform {
includeTags 'scenarioTest'
}
group 'verification'
description 'run the import jobs as tests'
mustRunAfter spotlessJava
}
// pitest mutation testing // pitest mutation testing
pitest { pitest {
targetClasses = ['net.hostsharing.hsadminng.**'] targetClasses = ['net.hostsharing.hsadminng.**']
@ -402,46 +391,3 @@ tasks.named("dependencyUpdates").configure {
isNonStable(it.candidate.version) isNonStable(it.candidate.version)
} }
} }
// Generate HTML from Markdown scenario-test-reports using Pandoc:
tasks.register('convertMarkdownToHtml') {
description = 'Generates HTML from Markdown scenario-test-reports using Pandoc.'
group = 'Conversion'
// Define the template file and input directory
def templateFile = file('doc/scenarios/.template.html')
// Task configuration and execution
doFirst {
// Check if pandoc is installed
try {
exec {
commandLine 'pandoc', '--version'
}
} catch (Exception) {
throw new GradleException("Pandoc is not installed or not found in the system path.")
}
// Check if the template file exists
if (!templateFile.exists()) {
throw new GradleException("Template file 'doc/scenarios/.template.html' not found.")
}
}
doLast {
// Gather all Markdown files in the current directory
fileTree(dir: '.', include: 'build/doc/scenarios/*.md').each { file ->
// Corrected way to create the output file path
def outputFile = new File(file.parent, file.name.replaceAll(/\.md$/, '.html'))
// Execute pandoc for each markdown file
exec {
commandLine 'pandoc', file.absolutePath, '--template', templateFile.absolutePath, '-o', outputFile.absolutePath
}
println "Converted ${file.name} to ${outputFile.name}"
}
}
}
convertMarkdownToHtml.dependsOn scenarioTests

View File

@ -1,124 +0,0 @@
### hsadminNg fachliches Glossar
<!--
Currently, this business glossary is only available in German because in many cases,
the German terms are important for comprehensibility for those using this software.
-->
Dieses ist eine Sammlung von Fachbegriffen, die in diesem Projekt benutzt werden.
Ebenfalls aufgenommen sind technische Begriffe, die für Benutzer für das Verständnis der Schnittstellen nötig sind.
Falls etwas fehlt, bitte Bescheid geben.
#### Partner
In diesem System ist ein _Partner_ grundsätzlich jeglicher Geschäftspartner der _Hostsharing eG_.
Dies können grundsätzlich Kunden, siehe [Debitor](#Debitor), wie Lieferanten sein.
Derzeit sind aber nur Debitoren implementiert.
Des Weiteren gibt es für jeden _Partner_ eine fünfstellige Partnernummer mit dem Prefix 'P-' (z.B. `P-123454`)
sowie Zusatzinformationen (z.B. Registergerichtnummer oder Geburtsdatum), die zur genauen Identifikation benötigt werden.
Für einen _Partner_ kann es gleichzeitig mehrere [Debitoren](#Debitor)
und zeitlich nacheinander mehrere [Mitgliedschaften](#Mitgliedschaft) geben.
Partner sind grundsätzlich als ist [Relation](#Relation) der Vertragsperson mit der Person _Hostsharing eG_ implementiert.
### Debitor
Ein `Debitor` ist quasi ein Rechnungsempfänger für einen [Partner](#Partner).
Für einen _Partner_ kann es gleichzeitig mehrere [Debitoren](#Debitor) geben,
z.B. für spezielle Projekte des Kunden oder verbundene Organisationen.
Des Weiteren gibt es für jeden _Partner_ eine fünfstellige Partnernummer mit dem Prefix 'P-' (z.B. `P-123454`)
sowie Zusatzinformationen (z.B. Registergerichtsnummer oder Geburtsdatum), die zur genauen Identifikation benötigt werden.
Debitoren sind grundsätzlich als ist [Relation](#Relation) der Vertragsperson mit der Person des Vertragspartners implementiert.
#### Relation
Eine _Relation_ ist eine typisierte und mit Kontaktdaten versehene Beziehung einer (_Holder_)-Person zu einer _Anchor_-Person.
Eine Relation ist eine Art Geschäftsrolle, wir haben hier aber keinen Begriff mit 'Rolle' verwendet,
weil 'Role' (engl.) zu leicht mit der [RBAC-Rolle](#RBAC-Role) verwechselt werden könnte.
Die _Relation_ ist auch ein technisches Konzept und gehört nicht zur Domänensprache.
Dieses Konzept ist jedoch für das Verständnis der ([API](#API)) notwendig.
#### Ex-Partner
Ex-Partner bilden [Personen](#Person) ab, die vormals [Partner](#Partner) waren.
Diese bleiben dadurch informationshalber im System verfügbar.
Implementiert ist der _Ex-Partner_ als eine besondere Form der [Relation](#Relation)
der Person des Ex-Partner (_Holder_) zum neuen Partner (_Anchor_) dargestellt.
Dieses kann zu einer Kettenbildung führen.
#### Representative-Contact (ehemals _contractual_)
Ein _Representative_ ist eine natürliche Person, die für eine nicht-natürliche Person vertretungsberechtigt ist.
Implementiert ist der _Representative_ als eine besondere Form der [Relation](#Relation)
der Person des Repräsentanten (_Holder_) zur repräsentierten Person (_Anchor_) dargestellt.
### VIP-Contact
Ein _VIP-Contact_ ist eine natürliche Person, die für einen Geschäftspartner eine wichtige Funktion übernimmt,
nicht aber deren offizieller Repräsentant ist.
Implementiert ist der _VIP-Contact_ als eine besondere Form der [Relation](#Relation)
der Person des VIP-Contact (_Holder_) zur repräsentierten Person (_Anchor_) dargestellt.
### Operations-Contact
Ein _Operations-_Contact_ ist_ eine natürliche Person, die für einen Geschäftspartner technischer Ansprechpartner ist.
Ein Seiteneffekt ist, dass diese Person im Ticketsystem Znuny direkt dem Geschäftspartner zugeordnet werden kann.
Im Legacy System waren das die Kontakte mit der Rolle `operation` und `silent`.
Implementiert ist der _Operations-Contact_ als eine besondere Form der [Relation](#Relation)
der Person des _Operations-Contact_ (_Holder_) zur repräsentierten Person (_Anchor_) dargestellt.
### OperationsAlert-Contact
Ein _OperationsAlert-_Contact_ ist_ eine natürliche Person, die für einen Geschäftspartner bei technischen Probleme kontaktiert werden soll.
Im Legacy System waren das die Kontakte mit der Rolle `operation`.
Implementiert ist der _OperationsAlert-Contact_ als eine besondere Form der [Relation](#Relation)
der Person des _OperationsAlert-Contact_ (_Holder_) zur repräsentierten Person (_Anchor_) dargestellt.
### Subscriber-Contact
Ein _Subscriber-_Contact_ ist_ eine natürliche Person, die für einen Geschäftspartner eine bestimmte Mailingliste abonniert.
Implementiert ist der _Subscriber-Contact_ als eine besondere Form der [Relation](#Relation)
der Person des _Subscriber-Contact_ (_Holder_) zur repräsentierten Person (_Anchor_) dargestellt.
Zusätzlich wird diese Relation mit dem Kurznamen der abonnierten Mailingliste markiert.
#### Anchor / Relation-Anchor
siehe [Relation](#Relation)
#### Holder / Relation-Holder
siehe [Relation](#Relation)
#### API
Und API (Application-Programming-Interface) verstehen wir eine über HTTPS angesprochene programmatisch bedienbare Schnittstell
zur Funktionalität des hsAdmin-NG-Systems.

View File

@ -64,7 +64,7 @@ classDiagram
} }
class partner-MeierGmbH { class partner-MeierGmbH {
+Numeric partnerNumber: P-12345 +Numeric partnerNumber: 12345
+Relation partnerRel +Relation partnerRel
} }
partner-MeierGmbH *-- rel-MeierGmbH partner-MeierGmbH *-- rel-MeierGmbH

View File

@ -1,124 +0,0 @@
<!doctype html>
<html $if(lang)$ lang="$lang$" $endif$>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!--[if lt IE 9]>
<script src="http://css3-mediaqueries-js.googlecode.com/svn/trunk/css3-mediaqueries.js"></script>
<![endif]-->
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<!-- <link rel="stylesheet" type="text/css" href="template.css" /> -->
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/diversen/pandoc-bootstrap-adaptive-template@959c3622/template.css" />
<link href="https://vjs.zencdn.net/5.4.4/video-js.css" rel="stylesheet" />
<script src="https://code.jquery.com/jquery-2.2.1.min.js"></script>
<!-- <script type='text/javascript' src='menu/js/jquery.cookie.js'></script> -->
<!-- <script type='text/javascript' src='menu/js/jquery.hoverIntent.minified.js'></script> -->
<!-- <script type='text/javascript' src='menu/js/jquery.dcjqaccordion.2.7.min.js'></script> -->
<!-- <link href="menu/css/skins/blue.css" rel="stylesheet" type="text/css" /> -->
<!-- <link href="menu/css/skins/graphite.css" rel="stylesheet" type="text/css" /> -->
<!-- <link href="menu/css/skins/grey.css" rel="stylesheet" type="text/css" /> -->
<!-- <script src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script> -->
<!-- <script src="script.js"></script> -->
<!-- <script src="jquery.sticky-kit.js "></script> -->
<script type='text/javascript' src='https://cdn.jsdelivr.net/gh/diversen/pandoc-bootstrap-adaptive-template@959c3622/menu/js/jquery.cookie.js'></script>
<script type='text/javascript' src='https://cdn.jsdelivr.net/gh/diversen/pandoc-bootstrap-adaptive-template@959c3622/menu/js/jquery.hoverIntent.minified.js'></script>
<script type='text/javascript' src='https://cdn.jsdelivr.net/gh/diversen/pandoc-bootstrap-adaptive-template@959c3622/menu/js/jquery.dcjqaccordion.2.7.min.js'></script>
<link href="https://cdn.jsdelivr.net/gh/diversen/pandoc-bootstrap-adaptive-template@959c3622/menu/css/skins/blue.css" rel="stylesheet" type="text/css" />
<link href="https://cdn.jsdelivr.net/gh/diversen/pandoc-bootstrap-adaptive-template@959c3622/menu/css/skins/graphite.css" rel="stylesheet" type="text/css" />
<link href="https://cdn.jsdelivr.net/gh/diversen/pandoc-bootstrap-adaptive-template@959c3622/menu/css/skins/grey.css" rel="stylesheet" type="text/css" />
<link href="https://cdn.jsdelivr.net/gh/ryangrose/easy-pandoc-templates@948e28e5/css/elegant_bootstrap.css" rel="stylesheet" type="text/css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script src="https://cdn.jsdelivr.net/gh/diversen/pandoc-bootstrap-adaptive-template@959c3622/script.js"></script>
<script src="https://cdn.jsdelivr.net/gh/diversen/pandoc-bootstrap-adaptive-template@959c3622/jquery.sticky-kit.js"></script>
<meta name="generator" content="pandoc" />
$for(author-meta)$
<meta name="author" content="$author-meta$" />
$endfor$
$if(date-meta)$
<meta name="date" content="$date-meta$" />
$endif$
<title>$if(title-prefix)$$title-prefix$ - $endif$$pagetitle$</title>
<style type="text/css">code{white-space: pre;}</style>
$if(quotes)$
<style type="text/css">q { quotes: "“" "”" "" ""; }</style>
$endif$
$if(highlighting-css)$
<style type="text/css">
$highlighting-css$
</style>
$endif$
$for(css)$
<link rel="stylesheet" href="$css$" $if(html5)$$else$type="text/css" $endif$/>
$endfor$
$if(math)$
$math$
$endif$
$for(header-includes)$
$header-includes$
$endfor$
</head>
<body>
$if(title)$
<div class="navbar navbar-static-top">
<div class="navbar-inner">
<div class="container">
<span class="doc-title">$title$</span>
<ul class="nav pull-right doc-info">
$for(author)$
<li><p class="navbar-text">$author$</p></li>
$endfor$
$if(date)$
<li><p class="navbar-text">$date$</p></li>
$endif$
</ul>
</div>
</div>
</div>
$endif$
<div class="container">
<div class="row">
$if(toc)$
<div id="$idprefix$TOC" class="span3">
<div class="well toc">
$toc$
</div>
</div>
$endif$
<div class="span$if(toc)$9$else$12$endif$">
$if(abstract)$
<H1>$abstract-title$</H1>
$abstract$
$endif$
$for(include-before)$
$include-before$
$endfor$
$body$
$for(include-after)$
$include-after$
$endfor$
</div>
</div>
</div>
<script src="https://vjs.zencdn.net/5.4.4/video.js"></script>
</body>
</html>

View File

@ -1 +0,0 @@
find the generated ScenarioReports in build/doc/scenarios

View File

@ -90,20 +90,6 @@ Acceptance-tests, are blackbox-tests and do <u>not</u> count into test-code-cove
TODO.test: Complete the Acceptance-Tests test concept. TODO.test: Complete the Acceptance-Tests test concept.
#### Scenario-Tests
Our Scenario-tests are induced by business use-cases.
They test from the REST API all the way down to the database.
Most scenario-tests are positive tests, they test if business scenarios do work.
But few might be negative tests, which test if specific forbidden data gets rejected.
Our scenario tests also generate test-reports which contain the REST-API calls needed for each scenario.
These reports can be used as examples for the API usage from a business perspective.
There is an extra document regarding scenario-test, see [Scenario-Tests README](../src/test/java/net/hostsharing/hsadminng/hs/office/scenarios/README.md).
#### Performance-Tests #### Performance-Tests
Performance-critical scenarios have to be identified and a special performance-test has to be implemented. Performance-critical scenarios have to be identified and a special performance-test has to be implemented.

View File

@ -1,6 +1,10 @@
FROM eclipse-temurin:21-jdk FROM eclipse-temurin:21-jdk
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y bind9-utils pandoc && \ apt-get install -y bind9-utils && \
apt-get clean && \ apt-get clean && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
# RUN mkdir /opt/app
# COPY japp.jar /opt
# CMD ["java", "-jar", "/opt/app/japp.jar"]

View File

@ -1,7 +1,6 @@
package net.hostsharing.hsadminng.config; package net.hostsharing.hsadminng.config;
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.openapitools.jackson.nullable.JsonNullableModule; import org.openapitools.jackson.nullable.JsonNullableModule;
@ -10,20 +9,15 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
@Configuration @Configuration
public class JsonObjectMapperConfiguration { public class JsonObjectMapperConfiguration {
@Bean @Bean
@Primary @Primary
public Jackson2ObjectMapperBuilder customObjectMapper() { public Jackson2ObjectMapperBuilder customObjectMapper() {
// HOWTO: add JSON converters and specify other JSON mapping configurations
return new Jackson2ObjectMapperBuilder() return new Jackson2ObjectMapperBuilder()
.modules(new JsonNullableModule(), new JavaTimeModule()) .modules(new JsonNullableModule(), new JavaTimeModule())
.featuresToEnable( .featuresToEnable(JsonParser.Feature.ALLOW_COMMENTS, JsonParser.Feature.ALLOW_COMMENTS)
JsonParser.Feature.ALLOW_COMMENTS,
DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS
)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
} }
} }

View File

@ -46,7 +46,6 @@ public class CustomErrorResponse {
this.path = path; this.path = path;
this.statusCode = status.value(); this.statusCode = status.value();
this.statusPhrase = status.getReasonPhrase(); this.statusPhrase = status.getReasonPhrase();
// HOWTO: debug serverside error response - set a breakpoint here
this.message = message.startsWith("ERROR: [") ? message : "ERROR: [" + statusCode + "] " + message; this.message = message.startsWith("ERROR: [") ? message : "ERROR: [" + statusCode + "] " + message;
} }
} }

View File

@ -5,8 +5,8 @@ import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import net.hostsharing.hsadminng.errors.DisplayAs; import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.repr.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
@ -14,7 +14,7 @@ import jakarta.persistence.Id;
import jakarta.persistence.Table; import jakarta.persistence.Table;
import java.util.UUID; import java.util.UUID;
import static net.hostsharing.hsadminng.repr.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
// a partial HsOfficeDebitorEntity to reduce the number of SQL queries to load the entity // a partial HsOfficeDebitorEntity to reduce the number of SQL queries to load the entity
@Entity @Entity

View File

@ -15,8 +15,8 @@ import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectRealEntity;
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider; import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper; import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import net.hostsharing.hsadminng.persistence.BaseEntity; import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.repr.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.Type; import org.hibernate.annotations.Type;
import jakarta.persistence.CascadeType; import jakarta.persistence.CascadeType;
@ -45,7 +45,7 @@ import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.lowerInclusiveFromPostgresDateRange; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.lowerInclusiveFromPostgresDateRange;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.upperInclusiveFromPostgresDateRange; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.upperInclusiveFromPostgresDateRange;
import static net.hostsharing.hsadminng.repr.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@MappedSuperclass @MappedSuperclass
@Getter @Getter

View File

@ -4,14 +4,14 @@ import lombok.*;
import lombok.experimental.SuperBuilder; import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity; import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity; import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.repr.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.util.UUID; import java.util.UUID;
import static java.util.Optional.ofNullable; import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.repr.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@MappedSuperclass @MappedSuperclass
@Getter @Getter

View File

@ -15,8 +15,8 @@ import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider; import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper; import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import net.hostsharing.hsadminng.persistence.BaseEntity; import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.repr.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.Type; import org.hibernate.annotations.Type;
import jakarta.persistence.CascadeType; import jakarta.persistence.CascadeType;
@ -42,7 +42,7 @@ import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
import static net.hostsharing.hsadminng.repr.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@MappedSuperclass @MappedSuperclass
@Getter @Getter

View File

@ -5,8 +5,8 @@ import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.errors.DisplayAs; import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.persistence.BaseEntity; import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView; import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.repr.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.io.IOException; import java.io.IOException;
@ -16,7 +16,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*; import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR; import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*; import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
import static net.hostsharing.hsadminng.repr.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity @Entity
@Table(schema = "hs_office", name = "bankaccount_rv") @Table(schema = "hs_office", name = "bankaccount_rv")

View File

@ -12,8 +12,8 @@ import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.errors.DisplayAs; import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper; import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
import net.hostsharing.hsadminng.persistence.BaseEntity; import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.repr.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Type; import org.hibernate.annotations.Type;
@ -27,7 +27,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static net.hostsharing.hsadminng.repr.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@MappedSuperclass @MappedSuperclass
@Getter @Getter
@ -54,14 +54,8 @@ public class HsOfficeContact implements Stringifyable, BaseEntity<HsOfficeContac
@Column(name = "caption") @Column(name = "caption")
private String caption; private String caption;
@Builder.Default
@Setter(AccessLevel.NONE)
@Type(JsonType.class)
@Column(name = "postaladdress") @Column(name = "postaladdress")
private Map<String, String> postalAddress = new HashMap<>(); private String postalAddress; // multiline free-format text
@Transient
private PatchableMapWrapper<String> postalAddressWrapper;
@Builder.Default @Builder.Default
@Setter(AccessLevel.NONE) @Setter(AccessLevel.NONE)
@ -81,17 +75,6 @@ public class HsOfficeContact implements Stringifyable, BaseEntity<HsOfficeContac
@Transient @Transient
private PatchableMapWrapper<String> phoneNumbersWrapper; private PatchableMapWrapper<String> phoneNumbersWrapper;
public PatchableMapWrapper<String> getPostalAddress() {
return PatchableMapWrapper.of(
postalAddressWrapper,
(newWrapper) -> {postalAddressWrapper = newWrapper;},
postalAddress);
}
public void putPostalAddress(Map<String, String> newPostalAddress) {
getPostalAddress().assign(newPostalAddress);
}
public PatchableMapWrapper<String> getEmailAddresses() { public PatchableMapWrapper<String> getEmailAddresses() {
return PatchableMapWrapper.of( return PatchableMapWrapper.of(
emailAddressesWrapper, emailAddressesWrapper,

View File

@ -18,8 +18,7 @@ class HsOfficeContactEntityPatcher implements EntityPatcher<HsOfficeContactPatch
@Override @Override
public void apply(final HsOfficeContactPatchResource resource) { public void apply(final HsOfficeContactPatchResource resource) {
OptionalFromJson.of(resource.getCaption()).ifPresent(entity::setCaption); OptionalFromJson.of(resource.getCaption()).ifPresent(entity::setCaption);
Optional.ofNullable(resource.getPostalAddress()) OptionalFromJson.of(resource.getPostalAddress()).ifPresent(entity::setPostalAddress);
.ifPresent(r -> entity.getPostalAddress().patch(KeyValueMap.from(resource.getPostalAddress())));
Optional.ofNullable(resource.getEmailAddresses()) Optional.ofNullable(resource.getEmailAddresses())
.ifPresent(r -> entity.getEmailAddresses().patch(KeyValueMap.from(resource.getEmailAddresses()))); .ifPresent(r -> entity.getEmailAddresses().patch(KeyValueMap.from(resource.getEmailAddresses())));
Optional.ofNullable(resource.getPhoneNumbers()) Optional.ofNullable(resource.getPhoneNumbers())

View File

@ -36,7 +36,7 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public ResponseEntity<List<HsOfficeCoopAssetsTransactionResource>> getListOfCoopAssets( public ResponseEntity<List<HsOfficeCoopAssetsTransactionResource>> listCoopAssets(
final String currentSubject, final String currentSubject,
final String assumedRoles, final String assumedRoles,
final UUID membershipUuid, final UUID membershipUuid,
@ -55,7 +55,7 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
@Override @Override
@Transactional @Transactional
public ResponseEntity<HsOfficeCoopAssetsTransactionResource> postNewCoopAssetTransaction( public ResponseEntity<HsOfficeCoopAssetsTransactionResource> addCoopAssetsTransaction(
final String currentSubject, final String currentSubject,
final String assumedRoles, final String assumedRoles,
final HsOfficeCoopAssetsTransactionInsertResource requestBody) { final HsOfficeCoopAssetsTransactionInsertResource requestBody) {
@ -77,7 +77,8 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public ResponseEntity<HsOfficeCoopAssetsTransactionResource> getSingleCoopAssetTransactionByUuid(
public ResponseEntity<HsOfficeCoopAssetsTransactionResource> getCoopAssetTransactionByUuid(
final String currentSubject, final String assumedRoles, final UUID assetTransactionUuid) { final String currentSubject, final String assumedRoles, final UUID assetTransactionUuid) {
context.define(currentSubject, assumedRoles); context.define(currentSubject, assumedRoles);
@ -128,9 +129,9 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
} }
final BiConsumer<HsOfficeCoopAssetsTransactionInsertResource, HsOfficeCoopAssetsTransactionEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { final BiConsumer<HsOfficeCoopAssetsTransactionInsertResource, HsOfficeCoopAssetsTransactionEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
if ( resource.getRevertedAssetTxUuid() != null ) { if ( resource.getReverseEntryUuid() != null ) {
entity.setRevertedAssetTx(coopAssetsTransactionRepo.findByUuid(resource.getRevertedAssetTxUuid()) entity.setAdjustedAssetTx(coopAssetsTransactionRepo.findByUuid(resource.getReverseEntryUuid())
.orElseThrow(() -> new EntityNotFoundException("ERROR: [400] reverseEntityUuid %s not found".formatted(resource.getRevertedAssetTxUuid())))); .orElseThrow(() -> new EntityNotFoundException("ERROR: [400] reverseEntityUuid %s not found".formatted(resource.getReverseEntryUuid()))));
} }
}; };
}; };

View File

@ -10,8 +10,8 @@ import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity; import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity; import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView; import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.repr.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.GenericGenerator;
import jakarta.persistence.*; import jakarta.persistence.*;
@ -31,7 +31,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.ADMIN;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.AGENT; import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.AGENT;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn; import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
import static net.hostsharing.hsadminng.repr.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity @Entity
@Table(schema = "hs_office", name = "coopassettx_rv") @Table(schema = "hs_office", name = "coopassettx_rv")
@ -50,8 +50,8 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, BaseE
.withProp(HsOfficeCoopAssetsTransactionEntity::getAssetValue) .withProp(HsOfficeCoopAssetsTransactionEntity::getAssetValue)
.withProp(HsOfficeCoopAssetsTransactionEntity::getReference) .withProp(HsOfficeCoopAssetsTransactionEntity::getReference)
.withProp(HsOfficeCoopAssetsTransactionEntity::getComment) .withProp(HsOfficeCoopAssetsTransactionEntity::getComment)
.withProp(at -> ofNullable(at.getRevertedAssetTx()).map(HsOfficeCoopAssetsTransactionEntity::toShortString).orElse(null)) .withProp(at -> ofNullable(at.getAdjustedAssetTx()).map(HsOfficeCoopAssetsTransactionEntity::toShortString).orElse(null))
.withProp(at -> ofNullable(at.getReversalAssetTx()).map(HsOfficeCoopAssetsTransactionEntity::toShortString).orElse(null)) .withProp(at -> ofNullable(at.getAdjustmentAssetTx()).map(HsOfficeCoopAssetsTransactionEntity::toShortString).orElse(null))
.quotedValues(false); .quotedValues(false);
@Id @Id
@ -77,7 +77,7 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, BaseE
* The signed value which directly affects the booking balance. * The signed value which directly affects the booking balance.
* *
* <p>This means, that a DEPOSIT is always positive, a DISBURSAL is always negative, * <p>This means, that a DEPOSIT is always positive, a DISBURSAL is always negative,
* but an REVERSAL can bei either positive or negative. * but an ADJUSTMENT can bei either positive or negative.
* See {@link HsOfficeCoopAssetsTransactionType} for</p> more information. * See {@link HsOfficeCoopAssetsTransactionType} for</p> more information.
*/ */
@Column(name = "assetvalue") @Column(name = "assetvalue")
@ -96,14 +96,14 @@ public class HsOfficeCoopAssetsTransactionEntity implements Stringifyable, BaseE
private String comment; private String comment;
/** /**
* Optionally, the UUID of the corresponding transaction for an reversal transaction. * Optionally, the UUID of the corresponding transaction for an adjustment transaction.
*/ */
@OneToOne @OneToOne
@JoinColumn(name = "revertedassettxuuid") @JoinColumn(name = "adjustedassettxuuid")
private HsOfficeCoopAssetsTransactionEntity revertedAssetTx; private HsOfficeCoopAssetsTransactionEntity adjustedAssetTx;
@OneToOne(mappedBy = "revertedAssetTx") @OneToOne(mappedBy = "adjustedAssetTx")
private HsOfficeCoopAssetsTransactionEntity reversalAssetTx; private HsOfficeCoopAssetsTransactionEntity adjustmentAssetTx;
@Override @Override
public HsOfficeCoopAssetsTransactionEntity load() { public HsOfficeCoopAssetsTransactionEntity load() {

View File

@ -4,7 +4,7 @@ public enum HsOfficeCoopAssetsTransactionType {
/** /**
* correction of wrong bookings, value can be positive or negative * correction of wrong bookings, value can be positive or negative
*/ */
REVERSAL, ADJUSTMENT,
/** /**
* payment received from member after signing shares, value >0 * payment received from member after signing shares, value >0

View File

@ -38,7 +38,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public ResponseEntity<List<HsOfficeCoopSharesTransactionResource>> getListOfCoopShares( public ResponseEntity<List<HsOfficeCoopSharesTransactionResource>> listCoopShares(
final String currentSubject, final String currentSubject,
final String assumedRoles, final String assumedRoles,
final UUID membershipUuid, final UUID membershipUuid,
@ -57,7 +57,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
@Override @Override
@Transactional @Transactional
public ResponseEntity<HsOfficeCoopSharesTransactionResource> postNewCoopSharesTransaction( public ResponseEntity<HsOfficeCoopSharesTransactionResource> addCoopSharesTransaction(
final String currentSubject, final String currentSubject,
final String assumedRoles, final String assumedRoles,
final HsOfficeCoopSharesTransactionInsertResource requestBody) { final HsOfficeCoopSharesTransactionInsertResource requestBody) {
@ -80,7 +80,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public ResponseEntity<HsOfficeCoopSharesTransactionResource> getSingleCoopShareTransactionByUuid( public ResponseEntity<HsOfficeCoopSharesTransactionResource> getCoopShareTransactionByUuid(
final String currentSubject, final String assumedRoles, final UUID shareTransactionUuid) { final String currentSubject, final String assumedRoles, final UUID shareTransactionUuid) {
context.define(currentSubject, assumedRoles); context.define(currentSubject, assumedRoles);
@ -131,9 +131,9 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
} }
final BiConsumer<HsOfficeCoopSharesTransactionInsertResource, HsOfficeCoopSharesTransactionEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { final BiConsumer<HsOfficeCoopSharesTransactionInsertResource, HsOfficeCoopSharesTransactionEntity> RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {
if ( resource.getRevertedShareTxUuid() != null ) { if ( resource.getAdjustedShareTxUuid() != null ) {
entity.setRevertedShareTx(coopSharesTransactionRepo.findByUuid(resource.getRevertedShareTxUuid()) entity.setAdjustedShareTx(coopSharesTransactionRepo.findByUuid(resource.getAdjustedShareTxUuid())
.orElseThrow(() -> new EntityNotFoundException("ERROR: [400] revertedShareTxUuid %s not found".formatted(resource.getRevertedShareTxUuid())))); .orElseThrow(() -> new EntityNotFoundException("ERROR: [400] adjustedShareTxUuid %s not found".formatted(resource.getAdjustedShareTxUuid()))));
} }
}; };
} }

View File

@ -10,8 +10,8 @@ import net.hostsharing.hsadminng.hs.office.membership.HsOfficeMembershipEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView; import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.persistence.BaseEntity; import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL; import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
import net.hostsharing.hsadminng.repr.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.io.IOException; import java.io.IOException;
@ -29,7 +29,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.ADMIN;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.AGENT; import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.AGENT;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn; import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
import static net.hostsharing.hsadminng.repr.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity @Entity
@Table(schema = "hs_office", name = "coopsharetx_rv") @Table(schema = "hs_office", name = "coopsharetx_rv")
@ -48,8 +48,8 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, BaseE
.withProp(HsOfficeCoopSharesTransactionEntity::getShareCount) .withProp(HsOfficeCoopSharesTransactionEntity::getShareCount)
.withProp(HsOfficeCoopSharesTransactionEntity::getReference) .withProp(HsOfficeCoopSharesTransactionEntity::getReference)
.withProp(HsOfficeCoopSharesTransactionEntity::getComment) .withProp(HsOfficeCoopSharesTransactionEntity::getComment)
.withProp(at -> ofNullable(at.getRevertedShareTx()).map(HsOfficeCoopSharesTransactionEntity::toShortString).orElse(null)) .withProp(at -> ofNullable(at.getAdjustedShareTx()).map(HsOfficeCoopSharesTransactionEntity::toShortString).orElse(null))
.withProp(at -> ofNullable(at.getReversalShareTx()).map(HsOfficeCoopSharesTransactionEntity::toShortString).orElse(null)) .withProp(at -> ofNullable(at.getAdjustmentShareTx()).map(HsOfficeCoopSharesTransactionEntity::toShortString).orElse(null))
.quotedValues(false); .quotedValues(false);
@Id @Id
@ -71,7 +71,7 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, BaseE
* The signed value which directly affects the booking balance. * The signed value which directly affects the booking balance.
* *
* <p>This means, that a SUBSCRIPTION is always positive, a CANCELLATION is always negative, * <p>This means, that a SUBSCRIPTION is always positive, a CANCELLATION is always negative,
* but an REVERSAL can bei either positive or negative. * but an ADJUSTMENT can bei either positive or negative.
* See {@link HsOfficeCoopSharesTransactionType} for</p> more information. * See {@link HsOfficeCoopSharesTransactionType} for</p> more information.
*/ */
@Column(name = "valuedate") @Column(name = "valuedate")
@ -93,14 +93,14 @@ public class HsOfficeCoopSharesTransactionEntity implements Stringifyable, BaseE
private String comment; private String comment;
/** /**
* Optionally, the UUID of the corresponding transaction for a REVERSAL transaction. * Optionally, the UUID of the corresponding transaction for an adjustment transaction.
*/ */
@OneToOne @OneToOne
@JoinColumn(name = "revertedsharetxuuid") @JoinColumn(name = "adjustedsharetxuuid")
private HsOfficeCoopSharesTransactionEntity revertedShareTx; private HsOfficeCoopSharesTransactionEntity adjustedShareTx;
@OneToOne(mappedBy = "revertedShareTx") @OneToOne(mappedBy = "adjustedShareTx")
private HsOfficeCoopSharesTransactionEntity reversalShareTx; private HsOfficeCoopSharesTransactionEntity adjustmentShareTx;
@Override @Override
public HsOfficeCoopSharesTransactionEntity load() { public HsOfficeCoopSharesTransactionEntity load() {

View File

@ -2,9 +2,9 @@ package net.hostsharing.hsadminng.hs.office.coopshares;
public enum HsOfficeCoopSharesTransactionType { public enum HsOfficeCoopSharesTransactionType {
/** /**
* reversal of wrong bookings, with either positive or negative value identical to reversed transaction * correction of wrong bookings, with either positive or negative value
*/ */
REVERSAL, ADJUSTMENT,
/** /**
* shares signed, e.g. with the declaration of accession, value >0 * shares signed, e.g. with the declaration of accession, value >0

View File

@ -22,10 +22,8 @@ import jakarta.persistence.PersistenceContext;
import jakarta.validation.ValidationException; import jakarta.validation.ValidationException;
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.hs.office.relation.HsOfficeRelationType.DEBITOR; import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.DEBITOR;
import static net.hostsharing.hsadminng.repr.TaggedNumber.cropTag;
@RestController @RestController
@ -51,24 +49,24 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public ResponseEntity<List<HsOfficeDebitorResource>> getListOfDebitors( public ResponseEntity<List<HsOfficeDebitorResource>> listDebitors(
final String currentSubject, final String currentSubject,
final String assumedRoles, final String assumedRoles,
final String name, final String name,
final String debitorNumber) { final Integer debitorNumber) {
context.define(currentSubject, assumedRoles); context.define(currentSubject, assumedRoles);
final var entities = debitorNumber != null final var entities = debitorNumber != null
? debitorRepo.findDebitorByDebitorNumber(cropTag("D-", debitorNumber)) ? debitorRepo.findDebitorByDebitorNumber(debitorNumber)
: debitorRepo.findDebitorByOptionalNameLike(name); : debitorRepo.findDebitorByOptionalNameLike(name);
final var resources = mapper.mapList(entities, HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER); final var resources = mapper.mapList(entities, HsOfficeDebitorResource.class);
return ResponseEntity.ok(resources); return ResponseEntity.ok(resources);
} }
@Override @Override
@Transactional @Transactional
public ResponseEntity<HsOfficeDebitorResource> postNewDebitor( public ResponseEntity<HsOfficeDebitorResource> addDebitor(
String currentSubject, String currentSubject,
String assumedRoles, String assumedRoles,
HsOfficeDebitorInsertResource body) { HsOfficeDebitorInsertResource body) {
@ -79,13 +77,16 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
"ERROR: [400] exactly one of debitorRel and debitorRelUuid must be supplied, but found both"); "ERROR: [400] exactly one of debitorRel and debitorRelUuid must be supplied, but found both");
Validate.isTrue(body.getDebitorRel() != null || body.getDebitorRelUuid() != null, Validate.isTrue(body.getDebitorRel() != null || body.getDebitorRelUuid() != null,
"ERROR: [400] exactly one of debitorRel and debitorRelUuid must be supplied, but found none"); "ERROR: [400] exactly one of debitorRel and debitorRelUuid must be supplied, but found none");
Validate.isTrue(body.getDebitorRel() == null ||
body.getDebitorRel().getType() == null || DEBITOR.name().equals(body.getDebitorRel().getType()),
"ERROR: [400] debitorRel.type must be '"+DEBITOR.name()+"' or null for default");
Validate.isTrue(body.getDebitorRel() == null || body.getDebitorRel().getMark() == null, Validate.isTrue(body.getDebitorRel() == null || body.getDebitorRel().getMark() == null,
"ERROR: [400] debitorRel.mark must be null"); "ERROR: [400] debitorRel.mark must be null");
final var entityToSave = mapper.map(body, HsOfficeDebitorEntity.class); final var entityToSave = mapper.map(body, HsOfficeDebitorEntity.class);
if (body.getDebitorRel() != null) { if ( body.getDebitorRel() != null ) {
body.getDebitorRel().setType(DEBITOR.name());
final var debitorRel = mapper.map("debitorRel.", body.getDebitorRel(), HsOfficeRelationRealEntity.class); final var debitorRel = mapper.map("debitorRel.", body.getDebitorRel(), HsOfficeRelationRealEntity.class);
debitorRel.setType(DEBITOR);
entityValidator.validateEntityExists("debitorRel.anchorUuid", debitorRel.getAnchor()); entityValidator.validateEntityExists("debitorRel.anchorUuid", debitorRel.getAnchor());
entityValidator.validateEntityExists("debitorRel.holderUuid", debitorRel.getHolder()); entityValidator.validateEntityExists("debitorRel.holderUuid", debitorRel.getHolder());
entityValidator.validateEntityExists("debitorRel.contactUuid", debitorRel.getContact()); entityValidator.validateEntityExists("debitorRel.contactUuid", debitorRel.getContact());
@ -94,10 +95,7 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
final var debitorRelOptional = relrealRepo.findByUuid(body.getDebitorRelUuid()); final var debitorRelOptional = relrealRepo.findByUuid(body.getDebitorRelUuid());
debitorRelOptional.ifPresentOrElse( debitorRelOptional.ifPresentOrElse(
debitorRel -> {entityToSave.setDebitorRel(relrealRepo.save(debitorRel));}, debitorRel -> {entityToSave.setDebitorRel(relrealRepo.save(debitorRel));},
() -> { () -> { throw new ValidationException("Unable to find RealRelation by debitorRelUuid: " + body.getDebitorRelUuid());});
throw new ValidationException(
"Unable to find RealRelation by debitorRelUuid: " + body.getDebitorRelUuid());
});
} }
final var savedEntity = debitorRepo.save(entityToSave); final var savedEntity = debitorRepo.save(entityToSave);
@ -109,13 +107,13 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
.path("/api/hs/office/debitors/{id}") .path("/api/hs/office/debitors/{id}")
.buildAndExpand(savedEntity.getUuid()) .buildAndExpand(savedEntity.getUuid())
.toUri(); .toUri();
final var mapped = mapper.map(savedEntity, HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER); final var mapped = mapper.map(savedEntity, HsOfficeDebitorResource.class);
return ResponseEntity.created(uri).body(mapped); return ResponseEntity.created(uri).body(mapped);
} }
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public ResponseEntity<HsOfficeDebitorResource> getSingleDebitorByUuid( public ResponseEntity<HsOfficeDebitorResource> getDebitorByUuid(
final String currentSubject, final String currentSubject,
final String assumedRoles, final String assumedRoles,
final UUID debitorUuid) { final UUID debitorUuid) {
@ -126,7 +124,7 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
if (result.isEmpty()) { if (result.isEmpty()) {
return ResponseEntity.notFound().build(); return ResponseEntity.notFound().build();
} }
return ResponseEntity.ok(mapper.map(result.get(), HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER)); return ResponseEntity.ok(mapper.map(result.get(), HsOfficeDebitorResource.class));
} }
@Override @Override
@ -161,11 +159,7 @@ public class HsOfficeDebitorController implements HsOfficeDebitorsApi {
final var saved = debitorRepo.save(current); final var saved = debitorRepo.save(current);
Hibernate.initialize(saved); Hibernate.initialize(saved);
final var mapped = mapper.map(saved, HsOfficeDebitorResource.class, ENTITY_TO_RESOURCE_POSTMAPPER); final var mapped = mapper.map(saved, HsOfficeDebitorResource.class);
return ResponseEntity.ok(mapped); return ResponseEntity.ok(mapped);
} }
final BiConsumer<HsOfficeDebitorEntity, HsOfficeDebitorResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
resource.setDebitorNumber(entity.getTaggedDebitorNumber());
};
} }

View File

@ -14,8 +14,8 @@ import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity; import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView; import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL; import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
import net.hostsharing.hsadminng.repr.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.GenericGenerator; import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.JoinFormula; import org.hibernate.annotations.JoinFormula;
import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFound;
@ -51,7 +51,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn; import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.fetchedBySql; import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.fetchedBySql;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
import static net.hostsharing.hsadminng.repr.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity @Entity
@Table(schema = "hs_office", name = "debitor_rv") @Table(schema = "hs_office", name = "debitor_rv")
@ -142,14 +142,19 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
return this; return this;
} }
public String getTaggedDebitorNumber() { private String getDebitorNumberString() {
return ofNullable(partner) return ofNullable(partner)
.filter(partner -> debitorNumberSuffix != null) .filter(partner -> debitorNumberSuffix != null)
.map(HsOfficePartnerEntity::getPartnerNumber) .map(HsOfficePartnerEntity::getPartnerNumber)
.map(partnerNumber -> DEBITOR_NUMBER_TAG + partnerNumber + debitorNumberSuffix) .map(Object::toString)
.map(partnerNumber -> partnerNumber + debitorNumberSuffix)
.orElse(null); .orElse(null);
} }
public Integer getDebitorNumber() {
return ofNullable(getDebitorNumberString()).map(Integer::parseInt).orElse(null);
}
@Override @Override
public String toString() { public String toString() {
return stringify.apply(this); return stringify.apply(this);
@ -157,7 +162,7 @@ public class HsOfficeDebitorEntity implements BaseEntity<HsOfficeDebitorEntity>,
@Override @Override
public String toShortString() { public String toShortString() {
return getTaggedDebitorNumber(); return DEBITOR_NUMBER_TAG + getDebitorNumberString();
} }
public static RbacView rbac() { public static RbacView rbac() {

View File

@ -16,16 +16,13 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
JOIN HsOfficePartnerEntity partner JOIN HsOfficePartnerEntity partner
ON partner.partnerRel.holder = debitor.debitorRel.anchor ON partner.partnerRel.holder = debitor.debitorRel.anchor
AND partner.partnerRel.type = 'PARTNER' AND debitor.debitorRel.type = 'DEBITOR' AND partner.partnerRel.type = 'PARTNER' AND debitor.debitorRel.type = 'DEBITOR'
WHERE partner.partnerNumber = :partnerNumber WHERE cast(partner.partnerNumber as integer) = :partnerNumber
AND debitor.debitorNumberSuffix = :debitorNumberSuffix AND cast(debitor.debitorNumberSuffix as integer) = :debitorNumberSuffix
""") """)
List<HsOfficeDebitorEntity> findDebitorByDebitorNumber(int partnerNumber, String debitorNumberSuffix); List<HsOfficeDebitorEntity> findDebitorByDebitorNumber(int partnerNumber, byte debitorNumberSuffix);
default List<HsOfficeDebitorEntity> findDebitorByDebitorNumber(int debitorNumber) { default List<HsOfficeDebitorEntity> findDebitorByDebitorNumber(int debitorNumber) {
final var partnerNumber = debitorNumber / 100; return findDebitorByDebitorNumber( debitorNumber/100, (byte) (debitorNumber%100));
final String suffix = String.format("%02d", debitorNumber % 100);
final var result = findDebitorByDebitorNumber(partnerNumber, suffix);
return result;
} }
@Query(""" @Query("""

View File

@ -16,10 +16,8 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.repr.TaggedNumber.cropTag;
@RestController @RestController
public class HsOfficeMembershipController implements HsOfficeMembershipsApi { public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
@Autowired @Autowired
@ -33,18 +31,16 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public ResponseEntity<List<HsOfficeMembershipResource>> getListOfMemberships( public ResponseEntity<List<HsOfficeMembershipResource>> listMemberships(
final String currentSubject, final String currentSubject,
final String assumedRoles, final String assumedRoles,
final UUID partnerUuid, UUID partnerUuid,
final String memberNumber) { Integer memberNumber) {
context.define(currentSubject, assumedRoles); context.define(currentSubject, assumedRoles);
final var entities = (memberNumber != null) final var entities = ( memberNumber != null)
? ofNullable(membershipRepo.findMembershipByMemberNumber( ? List.of(membershipRepo.findMembershipByMemberNumber(memberNumber))
cropTag(HsOfficeMembershipEntity.MEMBER_NUMBER_TAG, memberNumber))).stream() : membershipRepo.findMembershipsByOptionalPartnerUuid(partnerUuid);
.toList()
: membershipRepo.findMembershipsByOptionalPartnerUuid(partnerUuid);
final var resources = mapper.mapList(entities, HsOfficeMembershipResource.class, final var resources = mapper.mapList(entities, HsOfficeMembershipResource.class,
SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER); SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER);
@ -53,7 +49,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
@Override @Override
@Transactional @Transactional
public ResponseEntity<HsOfficeMembershipResource> postNewMembership( public ResponseEntity<HsOfficeMembershipResource> addMembership(
final String currentSubject, final String currentSubject,
final String assumedRoles, final String assumedRoles,
final HsOfficeMembershipInsertResource body) { final HsOfficeMembershipInsertResource body) {
@ -76,7 +72,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
@Override @Override
@Transactional(readOnly = true) @Transactional(readOnly = true)
public ResponseEntity<HsOfficeMembershipResource> getSingleMembershipByUuid( public ResponseEntity<HsOfficeMembershipResource> getMembershipByUuid(
final String currentSubject, final String currentSubject,
final String assumedRoles, final String assumedRoles,
final UUID membershipUuid) { final UUID membershipUuid) {
@ -127,7 +123,7 @@ public class HsOfficeMembershipController implements HsOfficeMembershipsApi {
} }
final BiConsumer<HsOfficeMembershipEntity, HsOfficeMembershipResource> SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> { final BiConsumer<HsOfficeMembershipEntity, HsOfficeMembershipResource> SEPA_MANDATE_ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource) -> {
resource.setMemberNumber(entity.getTaggedMemberNumber()); // TODO.refa: this should be possible via ModelMapper config
resource.setValidFrom(entity.getValidity().lower()); resource.setValidFrom(entity.getValidity().lower());
if (entity.getValidity().hasUpperBound()) { if (entity.getValidity().hasUpperBound()) {
resource.setValidTo(entity.getValidity().upper().minusDays(1)); resource.setValidTo(entity.getValidity().upper().minusDays(1));

View File

@ -13,8 +13,8 @@ import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity; import net.hostsharing.hsadminng.hs.office.partner.HsOfficePartnerEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView; import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL; import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
import net.hostsharing.hsadminng.repr.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.Type; import org.hibernate.annotations.Type;
import jakarta.persistence.Column; import jakarta.persistence.Column;
@ -53,7 +53,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.OWNER;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.TENANT; import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.TENANT;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.fetchedBySql; import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.fetchedBySql;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
import static net.hostsharing.hsadminng.repr.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity @Entity
@Table(schema = "hs_office", name = "membership_rv") @Table(schema = "hs_office", name = "membership_rv")
@ -130,7 +130,6 @@ public class HsOfficeMembershipEntity implements BaseEntity<HsOfficeMembershipEn
} }
return validity; return validity;
} }
public Integer getMemberNumber() { public Integer getMemberNumber() {
if (partner == null || partner.getPartnerNumber() == null || memberNumberSuffix == null ) { if (partner == null || partner.getPartnerNumber() == null || memberNumberSuffix == null ) {
return null; return null;
@ -139,10 +138,6 @@ public class HsOfficeMembershipEntity implements BaseEntity<HsOfficeMembershipEn
return getPartner().getPartnerNumber() * 100 + Integer.parseInt(memberNumberSuffix, 10); return getPartner().getPartnerNumber() * 100 + Integer.parseInt(memberNumberSuffix, 10);
} }
public String getTaggedMemberNumber() {
return MEMBER_NUMBER_TAG + getMemberNumber();
}
@Override @Override
public String toString() { public String toString() {
return stringify.apply(this); return stringify.apply(this);

View File

@ -14,16 +14,14 @@ public interface HsOfficeMembershipRepository extends Repository<HsOfficeMembers
HsOfficeMembershipEntity save(final HsOfficeMembershipEntity entity); HsOfficeMembershipEntity save(final HsOfficeMembershipEntity entity);
List<HsOfficeMembershipEntity> findAll();
@Query(""" @Query("""
SELECT membership FROM HsOfficeMembershipEntity membership SELECT membership FROM HsOfficeMembershipEntity membership
WHERE ( CAST(:partnerUuid as org.hibernate.type.UUIDCharType) IS NULL WHERE ( CAST(:partnerUuid as org.hibernate.type.UUIDCharType) IS NULL
OR membership.partner.uuid = :partnerUuid ) OR membership.partner.uuid = :partnerUuid )
ORDER BY membership.partner.partnerNumber, membership.memberNumberSuffix ORDER BY membership.partner.partnerNumber, membership.memberNumberSuffix
""") """)
List<HsOfficeMembershipEntity> findMembershipsByOptionalPartnerUuid(UUID partnerUuid); List<HsOfficeMembershipEntity> findMembershipsByOptionalPartnerUuid(UUID partnerUuid);
@Query(""" @Query("""
SELECT membership FROM HsOfficeMembershipEntity membership SELECT membership FROM HsOfficeMembershipEntity membership
WHERE (:partnerNumber = membership.partner.partnerNumber) WHERE (:partnerNumber = membership.partner.partnerNumber)
@ -33,12 +31,10 @@ public interface HsOfficeMembershipRepository extends Repository<HsOfficeMembers
HsOfficeMembershipEntity findMembershipByPartnerNumberAndSuffix( HsOfficeMembershipEntity findMembershipByPartnerNumberAndSuffix(
@NotNull Integer partnerNumber, @NotNull Integer partnerNumber,
@NotNull String suffix); @NotNull String suffix);
default HsOfficeMembershipEntity findMembershipByMemberNumber(Integer memberNumber) { default HsOfficeMembershipEntity findMembershipByMemberNumber(Integer memberNumber) {
final var partnerNumber = memberNumber / 100; final var partnerNumber = memberNumber / 100;
final String suffix = String.format("%02d", memberNumber % 100); final var suffix = memberNumber % 100;
final var result = findMembershipByPartnerNumberAndSuffix(partnerNumber, suffix); return findMembershipByPartnerNumberAndSuffix(partnerNumber, String.format("%02d", suffix));
return result;
} }
long count(); long count();

View File

@ -27,7 +27,6 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.EX_PARTNER; import static net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType.EX_PARTNER;
import static net.hostsharing.hsadminng.repr.TaggedNumber.cropTag;
@RestController @RestController
@ -144,14 +143,13 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
private void optionallyCreateExPartnerRelation(final HsOfficePartnerEntity saved, final HsOfficeRelationRealEntity previousPartnerRel) { private void optionallyCreateExPartnerRelation(final HsOfficePartnerEntity saved, final HsOfficeRelationRealEntity previousPartnerRel) {
if (!saved.getPartnerRel().getUuid().equals(previousPartnerRel.getUuid())) { if (!saved.getPartnerRel().getUuid().equals(previousPartnerRel.getUuid())) {
// TODO.impl: we also need to use the new partner-person as the anchor
relationRepo.save(previousPartnerRel.toBuilder().uuid(null).type(EX_PARTNER).build()); relationRepo.save(previousPartnerRel.toBuilder().uuid(null).type(EX_PARTNER).build());
} }
} }
private HsOfficePartnerEntity createPartnerEntity(final HsOfficePartnerInsertResource body) { private HsOfficePartnerEntity createPartnerEntity(final HsOfficePartnerInsertResource body) {
final var entityToSave = new HsOfficePartnerEntity(); final var entityToSave = new HsOfficePartnerEntity();
entityToSave.setPartnerNumber(cropTag(HsOfficePartnerEntity.PARTNER_NUMBER_TAG, body.getPartnerNumber())); entityToSave.setPartnerNumber(body.getPartnerNumber());
entityToSave.setPartnerRel(persistPartnerRel(body.getPartnerRel())); entityToSave.setPartnerRel(persistPartnerRel(body.getPartnerRel()));
entityToSave.setDetails(mapper.map(body.getDetails(), HsOfficePartnerDetailsEntity.class)); entityToSave.setDetails(mapper.map(body.getDetails(), HsOfficePartnerDetailsEntity.class));
return entityToSave; return entityToSave;

View File

@ -5,8 +5,8 @@ import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.persistence.BaseEntity; import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView; import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL; import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
import net.hostsharing.hsadminng.repr.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.io.IOException; import java.io.IOException;
@ -17,7 +17,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*; import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*; import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
import static net.hostsharing.hsadminng.repr.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity @Entity
@Table(schema = "hs_office", name = "partner_details_rv") @Table(schema = "hs_office", name = "partner_details_rv")

View File

@ -14,8 +14,8 @@ import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelation; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelation;
import net.hostsharing.hsadminng.rbac.generator.RbacView; import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL; import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
import net.hostsharing.hsadminng.repr.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.NotFoundAction;
@ -33,7 +33,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn; import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.directlyFetchedByDependsOnColumn;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
import static java.util.Optional.ofNullable; import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.repr.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity @Entity
@Table(schema = "hs_office", name = "partner_rv") @Table(schema = "hs_office", name = "partner_rv")

View File

@ -6,8 +6,8 @@ import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.persistence.BaseEntity; import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView; import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL; import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
import net.hostsharing.hsadminng.repr.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import jakarta.persistence.*; import jakarta.persistence.*;
@ -19,7 +19,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR; import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*; import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
import static net.hostsharing.hsadminng.repr.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
// TODO.refa: split HsOfficePersonEntity into Real+Rbac-Entity // TODO.refa: split HsOfficePersonEntity into Real+Rbac-Entity
@Entity @Entity

View File

@ -4,7 +4,6 @@ import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter; import jakarta.persistence.Converter;
import java.util.stream.Stream; import java.util.stream.Stream;
// HOWTO: convert data types for exchange between PostgreSQL and Java/Hibernate/JPA-Entities
@Converter(autoApply = true) @Converter(autoApply = true)
public class HsOfficePersonTypeConverter implements AttributeConverter<HsOfficePersonType, String> { public class HsOfficePersonTypeConverter implements AttributeConverter<HsOfficePersonType, String> {

View File

@ -6,14 +6,14 @@ import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity; import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity; import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.repr.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import java.util.UUID; import java.util.UUID;
import static net.hostsharing.hsadminng.repr.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@MappedSuperclass @MappedSuperclass
@NoArgsConstructor(access = AccessLevel.PROTECTED) @NoArgsConstructor(access = AccessLevel.PROTECTED)

View File

@ -47,7 +47,7 @@ public interface HsOfficeRelationRbacRepository extends Repository<HsOfficeRelat
OR lower(rel.anchor.givenName) LIKE :personData OR lower(rel.holder.givenName) LIKE :personData ) OR lower(rel.anchor.givenName) LIKE :personData OR lower(rel.holder.givenName) LIKE :personData )
AND ( :contactData IS NULL AND ( :contactData IS NULL
OR lower(rel.contact.caption) LIKE :contactData OR lower(rel.contact.caption) LIKE :contactData
OR lower(CAST(rel.contact.postalAddress AS String)) LIKE :contactData OR lower(rel.contact.postalAddress) LIKE :contactData
OR lower(CAST(rel.contact.emailAddresses AS String)) LIKE :contactData OR lower(CAST(rel.contact.emailAddresses AS String)) LIKE :contactData
OR lower(CAST(rel.contact.phoneNumbers AS String)) LIKE :contactData ) OR lower(CAST(rel.contact.phoneNumbers AS String)) LIKE :contactData )
""") """)

View File

@ -8,6 +8,5 @@ public enum HsOfficeRelationType {
VIP_CONTACT, VIP_CONTACT,
DEBITOR, DEBITOR,
OPERATIONS, OPERATIONS,
OPERATIONS_ALERT,
SUBSCRIBER SUBSCRIBER
} }

View File

@ -131,7 +131,7 @@ public class HsOfficeSepaMandateController implements HsOfficeSepaMandatesApi {
if (entity.getValidity().hasUpperBound()) { if (entity.getValidity().hasUpperBound()) {
resource.setValidTo(entity.getValidity().upper().minusDays(1)); resource.setValidTo(entity.getValidity().upper().minusDays(1));
} }
resource.getDebitor().setDebitorNumber(entity.getDebitor().getTaggedDebitorNumber()); resource.getDebitor().setDebitorNumber(entity.getDebitor().getDebitorNumber());
}; };
final BiConsumer<HsOfficeSepaMandateInsertResource, HsOfficeSepaMandateEntity> SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> { final BiConsumer<HsOfficeSepaMandateInsertResource, HsOfficeSepaMandateEntity> SEPA_MANDATE_RESOURCE_TO_ENTITY_POSTMAPPER = (resource, entity) -> {

View File

@ -9,8 +9,8 @@ import net.hostsharing.hsadminng.hs.office.debitor.HsOfficeDebitorEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity; import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity; import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView; import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.repr.Stringify; import net.hostsharing.hsadminng.stringify.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable; import net.hostsharing.hsadminng.stringify.Stringifyable;
import org.hibernate.annotations.Type; import org.hibernate.annotations.Type;
import jakarta.persistence.*; import jakarta.persistence.*;
@ -30,7 +30,7 @@ import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectRefer
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*; import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.*; import static net.hostsharing.hsadminng.rbac.generator.RbacView.SQL.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor; import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
import static net.hostsharing.hsadminng.repr.Stringify.stringify; import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
@Entity @Entity
@Table(schema = "hs_office", name = "sepamandate_rv") @Table(schema = "hs_office", name = "sepamandate_rv")

View File

@ -1,15 +0,0 @@
package net.hostsharing.hsadminng.repr;
import lombok.experimental.UtilityClass;
@UtilityClass
public class TaggedNumber {
public static Integer cropTag(final String tag, final String taggedNumber) {
return taggedNumber.startsWith(tag) ? Integer.valueOf(taggedNumber.substring(tag.length())) : invalidTag(tag, taggedNumber);
}
private static Integer invalidTag(final String tag, final String taggedNumber) {
throw new IllegalArgumentException("Expected " + tag + "... but got: " + taggedNumber);
}
}

View File

@ -1,4 +1,4 @@
package net.hostsharing.hsadminng.repr; package net.hostsharing.hsadminng.stringify;
import net.hostsharing.hsadminng.errors.DisplayAs; import net.hostsharing.hsadminng.errors.DisplayAs;

View File

@ -1,4 +1,4 @@
package net.hostsharing.hsadminng.repr; package net.hostsharing.hsadminng.stringify;
public interface Stringifyable { public interface Stringifyable {

View File

@ -14,7 +14,6 @@ public class SystemProcess {
@Getter @Getter
private String stdOut; private String stdOut;
@Getter @Getter
private String stdErr; private String stdErr;
@ -22,6 +21,7 @@ public class SystemProcess {
this.processBuilder = new ProcessBuilder(command); this.processBuilder = new ProcessBuilder(command);
} }
public String getCommand() { public String getCommand() {
return processBuilder.command().toString(); return processBuilder.command().toString();
} }

View File

@ -52,11 +52,11 @@ components:
HsBookingItemInsert: HsBookingItemInsert:
type: object type: object
properties: properties:
project.uuid: projectUuid:
type: string type: string
format: uuid format: uuid
nullable: false nullable: false
parentItem.uuid: parentItemUuid:
type: string type: string
format: uuid format: uuid
nullable: false nullable: false
@ -77,7 +77,7 @@ components:
$ref: '../hs-hosting/hs-hosting-asset-schemas.yaml#/components/schemas/HsHostingAssetAutoInsert' $ref: '../hs-hosting/hs-hosting-asset-schemas.yaml#/components/schemas/HsHostingAssetAutoInsert'
required: required:
- caption - caption
- project.uuid - projectUuid
- validFrom - validFrom
- resources - resources
additionalProperties: false additionalProperties: false

View File

@ -25,7 +25,7 @@ components:
HsBookingProjectInsert: HsBookingProjectInsert:
type: object type: object
properties: properties:
debitor.uuid: debitorUuid:
type: string type: string
format: uuid format: uuid
nullable: false nullable: false
@ -35,6 +35,6 @@ components:
maxLength: 80 maxLength: 80
nullable: false nullable: false
required: required:
- debitor.uuid - debitorUuid
- caption - caption
additionalProperties: false additionalProperties: false

View File

@ -54,7 +54,7 @@ components:
caption: caption:
type: string type: string
nullable: true nullable: true
alarmContact.uuid: alarmContactUuid:
type: string type: string
format: uuid format: uuid
nullable: true nullable: true
@ -64,11 +64,11 @@ components:
HsHostingAssetInsert: HsHostingAssetInsert:
type: object type: object
properties: properties:
bookingItem.uuid: bookingItemUuid:
type: string type: string
format: uuid format: uuid
nullable: true nullable: true
parentAsset.uuid: parentAssetUuid:
type: string type: string
format: uuid format: uuid
nullable: true nullable: true
@ -84,7 +84,7 @@ components:
minLength: 3 minLength: 3
maxLength: 80 maxLength: 80
nullable: false nullable: false
alarmContact.uuid: alarmContactUuid:
type: string type: string
format: uuid format: uuid
nullable: true nullable: true
@ -99,11 +99,11 @@ components:
HsHostingAssetAutoInsert: HsHostingAssetAutoInsert:
type: object type: object
properties: properties:
parentAsset.uuid: parentAssetUuid:
type: string type: string
format: uuid format: uuid
nullable: true nullable: true
assignedToAsset.uuid: assignedToAssetUuid:
type: string type: string
format: uuid format: uuid
type: type:
@ -118,7 +118,7 @@ components:
minLength: 3 minLength: 3
maxLength: 80 maxLength: 80
nullable: false nullable: false
alarmContact.uuid: alarmContactUuid:
type: string type: string
format: uuid format: uuid
nullable: true nullable: true
@ -147,10 +147,10 @@ components:
minLength: 3 minLength: 3
maxLength: 80 maxLength: 80
nullable: false nullable: false
assignedToAsset.uuid: assignedToAssetUuid:
type: string type: string
format: uuid format: uuid
alarmContact.uuid: alarmContactUuid:
type: string type: string
format: uuid format: uuid
nullable: true nullable: true

View File

@ -12,7 +12,7 @@ components:
caption: caption:
type: string type: string
postalAddress: postalAddress:
$ref: '#/components/schemas/HsOfficeContactPostalAddress' type: string
emailAddresses: emailAddresses:
$ref: '#/components/schemas/HsOfficeContactEmailAddresses' $ref: '#/components/schemas/HsOfficeContactEmailAddresses'
phoneNumbers: phoneNumbers:
@ -24,7 +24,7 @@ components:
caption: caption:
type: string type: string
postalAddress: postalAddress:
$ref: '#/components/schemas/HsOfficeContactPostalAddress' type: string
emailAddresses: emailAddresses:
$ref: '#/components/schemas/HsOfficeContactEmailAddresses' $ref: '#/components/schemas/HsOfficeContactEmailAddresses'
phoneNumbers: phoneNumbers:
@ -39,48 +39,21 @@ components:
type: string type: string
nullable: true nullable: true
postalAddress: postalAddress:
$ref: '#/components/schemas/HsOfficeContactPostalAddress' type: string
nullable: true
emailAddresses: emailAddresses:
$ref: '#/components/schemas/HsOfficeContactEmailAddresses' $ref: '#/components/schemas/HsOfficeContactEmailAddresses'
phoneNumbers: phoneNumbers:
$ref: '#/components/schemas/HsOfficeContactPhoneNumbers' $ref: '#/components/schemas/HsOfficeContactPhoneNumbers'
HsOfficeContactPostalAddress:
# forces generating a java.lang.Object containing a Map, instead of a class with fixed properties
anyOf:
- type: object
properties:
firm:
type: string
nullable: true
name:
type: string
nullable: true
co:
type: string
nullable: true
street:
type: string
nullable: true
zipcode:
type: string
nullable: true
city:
type: string
nullable: true
country:
type: string
nullable: true
additionalProperties: true
HsOfficeContactEmailAddresses: HsOfficeContactEmailAddresses:
# forces generating a java.lang.Object containing a Map, instead of a class with fixed properties # forces generating a java.lang.Object containing a Map, instead of class HsOfficeContactEmailAddresses
anyOf: anyOf:
- type: object - type: object
additionalProperties: true additionalProperties: true
HsOfficeContactPhoneNumbers: HsOfficeContactPhoneNumbers:
# forces generating a java.lang.Object containing a Map, instead of a class with fixed properties # forces generating a java.lang.Object containing a Map, instead of class HsOfficeContactEmailAddresses
anyOf: anyOf:
- type: object - type: object
properties: properties:

View File

@ -6,7 +6,7 @@ components:
HsOfficeCoopAssetsTransactionType: HsOfficeCoopAssetsTransactionType:
type: string type: string
enum: enum:
- REVERSAL - ADJUSTMENT
- DEPOSIT - DEPOSIT
- DISBURSAL - DISBURSAL
- TRANSFER - TRANSFER
@ -32,15 +32,15 @@ components:
type: string type: string
comment: comment:
type: string type: string
revertedAssetTx: adjustedAssetTx:
$ref: '#/components/schemas/HsOfficeReferencedCoopAssetsTransaction' $ref: '#/components/schemas/HsOfficeReferencedCoopAssetsTransaction'
reversalAssetTx: adjustmentAssetTx:
$ref: '#/components/schemas/HsOfficeReferencedCoopAssetsTransaction' $ref: '#/components/schemas/HsOfficeReferencedCoopAssetsTransaction'
HsOfficeReferencedCoopAssetsTransaction: HsOfficeReferencedCoopAssetsTransaction:
description: description:
Similar to `HsOfficeCoopAssetsTransaction` but without the self-referencing properties Similar to `HsOfficeCoopAssetsTransaction` but without the self-referencing properties
(`revertedAssetTx` and `reversalAssetTx`), to avoid recursive JSON. (`adjustedAssetTx` and `adjustmentAssetTx`), to avoid recursive JSON.
type: object type: object
properties: properties:
uuid: uuid:
@ -62,7 +62,7 @@ components:
HsOfficeCoopAssetsTransactionInsert: HsOfficeCoopAssetsTransactionInsert:
type: object type: object
properties: properties:
membership.uuid: membershipUuid:
type: string type: string
format: uuid format: uuid
nullable: false nullable: false
@ -80,11 +80,11 @@ components:
maxLength: 48 maxLength: 48
comment: comment:
type: string type: string
revertedAssetTx.uuid: reverseEntryUuid:
type: string type: string
format: uuid format: uuid
required: required:
- membership.uuid - membershipUuid
- transactionType - transactionType
- assetValue - assetValue
- valueDate - valueDate

View File

@ -2,7 +2,7 @@ get:
tags: tags:
- hs-office-coopAssets - hs-office-coopAssets
description: 'Fetch a single asset transaction by its uuid, if visible for the current subject.' description: 'Fetch a single asset transaction by its uuid, if visible for the current subject.'
operationId: getSingleCoopAssetTransactionByUuid operationId: getCoopAssetTransactionByUuid
parameters: parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject' - $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'

View File

@ -3,7 +3,7 @@ get:
description: Returns the list of (optionally filtered) cooperative asset transactions which are visible to the current subject or any of it's assumed roles. description: Returns the list of (optionally filtered) cooperative asset transactions which are visible to the current subject or any of it's assumed roles.
tags: tags:
- hs-office-coopAssets - hs-office-coopAssets
operationId: getListOfCoopAssets operationId: listCoopAssets
parameters: parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject' - $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'
@ -46,7 +46,7 @@ post:
summary: Adds a new cooperative asset transaction. summary: Adds a new cooperative asset transaction.
tags: tags:
- hs-office-coopAssets - hs-office-coopAssets
operationId: postNewCoopAssetTransaction operationId: addCoopAssetsTransaction
parameters: parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject' - $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'

View File

@ -6,7 +6,7 @@ components:
HsOfficeCoopSharesTransactionType: HsOfficeCoopSharesTransactionType:
type: string type: string
enum: enum:
- REVERSAL - ADJUSTMENT
- SUBSCRIPTION - SUBSCRIPTION
- CANCELLATION - CANCELLATION
@ -27,15 +27,15 @@ components:
type: string type: string
comment: comment:
type: string type: string
revertedShareTx: adjustedShareTx:
$ref: '#/components/schemas/HsOfficeReferencedCoopSharesTransaction' $ref: '#/components/schemas/HsOfficeReferencedCoopSharesTransaction'
reversalShareTx: adjustmentShareTx:
$ref: '#/components/schemas/HsOfficeReferencedCoopSharesTransaction' $ref: '#/components/schemas/HsOfficeReferencedCoopSharesTransaction'
HsOfficeReferencedCoopSharesTransaction: HsOfficeReferencedCoopSharesTransaction:
description: description:
Similar to `HsOfficeCoopSharesTransaction` but without the self-referencing properties Similar to `HsOfficeCoopSharesTransaction` but without the self-referencing properties
(`revertedShareTx` and `reversalShareTx`), to avoid recursive JSON. (`adjustedShareTx` and `adjustmentShareTx`), to avoid recursive JSON.
type: object type: object
properties: properties:
uuid: uuid:
@ -56,7 +56,7 @@ components:
HsOfficeCoopSharesTransactionInsert: HsOfficeCoopSharesTransactionInsert:
type: object type: object
properties: properties:
membership.uuid: membershipUuid:
type: string type: string
format: uuid format: uuid
nullable: false nullable: false
@ -73,11 +73,11 @@ components:
maxLength: 48 maxLength: 48
comment: comment:
type: string type: string
revertedShareTx.uuid: adjustedShareTxUuid:
type: string type: string
format: uuid format: uuid
required: required:
- membership.uuid - membershipUuid
- transactionType - transactionType
- shareCount - shareCount
- valueDate - valueDate

View File

@ -2,7 +2,7 @@ get:
tags: tags:
- hs-office-coopShares - hs-office-coopShares
description: 'Fetch a single share transaction by its uuid, if visible for the current subject.' description: 'Fetch a single share transaction by its uuid, if visible for the current subject.'
operationId: getSingleCoopShareTransactionByUuid operationId: getCoopShareTransactionByUuid
parameters: parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject' - $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'

View File

@ -3,7 +3,7 @@ get:
description: Returns the list of (optionally filtered) cooperative share transactions which are visible to the current subject or any of it's assumed roles. description: Returns the list of (optionally filtered) cooperative share transactions which are visible to the current subject or any of it's assumed roles.
tags: tags:
- hs-office-coopShares - hs-office-coopShares
operationId: getListOfCoopShares operationId: listCoopShares
parameters: parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject' - $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'
@ -46,7 +46,7 @@ post:
summary: Adds a new cooperative share transaction. summary: Adds a new cooperative share transaction.
tags: tags:
- hs-office-coopShares - hs-office-coopShares
operationId: postNewCoopSharesTransaction operationId: addCoopSharesTransaction
parameters: parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject' - $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'

View File

@ -12,13 +12,15 @@ components:
debitorRel: debitorRel:
$ref: 'hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelation' $ref: 'hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelation'
debitorNumber: debitorNumber:
type: string type: integer
minLength: 9 format: int32
maxLength: 9 minimum: 1000000
pattern: 'D-[0-9]{7}' maximum: 9999999
debitorNumberSuffix: debitorNumberSuffix:
type: string type: integer
pattern: '^[0-9][0-9]$' format: int8
minimum: 00
maximum: 99
partner: partner:
$ref: 'hs-office-partner-schemas.yaml#/components/schemas/HsOfficePartner' $ref: 'hs-office-partner-schemas.yaml#/components/schemas/HsOfficePartner'
billable: billable:
@ -41,7 +43,7 @@ components:
HsOfficeDebitorPatch: HsOfficeDebitorPatch:
type: object type: object
properties: properties:
debitorRel.uuid: debitorRelUuid:
type: string type: string
format: uuid format: uuid
nullable: true nullable: true
@ -61,7 +63,7 @@ components:
vatReverseCharge: vatReverseCharge:
type: boolean type: boolean
nullable: false nullable: false
refundBankAccount.uuid: refundBankAccountUuid:
type: string type: string
format: uuid format: uuid
nullable: true nullable: true
@ -74,13 +76,15 @@ components:
type: object type: object
properties: properties:
debitorRel: debitorRel:
$ref: 'hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelationSubInsert' $ref: 'hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelationInsert'
debitorRel.uuid: debitorRelUuid:
type: string type: string
format: uuid format: uuid
debitorNumberSuffix: debitorNumberSuffix:
type: string type: integer
pattern: '^[0-9][0-9]$' format: int8
minimum: 00
maximum: 99
billable: billable:
type: boolean type: boolean
vatId: vatId:
@ -92,7 +96,7 @@ components:
type: boolean type: boolean
vatReverseCharge: vatReverseCharge:
type: boolean type: boolean
refundBankAccount.uuid: refundBankAccountUuid:
type: string type: string
format: uuid format: uuid
defaultPrefix: defaultPrefix:

View File

@ -2,7 +2,7 @@ get:
tags: tags:
- hs-office-debitors - hs-office-debitors
description: 'Fetch a single debitor by its uuid, if visible for the current subject.' description: 'Fetch a single debitor by its uuid, if visible for the current subject.'
operationId: getSingleDebitorByUuid operationId: getDebitorByUuid
parameters: parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject' - $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'

View File

@ -3,7 +3,7 @@ get:
description: Returns the list of (optionally filtered) debitors which are visible to the current subject or any of it's assumed roles. description: Returns the list of (optionally filtered) debitors which are visible to the current subject or any of it's assumed roles.
tags: tags:
- hs-office-debitors - hs-office-debitors
operationId: getListOfDebitors operationId: listDebitors
parameters: parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject' - $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'
@ -17,10 +17,7 @@ get:
in: query in: query
required: false required: false
schema: schema:
type: string type: integer
minLength: 9
maxLength: 9
pattern: 'D-[0-9]{7}'
description: Debitor number of the requested debitor. description: Debitor number of the requested debitor.
responses: responses:
"200": "200":
@ -40,7 +37,7 @@ post:
summary: Adds a new debitor. summary: Adds a new debitor.
tags: tags:
- hs-office-debitors - hs-office-debitors
operationId: postNewDebitor operationId: addDebitor
parameters: parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject' - $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'

View File

@ -26,10 +26,9 @@ components:
mainDebitor: mainDebitor:
$ref: 'hs-office-debitor-schemas.yaml#/components/schemas/HsOfficeDebitor' $ref: 'hs-office-debitor-schemas.yaml#/components/schemas/HsOfficeDebitor'
memberNumber: memberNumber:
type: string type: integer
minLength: 9 minimum: 1000000
maxLength: 9 maximum: 9999999
pattern: 'M-[0-9]{7}'
memberNumberSuffix: memberNumberSuffix:
type: string type: string
minLength: 2 minLength: 2
@ -63,7 +62,7 @@ components:
HsOfficeMembershipInsert: HsOfficeMembershipInsert:
type: object type: object
properties: properties:
partner.uuid: partnerUuid:
type: string type: string
format: uuid format: uuid
nullable: false nullable: false
@ -87,7 +86,7 @@ components:
nullable: false nullable: false
type: boolean type: boolean
required: required:
- partner.uuid - partnerUuid
- memberNumberSuffix - memberNumberSuffix
- validFrom - validFrom
- membershipFeeBillable - membershipFeeBillable

View File

@ -2,7 +2,7 @@ get:
tags: tags:
- hs-office-memberships - hs-office-memberships
description: 'Fetch a single membership by its uuid, if visible for the current subject.' description: 'Fetch a single membership by its uuid, if visible for the current subject.'
operationId: getSingleMembershipByUuid operationId: getMembershipByUuid
parameters: parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject' - $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'

View File

@ -4,7 +4,7 @@ get:
The list can optionally be filtered by either the `partnerUuid` or the `memberNumber` - not both at the same time. The list can optionally be filtered by either the `partnerUuid` or the `memberNumber` - not both at the same time.
tags: tags:
- hs-office-memberships - hs-office-memberships
operationId: getListOfMemberships operationId: listMemberships
parameters: parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject' - $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'
@ -19,10 +19,7 @@ get:
in: query in: query
required: false required: false
schema: schema:
type: string type: integer
minLength: 9
maxLength: 9
pattern: 'M-[0-9]{7}'
description: Member number, exclusive to `partnerUuid`. description: Member number, exclusive to `partnerUuid`.
responses: responses:
"200": "200":
@ -42,7 +39,7 @@ post:
summary: Adds a new membership. summary: Adds a new membership.
tags: tags:
- hs-office-memberships - hs-office-memberships
operationId: postNewMembership operationId: addMembership
parameters: parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject' - $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'

View File

@ -10,10 +10,10 @@ components:
type: string type: string
format: uuid format: uuid
partnerNumber: partnerNumber:
type: string type: integer
minLength: 7 format: int8
maxLength: 7 minimum: 10000
pattern: 'P-[0-9]{5}' maximum: 99999
partnerRel: partnerRel:
$ref: 'hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelation' $ref: 'hs-office-relation-schemas.yaml#/components/schemas/HsOfficeRelation'
details: details:
@ -50,7 +50,7 @@ components:
HsOfficePartnerPatch: HsOfficePartnerPatch:
type: object type: object
properties: properties:
partnerRel.uuid: partnerRelUuid:
type: string type: string
format: uuid format: uuid
nullable: true nullable: true
@ -86,10 +86,10 @@ components:
type: object type: object
properties: properties:
partnerNumber: partnerNumber:
type: string type: integer
minLength: 7 format: int8
maxLength: 7 minimum: 10000
pattern: 'P-[0-9]{5}' maximum: 99999
partnerRel: partnerRel:
$ref: '#/components/schemas/HsOfficePartnerRelInsert' $ref: '#/components/schemas/HsOfficePartnerRelInsert'
details: details:
@ -103,19 +103,19 @@ components:
type: object type: object
nullable: false nullable: false
properties: properties:
anchor.uuid: anchorUuid:
type: string type: string
format: uuid format: uuid
holder.uuid: holderUuid:
type: string type: string
format: uuid format: uuid
contact.uuid: contactUuid:
type: string type: string
format: uuid format: uuid
required: required:
- anchor.uuid - anchorUuid
- holder.uuid - holderUuid
- relContact.uuid - relContactUuid
HsOfficePartnerDetailsInsert: HsOfficePartnerDetailsInsert:
type: object type: object

View File

@ -13,7 +13,6 @@ components:
- REPRESENTATIVE - REPRESENTATIVE
- VIP_CONTACT - VIP_CONTACT
- OPERATIONS - OPERATIONS
- OPERATIONS_ALERT
- SUBSCRIBER - SUBSCRIBER
HsOfficeRelation: HsOfficeRelation:
@ -37,19 +36,18 @@ components:
HsOfficeRelationPatch: HsOfficeRelationPatch:
type: object type: object
properties: properties:
contact.uuid: contactUuid:
type: string type: string
format: uuid format: uuid
nullable: true nullable: true
# arbitrary relation with explicit type
HsOfficeRelationInsert: HsOfficeRelationInsert:
type: object type: object
properties: properties:
anchor.uuid: anchorUuid:
type: string type: string
format: uuid format: uuid
holder.uuid: holderUuid:
type: string type: string
format: uuid format: uuid
type: type:
@ -58,32 +56,11 @@ components:
mark: mark:
type: string type: string
nullable: true nullable: true
contact.uuid: contactUuid:
type: string type: string
format: uuid format: uuid
required: required:
- anchor.uuid - anchorUuid
- holder.uuid - holderUuid
- type - type
- contact.uuid - contactUuid
# relation created as a sub-element with implicitly known type
HsOfficeRelationSubInsert:
type: object
properties:
anchor.uuid:
type: string
format: uuid
holder.uuid:
type: string
format: uuid
mark:
type: string
nullable: true
contact.uuid:
type: string
format: uuid
required:
- anchor.uuid
- holder.uuid
- contact.uuid

View File

@ -48,11 +48,11 @@ components:
HsOfficeSepaMandateInsert: HsOfficeSepaMandateInsert:
type: object type: object
properties: properties:
debitor.uuid: debitorUuid:
type: string type: string
format: uuid format: uuid
nullable: false nullable: false
bankAccount.uuid: bankAccountUuid:
type: string type: string
format: uuid format: uuid
nullable: false nullable: false
@ -72,8 +72,8 @@ components:
format: date format: date
nullable: true nullable: true
required: required:
- debitor.uuid - debitorUuid
- bankAccount.uuid - bankAccountUuid
- reference - reference
- agreement - agreement
- validFrom - validFrom

View File

@ -7,7 +7,7 @@ get:
parameters: parameters:
- $ref: 'auth.yaml#/components/parameters/currentSubject' - $ref: 'auth.yaml#/components/parameters/currentSubject'
- $ref: 'auth.yaml#/components/parameters/assumedRoles' - $ref: 'auth.yaml#/components/parameters/assumedRoles'
- name: iban - name: name
in: query in: query
required: false required: false
schema: schema:

View File

@ -8,21 +8,21 @@ components:
properties: properties:
grantedByRoleIdName: grantedByRoleIdName:
type: string type: string
grantedByRole.uuid: grantedByRoleUuid:
type: string type: string
format: uuid format: uuid
assumed: assumed:
type: boolean type: boolean
grantedRoleIdName: grantedRoleIdName:
type: string type: string
grantedRole.uuid: grantedRoleUuid:
type: string type: string
format: uuid format: uuid
granteeSubjectName: granteeSubjectName:
type: string type: string
granteeSubject.uuid: granteeSubjectUuid:
type: string type: string
format: uuid format: uuid
required: required:
- grantedRole.uuid - grantedRoleUuid
- granteeSubject.uuid - granteeSubjectUuid

View File

@ -9,7 +9,7 @@ components:
uuid: uuid:
type: string type: string
format: uuid format: uuid
object.uuid: objectUuid:
type: string type: string
format: uuid format: uuid
objectTable: objectTable:

View File

@ -14,7 +14,7 @@ components:
RbacSubjectPermission: RbacSubjectPermission:
type: object type: object
properties: properties:
object.uuid: objectUuid:
type: string type: string
format: uuid format: uuid
objectTable: objectTable:
@ -23,10 +23,10 @@ components:
type: string type: string
roleName: roleName:
type: string type: string
role.uuid: roleUuid:
type: string type: string
format: uuid format: uuid
permission.uuid: permissionUuid:
type: string type: string
format: uuid format: uuid
op: op:

View File

@ -36,7 +36,7 @@ begin
call rbac.enterTriggerForObjectUuid(NEW.uuid); call rbac.enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM rbactest.customer WHERE uuid = NEW.customerUuid INTO newCustomer; SELECT * FROM rbactest.customer WHERE uuid = NEW.customerUuid INTO newCustomer;
assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s of package', NEW.customerUuid); assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s', NEW.customerUuid);
perform rbac.defineRoleWithGrants( perform rbac.defineRoleWithGrants(
@ -102,10 +102,10 @@ begin
call rbac.enterTriggerForObjectUuid(NEW.uuid); call rbac.enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM rbactest.customer WHERE uuid = OLD.customerUuid INTO oldCustomer; SELECT * FROM rbactest.customer WHERE uuid = OLD.customerUuid INTO oldCustomer;
assert oldCustomer.uuid is not null, format('oldCustomer must not be null for OLD.customerUuid = %s of package', OLD.customerUuid); assert oldCustomer.uuid is not null, format('oldCustomer must not be null for OLD.customerUuid = %s', OLD.customerUuid);
SELECT * FROM rbactest.customer WHERE uuid = NEW.customerUuid INTO newCustomer; SELECT * FROM rbactest.customer WHERE uuid = NEW.customerUuid INTO newCustomer;
assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s of package', NEW.customerUuid); assert newCustomer.uuid is not null, format('newCustomer must not be null for NEW.customerUuid = %s', NEW.customerUuid);
if NEW.customerUuid <> OLD.customerUuid then if NEW.customerUuid <> OLD.customerUuid then

View File

@ -36,7 +36,7 @@ begin
call rbac.enterTriggerForObjectUuid(NEW.uuid); call rbac.enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM rbactest.package WHERE uuid = NEW.packageUuid INTO newPackage; SELECT * FROM rbactest.package WHERE uuid = NEW.packageUuid INTO newPackage;
assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s of domain', NEW.packageUuid); assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s', NEW.packageUuid);
perform rbac.defineRoleWithGrants( perform rbac.defineRoleWithGrants(
@ -98,10 +98,10 @@ begin
call rbac.enterTriggerForObjectUuid(NEW.uuid); call rbac.enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM rbactest.package WHERE uuid = OLD.packageUuid INTO oldPackage; SELECT * FROM rbactest.package WHERE uuid = OLD.packageUuid INTO oldPackage;
assert oldPackage.uuid is not null, format('oldPackage must not be null for OLD.packageUuid = %s of domain', OLD.packageUuid); assert oldPackage.uuid is not null, format('oldPackage must not be null for OLD.packageUuid = %s', OLD.packageUuid);
SELECT * FROM rbactest.package WHERE uuid = NEW.packageUuid INTO newPackage; SELECT * FROM rbactest.package WHERE uuid = NEW.packageUuid INTO newPackage;
assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s of domain', NEW.packageUuid); assert newPackage.uuid is not null, format('newPackage must not be null for NEW.packageUuid = %s', NEW.packageUuid);
if NEW.packageUuid <> OLD.packageUuid then if NEW.packageUuid <> OLD.packageUuid then

View File

@ -9,7 +9,7 @@ create table if not exists hs_office.contact
uuid uuid unique references rbac.object (uuid) initially deferred, uuid uuid unique references rbac.object (uuid) initially deferred,
version int not null default 0, version int not null default 0,
caption varchar(128) not null, caption varchar(128) not null,
postalAddress jsonb not null, postalAddress text,
emailAddresses jsonb not null, emailAddresses jsonb not null,
phoneNumbers jsonb not null phoneNumbers jsonb not null
); );

View File

@ -11,6 +11,7 @@
create or replace procedure hs_office.contact_create_test_data(contCaption varchar) create or replace procedure hs_office.contact_create_test_data(contCaption varchar)
language plpgsql as $$ language plpgsql as $$
declare declare
postalAddr varchar;
emailAddr varchar; emailAddr varchar;
begin begin
emailAddr = 'contact-admin@' || base.cleanIdentifier(contCaption) || '.example.com'; emailAddr = 'contact-admin@' || base.cleanIdentifier(contCaption) || '.example.com';
@ -18,18 +19,14 @@ begin
perform rbac.create_subject(emailAddr); perform rbac.create_subject(emailAddr);
call base.defineContext('creating contact test-data', null, emailAddr); call base.defineContext('creating contact test-data', null, emailAddr);
postalAddr := E'Vorname Nachname\nStraße Hnr\nPLZ Stadt';
raise notice 'creating test contact: %', contCaption; raise notice 'creating test contact: %', contCaption;
insert insert
into hs_office.contact (caption, postaladdress, emailaddresses, phonenumbers) into hs_office.contact (caption, postaladdress, emailaddresses, phonenumbers)
values ( values (
contCaption, contCaption,
( '{ ' || postalAddr,
-- '"name": "' || contCaption || '",' ||
-- '"street": "Somewhere 1",' ||
-- '"zipcode": "12345",' ||
-- '"city": "Where-Ever",' ||
'"country": "Germany"' ||
'}')::jsonb,
('{ "main": "' || emailAddr || '" }')::jsonb, ('{ "main": "' || emailAddr || '" }')::jsonb,
('{ "phone_office": "+49 123 1234567" }')::jsonb ('{ "phone_office": "+49 123 1234567" }')::jsonb
); );

View File

@ -12,7 +12,6 @@ CREATE TYPE hs_office.RelationType AS ENUM (
'DEBITOR', 'DEBITOR',
'VIP_CONTACT', 'VIP_CONTACT',
'OPERATIONS', 'OPERATIONS',
'OPERATIONS_ALERT',
'SUBSCRIBER'); 'SUBSCRIBER');
CREATE CAST (character varying as hs_office.RelationType) WITH INOUT AS IMPLICIT; CREATE CAST (character varying as hs_office.RelationType) WITH INOUT AS IMPLICIT;
@ -29,9 +28,6 @@ create table if not exists hs_office.relation
); );
--// --//
-- TODO.impl: unique constraint, to prevent using the same person multiple times as a partner, or better:
-- ( anchorUuid, holderUuid, type)
-- ============================================================================ -- ============================================================================
--changeset michael.hoennig:hs-office-relation-MAIN-TABLE-JOURNAL endDelimiter:--// --changeset michael.hoennig:hs-office-relation-MAIN-TABLE-JOURNAL endDelimiter:--//

View File

@ -38,13 +38,13 @@ begin
call rbac.enterTriggerForObjectUuid(NEW.uuid); call rbac.enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM hs_office.person WHERE uuid = NEW.holderUuid INTO newHolderPerson; SELECT * FROM hs_office.person WHERE uuid = NEW.holderUuid INTO newHolderPerson;
assert newHolderPerson.uuid is not null, format('newHolderPerson must not be null for NEW.holderUuid = %s of relation', NEW.holderUuid); assert newHolderPerson.uuid is not null, format('newHolderPerson must not be null for NEW.holderUuid = %s', NEW.holderUuid);
SELECT * FROM hs_office.person WHERE uuid = NEW.anchorUuid INTO newAnchorPerson; SELECT * FROM hs_office.person WHERE uuid = NEW.anchorUuid INTO newAnchorPerson;
assert newAnchorPerson.uuid is not null, format('newAnchorPerson must not be null for NEW.anchorUuid = %s of relation', NEW.anchorUuid); assert newAnchorPerson.uuid is not null, format('newAnchorPerson must not be null for NEW.anchorUuid = %s', NEW.anchorUuid);
SELECT * FROM hs_office.contact WHERE uuid = NEW.contactUuid INTO newContact; SELECT * FROM hs_office.contact WHERE uuid = NEW.contactUuid INTO newContact;
assert newContact.uuid is not null, format('newContact must not be null for NEW.contactUuid = %s of relation', NEW.contactUuid); assert newContact.uuid is not null, format('newContact must not be null for NEW.contactUuid = %s', NEW.contactUuid);
perform rbac.defineRoleWithGrants( perform rbac.defineRoleWithGrants(

View File

@ -37,10 +37,10 @@ begin
call rbac.enterTriggerForObjectUuid(NEW.uuid); call rbac.enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM hs_office.relation WHERE uuid = NEW.partnerRelUuid INTO newPartnerRel; SELECT * FROM hs_office.relation WHERE uuid = NEW.partnerRelUuid INTO newPartnerRel;
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s of partner', NEW.partnerRelUuid); assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s', NEW.partnerRelUuid);
SELECT * FROM hs_office.partner_details WHERE uuid = NEW.detailsUuid INTO newPartnerDetails; SELECT * FROM hs_office.partner_details WHERE uuid = NEW.detailsUuid INTO newPartnerDetails;
assert newPartnerDetails.uuid is not null, format('newPartnerDetails must not be null for NEW.detailsUuid = %s of partner', NEW.detailsUuid); assert newPartnerDetails.uuid is not null, format('newPartnerDetails must not be null for NEW.detailsUuid = %s', NEW.detailsUuid);
call rbac.grantPermissionToRole(rbac.createPermission(NEW.uuid, 'DELETE'), hs_office.relation_OWNER(newPartnerRel)); call rbac.grantPermissionToRole(rbac.createPermission(NEW.uuid, 'DELETE'), hs_office.relation_OWNER(newPartnerRel));
call rbac.grantPermissionToRole(rbac.createPermission(NEW.uuid, 'SELECT'), hs_office.relation_TENANT(newPartnerRel)); call rbac.grantPermissionToRole(rbac.createPermission(NEW.uuid, 'SELECT'), hs_office.relation_TENANT(newPartnerRel));
@ -96,16 +96,16 @@ begin
call rbac.enterTriggerForObjectUuid(NEW.uuid); call rbac.enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM hs_office.relation WHERE uuid = OLD.partnerRelUuid INTO oldPartnerRel; SELECT * FROM hs_office.relation WHERE uuid = OLD.partnerRelUuid INTO oldPartnerRel;
assert oldPartnerRel.uuid is not null, format('oldPartnerRel must not be null for OLD.partnerRelUuid = %s of partner', OLD.partnerRelUuid); assert oldPartnerRel.uuid is not null, format('oldPartnerRel must not be null for OLD.partnerRelUuid = %s', OLD.partnerRelUuid);
SELECT * FROM hs_office.relation WHERE uuid = NEW.partnerRelUuid INTO newPartnerRel; SELECT * FROM hs_office.relation WHERE uuid = NEW.partnerRelUuid INTO newPartnerRel;
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s of partner', NEW.partnerRelUuid); assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerRelUuid = %s', NEW.partnerRelUuid);
SELECT * FROM hs_office.partner_details WHERE uuid = OLD.detailsUuid INTO oldPartnerDetails; SELECT * FROM hs_office.partner_details WHERE uuid = OLD.detailsUuid INTO oldPartnerDetails;
assert oldPartnerDetails.uuid is not null, format('oldPartnerDetails must not be null for OLD.detailsUuid = %s of partner', OLD.detailsUuid); assert oldPartnerDetails.uuid is not null, format('oldPartnerDetails must not be null for OLD.detailsUuid = %s', OLD.detailsUuid);
SELECT * FROM hs_office.partner_details WHERE uuid = NEW.detailsUuid INTO newPartnerDetails; SELECT * FROM hs_office.partner_details WHERE uuid = NEW.detailsUuid INTO newPartnerDetails;
assert newPartnerDetails.uuid is not null, format('newPartnerDetails must not be null for NEW.detailsUuid = %s of partner', NEW.detailsUuid); assert newPartnerDetails.uuid is not null, format('newPartnerDetails must not be null for NEW.detailsUuid = %s', NEW.detailsUuid);
if NEW.partnerRelUuid <> OLD.partnerRelUuid then if NEW.partnerRelUuid <> OLD.partnerRelUuid then

View File

@ -44,10 +44,10 @@ begin
WHERE partnerRel.type = 'PARTNER' WHERE partnerRel.type = 'PARTNER'
AND NEW.debitorRelUuid = debitorRel.uuid AND NEW.debitorRelUuid = debitorRel.uuid
INTO newPartnerRel; INTO newPartnerRel;
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.debitorRelUuid = %s of debitor', NEW.debitorRelUuid); assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.debitorRelUuid = %s', NEW.debitorRelUuid);
SELECT * FROM hs_office.relation WHERE uuid = NEW.debitorRelUuid INTO newDebitorRel; SELECT * FROM hs_office.relation WHERE uuid = NEW.debitorRelUuid INTO newDebitorRel;
assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorRelUuid = %s of debitor', NEW.debitorRelUuid); assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorRelUuid = %s', NEW.debitorRelUuid);
SELECT * FROM hs_office.bankaccount WHERE uuid = NEW.refundBankAccountUuid INTO newRefundBankAccount; SELECT * FROM hs_office.bankaccount WHERE uuid = NEW.refundBankAccountUuid INTO newRefundBankAccount;

View File

@ -37,14 +37,14 @@ begin
call rbac.enterTriggerForObjectUuid(NEW.uuid); call rbac.enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM hs_office.bankaccount WHERE uuid = NEW.bankAccountUuid INTO newBankAccount; SELECT * FROM hs_office.bankaccount WHERE uuid = NEW.bankAccountUuid INTO newBankAccount;
assert newBankAccount.uuid is not null, format('newBankAccount must not be null for NEW.bankAccountUuid = %s of sepamandate', NEW.bankAccountUuid); assert newBankAccount.uuid is not null, format('newBankAccount must not be null for NEW.bankAccountUuid = %s', NEW.bankAccountUuid);
SELECT debitorRel.* SELECT debitorRel.*
FROM hs_office.relation debitorRel FROM hs_office.relation debitorRel
JOIN hs_office.debitor debitor ON debitor.debitorRelUuid = debitorRel.uuid JOIN hs_office.debitor debitor ON debitor.debitorRelUuid = debitorRel.uuid
WHERE debitor.uuid = NEW.debitorUuid WHERE debitor.uuid = NEW.debitorUuid
INTO newDebitorRel; INTO newDebitorRel;
assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorUuid = %s of sepamandate', NEW.debitorUuid); assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorUuid = %s', NEW.debitorUuid);
perform rbac.defineRoleWithGrants( perform rbac.defineRoleWithGrants(

View File

@ -40,7 +40,7 @@ begin
JOIN hs_office.relation AS partnerRel ON partnerRel.uuid = partner.partnerRelUuid JOIN hs_office.relation AS partnerRel ON partnerRel.uuid = partner.partnerRelUuid
WHERE partner.uuid = NEW.partnerUuid WHERE partner.uuid = NEW.partnerUuid
INTO newPartnerRel; INTO newPartnerRel;
assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerUuid = %s of membership', NEW.partnerUuid); assert newPartnerRel.uuid is not null, format('newPartnerRel must not be null for NEW.partnerUuid = %s', NEW.partnerUuid);
perform rbac.defineRoleWithGrants( perform rbac.defineRoleWithGrants(

View File

@ -4,7 +4,7 @@
--changeset michael.hoennig:hs-office-coopshares-MAIN-TABLE endDelimiter:--// --changeset michael.hoennig:hs-office-coopshares-MAIN-TABLE endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
CREATE TYPE hs_office.CoopSharesTransactionType AS ENUM ('REVERSAL', 'SUBSCRIPTION', 'CANCELLATION'); CREATE TYPE hs_office.CoopSharesTransactionType AS ENUM ('ADJUSTMENT', 'SUBSCRIPTION', 'CANCELLATION');
CREATE CAST (character varying as hs_office.CoopSharesTransactionType) WITH INOUT AS IMPLICIT; CREATE CAST (character varying as hs_office.CoopSharesTransactionType) WITH INOUT AS IMPLICIT;
@ -17,7 +17,7 @@ create table if not exists hs_office.coopsharetx
valueDate date not null, valueDate date not null,
shareCount integer not null, shareCount integer not null,
reference varchar(48) not null, reference varchar(48) not null,
revertedShareTxUuid uuid unique REFERENCES hs_office.coopsharetx(uuid) DEFERRABLE INITIALLY DEFERRED, adjustedShareTxUuid uuid unique REFERENCES hs_office.coopsharetx(uuid) DEFERRABLE INITIALLY DEFERRED,
comment varchar(512) comment varchar(512)
); );
--// --//
@ -28,8 +28,8 @@ create table if not exists hs_office.coopsharetx
alter table hs_office.coopsharetx alter table hs_office.coopsharetx
add constraint reverse_entry_missing add constraint reverse_entry_missing
check ( transactionType = 'REVERSAL' and revertedShareTxUuid is not null check ( transactionType = 'ADJUSTMENT' and adjustedShareTxUuid is not null
or transactionType <> 'REVERSAL' and revertedShareTxUuid is null); or transactionType <> 'ADJUSTMENT' and adjustedShareTxUuid is null);
--// --//
-- ============================================================================ -- ============================================================================

View File

@ -36,7 +36,7 @@ begin
call rbac.enterTriggerForObjectUuid(NEW.uuid); call rbac.enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM hs_office.membership WHERE uuid = NEW.membershipUuid INTO newMembership; SELECT * FROM hs_office.membership WHERE uuid = NEW.membershipUuid INTO newMembership;
assert newMembership.uuid is not null, format('newMembership must not be null for NEW.membershipUuid = %s of coopshares', NEW.membershipUuid); assert newMembership.uuid is not null, format('newMembership must not be null for NEW.membershipUuid = %s', NEW.membershipUuid);
call rbac.grantPermissionToRole(rbac.createPermission(NEW.uuid, 'SELECT'), hs_office.membership_AGENT(newMembership)); call rbac.grantPermissionToRole(rbac.createPermission(NEW.uuid, 'SELECT'), hs_office.membership_AGENT(newMembership));
call rbac.grantPermissionToRole(rbac.createPermission(NEW.uuid, 'UPDATE'), hs_office.membership_ADMIN(newMembership)); call rbac.grantPermissionToRole(rbac.createPermission(NEW.uuid, 'UPDATE'), hs_office.membership_ADMIN(newMembership));

View File

@ -7,7 +7,7 @@
--changeset michael.hoennig:hs-office-coopshares-MIGRATION-mapping endDelimiter:--// --changeset michael.hoennig:hs-office-coopshares-MIGRATION-mapping endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
CREATE TABLE hs_office.coopsharetx_legacy_id CREATE TABLE hs_office.coopsharestransaction_legacy_id
( (
uuid uuid NOT NULL REFERENCES hs_office.coopsharetx(uuid), uuid uuid NOT NULL REFERENCES hs_office.coopsharetx(uuid),
member_share_id integer NOT NULL member_share_id integer NOT NULL
@ -19,10 +19,10 @@ CREATE TABLE hs_office.coopsharetx_legacy_id
--changeset michael.hoennig:hs-office-coopshares-MIGRATION-sequence endDelimiter:--// --changeset michael.hoennig:hs-office-coopshares-MIGRATION-sequence endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
CREATE SEQUENCE IF NOT EXISTS hs_office.coopsharetx_legacy_id_seq CREATE SEQUENCE IF NOT EXISTS hs_office.coopsharestransaction_legacy_id_seq
AS integer AS integer
START 1000000000 START 1000000000
OWNED BY hs_office.coopsharetx_legacy_id.member_share_id; OWNED BY hs_office.coopsharestransaction_legacy_id.member_share_id;
--// --//
@ -30,9 +30,9 @@ CREATE SEQUENCE IF NOT EXISTS hs_office.coopsharetx_legacy_id_seq
--changeset michael.hoennig:hs-office-coopshares-MIGRATION-default endDelimiter:--// --changeset michael.hoennig:hs-office-coopshares-MIGRATION-default endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
ALTER TABLE hs_office.coopsharetx_legacy_id ALTER TABLE hs_office.coopsharestransaction_legacy_id
ALTER COLUMN member_share_id ALTER COLUMN member_share_id
SET DEFAULT nextVal('hs_office.coopsharetx_legacy_id_seq'); SET DEFAULT nextVal('hs_office.coopsharestransaction_legacy_id_seq');
--/ --/
@ -41,8 +41,8 @@ ALTER TABLE hs_office.coopsharetx_legacy_id
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
CALL base.defineContext('schema-migration'); CALL base.defineContext('schema-migration');
INSERT INTO hs_office.coopsharetx_legacy_id(uuid, member_share_id) INSERT INTO hs_office.coopsharestransaction_legacy_id(uuid, member_share_id)
SELECT uuid, nextVal('hs_office.coopsharetx_legacy_id_seq') FROM hs_office.coopsharetx; SELECT uuid, nextVal('hs_office.coopsharestransaction_legacy_id_seq') FROM hs_office.coopsharetx;
--/ --/
@ -58,8 +58,8 @@ begin
raise exception 'invalid usage of trigger'; raise exception 'invalid usage of trigger';
end if; end if;
INSERT INTO hs_office.coopsharetx_legacy_id VALUES INSERT INTO hs_office.coopsharestransaction_legacy_id VALUES
(NEW.uuid, nextVal('hs_office.coopsharetx_legacy_id_seq')); (NEW.uuid, nextVal('hs_office.coopsharestransaction_legacy_id_seq'));
return NEW; return NEW;
end; $$; end; $$;
@ -83,7 +83,7 @@ begin
raise exception 'invalid usage of trigger'; raise exception 'invalid usage of trigger';
end if; end if;
DELETE FROM hs_office.coopsharetx_legacy_id DELETE FROM hs_office.coopsharestransaction_legacy_id
WHERE uuid = OLD.uuid; WHERE uuid = OLD.uuid;
return OLD; return OLD;

View File

@ -27,12 +27,12 @@ begin
raise notice 'creating test coopSharesTransaction: %', givenPartnerNumber::text || givenMemberNumberSuffix; raise notice 'creating test coopSharesTransaction: %', givenPartnerNumber::text || givenMemberNumberSuffix;
subscriptionEntryUuid := uuid_generate_v4(); subscriptionEntryUuid := uuid_generate_v4();
insert insert
into hs_office.coopsharetx(uuid, membershipuuid, transactiontype, valuedate, sharecount, reference, comment, revertedShareTxUuid) into hs_office.coopsharetx(uuid, membershipuuid, transactiontype, valuedate, sharecount, reference, comment, adjustedShareTxUuid)
values values
(uuid_generate_v4(), membership.uuid, 'SUBSCRIPTION', '2010-03-15', 4, 'ref '||givenPartnerNumber::text || givenMemberNumberSuffix||'-1', 'initial subscription', null), (uuid_generate_v4(), membership.uuid, 'SUBSCRIPTION', '2010-03-15', 4, 'ref '||givenPartnerNumber::text || givenMemberNumberSuffix||'-1', 'initial subscription', null),
(uuid_generate_v4(), membership.uuid, 'CANCELLATION', '2021-09-01', -2, 'ref '||givenPartnerNumber::text || givenMemberNumberSuffix||'-2', 'cancelling some', null), (uuid_generate_v4(), membership.uuid, 'CANCELLATION', '2021-09-01', -2, 'ref '||givenPartnerNumber::text || givenMemberNumberSuffix||'-2', 'cancelling some', null),
(subscriptionEntryUuid, membership.uuid, 'SUBSCRIPTION', '2022-10-20', 2, 'ref '||givenPartnerNumber::text || givenMemberNumberSuffix||'-3', 'some subscription', null), (subscriptionEntryUuid, membership.uuid, 'SUBSCRIPTION', '2022-10-20', 2, 'ref '||givenPartnerNumber::text || givenMemberNumberSuffix||'-3', 'some subscription', null),
(uuid_generate_v4(), membership.uuid, 'REVERSAL', '2022-10-21', -2, 'ref '||givenPartnerNumber::text || givenMemberNumberSuffix||'-4', 'some reversal', subscriptionEntryUuid); (uuid_generate_v4(), membership.uuid, 'ADJUSTMENT', '2022-10-21', -2, 'ref '||givenPartnerNumber::text || givenMemberNumberSuffix||'-4', 'some adjustment', subscriptionEntryUuid);
end; $$; end; $$;
--// --//

View File

@ -4,7 +4,7 @@
--changeset michael.hoennig:hs-office-coopassets-MAIN-TABLE endDelimiter:--// --changeset michael.hoennig:hs-office-coopassets-MAIN-TABLE endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
CREATE TYPE hs_office.CoopAssetsTransactionType AS ENUM ('REVERSAL', CREATE TYPE hs_office.CoopAssetsTransactionType AS ENUM ('ADJUSTMENT',
'DEPOSIT', 'DEPOSIT',
'DISBURSAL', 'DISBURSAL',
'TRANSFER', 'TRANSFER',
@ -22,9 +22,9 @@ create table if not exists hs_office.coopassettx
membershipUuid uuid not null references hs_office.membership(uuid), membershipUuid uuid not null references hs_office.membership(uuid),
transactionType hs_office.CoopAssetsTransactionType not null, transactionType hs_office.CoopAssetsTransactionType not null,
valueDate date not null, valueDate date not null,
assetValue numeric(12,2) not null, -- https://wiki.postgresql.org/wiki/Don't_Do_This#Don.27t_use_money assetValue money not null,
reference varchar(48) not null, reference varchar(48) not null,
revertedAssetTxUuid uuid unique REFERENCES hs_office.coopassettx(uuid) DEFERRABLE INITIALLY DEFERRED, adjustedAssetTxUuid uuid unique REFERENCES hs_office.coopassettx(uuid) DEFERRABLE INITIALLY DEFERRED,
comment varchar(512) comment varchar(512)
); );
--// --//
@ -36,20 +36,20 @@ create table if not exists hs_office.coopassettx
alter table hs_office.coopassettx alter table hs_office.coopassettx
add constraint reverse_entry_missing add constraint reverse_entry_missing
check ( transactionType = 'REVERSAL' and revertedAssetTxUuid is not null check ( transactionType = 'ADJUSTMENT' and adjustedAssetTxUuid is not null
or transactionType <> 'REVERSAL' and revertedAssetTxUuid is null); or transactionType <> 'ADJUSTMENT' and adjustedAssetTxUuid is null);
--// --//
-- ============================================================================ -- ============================================================================
--changeset michael.hoennig:hs-office-coopassets-ASSET-VALUE-CONSTRAINT endDelimiter:--// --changeset michael.hoennig:hs-office-coopassets-ASSET-VALUE-CONSTRAINT endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
create or replace function hs_office.coopassetstx_check_positive_total(forMembershipUuid UUID, newAssetValue numeric(12, 5)) create or replace function hs_office.coopassetstx_check_positive_total(forMembershipUuid UUID, newAssetValue money)
returns boolean returns boolean
language plpgsql as $$ language plpgsql as $$
declare declare
currentAssetValue numeric(12,2); currentAssetValue money;
totalAssetValue numeric(12,2); totalAssetValue money;
begin begin
select sum(cat.assetValue) select sum(cat.assetValue)
from hs_office.coopassettx cat from hs_office.coopassettx cat

View File

@ -36,7 +36,7 @@ begin
call rbac.enterTriggerForObjectUuid(NEW.uuid); call rbac.enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM hs_office.membership WHERE uuid = NEW.membershipUuid INTO newMembership; SELECT * FROM hs_office.membership WHERE uuid = NEW.membershipUuid INTO newMembership;
assert newMembership.uuid is not null, format('newMembership must not be null for NEW.membershipUuid = %s of coopasset', NEW.membershipUuid); assert newMembership.uuid is not null, format('newMembership must not be null for NEW.membershipUuid = %s', NEW.membershipUuid);
call rbac.grantPermissionToRole(rbac.createPermission(NEW.uuid, 'SELECT'), hs_office.membership_AGENT(newMembership)); call rbac.grantPermissionToRole(rbac.createPermission(NEW.uuid, 'SELECT'), hs_office.membership_AGENT(newMembership));
call rbac.grantPermissionToRole(rbac.createPermission(NEW.uuid, 'UPDATE'), hs_office.membership_ADMIN(newMembership)); call rbac.grantPermissionToRole(rbac.createPermission(NEW.uuid, 'UPDATE'), hs_office.membership_ADMIN(newMembership));

View File

@ -27,12 +27,12 @@ begin
raise notice 'creating test coopAssetsTransaction: %', givenPartnerNumber || givenMemberNumberSuffix; raise notice 'creating test coopAssetsTransaction: %', givenPartnerNumber || givenMemberNumberSuffix;
lossEntryUuid := uuid_generate_v4(); lossEntryUuid := uuid_generate_v4();
insert insert
into hs_office.coopassettx(uuid, membershipuuid, transactiontype, valuedate, assetvalue, reference, comment, revertedAssetTxUuid) into hs_office.coopassettx(uuid, membershipuuid, transactiontype, valuedate, assetvalue, reference, comment, adjustedAssetTxUuid)
values values
(uuid_generate_v4(), membership.uuid, 'DEPOSIT', '2010-03-15', 320.00, 'ref '||givenPartnerNumber || givenMemberNumberSuffix||'-1', 'initial deposit', null), (uuid_generate_v4(), membership.uuid, 'DEPOSIT', '2010-03-15', 320.00, 'ref '||givenPartnerNumber || givenMemberNumberSuffix||'-1', 'initial deposit', null),
(uuid_generate_v4(), membership.uuid, 'DISBURSAL', '2021-09-01', -128.00, 'ref '||givenPartnerNumber || givenMemberNumberSuffix||'-2', 'partial disbursal', null), (uuid_generate_v4(), membership.uuid, 'DISBURSAL', '2021-09-01', -128.00, 'ref '||givenPartnerNumber || givenMemberNumberSuffix||'-2', 'partial disbursal', null),
(lossEntryUuid, membership.uuid, 'DEPOSIT', '2022-10-20', 128.00, 'ref '||givenPartnerNumber || givenMemberNumberSuffix||'-3', 'some loss', null), (lossEntryUuid, membership.uuid, 'DEPOSIT', '2022-10-20', 128.00, 'ref '||givenPartnerNumber || givenMemberNumberSuffix||'-3', 'some loss', null),
(uuid_generate_v4(), membership.uuid, 'REVERSAL', '2022-10-21', -128.00, 'ref '||givenPartnerNumber || givenMemberNumberSuffix||'-3', 'some reversal', lossEntryUuid); (uuid_generate_v4(), membership.uuid, 'ADJUSTMENT', '2022-10-21', -128.00, 'ref '||givenPartnerNumber || givenMemberNumberSuffix||'-3', 'some adjustment', lossEntryUuid);
end; $$; end; $$;
--// --//

View File

@ -37,14 +37,14 @@ begin
call rbac.enterTriggerForObjectUuid(NEW.uuid); call rbac.enterTriggerForObjectUuid(NEW.uuid);
SELECT * FROM hs_office.debitor WHERE uuid = NEW.debitorUuid INTO newDebitor; SELECT * FROM hs_office.debitor WHERE uuid = NEW.debitorUuid INTO newDebitor;
assert newDebitor.uuid is not null, format('newDebitor must not be null for NEW.debitorUuid = %s of project', NEW.debitorUuid); assert newDebitor.uuid is not null, format('newDebitor must not be null for NEW.debitorUuid = %s', NEW.debitorUuid);
SELECT debitorRel.* SELECT debitorRel.*
FROM hs_office.relation debitorRel FROM hs_office.relation debitorRel
JOIN hs_office.debitor debitor ON debitor.debitorRelUuid = debitorRel.uuid JOIN hs_office.debitor debitor ON debitor.debitorRelUuid = debitorRel.uuid
WHERE debitor.uuid = NEW.debitorUuid WHERE debitor.uuid = NEW.debitorUuid
INTO newDebitorRel; INTO newDebitorRel;
assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorUuid = %s or project', NEW.debitorUuid); assert newDebitorRel.uuid is not null, format('newDebitorRel must not be null for NEW.debitorUuid = %s', NEW.debitorUuid);
perform rbac.defineRoleWithGrants( perform rbac.defineRoleWithGrants(

View File

@ -1,8 +0,0 @@
--liquibase formatted sql
-- ============================================================================
--changeset timotheus.pokorra:hs-integration-SCHEMA endDelimiter:--//
-- ----------------------------------------------------------------------------
CREATE SCHEMA hs_integration;
--//

View File

@ -1,18 +0,0 @@
--liquibase formatted sql
-- ============================================================================
--changeset timotheus.pokorra:hs-global-integration-kimai endDelimiter:--//
-- TODO.impl: also select column debitorNumber and do not filter anymore for '00'
CREATE OR REPLACE VIEW hs_integration.time_customer AS
SELECT p.partnernumber, debitor.defaultprefix
FROM hs_office.partner p
JOIN hs_office.relation AS pRel
ON pRel.type = 'PARTNER'
AND pRel.uuid = p.partnerRelUuid
JOIN hs_office.relation AS dRel
ON dRel.type = 'DEBITOR'
AND dRel.anchorUuid = pRel.holderUuid
JOIN hs_office.debitor AS debitor
ON debitor.debitorreluuid = dRel.uuid
AND debitor.debitornumbersuffix = '00';
--//

View File

@ -1,102 +0,0 @@
--liquibase formatted sql
-- ============================================================================
--changeset timotheus.pokorra:hs-global-integration-znuny endDelimiter:--//
-- TODO.impl: also select column debitorNumber and do not filter anymore for '00'
CREATE OR REPLACE VIEW hs_integration.contact AS
SELECT DISTINCT ON (uuid)
partner.partnernumber as partnernumber,
debitor.defaultprefix as defaultprefix,
c.uuid as uuid,
(CASE WHEN per.salutation <> '' THEN per.salutation ELSE NULL END) as salutation,
(CASE WHEN per.givenname <> '' THEN per.givenname ELSE NULL END) as givenname,
(CASE WHEN per.familyname <> '' THEN per.familyname ELSE NULL END) as familyname,
(CASE WHEN per.title <> '' THEN per.title ELSE NULL END) as title,
(CASE WHEN per.tradename <> '' THEN per.tradename ELSE NULL END) as tradename,
(CASE WHEN c.postaladdress->>'co' <> '' THEN c.postaladdress->>'co' ELSE NULL END) as co,
c.postaladdress->>'street' as street,
c.postaladdress->>'zipcode' as zipcode,
c.postaladdress->>'city' as city,
c.postaladdress->>'country' as country,
c.phonenumbers->>'phone_private' as phone_private,
c.phonenumbers->>'phone_office' as phone_office,
c.phonenumbers->>'phone_mobile' as phone_mobile,
c.phonenumbers->>'fax' as fax,
c.emailaddresses->>'main' as email
FROM hs_office.partner AS partner
JOIN hs_office.partner_legacy_id AS partner_lid ON partner_lid.uuid = partner.uuid
JOIN hs_office.relation AS pRel
ON pRel.type = 'PARTNER'
AND pRel.uuid = partner.partnerRelUuid
JOIN hs_office.relation AS dRel
ON dRel.type = 'DEBITOR'
AND dRel.anchorUuid = pRel.holderUuid
JOIN hs_office.debitor AS debitor
ON debitor.debitorreluuid = dRel.uuid
AND debitor.debitornumbersuffix = '00'
JOIN hs_office.contact AS c ON c.uuid = pRel.contactuuid
JOIN hs_office.person AS per ON per.uuid = pRel.holderuuid
UNION
SELECT DISTINCT ON (uuid)
partner.partnernumber as partnernumber,
debitor.defaultprefix as defaultprefix,
c.uuid as uuid,
(CASE WHEN per.salutation <> '' THEN per.salutation ELSE NULL END) as salutation,
(CASE WHEN per.givenname <> '' THEN per.givenname ELSE NULL END) as givenname,
(CASE WHEN per.familyname <> '' THEN per.familyname ELSE NULL END) as familyname,
(CASE WHEN per.title <> '' THEN per.title ELSE NULL END) as title,
(CASE WHEN per.tradename <> '' THEN per.tradename ELSE NULL END) as tradename,
(CASE WHEN c.postaladdress->>'co' <> '' THEN c.postaladdress->>'co' ELSE NULL END) as co,
c.postaladdress->>'street' as street,
c.postaladdress->>'zipcode' as zipcode,
c.postaladdress->>'city' as city,
c.postaladdress->>'country' as country,
c.phonenumbers->>'phone_private' as phone_private,
c.phonenumbers->>'phone_office' as phone_office,
c.phonenumbers->>'phone_mobile' as phone_mobile,
c.phonenumbers->>'fax' as fax,
c.emailaddresses->>'main' as email
FROM hs_office.partner AS partner
JOIN hs_office.relation AS pRel
ON pRel.type = 'PARTNER'
AND pRel.uuid = partner.partnerRelUuid
JOIN hs_office.relation AS dRel
ON dRel.type = 'DEBITOR'
AND dRel.anchorUuid = pRel.holderUuid
JOIN hs_office.debitor AS debitor
ON debitor.debitorreluuid = dRel.uuid
AND debitor.debitornumbersuffix = '00'
JOIN hs_office.relation AS rs1 ON rs1.uuid = partner.partnerreluuid AND rs1.type = 'PARTNER'
JOIN hs_office.relation AS relation ON relation.anchoruuid = rs1.holderuuid
JOIN hs_office.contact AS c ON c.uuid = relation.contactuuid
JOIN hs_office.person AS per ON per.uuid = relation.holderuuid;
CREATE OR REPLACE VIEW hs_integration.ticket_customer_user AS
SELECT c.uuid,
max(c.partnernumber)::text as number,
max(c.defaultprefix) as code,
max(c.email) as login,
max(c.salutation) as salut,
max(c.givenname) as firstname,
max(c.familyname) as lastname,
max(c.title) as title,
max(c.tradename) as firma,
max(c.co) as co,
max(c.street) as street,
max(c.zipcode) as zipcode,
max(c.city) as city,
max(c.country) as country,
max(concat_ws(', '::text, c.phone_office, c.phone_private)) AS phone,
max(c.phone_private) as phone_private,
max(c.phone_office) as phone_office,
max(c.phone_mobile) as mobile,
max(c.fax) as fax,
max(c.email) as email,
string_agg(CASE WHEN relation.mark IS NULL THEN relation.type::text ELSE CONCAT(relation.type::text, ':', relation.mark::text) END, '/'::text) AS comment,
1 AS valid
FROM hs_integration.contact AS c
JOIN hs_office.relation AS relation ON c.uuid = relation.contactuuid
WHERE (c.defaultprefix != 'hsh' OR (c.partnernumber = 10000 AND c.email = 'hostmaster@hostsharing.net'))
GROUP BY c.uuid;
--//

View File

@ -171,9 +171,3 @@ databaseChangeLog:
file: db/changelog/7-hs-hosting/701-hosting-asset/7018-hs-hosting-asset-test-data.sql file: db/changelog/7-hs-hosting/701-hosting-asset/7018-hs-hosting-asset-test-data.sql
- include: - include:
file: db/changelog/9-hs-global/9000-statistics.sql file: db/changelog/9-hs-global/9000-statistics.sql
- include:
file: db/changelog/9-hs-global/9100-hs-integration-schema.sql
- include:
file: db/changelog/9-hs-global/9110-integration-kimai.sql
- include:
file: db/changelog/9-hs-global/9120-integration-znuny.sql

View File

@ -46,7 +46,6 @@ public class ArchitectureTest {
"..lambda", "..lambda",
"..generated..", "..generated..",
"..persistence..", "..persistence..",
"..reflection",
"..system..", "..system..",
"..validation..", "..validation..",
"..hs.office.bankaccount", "..hs.office.bankaccount",
@ -55,7 +54,6 @@ public class ArchitectureTest {
"..hs.office.coopshares", "..hs.office.coopshares",
"..hs.office.debitor", "..hs.office.debitor",
"..hs.office.membership", "..hs.office.membership",
"..hs.office.scenarios..",
"..hs.migration", "..hs.migration",
"..hs.office.partner", "..hs.office.partner",
"..hs.office.person", "..hs.office.person",
@ -77,7 +75,7 @@ public class ArchitectureTest {
"..rbac.grant", "..rbac.grant",
"..rbac.role", "..rbac.role",
"..rbac.object", "..rbac.object",
"..repr" "..stringify"
// ATTENTION: Don't simply add packages here, also add arch rules for the new package! // ATTENTION: Don't simply add packages here, also add arch rules for the new package!
); );
@ -98,7 +96,7 @@ public class ArchitectureTest {
public static final ArchRule testClassesAreProperlyNamed = classes() public static final ArchRule testClassesAreProperlyNamed = classes()
.that().haveSimpleNameEndingWith("Test") .that().haveSimpleNameEndingWith("Test")
.and().doNotHaveModifier(ABSTRACT) .and().doNotHaveModifier(ABSTRACT)
.should().haveNameMatching(".*(UnitTest|RestTest|IntegrationTest|AcceptanceTest|ScenarioTest|ArchitectureTest)$"); .should().haveNameMatching(".*(UnitTest|RestTest|IntegrationTest|AcceptanceTest|ArchitectureTest)$");
@ArchTest @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")

View File

@ -150,7 +150,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"project.uuid": "{projectUuid}", "projectUuid": "{projectUuid}",
"type": "MANAGED_SERVER", "type": "MANAGED_SERVER",
"caption": "some new booking", "caption": "some new booking",
"validTo": "{validTo}", "validTo": "{validTo}",
@ -200,8 +200,8 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"project.uuid": "{projectUuid}", "projectUuid": "{projectUuid}",
"parentItem.uuid": "{managedServerUuid}", "parentItemUuid": "{managedServerUuid}",
"type": "MANAGED_WEBSPACE", "type": "MANAGED_WEBSPACE",
"caption": "some managed webspace", "caption": "some managed webspace",
"resources": { "resources": {
@ -270,7 +270,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"project.uuid": "{projectUuid}", "projectUuid": "{projectUuid}",
"type": "DOMAIN_SETUP", "type": "DOMAIN_SETUP",
"caption": "Domain-Setup for example.org", "caption": "Domain-Setup for example.org",
"resources": { "resources": {
@ -285,7 +285,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
}, },
{ {
"type": "DOMAIN_HTTP_SETUP", "type": "DOMAIN_HTTP_SETUP",
"assignedToAsset.uuid": "{unixUserUuid}" "assignedToAssetUuid": "{unixUserUuid}"
}, },
{ {
"type": "DOMAIN_MBOX_SETUP" "type": "DOMAIN_MBOX_SETUP"
@ -360,7 +360,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"project.uuid": "{projectUuid}", "projectUuid": "{projectUuid}",
"type": "DOMAIN_SETUP", "type": "DOMAIN_SETUP",
"caption": "some new domain-setup booking", "caption": "some new domain-setup booking",
"resources": { "resources": {
@ -375,7 +375,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
}, },
{ {
"type": "DOMAIN_HTTP_SETUP", "type": "DOMAIN_HTTP_SETUP",
"assignedToAsset.uuid": "{unixUserUuid}" "assignedToAssetUuid": "{unixUserUuid}"
}, },
{ {
"type": "DOMAIN_MBOX_SETUP" "type": "DOMAIN_MBOX_SETUP"

View File

@ -105,7 +105,7 @@ class HsBookingItemControllerRestTest {
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content(""" .content("""
{ {
"project.uuid": "{projectUuid}", "projectUuid": "{projectUuid}",
"type": "MANAGED_SERVER", "type": "MANAGED_SERVER",
"caption": "some new booking", "caption": "some new booking",
"validTo": "{validTo}", "validTo": "{validTo}",
@ -155,7 +155,7 @@ class HsBookingItemControllerRestTest {
.contentType(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)
.content(""" .content("""
{ {
"project.uuid": "{projectUuid}", "projectUuid": "{projectUuid}",
"type": "MANAGED_SERVER", "type": "MANAGED_SERVER",
"caption": "some new booking", "caption": "some new booking",
"validFrom": "{validFrom}", "validFrom": "{validFrom}",

View File

@ -92,7 +92,7 @@ class HsBookingProjectControllerAcceptanceTest extends ContextBasedTestWithClean
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"debitor.uuid": "%s", "debitorUuid": "%s",
"caption": "some new project" "caption": "some new project"
} }
""".formatted(givenDebitor.getUuid())) """.formatted(givenDebitor.getUuid()))

View File

@ -165,10 +165,10 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"bookingItem.uuid": "%s", "bookingItemUuid": "%s",
"type": "MANAGED_WEBSPACE", "type": "MANAGED_WEBSPACE",
"identifier": "fir10", "identifier": "fir10",
"parentAsset.uuid": "%s", "parentAssetUuid": "%s",
"caption": "some separate ManagedWebspace HA", "caption": "some separate ManagedWebspace HA",
"config": {} "config": {}
} }
@ -227,7 +227,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"parentAsset.uuid": "%s", "parentAssetUuid": "%s",
"type": "UNIX_USER", "type": "UNIX_USER",
"identifier": "fir01-temp", "identifier": "fir01-temp",
"caption": "some new UnixUser in client's ManagedWebspace", "caption": "some new UnixUser in client's ManagedWebspace",
@ -280,7 +280,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"bookingItem.uuid": "%s", "bookingItemUuid": "%s",
"type": "DOMAIN_SETUP", "type": "DOMAIN_SETUP",
"identifier": "example.com", "identifier": "example.com",
"caption": "some unrelated domain-setup", "caption": "some unrelated domain-setup",
@ -326,7 +326,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"bookingItem.uuid": "%s", "bookingItemUuid": "%s",
"type": "MANAGED_SERVER", "type": "MANAGED_SERVER",
"identifier": "vm1400", "identifier": "vm1400",
"caption": "some new ManagedServer", "caption": "some new ManagedServer",
@ -381,7 +381,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"parentAsset.uuid": "%s", "parentAssetUuid": "%s",
"type": "UNIX_USER", "type": "UNIX_USER",
"identifier": "fir01-extra", "identifier": "fir01-extra",
"caption": "some extra UnixUser", "caption": "some extra UnixUser",
@ -508,7 +508,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
.contentType(ContentType.JSON) .contentType(ContentType.JSON)
.body(""" .body("""
{ {
"alarmContact.uuid": "%s", "alarmContactUuid": "%s",
"config": { "config": {
"monit_max_ssd_usage": 85, "monit_max_ssd_usage": 85,
"monit_max_hdd_usage": null, "monit_max_hdd_usage": null,

View File

@ -110,6 +110,7 @@ public class HsHostingAssetControllerRestTest {
"caption": "some fake cloud-server", "caption": "some fake cloud-server",
"alarmContact": { "alarmContact": {
"caption": "some contact", "caption": "some contact",
"postalAddress": "address of some contact",
"emailAddresses": { "emailAddresses": {
"main": "some-contact@example.com" "main": "some-contact@example.com"
} }
@ -140,6 +141,7 @@ public class HsHostingAssetControllerRestTest {
"caption": "some fake managed-server", "caption": "some fake managed-server",
"alarmContact": { "alarmContact": {
"caption": "some contact", "caption": "some contact",
"postalAddress": "address of some contact",
"emailAddresses": { "emailAddresses": {
"main": "some-contact@example.com" "main": "some-contact@example.com"
} }

View File

@ -83,7 +83,7 @@ class DomainSetupHostingAssetFactoryUnitTest {
"subHostingAssets": [ "subHostingAssets": [
{ {
"type": "DOMAIN_HTTP_SETUP", "type": "DOMAIN_HTTP_SETUP",
"assignedToAsset.uuid": "{unixUserHostingAssetUuid}" "assignedToAssetUuid": "{unixUserHostingAssetUuid}"
}, },
{ {
"type": "DOMAIN_DNS_SETUP" "type": "DOMAIN_DNS_SETUP"
@ -124,7 +124,7 @@ class DomainSetupHostingAssetFactoryUnitTest {
"subHostingAssets": [ "subHostingAssets": [
{ {
"type": "DOMAIN_HTTP_SETUP", "type": "DOMAIN_HTTP_SETUP",
"assignedToAsset.uuid": "{unixUserHostingAssetUuid}" "assignedToAssetUuid": "{unixUserHostingAssetUuid}"
}, },
{ {
"type": "DOMAIN_DNS_SETUP" "type": "DOMAIN_DNS_SETUP"
@ -164,7 +164,7 @@ class DomainSetupHostingAssetFactoryUnitTest {
"subHostingAssets": [ "subHostingAssets": [
{ {
"type": "DOMAIN_HTTP_SETUP", "type": "DOMAIN_HTTP_SETUP",
"assignedToAsset.uuid": "{unixUserHostingAssetUuid}" "assignedToAssetUuid": "{unixUserHostingAssetUuid}"
}, },
{ {
"type": "DOMAIN_DNS_SETUP" "type": "DOMAIN_DNS_SETUP"
@ -206,7 +206,7 @@ class DomainSetupHostingAssetFactoryUnitTest {
"subHostingAssets": [ "subHostingAssets": [
{ {
"type": "DOMAIN_HTTP_SETUP", "type": "DOMAIN_HTTP_SETUP",
"assignedToAsset.uuid": "{unixUserHostingAssetUuid}" "assignedToAssetUuid": "{unixUserHostingAssetUuid}"
}, },
{ {
"type": "DOMAIN_DNS_SETUP" "type": "DOMAIN_DNS_SETUP"

View File

@ -54,7 +54,7 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
"subscriber:customers-announce" "subscriber:customers-announce"
}; };
private static final String[] KNOWN_ROLES = ArrayUtils.addAll( private static final String[] KNOWN_ROLES = ArrayUtils.addAll(
new String[] { "partner", "vip-contact", "ex-partner", "billing", "contractual", "operation", "silent" }, new String[] { "partner", "vip-contact", "ex-partner", "billing", "contractual", "operation" },
SUBSCRIBER_ROLES); SUBSCRIBER_ROLES);
// at least as the number of lines in business_partners.csv from test-data, but less than real data partner count // at least as the number of lines in business_partners.csv from test-data, but less than real data partner count
@ -65,21 +65,18 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
static int relationId = INITIAL_RELATION_ID; static int relationId = INITIAL_RELATION_ID;
private static final List<Integer> IGNORE_BUSINESS_PARTNERS = Arrays.asList( private static final List<Integer> IGNORE_BUSINESS_PARTNERS = Arrays.asList(
512167, // 11139, partner without contractual contact
512170, // 11142, partner without contractual contact
511725, // 10764, partner without contractual contact
// 512171, // 11143, partner without partner contact -- exception
-1 -1
); );
private static final List<Integer> IGNORE_CONTACTS = Arrays.asList( private static final List<Integer> IGNORE_CONTACTS = Arrays.asList(
90547, // Kontakt hat keine Rolle
-1 -1
); );
private static final Map<Integer, HsOfficePersonType> PERSON_TYPES_BY_CONTACT = Map.of(
90072, HsOfficePersonType.NATURAL_PERSON,
90641, HsOfficePersonType.LEGAL_PERSON,
90368, HsOfficePersonType.LEGAL_PERSON,
90564, HsOfficePersonType.NATURAL_PERSON,
-1, HsOfficePersonType.LEGAL_PERSON
);
static Map<Integer, HsOfficeContactRealEntity> contacts = new WriteOnceMap<>(); static Map<Integer, HsOfficeContactRealEntity> contacts = new WriteOnceMap<>();
static Map<Integer, HsOfficePersonEntity> persons = new WriteOnceMap<>(); static Map<Integer, HsOfficePersonEntity> persons = new WriteOnceMap<>();
static Map<Integer, HsOfficePartnerEntity> partners = new WriteOnceMap<>(); static Map<Integer, HsOfficePartnerEntity> partners = new WriteOnceMap<>();
@ -195,56 +192,56 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
assertThat(toJsonFormattedString(partners)).isEqualToIgnoringWhitespace(""" assertThat(toJsonFormattedString(partners)).isEqualToIgnoringWhitespace("""
{ {
100=partner(P-10003: ?? Michael Mellis, Herr Michael Mellis, Michael Mellis), 100=partner(P-10003: ?? Michael Mellis, Herr Michael Mellis , Michael Mellis),
120=partner(P-10020: LP JM GmbH, Herr Philip Meyer-Contract, JM GmbH), 120=partner(P-10020: LP JM GmbH, Herr Philip Meyer-Contract , JM GmbH),
122=partner(P-11022: ?? Test PS, Petra Schmidt, Test PS), 122=partner(P-11022: ?? Test PS, Petra Schmidt , Test PS),
132=partner(P-10152: ?? Ragnar IT-Beratung, Herr Ragnar Richter, Ragnar IT-Beratung), 132=partner(P-10152: ?? Ragnar IT-Beratung, Herr Ragnar Richter , Ragnar IT-Beratung),
190=partner(P-19090: NP Camus, Cecilia, Frau Cecilia Camus), 190=partner(P-19090: NP Camus, Cecilia, Frau Cecilia Camus ),
199=partner(P-19999: null null, null), 199=partner(P-19999: null null, null),
213=partner(P-10000: LP Hostsharing e.G., Firma Hostmaster Hostsharing, Hostsharing e.G.), 213=partner(P-10000: LP Hostsharing e.G., Firma Hostmaster Hostsharing , Hostsharing e.G.),
541=partner(P-11018: ?? Wasserwerk Südholstein, Frau Christiane Milberg, Wasserwerk Südholstein), 541=partner(P-11018: ?? Wasserwerk Südholstein, Frau Christiane Milberg , Wasserwerk Südholstein),
542=partner(P-11019: ?? Das Perfekte Haus, Herr Richard Wiese, Das Perfekte Haus) 542=partner(P-11019: ?? Das Perfekte Haus, Herr Richard Wiese , Das Perfekte Haus)
} }
"""); """);
assertThat(toJsonFormattedString(contacts)).isEqualToIgnoringWhitespace(""" assertThat(toJsonFormattedString(contacts)).isEqualToIgnoringWhitespace("""
{ {
100=contact(caption='Herr Michael Mellis, Michael Mellis', emailAddresses='{ "main": "michael@Mellis.example.org"}'), 100=contact(caption='Herr Michael Mellis , Michael Mellis', emailAddresses='{ "main": "michael@Mellis.example.org"}'),
1200=contact(caption='JM e.K.', emailAddresses='{ "main": "jm-ex-partner@example.org"}'), 1200=contact(caption='JM e.K.', emailAddresses='{ "main": "jm-ex-partner@example.org"}'),
1201=contact(caption='Frau Dr. Jenny Meyer-Billing, JM GmbH', emailAddresses='{ "main": "jm-billing@example.org"}'), 1201=contact(caption='Frau Dr. Jenny Meyer-Billing , JM GmbH', emailAddresses='{ "main": "jm-billing@example.org"}'),
1202=contact(caption='Herr Andrew Meyer-Operation, JM GmbH', emailAddresses='{ "main": "am-operation@example.org"}'), 1202=contact(caption='Herr Andrew Meyer-Operation , JM GmbH', emailAddresses='{ "main": "am-operation@example.org"}'),
1203=contact(caption='Herr Philip Meyer-Contract, JM GmbH', emailAddresses='{ "main": "pm-partner@example.org"}'), 1203=contact(caption='Herr Philip Meyer-Contract , JM GmbH', emailAddresses='{ "main": "pm-partner@example.org"}'),
1204=contact(caption='Frau Tammy Meyer-VIP, JM GmbH', emailAddresses='{ "main": "tm-vip@example.org"}'), 1204=contact(caption='Frau Tammy Meyer-VIP , JM GmbH', emailAddresses='{ "main": "tm-vip@example.org"}'),
1301=contact(caption='Petra Schmidt, Test PS', emailAddresses='{ "main": "ps@example.com"}'), 1301=contact(caption='Petra Schmidt , Test PS', emailAddresses='{ "main": "ps@example.com"}'),
132=contact(caption='Herr Ragnar Richter, Ragnar IT-Beratung', emailAddresses='{ "main": "hostsharing@ragnar-richter.de"}'), 132=contact(caption='Herr Ragnar Richter , Ragnar IT-Beratung', emailAddresses='{ "main": "hostsharing@ragnar-richter.de"}'),
1401=contact(caption='Frau Frauke Fanninga', emailAddresses='{ "main": "ff@example.org"}'), 1401=contact(caption='Frau Frauke Fanninga ', emailAddresses='{ "main": "ff@example.org"}'),
1501=contact(caption='Frau Cecilia Camus', emailAddresses='{ "main": "cc@example.org"}'), 1501=contact(caption='Frau Cecilia Camus ', emailAddresses='{ "main": "cc@example.org"}'),
212=contact(caption='Firma Hostmaster Hostsharing, Hostsharing e.G.', emailAddresses='{ "main": "hostmaster@hostsharing.net"}'), 212=contact(caption='Firma Hostmaster Hostsharing , Hostsharing e.G.', emailAddresses='{ "main": "hostmaster@hostsharing.net"}'),
90436=contact(caption='Frau Christiane Milberg, Wasserwerk Südholstein', emailAddresses='{ "main": "rechnung@ww-sholst.example.org"}'), 90436=contact(caption='Frau Christiane Milberg , Wasserwerk Südholstein', emailAddresses='{ "main": "rechnung@ww-sholst.example.org"}'),
90437=contact(caption='Herr Richard Wiese, Das Perfekte Haus', emailAddresses='{ "main": "admin@das-perfekte-haus.example.org"}'), 90437=contact(caption='Herr Richard Wiese , Das Perfekte Haus', emailAddresses='{ "main": "admin@das-perfekte-haus.example.org"}'),
90438=contact(caption='Herr Karim Metzger, Wasswerwerk Südholstein', emailAddresses='{ "main": "karim.metzger@ww-sholst.example.org"}'), 90438=contact(caption='Herr Karim Metzger , Wasswerwerk Südholstein', emailAddresses='{ "main": "karim.metzger@ww-sholst.example.org"}'),
90590=contact(caption='Herr Inhaber R. Wiese, Das Perfekte Haus', emailAddresses='{ "main": "515217@kkemail.example.org"}'), 90590=contact(caption='Herr Inhaber R. Wiese , Das Perfekte Haus', emailAddresses='{ "main": "515217@kkemail.example.org"}'),
90629=contact(caption='Ragnar Richter', emailAddresses='{ "main": "mail@ragnar-richter..example.org"}'), 90629=contact(caption='Ragnar Richter ', emailAddresses='{ "main": "mail@ragnar-richter..example.org"}'),
90677=contact(caption='Eike Henning', emailAddresses='{ "main": "hostsharing@eike-henning..example.org"}'), 90677=contact(caption='Eike Henning ', emailAddresses='{ "main": "hostsharing@eike-henning..example.org"}'),
90698=contact(caption='Jan Henning', emailAddresses='{ "main": "mail@jan-henning.example.org"}') 90698=contact(caption='Jan Henning ', emailAddresses='{ "main": "mail@jan-henning.example.org"}')
} }
"""); """);
assertThat(toJsonFormattedString(persons)).isEqualToIgnoringWhitespace(""" assertThat(toJsonFormattedString(persons)).isEqualToIgnoringWhitespace("""
{ {
100=person(personType='??', tradeName='Michael Mellis', salutation='Herr', familyName='Mellis', givenName='Michael'), 100=person(personType='??', tradeName='Michael Mellis', familyName='Mellis', givenName='Michael'),
1200=person(personType='LP', tradeName='JM e.K.'), 1200=person(personType='LP', tradeName='JM e.K.'),
1201=person(personType='LP', tradeName='JM GmbH', salutation='Frau', title='Dr.', familyName='Meyer-Billing', givenName='Jenny'), 1201=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-Billing', givenName='Jenny'),
1202=person(personType='LP', tradeName='JM GmbH', salutation='Herr', familyName='Meyer-Operation', givenName='Andrew'), 1202=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-Operation', givenName='Andrew'),
1203=person(personType='LP', tradeName='JM GmbH', salutation='Herr', familyName='Meyer-Contract', givenName='Philip'), 1203=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-Contract', givenName='Philip'),
1204=person(personType='LP', tradeName='JM GmbH', salutation='Frau', 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'),
132=person(personType='??', tradeName='Ragnar IT-Beratung', salutation='Herr', familyName='Richter', givenName='Ragnar'), 132=person(personType='??', tradeName='Ragnar IT-Beratung', familyName='Richter', givenName='Ragnar'),
1401=person(personType='NP', salutation='Frau', familyName='Fanninga', givenName='Frauke'), 1401=person(personType='NP', familyName='Fanninga', givenName='Frauke'),
1501=person(personType='NP', salutation='Frau', familyName='Camus', givenName='Cecilia'), 1501=person(personType='NP', familyName='Camus', givenName='Cecilia'),
212=person(personType='LP', tradeName='Hostsharing e.G.', salutation='Firma', familyName='Hostsharing', givenName='Hostmaster'), 212=person(personType='LP', tradeName='Hostsharing e.G.', familyName='Hostsharing', givenName='Hostmaster'),
90436=person(personType='??', tradeName='Wasserwerk Südholstein', salutation='Frau', familyName='Milberg', givenName='Christiane'), 90436=person(personType='??', tradeName='Wasserwerk Südholstein', familyName='Milberg', givenName='Christiane'),
90437=person(personType='??', tradeName='Das Perfekte Haus', salutation='Herr', familyName='Wiese', givenName='Richard'), 90437=person(personType='??', tradeName='Das Perfekte Haus', familyName='Wiese', givenName='Richard'),
90438=person(personType='??', tradeName='Wasswerwerk Südholstein', salutation='Herr', familyName='Metzger', givenName='Karim'), 90438=person(personType='??', tradeName='Wasswerwerk Südholstein', familyName='Metzger', givenName='Karim'),
90590=person(personType='??', tradeName='Das Perfekte Haus', salutation='Herr', familyName='Wiese', givenName='Inhaber R.'), 90590=person(personType='??', tradeName='Das Perfekte Haus', familyName='Wiese', givenName='Inhaber R.'),
90629=person(personType='NP', familyName='Richter', givenName='Ragnar'), 90629=person(personType='NP', familyName='Richter', givenName='Ragnar'),
90677=person(personType='NP', familyName='Henning', givenName='Eike'), 90677=person(personType='NP', familyName='Henning', givenName='Eike'),
90698=person(personType='NP', familyName='Henning', givenName='Jan') 90698=person(personType='NP', familyName='Henning', givenName='Jan')
@ -275,81 +272,71 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
"""); """);
assertThat(toJsonFormattedString(relations)).isEqualToIgnoringWhitespace(""" assertThat(toJsonFormattedString(relations)).isEqualToIgnoringWhitespace("""
{ {
2000000=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='?? Michael Mellis', contact='Herr Michael Mellis, Michael Mellis'), 2000000=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'),
2000001=rel(anchor='?? Michael Mellis', type='DEBITOR', holder='?? Michael Mellis', contact='Herr Michael Mellis, Michael Mellis'), 2000001=rel(anchor='?? Michael Mellis', type='DEBITOR', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'),
2000002=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='?? Ragnar IT-Beratung', contact='Herr Ragnar Richter, Ragnar IT-Beratung'), 2000002=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='?? Ragnar IT-Beratung', contact='Herr Ragnar Richter , Ragnar IT-Beratung'),
2000003=rel(anchor='?? Ragnar IT-Beratung', type='DEBITOR', holder='?? Ragnar IT-Beratung', contact='Herr Ragnar Richter, Ragnar IT-Beratung'), 2000003=rel(anchor='?? Ragnar IT-Beratung', type='DEBITOR', holder='?? Ragnar IT-Beratung', contact='Herr Ragnar Richter , Ragnar IT-Beratung'),
2000004=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='LP Hostsharing e.G.', contact='Firma Hostmaster Hostsharing, Hostsharing e.G.'), 2000004=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='LP Hostsharing e.G.', contact='Firma Hostmaster Hostsharing , Hostsharing e.G.'),
2000005=rel(anchor='LP Hostsharing e.G.', type='DEBITOR', holder='LP Hostsharing e.G.', contact='Firma Hostmaster Hostsharing, Hostsharing e.G.'), 2000005=rel(anchor='LP Hostsharing e.G.', type='DEBITOR', holder='LP Hostsharing e.G.', contact='Firma Hostmaster Hostsharing , Hostsharing e.G.'),
2000006=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg, Wasserwerk Südholstein'), 2000006=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg , Wasserwerk Südholstein'),
2000007=rel(anchor='?? Wasserwerk Südholstein', type='DEBITOR', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg, Wasserwerk Südholstein'), 2000007=rel(anchor='?? Wasserwerk Südholstein', type='DEBITOR', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg , Wasserwerk Südholstein'),
2000008=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese, Das Perfekte Haus'), 2000008=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese , Das Perfekte Haus'),
2000009=rel(anchor='?? Das Perfekte Haus', type='DEBITOR', holder='?? Das Perfekte Haus', contact='Herr Inhaber R. Wiese, Das Perfekte Haus'), 2000009=rel(anchor='?? Das Perfekte Haus', type='DEBITOR', holder='?? Das Perfekte Haus', contact='Herr Inhaber R. Wiese , Das Perfekte Haus'),
2000010=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract, JM GmbH'), 2000010=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'),
2000011=rel(anchor='LP JM GmbH', type='DEBITOR', holder='LP JM GmbH', contact='Frau Dr. Jenny Meyer-Billing, JM GmbH'), 2000011=rel(anchor='LP JM GmbH', type='DEBITOR', holder='LP JM GmbH', contact='Frau Dr. Jenny Meyer-Billing , JM GmbH'),
2000012=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='?? Test PS', contact='Petra Schmidt, Test PS'), 2000012=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000013=rel(anchor='?? Test PS', type='DEBITOR', holder='?? Test PS', contact='Petra Schmidt, Test PS'), 2000013=rel(anchor='?? Test PS', type='DEBITOR', holder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000014=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus'), 2000014=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus '),
2000015=rel(anchor='NP Camus, Cecilia', type='DEBITOR', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus'), 2000015=rel(anchor='NP Camus, Cecilia', type='DEBITOR', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus '),
2000016=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='null null, null'), 2000016=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='null null, null'),
2000017=rel(anchor='null null, null', type='DEBITOR'), 2000017=rel(anchor='null null, null', type='DEBITOR'),
2000018=rel(anchor='LP Hostsharing e.G.', type='OPERATIONS_ALERT', holder='LP Hostsharing e.G.', contact='Firma Hostmaster Hostsharing, Hostsharing e.G.'), 2000018=rel(anchor='LP Hostsharing e.G.', type='OPERATIONS', holder='LP Hostsharing e.G.', contact='Firma Hostmaster Hostsharing , Hostsharing e.G.'),
2000019=rel(anchor='LP Hostsharing e.G.', type='OPERATIONS', holder='LP Hostsharing e.G.', contact='Firma Hostmaster Hostsharing, Hostsharing e.G.'), 2000019=rel(anchor='LP Hostsharing e.G.', type='REPRESENTATIVE', holder='LP Hostsharing e.G.', contact='Firma Hostmaster Hostsharing , Hostsharing e.G.'),
2000020=rel(anchor='LP Hostsharing e.G.', type='REPRESENTATIVE', holder='LP Hostsharing e.G.', contact='Firma Hostmaster Hostsharing, Hostsharing e.G.'), 2000020=rel(anchor='?? Michael Mellis', type='OPERATIONS', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'),
2000021=rel(anchor='?? Michael Mellis', type='OPERATIONS_ALERT', holder='?? Michael Mellis', contact='Herr Michael Mellis, Michael Mellis'), 2000021=rel(anchor='?? Michael Mellis', type='REPRESENTATIVE', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'),
2000022=rel(anchor='?? Michael Mellis', type='OPERATIONS', holder='?? Michael Mellis', contact='Herr Michael Mellis, Michael Mellis'), 2000022=rel(anchor='?? Michael Mellis', type='SUBSCRIBER', mark='operations-discussion', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'),
2000023=rel(anchor='?? Michael Mellis', type='REPRESENTATIVE', holder='?? Michael Mellis', contact='Herr Michael Mellis, Michael Mellis'), 2000023=rel(anchor='?? Michael Mellis', type='SUBSCRIBER', mark='operations-announce', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'),
2000024=rel(anchor='?? Michael Mellis', type='SUBSCRIBER', mark='operations-discussion', holder='?? Michael Mellis', contact='Herr Michael Mellis, Michael Mellis'), 2000024=rel(anchor='?? Michael Mellis', type='SUBSCRIBER', mark='generalversammlung', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'),
2000025=rel(anchor='?? Michael Mellis', type='SUBSCRIBER', mark='operations-announce', holder='?? Michael Mellis', contact='Herr Michael Mellis, Michael Mellis'), 2000025=rel(anchor='?? Michael Mellis', type='SUBSCRIBER', mark='members-announce', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'),
2000026=rel(anchor='?? Michael Mellis', type='SUBSCRIBER', mark='generalversammlung', holder='?? Michael Mellis', contact='Herr Michael Mellis, Michael Mellis'), 2000026=rel(anchor='?? Michael Mellis', type='SUBSCRIBER', mark='members-discussion', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'),
2000027=rel(anchor='?? Michael Mellis', type='SUBSCRIBER', mark='members-announce', holder='?? Michael Mellis', contact='Herr Michael Mellis, Michael Mellis'), 2000027=rel(anchor='?? Ragnar IT-Beratung', type='OPERATIONS', holder='?? Ragnar IT-Beratung', contact='Herr Ragnar Richter , Ragnar IT-Beratung'),
2000028=rel(anchor='?? Michael Mellis', type='SUBSCRIBER', mark='members-discussion', holder='?? Michael Mellis', contact='Herr Michael Mellis, Michael Mellis'), 2000028=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='operations-discussion', holder='?? Ragnar IT-Beratung', contact='Herr Ragnar Richter , Ragnar IT-Beratung'),
2000029=rel(anchor='?? Ragnar IT-Beratung', type='OPERATIONS_ALERT', holder='?? Ragnar IT-Beratung', contact='Herr Ragnar Richter, Ragnar IT-Beratung'), 2000029=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='operations-announce', holder='?? Ragnar IT-Beratung', contact='Herr Ragnar Richter , Ragnar IT-Beratung'),
2000030=rel(anchor='?? Ragnar IT-Beratung', type='OPERATIONS', holder='?? Ragnar IT-Beratung', contact='Herr Ragnar Richter, Ragnar IT-Beratung'), 2000030=rel(anchor='LP JM GmbH', type='EX_PARTNER', holder='LP JM e.K.', contact='JM e.K.'),
2000031=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='operations-discussion', holder='?? Ragnar IT-Beratung', contact='Herr Ragnar Richter, Ragnar IT-Beratung'), 2000031=rel(anchor='LP JM GmbH', type='OPERATIONS', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'),
2000032=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='operations-announce', holder='?? Ragnar IT-Beratung', contact='Herr Ragnar Richter, Ragnar IT-Beratung'), 2000032=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'),
2000033=rel(anchor='LP JM GmbH', type='EX_PARTNER', holder='LP JM e.K.', contact='JM e.K.'), 2000033=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='operations-announce', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'),
2000034=rel(anchor='LP JM GmbH', type='OPERATIONS_ALERT', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation, JM GmbH'), 2000034=rel(anchor='LP JM GmbH', type='REPRESENTATIVE', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'),
2000035=rel(anchor='LP JM GmbH', type='OPERATIONS', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation, JM GmbH'), 2000035=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='members-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'),
2000036=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation, JM GmbH'), 2000036=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='customers-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'),
2000037=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='operations-announce', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation, JM GmbH'), 2000037=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Frau Tammy Meyer-VIP , JM GmbH'),
2000038=rel(anchor='LP JM GmbH', type='REPRESENTATIVE', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract, JM GmbH'), 2000038=rel(anchor='?? Test PS', type='OPERATIONS', holder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000039=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='members-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract, JM GmbH'), 2000039=rel(anchor='?? Test PS', type='REPRESENTATIVE', holder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000040=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='customers-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract, JM GmbH'), 2000040=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='operations-announce', holder='NP Fanninga, Frauke', contact='Frau Frauke Fanninga '),
2000041=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Frau Tammy Meyer-VIP, JM GmbH'), 2000041=rel(anchor='NP Camus, Cecilia', type='OPERATIONS', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus '),
2000042=rel(anchor='?? Test PS', type='OPERATIONS_ALERT', holder='?? Test PS', contact='Petra Schmidt, Test PS'), 2000042=rel(anchor='NP Camus, Cecilia', type='REPRESENTATIVE', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus '),
2000043=rel(anchor='?? Test PS', type='OPERATIONS', holder='?? Test PS', contact='Petra Schmidt, Test PS'), 2000043=rel(anchor='?? Wasserwerk Südholstein', type='REPRESENTATIVE', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg , Wasserwerk Südholstein'),
2000044=rel(anchor='?? Test PS', type='REPRESENTATIVE', holder='?? Test PS', contact='Petra Schmidt, Test PS'), 2000044=rel(anchor='?? Wasserwerk Südholstein', type='SUBSCRIBER', mark='generalversammlung', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg , Wasserwerk Südholstein'),
2000045=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='operations-announce', holder='NP Fanninga, Frauke', contact='Frau Frauke Fanninga'), 2000045=rel(anchor='?? Wasserwerk Südholstein', type='SUBSCRIBER', mark='members-announce', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg , Wasserwerk Südholstein'),
2000046=rel(anchor='NP Camus, Cecilia', type='OPERATIONS_ALERT', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus'), 2000046=rel(anchor='?? Wasserwerk Südholstein', type='SUBSCRIBER', mark='members-discussion', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg , Wasserwerk Südholstein'),
2000047=rel(anchor='NP Camus, Cecilia', type='OPERATIONS', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus'), 2000047=rel(anchor='?? Das Perfekte Haus', type='OPERATIONS', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese , Das Perfekte Haus'),
2000048=rel(anchor='NP Camus, Cecilia', type='REPRESENTATIVE', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus'), 2000048=rel(anchor='?? Das Perfekte Haus', type='REPRESENTATIVE', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese , Das Perfekte Haus'),
2000049=rel(anchor='?? Wasserwerk Südholstein', type='REPRESENTATIVE', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg, Wasserwerk Südholstein'), 2000049=rel(anchor='?? Das Perfekte Haus', type='SUBSCRIBER', mark='operations-discussion', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese , Das Perfekte Haus'),
2000050=rel(anchor='?? Wasserwerk Südholstein', type='SUBSCRIBER', mark='generalversammlung', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg, Wasserwerk Südholstein'), 2000050=rel(anchor='?? Das Perfekte Haus', type='SUBSCRIBER', mark='operations-announce', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese , Das Perfekte Haus'),
2000051=rel(anchor='?? Wasserwerk Südholstein', type='SUBSCRIBER', mark='members-announce', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg, Wasserwerk Südholstein'), 2000051=rel(anchor='?? Das Perfekte Haus', type='SUBSCRIBER', mark='generalversammlung', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese , Das Perfekte Haus'),
2000052=rel(anchor='?? Wasserwerk Südholstein', type='SUBSCRIBER', mark='members-discussion', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg, Wasserwerk Südholstein'), 2000052=rel(anchor='?? Das Perfekte Haus', type='SUBSCRIBER', mark='members-announce', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese , Das Perfekte Haus'),
2000053=rel(anchor='?? Das Perfekte Haus', type='OPERATIONS_ALERT', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese, Das Perfekte Haus'), 2000053=rel(anchor='?? Das Perfekte Haus', type='SUBSCRIBER', mark='members-discussion', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese , Das Perfekte Haus'),
2000054=rel(anchor='?? Das Perfekte Haus', type='OPERATIONS', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese, Das Perfekte Haus'), 2000054=rel(anchor='?? Wasserwerk Südholstein', type='OPERATIONS', holder='?? Wasswerwerk Südholstein', contact='Herr Karim Metzger , Wasswerwerk Südholstein'),
2000055=rel(anchor='?? Das Perfekte Haus', type='REPRESENTATIVE', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese, Das Perfekte Haus'), 2000055=rel(anchor='?? Wasserwerk Südholstein', type='SUBSCRIBER', mark='operations-discussion', holder='?? Wasswerwerk Südholstein', contact='Herr Karim Metzger , Wasswerwerk Südholstein'),
2000056=rel(anchor='?? Das Perfekte Haus', type='SUBSCRIBER', mark='operations-discussion', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese, Das Perfekte Haus'), 2000056=rel(anchor='?? Wasserwerk Südholstein', type='SUBSCRIBER', mark='operations-announce', holder='?? Wasswerwerk Südholstein', contact='Herr Karim Metzger , Wasswerwerk Südholstein'),
2000057=rel(anchor='?? Das Perfekte Haus', type='SUBSCRIBER', mark='operations-announce', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese, Das Perfekte Haus'), 2000057=rel(anchor='?? Ragnar IT-Beratung', type='REPRESENTATIVE', holder='NP Richter, Ragnar', contact='Ragnar Richter '),
2000058=rel(anchor='?? Das Perfekte Haus', type='SUBSCRIBER', mark='generalversammlung', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese, Das Perfekte Haus'), 2000058=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='generalversammlung', holder='NP Richter, Ragnar', contact='Ragnar Richter '),
2000059=rel(anchor='?? Das Perfekte Haus', type='SUBSCRIBER', mark='members-announce', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese, Das Perfekte Haus'), 2000059=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='members-announce', holder='NP Richter, Ragnar', contact='Ragnar Richter '),
2000060=rel(anchor='?? Das Perfekte Haus', type='SUBSCRIBER', mark='members-discussion', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese, Das Perfekte Haus'), 2000060=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='members-discussion', holder='NP Richter, Ragnar', contact='Ragnar Richter '),
2000061=rel(anchor='?? Wasserwerk Südholstein', type='OPERATIONS_ALERT', holder='?? Wasswerwerk Südholstein', contact='Herr Karim Metzger, Wasswerwerk Südholstein'), 2000061=rel(anchor='?? Ragnar IT-Beratung', type='OPERATIONS', holder='NP Henning, Eike', contact='Eike Henning '),
2000062=rel(anchor='?? Wasserwerk Südholstein', type='OPERATIONS', holder='?? Wasswerwerk Südholstein', contact='Herr Karim Metzger, Wasswerwerk Südholstein'), 2000062=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='operations-discussion', holder='NP Henning, Eike', contact='Eike Henning '),
2000063=rel(anchor='?? Wasserwerk Südholstein', type='SUBSCRIBER', mark='operations-discussion', holder='?? Wasswerwerk Südholstein', contact='Herr Karim Metzger, Wasswerwerk Südholstein'), 2000063=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='operations-announce', holder='NP Henning, Eike', contact='Eike Henning '),
2000064=rel(anchor='?? Wasserwerk Südholstein', type='SUBSCRIBER', mark='operations-announce', holder='?? Wasswerwerk Südholstein', contact='Herr Karim Metzger, Wasswerwerk Südholstein'), 2000064=rel(anchor='?? Ragnar IT-Beratung', type='OPERATIONS', holder='NP Henning, Jan', contact='Jan Henning ')
2000065=rel(anchor='?? Ragnar IT-Beratung', type='REPRESENTATIVE', holder='NP Richter, Ragnar', contact='Ragnar Richter'),
2000066=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='generalversammlung', holder='NP Richter, Ragnar', contact='Ragnar Richter'),
2000067=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='members-announce', holder='NP Richter, Ragnar', contact='Ragnar Richter'),
2000068=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='members-discussion', holder='NP Richter, Ragnar', contact='Ragnar Richter'),
2000069=rel(anchor='?? Ragnar IT-Beratung', type='OPERATIONS_ALERT', holder='NP Henning, Eike', contact='Eike Henning'),
2000070=rel(anchor='?? Ragnar IT-Beratung', type='OPERATIONS', holder='NP Henning, Eike', contact='Eike Henning'),
2000071=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='operations-discussion', holder='NP Henning, Eike', contact='Eike Henning'),
2000072=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='operations-announce', holder='NP Henning, Eike', contact='Eike Henning'),
2000073=rel(anchor='?? Ragnar IT-Beratung', type='OPERATIONS_ALERT', holder='NP Henning, Jan', contact='Jan Henning'),
2000074=rel(anchor='?? Ragnar IT-Beratung', type='OPERATIONS', holder='NP Henning, Jan', contact='Jan Henning')
} }
"""); """);
} }
@ -455,7 +442,7 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
34002=CoopAssetsTransaction(M-1002000: 2016-12-31, DISBURSAL, -100.00, 1002000, 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, 1002000, 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, 1909000, for subscription E), 35001=CoopAssetsTransaction(M-1909000: 2024-01-15, DEPOSIT, 128.00, 1909000, for subscription E),
35002=CoopAssetsTransaction(M-1909000: 2024-01-20, REVERSAL, -128.00, 1909000, 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),
358=CoopAssetsTransaction(M-1000300: 2000-12-06, DEPOSIT, 5120, 1000300, for subscription A), 358=CoopAssetsTransaction(M-1000300: 2000-12-06, DEPOSIT, 5120, 1000300, for subscription A),
442=CoopAssetsTransaction(M-1015200: 2003-07-07, DEPOSIT, 64, 1015200), 442=CoopAssetsTransaction(M-1015200: 2003-07-07, DEPOSIT, 64, 1015200),
577=CoopAssetsTransaction(M-1000300: 2011-12-12, DEPOSIT, 1024, 1000300), 577=CoopAssetsTransaction(M-1000300: 2011-12-12, DEPOSIT, 1024, 1000300),
@ -515,7 +502,7 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
// this happens if a natural person is marked as 'contractual' for itself // this happens if a natural person is marked as 'contractual' for itself
final var idsToRemove = new HashSet<Integer>(); final var idsToRemove = new HashSet<Integer>();
relations.forEach((id, r) -> { relations.forEach((id, r) -> {
if (r.getType() == HsOfficeRelationType.REPRESENTATIVE && r.getHolder() == r.getAnchor()) { if (r.getHolder() == r.getAnchor()) {
idsToRemove.add(id); idsToRemove.add(id);
} }
}); });
@ -683,7 +670,7 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
jpaAttempt.transacted(() -> { jpaAttempt.transacted(() -> {
context(rbacSuperuser); context(rbacSuperuser);
coopShares.forEach(this::persist); coopShares.forEach(this::persist);
updateLegacyIds(coopShares, "hs_office.coopsharetx_legacy_id", "member_share_id"); updateLegacyIds(coopShares, "hs_office.coopsharestransaction_legacy_id", "member_share_id");
}).assertSuccessful(); }).assertSuccessful();
@ -808,23 +795,23 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
? HsOfficeCoopSharesTransactionType.SUBSCRIPTION ? HsOfficeCoopSharesTransactionType.SUBSCRIPTION
: "UNSUBSCRIPTION".equals(rec.getString("action")) : "UNSUBSCRIPTION".equals(rec.getString("action"))
? HsOfficeCoopSharesTransactionType.CANCELLATION ? HsOfficeCoopSharesTransactionType.CANCELLATION
: HsOfficeCoopSharesTransactionType.REVERSAL : HsOfficeCoopSharesTransactionType.ADJUSTMENT
) )
.shareCount(rec.getInteger("quantity")) .shareCount(rec.getInteger("quantity"))
.comment(rec.getString("comment")) .comment(rec.getString("comment"))
.reference(member.getMemberNumber().toString()) .reference(member.getMemberNumber().toString())
.build(); .build();
if (shareTransaction.getTransactionType() == HsOfficeCoopSharesTransactionType.REVERSAL) { if (shareTransaction.getTransactionType() == HsOfficeCoopSharesTransactionType.ADJUSTMENT) {
final var negativeValue = -shareTransaction.getShareCount(); final var negativeValue = -shareTransaction.getShareCount();
final var revertedShareTx = coopShares.values().stream().filter(a -> final var adjustedShareTx = coopShares.values().stream().filter(a ->
a.getTransactionType() != HsOfficeCoopSharesTransactionType.REVERSAL && a.getTransactionType() != HsOfficeCoopSharesTransactionType.ADJUSTMENT &&
a.getMembership() == shareTransaction.getMembership() && a.getMembership() == shareTransaction.getMembership() &&
a.getShareCount() == negativeValue) a.getShareCount() == negativeValue)
.findAny() .findAny()
.orElseThrow(() -> new IllegalStateException( .orElseThrow(() -> new IllegalStateException(
"cannot determine share reverse entry for reversal " + shareTransaction)); "cannot determine share reverse entry for adjustment " + shareTransaction));
shareTransaction.setRevertedShareTx(revertedShareTx); shareTransaction.setAdjustedShareTx(adjustedShareTx);
} }
coopShares.put(rec.getInteger("member_share_id"), shareTransaction); coopShares.put(rec.getInteger("member_share_id"), shareTransaction);
}); });
@ -850,7 +837,7 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
final var assetTypeMapping = new HashMap<String, HsOfficeCoopAssetsTransactionType>() { final var assetTypeMapping = new HashMap<String, HsOfficeCoopAssetsTransactionType>() {
{ {
put("ADJUSTMENT", HsOfficeCoopAssetsTransactionType.REVERSAL); put("ADJUSTMENT", HsOfficeCoopAssetsTransactionType.ADJUSTMENT);
put("HANDOVER", HsOfficeCoopAssetsTransactionType.TRANSFER); put("HANDOVER", HsOfficeCoopAssetsTransactionType.TRANSFER);
put("ADOPTION", HsOfficeCoopAssetsTransactionType.ADOPTION); put("ADOPTION", HsOfficeCoopAssetsTransactionType.ADOPTION);
put("LOSS", HsOfficeCoopAssetsTransactionType.LOSS); put("LOSS", HsOfficeCoopAssetsTransactionType.LOSS);
@ -878,16 +865,16 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
.reference(member.getMemberNumber().toString()) .reference(member.getMemberNumber().toString())
.build(); .build();
if (assetTransaction.getTransactionType() == HsOfficeCoopAssetsTransactionType.REVERSAL) { if (assetTransaction.getTransactionType() == HsOfficeCoopAssetsTransactionType.ADJUSTMENT) {
final var negativeValue = assetTransaction.getAssetValue().negate(); final var negativeValue = assetTransaction.getAssetValue().negate();
final var revertedAssetTx = coopAssets.values().stream().filter(a -> final var adjustedAssetTx = coopAssets.values().stream().filter(a ->
a.getTransactionType() != HsOfficeCoopAssetsTransactionType.REVERSAL && a.getTransactionType() != HsOfficeCoopAssetsTransactionType.ADJUSTMENT &&
a.getMembership() == assetTransaction.getMembership() && a.getMembership() == assetTransaction.getMembership() &&
a.getAssetValue().equals(negativeValue)) a.getAssetValue().equals(negativeValue))
.findAny() .findAny()
.orElseThrow(() -> new IllegalStateException( .orElseThrow(() -> new IllegalStateException(
"cannot determine asset reverse entry for reversal " + assetTransaction)); "cannot determine asset reverse entry for adjustment " + assetTransaction));
assetTransaction.setRevertedAssetTx(revertedAssetTx); assetTransaction.setAdjustedAssetTx(adjustedAssetTx);
} }
coopAssets.put(rec.getInteger("member_asset_id"), assetTransaction); coopAssets.put(rec.getInteger("member_asset_id"), assetTransaction);
@ -971,9 +958,6 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
HsOfficePersonEntity contactPerson = partnerPerson; HsOfficePersonEntity contactPerson = partnerPerson;
if (!StringUtils.equals(rec.getString("firma"), partnerPerson.getTradeName()) || if (!StringUtils.equals(rec.getString("firma"), partnerPerson.getTradeName()) ||
partnerPerson.getPersonType() != determinePersonType(rec) ||
!StringUtils.equals(rec.getString("title"), partnerPerson.getTitle()) ||
!StringUtils.equals(rec.getString("salut"), partnerPerson.getSalutation()) ||
!StringUtils.equals(rec.getString("first_name"), partnerPerson.getGivenName()) || !StringUtils.equals(rec.getString("first_name"), partnerPerson.getGivenName()) ||
!StringUtils.equals(rec.getString("last_name"), partnerPerson.getFamilyName())) { !StringUtils.equals(rec.getString("last_name"), partnerPerson.getFamilyName())) {
contactPerson = addPerson(HsOfficePersonEntity.builder().build(), rec); contactPerson = addPerson(HsOfficePersonEntity.builder().build(), rec);
@ -992,10 +976,6 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
debitor.getDebitorRel().setContact(contact); debitor.getDebitorRel().setContact(contact);
} }
if (containsRole(rec, "operation")) { if (containsRole(rec, "operation")) {
addRelation(HsOfficeRelationType.OPERATIONS_ALERT, partnerPerson, contactPerson, contact);
addRelation(HsOfficeRelationType.OPERATIONS, partnerPerson, contactPerson, contact);
}
if (containsRole(rec, "silent")) {
addRelation(HsOfficeRelationType.OPERATIONS, partnerPerson, contactPerson, contact); addRelation(HsOfficeRelationType.OPERATIONS, partnerPerson, contactPerson, contact);
} }
if (containsRole(rec, "contractual")) { if (containsRole(rec, "contractual")) {
@ -1073,60 +1053,34 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
} }
private HsOfficePersonEntity addPerson(final HsOfficePersonEntity person, final Record contactRecord) { private HsOfficePersonEntity addPerson(final HsOfficePersonEntity person, final Record contactRecord) {
person.setSalutation(contactRecord.getString("salut")); // TODO: title+salutation: add to person
person.setTitle(contactRecord.getString("title"));
person.setGivenName(contactRecord.getString("first_name")); person.setGivenName(contactRecord.getString("first_name"));
person.setFamilyName(contactRecord.getString("last_name")); person.setFamilyName(contactRecord.getString("last_name"));
person.setTradeName(contactRecord.getString("firma")); person.setTradeName(contactRecord.getString("firma"));
person.setPersonType(determinePersonType(contactRecord)); determinePersonType(person, contactRecord.getString("roles"));
persons.put(contactRecord.getInteger("contact_id"), person); persons.put(contactRecord.getInteger("contact_id"), person);
return person; return person;
} }
private static HsOfficePersonType determinePersonType(final Record contactRecord) { private static void determinePersonType(final HsOfficePersonEntity person, final String roles) {
String roles = contactRecord.getString("roles"); if (person.getTradeName().isBlank()) {
String country = contactRecord.getString("country"); person.setPersonType(HsOfficePersonType.NATURAL_PERSON);
String familyName = contactRecord.getString("last_name");
String givenName = contactRecord.getString("first_name");
String tradeName = contactRecord.getString("firma");
if (PERSON_TYPES_BY_CONTACT.containsKey(contactRecord.getInteger("contact_id"))) {
return PERSON_TYPES_BY_CONTACT.get(contactRecord.getInteger("contact_id"));
}
if (tradeName.isBlank() || tradeName.startsWith("verstorben")) {
return HsOfficePersonType.NATURAL_PERSON;
} else } else
// contractual && !partner with a firm and a natural person name // contractual && !partner with a firm and a natural person name
// should actually be split up into two persons // should actually be split up into two persons
// but the legacy database consists such records // but the legacy database consists such records
if (roles.contains("contractual") && !roles.contains("partner") &&
if (endsWithWord(tradeName, "OHG", "GbR", "KG", "UG", "PartGmbB", "mbB")) { !person.getFamilyName().isBlank() && !person.getGivenName().isBlank()) {
return HsOfficePersonType.INCORPORATED_FIRM; // Personengesellschaft. Gesellschafter haften persönlich. person.setPersonType(HsOfficePersonType.NATURAL_PERSON);
} else if (containsWord(tradeName, "e.K.", "e.G.", "eG", "gGmbH", "GmbH", "mbH", "AG", "e.V.", "eV", "e.V") } else if (endsWithWord(person.getTradeName(), "e.K.", "e.G.", "eG", "GmbH", "AG", "KG")) {
|| tradeName.toLowerCase().contains("haftungsbeschränkt") person.setPersonType(HsOfficePersonType.LEGAL_PERSON);
|| tradeName.toLowerCase().contains("stiftung") } else if (endsWithWord(person.getTradeName(), "OHG")) {
|| tradeName.toLowerCase().contains("stichting") person.setPersonType(HsOfficePersonType.INCORPORATED_FIRM);
|| tradeName.toLowerCase().contains("foundation") } else if (endsWithWord(person.getTradeName(), "GbR")) {
|| tradeName.toLowerCase().contains("schule") person.setPersonType(HsOfficePersonType.INCORPORATED_FIRM);
|| tradeName.toLowerCase().contains("verein")
|| tradeName.toLowerCase().contains("gewerkschaft")
|| tradeName.toLowerCase().contains("gesellschaft")
|| tradeName.toLowerCase().contains("kirche")
|| tradeName.toLowerCase().contains("fraktion")
|| tradeName.toLowerCase().contains("landkreis")
|| tradeName.toLowerCase().contains("behörde")
|| tradeName.toLowerCase().contains("bundesamt")
|| tradeName.toLowerCase().contains("bezirksamt")
) {
return HsOfficePersonType.LEGAL_PERSON; // Haftungsbeschränkt
} else if (roles.contains("contractual") && !roles.contains("partner") &&
!familyName.isBlank() && !givenName.isBlank()) {
// REPRESENTATIVES are always natural persons
return HsOfficePersonType.NATURAL_PERSON;
} else { } else {
return HsOfficePersonType.UNKNOWN_PERSON_TYPE; person.setPersonType(HsOfficePersonType.UNKNOWN_PERSON_TYPE);
} }
} }
@ -1140,19 +1094,6 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
return false; return false;
} }
private static boolean containsWord(final String value, final String... endings) {
final var lowerCaseValue = value.toLowerCase();
for (String ending : endings) {
if (lowerCaseValue.equals(ending.toLowerCase()) ||
lowerCaseValue.startsWith(ending.toLowerCase() + " ") ||
lowerCaseValue.contains(" " + ending.toLowerCase() + " ") ||
lowerCaseValue.endsWith(" " + ending.toLowerCase())) {
return true;
}
}
return false;
}
private void verifyContainsOnlyKnownRoles(final String roles) { private void verifyContainsOnlyKnownRoles(final String roles) {
final var allowedRolesSet = stream(KNOWN_ROLES).collect(Collectors.toSet()); final var allowedRolesSet = stream(KNOWN_ROLES).collect(Collectors.toSet());
final var givenRolesSet = stream(roles.replace(" ", "").split(",")).collect(Collectors.toSet()); final var givenRolesSet = stream(roles.replace(" ", "").split(",")).collect(Collectors.toSet());
@ -1170,7 +1111,7 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
contactRecord.getString("last_name"), contactRecord.getString("last_name"),
contactRecord.getString("firma"))); contactRecord.getString("firma")));
contact.putEmailAddresses(Map.of("main", contactRecord.getString("email"))); contact.putEmailAddresses(Map.of("main", contactRecord.getString("email")));
contact.putPostalAddress(toAddress(contactRecord)); contact.setPostalAddress(toAddress(contactRecord));
contact.putPhoneNumbers(toPhoneNumbers(contactRecord)); contact.putPhoneNumbers(toPhoneNumbers(contactRecord));
contacts.put(contactRecord.getInteger("contact_id"), contact); contacts.put(contactRecord.getInteger("contact_id"), contact);
@ -1190,23 +1131,36 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
return phoneNumbers; return phoneNumbers;
} }
private Map<String, String> toAddress(final Record rec) { private String toAddress(final Record rec) {
final var result = new LinkedHashMap<String, String>(); final var result = new StringBuilder();
final var name = toName( final var name = toName(
rec.getString("salut"), rec.getString("salut"),
rec.getString("title"), rec.getString("title"),
rec.getString("first_name"), rec.getString("first_name"),
rec.getString("last_name")); rec.getString("last_name"));
if (isNotBlank(name)) if (isNotBlank(name))
result.put("name", name); result.append(name + "\n");
if (isNotBlank(rec.getString("firma"))) if (isNotBlank(rec.getString("firma")))
result.put("firm", name); result.append(rec.getString("firma") + "\n");
if (isNotBlank(rec.getString("co")))
result.append("c/o " + rec.getString("co") + "\n");
if (isNotBlank(rec.getString("street")))
result.append(rec.getString("street") + "\n");
final var zipcodeAndCity = toZipcodeAndCity(rec);
if (isNotBlank(zipcodeAndCity))
result.append(zipcodeAndCity + "\n");
return result.toString();
}
List.of("co", "street", "zipcode", "city", "country").forEach(key -> { private String toZipcodeAndCity(final Record rec) {
if (isNotBlank(rec.getString(key))) final var result = new StringBuilder();
result.put(key, rec.getString(key)); if (isNotBlank(rec.getString("country")))
}); result.append(rec.getString("country") + " ");
return result; if (isNotBlank(rec.getString("zipcode")))
result.append(rec.getString("zipcode") + " ");
if (isNotBlank(rec.getString("city")))
result.append(rec.getString("city"));
return result.toString();
} }
private String toCaption( private String toCaption(
@ -1217,13 +1171,13 @@ public abstract class BaseOfficeDataImport extends CsvDataImport {
final String firm) { final String firm) {
final var result = new StringBuilder(); final var result = new StringBuilder();
if (isNotBlank(salut)) if (isNotBlank(salut))
result.append((isBlank(result) ? "" : " ") + salut); result.append(salut + " ");
if (isNotBlank(title)) if (isNotBlank(title))
result.append((isBlank(result) ? "" : " ") + title); result.append(title + " ");
if (isNotBlank(firstname)) if (isNotBlank(firstname))
result.append((isBlank(result) ? "" : " ") + firstname); result.append(firstname + " ");
if (isNotBlank(lastname)) if (isNotBlank(lastname))
result.append((isBlank(result) ? "" : " ") + lastname); result.append(lastname + " ");
if (isNotBlank(firm)) { if (isNotBlank(firm)) {
result.append((isBlank(result) ? "" : ", ") + firm); result.append((isBlank(result) ? "" : ", ") + firm);
} }

View File

@ -255,7 +255,7 @@ public class CsvDataImport extends ContextBasedTest {
em.createNativeQuery("delete from hs_office.coopassettx where true").executeUpdate(); em.createNativeQuery("delete from hs_office.coopassettx where true").executeUpdate();
em.createNativeQuery("delete from hs_office.coopassettx_legacy_id where true").executeUpdate(); em.createNativeQuery("delete from hs_office.coopassettx_legacy_id where true").executeUpdate();
em.createNativeQuery("delete from hs_office.coopsharetx where true").executeUpdate(); em.createNativeQuery("delete from hs_office.coopsharetx where true").executeUpdate();
em.createNativeQuery("delete from hs_office.coopsharetx_legacy_id where true").executeUpdate(); em.createNativeQuery("delete from hs_office.coopsharestransaction_legacy_id where true").executeUpdate();
em.createNativeQuery("delete from hs_office.membership where true").executeUpdate(); em.createNativeQuery("delete from hs_office.membership where true").executeUpdate();
em.createNativeQuery("delete from hs_office.sepamandate where true").executeUpdate(); em.createNativeQuery("delete from hs_office.sepamandate where true").executeUpdate();
em.createNativeQuery("delete from hs_office.sepamandate_legacy_id where true").executeUpdate(); em.createNativeQuery("delete from hs_office.sepamandate_legacy_id where true").executeUpdate();
@ -275,7 +275,7 @@ public class CsvDataImport extends ContextBasedTest {
em.createNativeQuery("alter sequence hs_office.contact_legacy_id_seq restart with 1000000000;").executeUpdate(); em.createNativeQuery("alter sequence hs_office.contact_legacy_id_seq restart with 1000000000;").executeUpdate();
em.createNativeQuery("alter sequence hs_office.coopassettx_legacy_id_seq restart with 1000000000;") em.createNativeQuery("alter sequence hs_office.coopassettx_legacy_id_seq restart with 1000000000;")
.executeUpdate(); .executeUpdate();
em.createNativeQuery("alter sequence public.hs_office.coopsharetx_legacy_id_seq restart with 1000000000;") em.createNativeQuery("alter sequence public.hs_office.coopsharestransaction_legacy_id_seq restart with 1000000000;")
.executeUpdate(); .executeUpdate();
em.createNativeQuery("alter sequence public.hs_office.partner_legacy_id_seq restart with 1000000000;") em.createNativeQuery("alter sequence public.hs_office.partner_legacy_id_seq restart with 1000000000;")
.executeUpdate(); .executeUpdate();

Some files were not shown because too many files have changed in this diff Show More