hosting-asset-data-migration (#79)

Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: #79
Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
Michael Hoennig 2024-07-22 11:30:33 +02:00
parent c191af2ea1
commit 4d27a98c9a
51 changed files with 1761 additions and 602 deletions

View File

@ -1,4 +1,4 @@
# For using the alias import-office-tables, # For using the alias gw-importOfficeData or gw-importHostingAssets,
# copy the file .tc-environment to .environment (ignored by git) # copy the file .tc-environment to .environment (ignored by git)
# and amend them according to your external DB. # and amend them according to your external DB.
@ -42,19 +42,29 @@ postgresAutodoc () {
} }
alias postgres-autodoc=postgresAutodoc alias postgres-autodoc=postgresAutodoc
function importOfficeData() { function importLegacyData() {
source .tc-environment export target=$1
if [ -z "$target" ]; then
if [ -f .environment ]; then echo "importLegacyData needs target argument, but none was given" >&2
source .environment else
fi source .tc-environment
echo "using environment (with ending ';' for use in IntelliJ IDEA):" if [ -f .environment ]; then
set | grep ^HSADMINNG_ | sed 's/$/;/' source .environment
fi
./gradlew importOfficeData --rerun echo "using environment (with ending ';' for use in IntelliJ IDEA):"
echo "--- BEGIN: ---"
set | grep ^HSADMINNG_ | sed 's/$/;/'
echo "---- END. ----"
echo
echo ./gradlew $target --rerun
./gradlew $target --rerun
fi
} }
alias gw-importOfficeData=importOfficeData alias gw-importOfficeData='importLegacyData importOfficeData'
alias gw-importHostingAssets='importLegacyData importHostingAssets'
alias podman-start='systemctl --user enable --now podman.socket && systemctl --user status podman.socket && ls -la /run/user/$UID/podman/podman.sock' alias podman-start='systemctl --user enable --now podman.socket && systemctl --user status podman.socket && ls -la /run/user/$UID/podman/podman.sock'
alias podman-stop='systemctl --user disable --now podman.socket && systemctl --user status podman.socket && ls -la /run/user/$UID/podman/podman.sock' alias podman-stop='systemctl --user disable --now podman.socket && systemctl --user status podman.socket && ls -la /run/user/$UID/podman/podman.sock'

5
.gitignore vendored
View File

@ -136,4 +136,9 @@ Desktop.ini
# ESLint # ESLint
###################### ######################
.eslintcache .eslintcache
######################
# Project Related
######################
/.environment* /.environment*
/src/test/resources/migration-prod/*

View File

@ -318,7 +318,7 @@ jacocoTestCoverageVerification {
tasks.register('importOfficeData', Test) { tasks.register('importOfficeData', Test) {
useJUnitPlatform { useJUnitPlatform {
includeTags 'import' includeTags 'importOfficeData'
} }
group 'verification' group 'verification'
@ -327,6 +327,16 @@ tasks.register('importOfficeData', Test) {
mustRunAfter spotlessJava mustRunAfter spotlessJava
} }
tasks.register('importHostingAssets', Test) {
useJUnitPlatform {
includeTags 'importHostingAssets'
}
group 'verification'
description 'run the import jobs as tests'
mustRunAfter spotlessJava
}
// pitest mutation testing // pitest mutation testing
pitest { pitest {

View File

@ -18,7 +18,7 @@ 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
@Table(name = "hs_booking_debitor_rv") @Table(name = "hs_booking_debitor_xv")
@Getter @Getter
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor

View File

@ -184,7 +184,9 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject, Propertie
} }
public HsBookingProjectEntity getRelatedProject() { public HsBookingProjectEntity getRelatedProject() {
return project != null ? project : parentItem.getRelatedProject(); return project != null ? project
: parentItem != null ? parentItem.getRelatedProject()
: null; // can be the case for technical assets like IP-numbers
} }
public static RbacView rbac() { public static RbacView rbac() {

View File

@ -22,6 +22,10 @@ public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingIte
@Override @Override
public List<String> validateEntity(final HsBookingItemEntity bookingItem) { public List<String> validateEntity(final HsBookingItemEntity bookingItem) {
// TODO.impl: HsBookingItemType could do this similar to HsHostingAssetType
if ( bookingItem.getParentItem() == null && bookingItem.getProject() == null) {
return List.of(bookingItem + ".'parentItem' or .'project' expected to be set, but both are null");
}
return enrich(prefix(bookingItem.toShortString(), "resources"), super.validateProperties(bookingItem)); return enrich(prefix(bookingItem.toShortString(), "resources"), super.validateProperties(bookingItem));
} }

View File

@ -11,11 +11,12 @@ class HsCloudServerBookingItemValidator extends HsBookingItemEntityValidator {
// @formatter:off // @formatter:off
booleanProperty("active") .withDefault(true), booleanProperty("active") .withDefault(true),
integerProperty("CPUs") .min( 1) .max( 32) .required(), integerProperty("CPU") .min( 1) .max( 32) .required(),
integerProperty("RAM").unit("GB") .min( 1) .max( 128) .required(), integerProperty("RAM").unit("GB") .min( 1) .max( 8192) .required(),
integerProperty("SSD").unit("GB") .min( 0) .max( 1000) .step(25).required(), // (1) integerProperty("SSD").unit("GB") .min( 25) .max( 1000) .step(25).requiresAtLeastOneOf("SDD", "HDD"),
integerProperty("HDD").unit("GB") .min( 0) .max( 4000) .step(250).withDefault(0), integerProperty("HDD").unit("GB") .min(250) .max( 4000) .step(250).requiresAtLeastOneOf("SSD", "HDD"),
integerProperty("Traffic").unit("GB") .min(250) .max(10000) .step(250).required(), integerProperty("Traffic").unit("GB") .min(250) .max(10000) .step(250).requiresAtMaxOneOf("Bandwidth", "Traffic"),
integerProperty("Bandwidth").unit("GB") .min(250) .max(10000) .step(250).requiresAtMaxOneOf("Bandwidth", "Traffic"), // TODO.spec
enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional() enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional()
// @formatter:on // @formatter:on

View File

@ -10,11 +10,12 @@ class HsManagedServerBookingItemValidator extends HsBookingItemEntityValidator {
HsManagedServerBookingItemValidator() { HsManagedServerBookingItemValidator() {
super( super(
integerProperty("CPUs").min(1).max(32).required(), integerProperty("CPU").min(1).max(32).required(),
integerProperty("RAM").unit("GB").min(1).max(128).required(), integerProperty("RAM").unit("GB").min(1).max(128).required(),
integerProperty("SSD").unit("GB").min(25).max(1000).step(25).required().asTotalLimit().withThreshold(200), integerProperty("SSD").unit("GB").min(25).max(2000).step(25).requiresAtLeastOneOf("SSD", "HDD").asTotalLimit().withThreshold(200),
integerProperty("HDD").unit("GB").min(0).max(4000).step(250).withDefault(0).asTotalLimit().withThreshold(200), integerProperty("HDD").unit("GB").min(250).max(10000).step(250).requiresAtLeastOneOf("SSD", "HDD").asTotalLimit().withThreshold(200),
integerProperty("Traffic").unit("GB").min(250).max(10000).step(250).required().asTotalLimit().withThreshold(200), integerProperty("Traffic").unit("GB").min(250).max(64000).step(250).requiresAtMaxOneOf("Bandwidth", "Traffic").asTotalLimit().withThreshold(200),
integerProperty("Bandwidth").unit("GB").min(250).max(64000).step(250).requiresAtMaxOneOf("Bandwidth", "Traffic").asTotalLimit().withThreshold(200), // TODO.spec
enumerationProperty("SLA-Platform").values("BASIC", "EXT8H", "EXT4H", "EXT2H").withDefault("BASIC"), enumerationProperty("SLA-Platform").values("BASIC", "EXT8H", "EXT4H", "EXT2H").withDefault("BASIC"),
booleanProperty("SLA-EMail").falseIf("SLA-Platform", "BASIC").withDefault(false), booleanProperty("SLA-EMail").falseIf("SLA-Platform", "BASIC").withDefault(false),
booleanProperty("SLA-Maria").falseIf("SLA-Platform", "BASIC").optional(), booleanProperty("SLA-Maria").falseIf("SLA-Platform", "BASIC").optional(),

View File

@ -23,16 +23,17 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator
public HsManagedWebspaceBookingItemValidator() { public HsManagedWebspaceBookingItemValidator() {
super( super(
integerProperty("SSD").unit("GB").min(1).max(100).step(1).required(), integerProperty("SSD").unit("GB").min(1).max(2000).step(1).required(),
integerProperty("HDD").unit("GB").min(0).max(250).step(10).optional(), integerProperty("HDD").unit("GB").min(0).max(10000).step(10).optional(),
integerProperty("Traffic").unit("GB").min(10).max(1000).step(10).required(), integerProperty("Traffic").unit("GB").min(10).max(64000).step(10).requiresAtMaxOneOf("Bandwidth", "Traffic"),
integerProperty("Bandwidth").unit("GB").min(10).max(1000).step(10).requiresAtMaxOneOf("Bandwidth", "Traffic"), // TODO.spec
integerProperty("Multi").min(1).max(100).step(1).withDefault(1) integerProperty("Multi").min(1).max(100).step(1).withDefault(1)
.eachComprising( 25, unixUsers()) .eachComprising( 25, unixUsers())
.eachComprising( 5, databaseUsers()) .eachComprising( 5, databaseUsers())
.eachComprising( 5, databases()) .eachComprising( 5, databases())
.eachComprising(250, eMailAddresses()), .eachComprising(250, eMailAddresses()),
integerProperty("Daemons").min(0).max(10).withDefault(0), integerProperty("Daemons").min(0).max(16).withDefault(0),
booleanProperty("Online Office Server").optional(), booleanProperty("Online Office Server").optional(), // TODO.impl: shorten to "Office"
enumerationProperty("SLA-Platform").values("BASIC", "EXT24H").withDefault("BASIC") enumerationProperty("SLA-Platform").values("BASIC", "EXT24H").withDefault("BASIC")
); );
} }

View File

@ -7,15 +7,16 @@ class HsPrivateCloudBookingItemValidator extends HsBookingItemEntityValidator {
HsPrivateCloudBookingItemValidator() { HsPrivateCloudBookingItemValidator() {
super( super(
// @formatter:off // @formatter:off
integerProperty("CPUs") .min( 1).max( 128).required().asTotalLimit(), integerProperty("CPU") .min( 1).max( 128).required().asTotalLimit(),
integerProperty("RAM").unit("GB") .min( 1).max( 512).required().asTotalLimit(), integerProperty("RAM").unit("GB") .min( 1).max( 512).required().asTotalLimit(),
integerProperty("SSD").unit("GB") .min( 25).max( 4000).step(25).required().asTotalLimit(), integerProperty("SSD").unit("GB") .min( 25).max( 4000).step(25).requiresAtLeastOneOf("SSD", "HDD").asTotalLimit(),
integerProperty("HDD").unit("GB") .min( 0).max(16000).step(250).withDefault(0).asTotalLimit(), integerProperty("HDD").unit("GB") .min(250).max(16000).step(250).requiresAtLeastOneOf("SSD", "HDD").asTotalLimit(),
integerProperty("Traffic").unit("GB") .min(250).max(40000).step(250).required().asTotalLimit(), integerProperty("Traffic").unit("GB") .min(250).max(64000).step(250).requiresAtMaxOneOf("Bandwidth", "Traffic").asTotalLimit(),
integerProperty("Bandwidth").unit("GB") .min(250).max(64000).step(250).requiresAtMaxOneOf("Bandwidth", "Traffic").asTotalLimit(), // TODO.spec
// Alternatively we could specify it similarly to "Multi" option but exclusively counting: // Alternatively we could specify it similarly to "Multi" option but exclusively counting:
// integerProperty("Resource-Points") .min(4).max(100).required() // integerProperty("Resource-Points") .min(4).max(100).required()
// .each("CPUs").countsAs(64) // .each("CPU").countsAs(64)
// .each("RAM").countsAs(64) // .each("RAM").countsAs(64)
// .each("SSD").countsAs(18) // .each("SSD").countsAs(18)
// .each("HDD").countsAs(2) // .each("HDD").countsAs(2)

View File

@ -8,6 +8,7 @@ import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity; import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
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;
@ -38,6 +39,7 @@ import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
@ -108,7 +110,7 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Properti
private HsOfficeContactEntity alarmContact; private HsOfficeContactEntity alarmContact;
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true, fetch = FetchType.LAZY) @OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true, fetch = FetchType.LAZY)
@JoinColumn(name="parentassetuuid", referencedColumnName="uuid") @JoinColumn(name = "parentassetuuid", referencedColumnName = "uuid")
private List<HsHostingAssetEntity> subHostingAssets; private List<HsHostingAssetEntity> subHostingAssets;
@Column(name = "identifier") @Column(name = "identifier")
@ -134,12 +136,20 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Properti
this.isLoaded = true; this.isLoaded = true;
} }
public HsBookingProjectEntity getRelatedProject() {
return Optional.ofNullable(bookingItem)
.map(HsBookingItemEntity::getRelatedProject)
.orElseGet(() -> Optional.ofNullable(parentAsset)
.map(HsHostingAssetEntity::getRelatedProject)
.orElse(null));
}
public PatchableMapWrapper<Object> getConfig() { public PatchableMapWrapper<Object> getConfig() {
return PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config ); return PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper;}, config);
} }
public void putConfig(Map<String, Object> newConfig) { public void putConfig(Map<String, Object> newConfig) {
PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config).assign(newConfig); PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper;}, config).assign(newConfig);
} }
@Override @Override
@ -150,20 +160,19 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Properti
@Override @Override
public Object getContextValue(final String propName) { public Object getContextValue(final String propName) {
final var v = config.get(propName); final var v = config.get(propName);
if (v!= null) { if (v != null) {
return v; return v;
} }
if (bookingItem!=null) { if (bookingItem != null) {
return bookingItem.getResources().get(propName); return bookingItem.getResources().get(propName);
} }
if (parentAsset!=null && parentAsset.getBookingItem()!=null) { if (parentAsset != null && parentAsset.getBookingItem() != null) {
return parentAsset.getBookingItem().getResources().get(propName); return parentAsset.getBookingItem().getResources().get(propName);
} }
return emptyMap(); return emptyMap();
} }
@Override @Override
public String toString() { public String toString() {
return stringify.apply(this); return stringify.apply(this);
@ -182,9 +191,9 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Properti
.toRole(GLOBAL, ADMIN).grantPermission(INSERT) // TODO.impl: Why is this necessary to insert test data? .toRole(GLOBAL, ADMIN).grantPermission(INSERT) // TODO.impl: Why is this necessary to insert test data?
.importEntityAlias("bookingItem", HsBookingItemEntity.class, usingDefaultCase(), .importEntityAlias("bookingItem", HsBookingItemEntity.class, usingDefaultCase(),
dependsOnColumn("bookingItemUuid"), dependsOnColumn("bookingItemUuid"),
directlyFetchedByDependsOnColumn(), directlyFetchedByDependsOnColumn(),
NULLABLE) NULLABLE)
.importEntityAlias("parentAsset", HsHostingAssetEntity.class, usingDefaultCase(), .importEntityAlias("parentAsset", HsHostingAssetEntity.class, usingDefaultCase(),
dependsOnColumn("parentAssetUuid"), dependsOnColumn("parentAssetUuid"),
@ -202,7 +211,8 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Properti
directlyFetchedByDependsOnColumn(), directlyFetchedByDependsOnColumn(),
NULLABLE) NULLABLE)
.switchOnColumn("type", .switchOnColumn(
"type",
inCaseOf("DOMAIN_SETUP", then -> { inCaseOf("DOMAIN_SETUP", then -> {
then.toRole(GLOBAL, GUEST).grantPermission(INSERT); then.toRole(GLOBAL, GUEST).grantPermission(INSERT);
}) })
@ -231,7 +241,14 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Properti
with.permission(SELECT); with.permission(SELECT);
}) })
.limitDiagramTo("asset", "bookingItem", "bookingItem.debitorRel", "parentAsset", "assignedToAsset", "alarmContact", "global"); .limitDiagramTo(
"asset",
"bookingItem",
"bookingItem.debitorRel",
"parentAsset",
"assignedToAsset",
"alarmContact",
"global");
} }
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {

View File

@ -18,7 +18,7 @@ class HsManagedWebspaceHostingAssetValidator extends HostingAssetEntityValidator
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) { protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
final var prefixPattern = final var prefixPattern =
!assetEntity.isLoaded() !assetEntity.isLoaded()
? assetEntity.getParentAsset().getBookingItem().getProject().getDebitor().getDefaultPrefix() ? assetEntity.getRelatedProject().getDebitor().getDefaultPrefix()
: "[a-z][a-z0-9][a-z0-9]"; : "[a-z][a-z0-9][a-z0-9]";
return Pattern.compile("^" + prefixPattern + "[0-9][0-9]$"); return Pattern.compile("^" + prefixPattern + "[0-9][0-9]$");
} }

View File

@ -68,7 +68,7 @@ public class HsOfficeMembershipEntity implements RbacObject, Stringifyable {
private static Stringify<HsOfficeMembershipEntity> stringify = stringify(HsOfficeMembershipEntity.class) private static Stringify<HsOfficeMembershipEntity> stringify = stringify(HsOfficeMembershipEntity.class)
.withProp(e -> MEMBER_NUMBER_TAG + e.getMemberNumber()) .withProp(e -> MEMBER_NUMBER_TAG + e.getMemberNumber())
.withProp(e -> e.getPartner().toShortString()) .withProp(HsOfficeMembershipEntity::getPartner)
.withProp(e -> e.getValidity().asString()) .withProp(e -> e.getValidity().asString())
.withProp(HsOfficeMembershipEntity::getStatus) .withProp(HsOfficeMembershipEntity::getStatus)
.quotedValues(false); .quotedValues(false);

View File

@ -13,10 +13,12 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import static java.lang.Boolean.FALSE; import static java.lang.Boolean.FALSE;
@ -30,7 +32,7 @@ import static org.apache.commons.lang3.ObjectUtils.isArray;
public abstract class ValidatableProperty<P extends ValidatableProperty<?, ?>, T> { public abstract class ValidatableProperty<P extends ValidatableProperty<?, ?>, T> {
protected static final String[] KEY_ORDER_HEAD = Array.of("propertyName"); protected static final String[] KEY_ORDER_HEAD = Array.of("propertyName");
protected static final String[] KEY_ORDER_TAIL = Array.of("required", "defaultValue", "readOnly", "writeOnly", "computed", "isTotalsValidator", "thresholdPercentage"); protected static final String[] KEY_ORDER_TAIL = Array.of("required", "requiresAtLeastOneOf", "requiresAtMaxOneOf", "defaultValue", "readOnly", "writeOnly", "computed", "isTotalsValidator", "thresholdPercentage");
protected static final String[] KEY_ORDER = Array.join(KEY_ORDER_HEAD, KEY_ORDER_TAIL); protected static final String[] KEY_ORDER = Array.join(KEY_ORDER_HEAD, KEY_ORDER_TAIL);
final Class<T> type; final Class<T> type;
@ -40,6 +42,8 @@ public abstract class ValidatableProperty<P extends ValidatableProperty<?, ?>, T
private final String[] keyOrder; private final String[] keyOrder;
private Boolean required; private Boolean required;
private Set<String> requiresAtLeastOneOf;
private Set<String> requiresAtMaxOneOf;
private T defaultValue; private T defaultValue;
@JsonIgnore @JsonIgnore
@ -100,9 +104,19 @@ protected void setDeferredInit(final Function<ValidatableProperty<?, ?>[], T[]>
return self(); return self();
} }
public ValidatableProperty<P, T> optional() { public P optional() {
required = FALSE; required = FALSE;
return this; return self();
}
public P requiresAtLeastOneOf(final String... propNames) {
requiresAtLeastOneOf = new LinkedHashSet<>(List.of(propNames));
return self();
}
public P requiresAtMaxOneOf(final String... propNames) {
requiresAtMaxOneOf = new LinkedHashSet<>(List.of(propNames));
return self();
} }
public P withDefault(final T value) { public P withDefault(final T value) {
@ -172,28 +186,57 @@ protected void setDeferredInit(final Function<ValidatableProperty<?, ?>[], T[]>
final var result = new ArrayList<String>(); final var result = new ArrayList<String>();
final var props = propsProvider.directProps(); final var props = propsProvider.directProps();
final var propValue = props.get(propertyName); final var propValue = props.get(propertyName);
if (propValue == null) { if (propValue == null) {
if (required) { if (required == TRUE) {
result.add(propertyName + "' is required but missing"); result.add(propertyName + "' is required but missing");
} }
validateRequiresAtLeastOneOf(result, propsProvider);
} }
if (propValue != null){ if (propValue != null){
validateRequiresAtMaxOneOf(result, propsProvider);
if ( type.isInstance(propValue)) { if ( type.isInstance(propValue)) {
//noinspection unchecked //noinspection unchecked
validate(result, (T) propValue, propsProvider); validate(result, (T) propValue, propsProvider);
} else { } else {
result.add(propertyName + "' is expected to be of type " + type.getSimpleName() + ", " + result.add(propertyName + "' is expected to be of type " + type.getSimpleName() + ", " +
"but is of type " + propValue.getClass().getSimpleName() + ""); "but is of type " + propValue.getClass().getSimpleName());
} }
} }
return result; return result;
} }
private void validateRequiresAtLeastOneOf(final ArrayList<String> result, final PropertiesProvider propsProvider) {
if (requiresAtLeastOneOf != null ) {
final var allPropNames = propsProvider.directProps().keySet();
final var entriesWithValue = allPropNames.stream()
.filter(name -> requiresAtLeastOneOf.contains(name))
.count();
if (entriesWithValue == 0) {
result.add(propertyName + "' is required once in group " + requiresAtLeastOneOf + " but missing");
}
}
}
private void validateRequiresAtMaxOneOf(final ArrayList<String> result, final PropertiesProvider propsProvider) {
if (requiresAtMaxOneOf != null) {
final var allPropNames = propsProvider.directProps().keySet();
final var entriesWithValue = allPropNames.stream()
.filter(name -> requiresAtMaxOneOf.contains(name))
.count();
if (entriesWithValue > 1) {
result.add(propertyName + "' is required at max once in group " + requiresAtMaxOneOf
+ " but multiple properties are set");
}
}
}
protected abstract void validate(final List<String> result, final T propValue, final PropertiesProvider propProvider); protected abstract void validate(final List<String> result, final T propValue, final PropertiesProvider propProvider);
public void verifyConsistency(final Map.Entry<? extends Enum<?>, ?> typeDef) { public void verifyConsistency(final Map.Entry<? extends Enum<?>, ?> typeDef) {
if (required == null ) { if (required == null && requiresAtLeastOneOf == null && requiresAtMaxOneOf == null) {
throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .required() or .optional()" ); throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .required(), .optional(), .withDefault(...), .requiresAtLeastOneOf(...) or .requiresAtMaxOneOf(...)" );
} }
} }

View File

@ -4,12 +4,12 @@
--changeset hs-booking-debitor-RESTRICTED-VIEW:1 endDelimiter:--// --changeset hs-booking-debitor-RESTRICTED-VIEW:1 endDelimiter:--//
-- ---------------------------------------------------------------------------- -- ----------------------------------------------------------------------------
create view hs_booking_debitor_rv as create view hs_booking_debitor_xv as
select debitor.uuid, select debitor.uuid,
debitor.version, debitor.version,
(partner.partnerNumber::varchar || debitor.debitorNumberSuffix)::numeric as debitorNumber, (partner.partnerNumber::varchar || debitor.debitorNumberSuffix)::numeric as debitorNumber,
debitor.defaultPrefix debitor.defaultPrefix
from hs_office_debitor_rv debitor from hs_office_debitor debitor
-- RBAC for debitor is sufficient, for faster access we are bypassing RBAC for the join tables -- RBAC for debitor is sufficient, for faster access we are bypassing RBAC for the join tables
join hs_office_relation debitorRel on debitor.debitorReluUid=debitorRel.uuid join hs_office_relation debitorRel on debitor.debitorReluUid=debitorRel.uuid
join hs_office_relation partnerRel on partnerRel.holderUuid=debitorRel.anchorUuid join hs_office_relation partnerRel on partnerRel.holderUuid=debitorRel.anchorUuid

View File

@ -33,11 +33,11 @@ begin
managedServerUuid := uuid_generate_v4(); managedServerUuid := uuid_generate_v4();
insert insert
into hs_booking_item (uuid, projectuuid, type, parentitemuuid, caption, validity, resources) into hs_booking_item (uuid, projectuuid, type, parentitemuuid, caption, validity, resources)
values (privateCloudUuid, relatedProject.uuid, 'PRIVATE_CLOUD', null, 'some PrivateCloud', daterange('20240401', null, '[]'), '{ "CPUs": 10, "RAM": 32, "SSD": 4000, "HDD": 10000, "Traffic": 2000 }'::jsonb), values (privateCloudUuid, relatedProject.uuid, 'PRIVATE_CLOUD', null, 'some PrivateCloud', daterange('20240401', null, '[]'), '{ "CPU": 10, "RAM": 32, "SSD": 4000, "HDD": 10000, "Traffic": 2000 }'::jsonb),
(uuid_generate_v4(), null, 'MANAGED_SERVER', privateCloudUuid, 'some ManagedServer', daterange('20230115', '20240415', '[)'), '{ "CPUs": 2, "RAM": 4, "SSD": 500, "Traffic": 500 }'::jsonb), (uuid_generate_v4(), null, 'MANAGED_SERVER', privateCloudUuid, 'some ManagedServer', daterange('20230115', '20240415', '[)'), '{ "CPU": 2, "RAM": 4, "SSD": 500, "Traffic": 500 }'::jsonb),
(uuid_generate_v4(), null, 'CLOUD_SERVER', privateCloudUuid, 'test CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPUs": 2, "RAM": 4, "SSD": 750, "Traffic": 500 }'::jsonb), (uuid_generate_v4(), null, 'CLOUD_SERVER', privateCloudUuid, 'test CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPU": 2, "RAM": 4, "SSD": 750, "Traffic": 500 }'::jsonb),
(uuid_generate_v4(), null, 'CLOUD_SERVER', privateCloudUuid, 'prod CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPUs": 4, "RAM": 16, "SSD": 1000, "Traffic": 500 }'::jsonb), (uuid_generate_v4(), null, 'CLOUD_SERVER', privateCloudUuid, 'prod CloudServer', daterange('20230115', '20240415', '[)'), '{ "CPU": 4, "RAM": 16, "SSD": 1000, "Traffic": 500 }'::jsonb),
(managedServerUuid, relatedProject.uuid, 'MANAGED_SERVER', null, 'separate ManagedServer', daterange('20221001', null, '[]'), '{ "CPUs": 2, "RAM": 8, "SSD": 500, "Traffic": 500 }'::jsonb), (managedServerUuid, relatedProject.uuid, 'MANAGED_SERVER', null, 'separate ManagedServer', daterange('20221001', null, '[]'), '{ "CPU": 2, "RAM": 8, "SSD": 500, "Traffic": 500 }'::jsonb),
(uuid_generate_v4(), null, 'MANAGED_WEBSPACE', managedServerUuid, 'some ManagedWebspace', daterange('20221001', null, '[]'), '{ "SSD": 50, "Traffic": 20, "Daemons": 2, "Multi": 4 }'::jsonb), (uuid_generate_v4(), null, 'MANAGED_WEBSPACE', managedServerUuid, 'some ManagedWebspace', daterange('20221001', null, '[]'), '{ "SSD": 50, "Traffic": 20, "Daemons": 2, "Multi": 4 }'::jsonb),
(uuid_generate_v4(), relatedProject.uuid, 'MANAGED_WEBSPACE', null, 'separate ManagedWebspace', daterange('20221001', null, '[]'), '{ "SSD": 100, "Traffic": 50, "Daemons": 0, "Multi": 1 }'::jsonb); (uuid_generate_v4(), relatedProject.uuid, 'MANAGED_WEBSPACE', null, 'separate ManagedWebspace', daterange('20221001', null, '[]'), '{ "SSD": 100, "Traffic": 50, "Daemons": 0, "Multi": 1 }'::jsonb);
end; $$; end; $$;

View File

@ -42,7 +42,7 @@ create table if not exists hs_hosting_asset
alarmContactUuid uuid null references hs_office_contact(uuid) initially deferred, alarmContactUuid uuid null references hs_office_contact(uuid) initially deferred,
constraint chk_hs_hosting_asset_has_booking_item_or_parent_asset constraint chk_hs_hosting_asset_has_booking_item_or_parent_asset
check (bookingItemUuid is not null or parentAssetUuid is not null or type='DOMAIN_SETUP') check (bookingItemUuid is not null or parentAssetUuid is not null or type in ('DOMAIN_SETUP', 'IPV4_NUMBER', 'IPV6_NUMBER'))
); );
--// --//

View File

@ -51,7 +51,7 @@ public class ArchitectureTest {
"..hs.office.coopshares", "..hs.office.coopshares",
"..hs.office.debitor", "..hs.office.debitor",
"..hs.office.membership", "..hs.office.membership",
"..hs.office.migration", "..hs.migration",
"..hs.office.partner", "..hs.office.partner",
"..hs.office.person", "..hs.office.person",
"..hs.office.relation", "..hs.office.relation",
@ -156,6 +156,7 @@ public class ArchitectureTest {
"..hs.office.(*)..", "..hs.office.(*)..",
"..hs.booking.(*)..", "..hs.booking.(*)..",
"..hs.hosting.(*)..", "..hs.hosting.(*)..",
"..hs.migration",
"..rbac.rbacgrant" // TODO.test: just because of RbacGrantsDiagramServiceIntegrationTest "..rbac.rbacgrant" // TODO.test: just because of RbacGrantsDiagramServiceIntegrationTest
); );
@ -167,7 +168,8 @@ public class ArchitectureTest {
.resideInAnyPackage( .resideInAnyPackage(
"..hs.booking.(*)..", "..hs.booking.(*)..",
"..hs.hosting.(*)..", "..hs.hosting.(*)..",
"..hs.validation" // TODO.impl: Some Validators need to be refactored to booking package. "..hs.validation", // TODO.impl: Some Validators need to be refactored to booking package.
"..hs.migration.."
); );
@ArchTest @ArchTest
@ -177,7 +179,8 @@ public class ArchitectureTest {
.should().onlyBeAccessed().byClassesThat() .should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage( .resideInAnyPackage(
"..hs.hosting.(*)..", "..hs.hosting.(*)..",
"..hs.booking.(*).." // TODO.impl: fix this cyclic dependency "..hs.booking.(*)..", // TODO.impl: fix this cyclic dependency
"..hs.migration.."
); );
@ArchTest @ArchTest
@ -189,7 +192,7 @@ public class ArchitectureTest {
"..hs.office.bankaccount..", "..hs.office.bankaccount..",
"..hs.office.sepamandate..", "..hs.office.sepamandate..",
"..hs.office.debitor..", "..hs.office.debitor..",
"..hs.office.migration.."); "..hs.migration..");
@ArchTest @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -199,7 +202,7 @@ public class ArchitectureTest {
.resideInAnyPackage( .resideInAnyPackage(
"..hs.office.sepamandate..", "..hs.office.sepamandate..",
"..hs.office.debitor..", "..hs.office.debitor..",
"..hs.office.migration.."); "..hs.migration..");
@ArchTest @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -212,7 +215,7 @@ public class ArchitectureTest {
"..hs.office.partner..", "..hs.office.partner..",
"..hs.office.debitor..", "..hs.office.debitor..",
"..hs.office.membership..", "..hs.office.membership..",
"..hs.office.migration..", "..hs.migration..",
"..hs.hosting.asset.." "..hs.hosting.asset.."
); );
@ -227,7 +230,7 @@ public class ArchitectureTest {
"..hs.office.partner..", "..hs.office.partner..",
"..hs.office.debitor..", "..hs.office.debitor..",
"..hs.office.membership..", "..hs.office.membership..",
"..hs.office.migration..") "..hs.migration..")
.orShould().haveNameNotMatching(".*Test$"); .orShould().haveNameNotMatching(".*Test$");
@ -239,7 +242,7 @@ public class ArchitectureTest {
.resideInAnyPackage( .resideInAnyPackage(
"..hs.office.relation..", "..hs.office.relation..",
"..hs.office.partner..", "..hs.office.partner..",
"..hs.office.migration..") "..hs.migration..")
.orShould().haveNameNotMatching(".*Test$"); .orShould().haveNameNotMatching(".*Test$");
@ArchTest @ArchTest
@ -251,7 +254,7 @@ public class ArchitectureTest {
"..hs.office.partner..", "..hs.office.partner..",
"..hs.office.debitor..", "..hs.office.debitor..",
"..hs.office.membership..", "..hs.office.membership..",
"..hs.office.migration..") "..hs.migration..")
.orShould().haveNameNotMatching(".*Test$"); .orShould().haveNameNotMatching(".*Test$");
@ArchTest @ArchTest
@ -263,7 +266,7 @@ public class ArchitectureTest {
"..hs.office.membership..", "..hs.office.membership..",
"..hs.office.coopassets..", "..hs.office.coopassets..",
"..hs.office.coopshares..", "..hs.office.coopshares..",
"..hs.office.migration.."); "..hs.migration..");
@ArchTest @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -272,7 +275,7 @@ public class ArchitectureTest {
.should().onlyBeAccessed().byClassesThat() .should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage( .resideInAnyPackage(
"..hs.office.coopassets..", "..hs.office.coopassets..",
"..hs.office.migration.."); "..hs.migration..");
@ArchTest @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
@ -281,14 +284,14 @@ public class ArchitectureTest {
.should().onlyBeAccessed().byClassesThat() .should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage( .resideInAnyPackage(
"..hs.office.coopshares..", "..hs.office.coopshares..",
"..hs.office.migration.."); "..hs.migration..");
@ArchTest @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static final ArchRule hsOfficeMigrationPackageRule = classes() public static final ArchRule hsOfficeMigrationPackageRule = classes()
.that().resideInAPackage("..hs.office.migration..") .that().resideInAPackage("..hs.migration..")
.should().onlyBeAccessed().byClassesThat() .should().onlyBeAccessed().byClassesThat()
.resideInAnyPackage("..hs.office.migration.."); .resideInAnyPackage("..hs.migration..");
@ArchTest @ArchTest
@SuppressWarnings("unused") @SuppressWarnings("unused")

View File

@ -101,7 +101,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"resources": { "resources": {
"RAM": 8, "RAM": 8,
"SSD": 500, "SSD": 500,
"CPUs": 2, "CPU": 2,
"Traffic": 500 "Traffic": 500
} }
}, },
@ -114,7 +114,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"HDD": 10000, "HDD": 10000,
"RAM": 32, "RAM": 32,
"SSD": 4000, "SSD": 4000,
"CPUs": 10, "CPU": 10,
"Traffic": 2000 "Traffic": 2000
} }
} }
@ -148,7 +148,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"type": "MANAGED_SERVER", "type": "MANAGED_SERVER",
"caption": "some new booking", "caption": "some new booking",
"validTo": "{validTo}", "validTo": "{validTo}",
"resources": { "CPUs": 12, "RAM": 4, "SSD": 100, "Traffic": 250 } "resources": { "CPU": 12, "RAM": 4, "SSD": 100, "Traffic": 250 }
} }
""" """
.replace("{projectUuid}", givenProject.getUuid().toString()) .replace("{projectUuid}", givenProject.getUuid().toString())
@ -166,7 +166,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"caption": "some new booking", "caption": "some new booking",
"validFrom": "{today}", "validFrom": "{today}",
"validTo": "{todayPlus1Month}", "validTo": "{todayPlus1Month}",
"resources": { "CPUs": 12, "SSD": 100, "Traffic": 250 } "resources": { "CPU": 12, "SSD": 100, "Traffic": 250 }
} }
""" """
.replace("{today}", LocalDate.now().toString()) .replace("{today}", LocalDate.now().toString())
@ -267,7 +267,7 @@ class HsBookingItemControllerAcceptanceTest extends ContextBasedTestWithCleanup
"resources": { "resources": {
"RAM": 8, "RAM": 8,
"SSD": 500, "SSD": 500,
"CPUs": 2, "CPU": 2,
"Traffic": 500 "Traffic": 500
} }
} }

View File

@ -92,7 +92,7 @@ class HsBookingItemControllerRestTest {
"caption": "some new booking", "caption": "some new booking",
"validTo": "{validTo}", "validTo": "{validTo}",
"garbage": "should not be accepted", "garbage": "should not be accepted",
"resources": { "CPUs": 12, "RAM": 4, "SSD": 100, "Traffic": 250 } "resources": { "CPU": 12, "RAM": 4, "SSD": 100, "Traffic": 250 }
} }
""" """
.replace("{projectUuid}", givenProjectUuid.toString()) .replace("{projectUuid}", givenProjectUuid.toString())
@ -108,7 +108,7 @@ class HsBookingItemControllerRestTest {
"caption": "some new booking", "caption": "some new booking",
"validFrom": "{today}", "validFrom": "{today}",
"validTo": "{todayPlus1Month}", "validTo": "{todayPlus1Month}",
"resources": { "CPUs": 12, "SSD": 100, "Traffic": 250 } "resources": { "CPU": 12, "SSD": 100, "Traffic": 250 }
} }
""" """
.replace("{today}", LocalDate.now().toString()) .replace("{today}", LocalDate.now().toString())
@ -141,7 +141,7 @@ class HsBookingItemControllerRestTest {
"type": "MANAGED_SERVER", "type": "MANAGED_SERVER",
"caption": "some new booking", "caption": "some new booking",
"validFrom": "{validFrom}", "validFrom": "{validFrom}",
"resources": { "CPUs": 12, "RAM": 4, "SSD": 100, "Traffic": 250 } "resources": { "CPU": 12, "RAM": 4, "SSD": 100, "Traffic": 250 }
} }
""" """
.replace("{projectUuid}", givenProjectUuid.toString()) .replace("{projectUuid}", givenProjectUuid.toString())
@ -159,7 +159,7 @@ class HsBookingItemControllerRestTest {
"caption": "some new booking", "caption": "some new booking",
"validFrom": "{today}", "validFrom": "{today}",
"validTo": null, "validTo": null,
"resources": { "CPUs": 12, "SSD": 100, "Traffic": 250 } "resources": { "CPU": 12, "SSD": 100, "Traffic": 250 }
} }
""" """
.replace("{today}", LocalDate.now().toString()) .replace("{today}", LocalDate.now().toString())

View File

@ -25,7 +25,7 @@ class HsBookingItemEntityUnitTest {
.type(HsBookingItemType.CLOUD_SERVER) .type(HsBookingItemType.CLOUD_SERVER)
.caption("some caption") .caption("some caption")
.resources(Map.ofEntries( .resources(Map.ofEntries(
entry("CPUs", 2), entry("CPU", 2),
entry("SSD-storage", 512), entry("SSD-storage", 512),
entry("HDD-storage", 2048))) entry("HDD-storage", 2048)))
.validity(toPostgresDateRange(GIVEN_VALID_FROM, GIVEN_VALID_TO)) .validity(toPostgresDateRange(GIVEN_VALID_FROM, GIVEN_VALID_TO))
@ -53,7 +53,7 @@ class HsBookingItemEntityUnitTest {
void toStringContainsAllPropertiesAndResourcesSortedByKey() { void toStringContainsAllPropertiesAndResourcesSortedByKey() {
final var result = givenBookingItem.toString(); final var result = givenBookingItem.toString();
assertThat(result).isEqualToIgnoringWhitespace("HsBookingItemEntity(D-1234500:test project, CLOUD_SERVER, [2020-01-01,2031-01-01), some caption, { \"CPUs\": 2, \"HDD-storage\": 2048, \"SSD-storage\": 512 })"); assertThat(result).isEqualToIgnoringWhitespace("HsBookingItemEntity(D-1234500:test project, CLOUD_SERVER, [2020-01-01,2031-01-01), some caption, { \"CPU\": 2, \"HDD-storage\": 2048, \"SSD-storage\": 512 })");
} }
@Test @Test

View File

@ -171,8 +171,8 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
allTheseBookingItemsAreReturned( allTheseBookingItemsAreReturned(
result, result,
"HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_WEBSPACE, [2022-10-01,), separate ManagedWebspace, { Daemons: 0, Multi: 1, SSD: 100, Traffic: 50 } )", "HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_WEBSPACE, [2022-10-01,), separate ManagedWebspace, { Daemons: 0, Multi: 1, SSD: 100, Traffic: 50 } )",
"HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPUs: 2, RAM: 8, SSD: 500, Traffic: 500 } )", "HsBookingItemEntity(D-1000212:D-1000212 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPU: 2, RAM: 8, SSD: 500, Traffic: 500 } )",
"HsBookingItemEntity(D-1000212:D-1000212 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 } )"); "HsBookingItemEntity(D-1000212:D-1000212 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPU: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 } )");
assertThat(result.stream().filter(bi -> bi.getRelatedHostingAsset()!=null).findAny()) assertThat(result.stream().filter(bi -> bi.getRelatedHostingAsset()!=null).findAny())
.as("at least one relatedProject expected, but none found => fetching relatedProject does not work") .as("at least one relatedProject expected, but none found => fetching relatedProject does not work")
.isNotEmpty(); .isNotEmpty();
@ -194,8 +194,8 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
exactlyTheseBookingItemsAreReturned( exactlyTheseBookingItemsAreReturned(
result, result,
"HsBookingItemEntity(D-1000111:D-1000111 default project, MANAGED_WEBSPACE, [2022-10-01,), separate ManagedWebspace, { Daemons: 0, Multi: 1, SSD: 100, Traffic: 50 } )", "HsBookingItemEntity(D-1000111:D-1000111 default project, MANAGED_WEBSPACE, [2022-10-01,), separate ManagedWebspace, { Daemons: 0, Multi: 1, SSD: 100, Traffic: 50 } )",
"HsBookingItemEntity(D-1000111:D-1000111 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPUs: 2, RAM: 8, SSD: 500, Traffic: 500 } )", "HsBookingItemEntity(D-1000111:D-1000111 default project, MANAGED_SERVER, [2022-10-01,), separate ManagedServer, { CPU: 2, RAM: 8, SSD: 500, Traffic: 500 } )",
"HsBookingItemEntity(D-1000111:D-1000111 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPUs: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 } )"); "HsBookingItemEntity(D-1000111:D-1000111 default project, PRIVATE_CLOUD, [2024-04-01,), some PrivateCloud, { CPU: 10, HDD: 10000, RAM: 32, SSD: 4000, Traffic: 2000 } )");
} }
} }
@ -211,7 +211,7 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var foundBookingItem = em.find(HsBookingItemEntity.class, givenBookingItemUuid); final var foundBookingItem = em.find(HsBookingItemEntity.class, givenBookingItemUuid);
foundBookingItem.getResources().put("CPUs", 2); foundBookingItem.getResources().put("CPU", 2);
foundBookingItem.getResources().remove("SSD-storage"); foundBookingItem.getResources().remove("SSD-storage");
foundBookingItem.getResources().put("HSD-storage", 2048); foundBookingItem.getResources().put("HSD-storage", 2048);
foundBookingItem.setValidity(Range.closedOpen( foundBookingItem.setValidity(Range.closedOpen(
@ -336,7 +336,7 @@ class HsBookingItemRepositoryIntegrationTest extends ContextBasedTestWithCleanup
.validity(Range.closedOpen( .validity(Range.closedOpen(
LocalDate.parse("2020-01-01"), LocalDate.parse("2023-01-01"))) LocalDate.parse("2020-01-01"), LocalDate.parse("2023-01-01")))
.resources(Map.ofEntries( .resources(Map.ofEntries(
entry("CPUs", 1), entry("CPU", 1),
entry("SSD-storage", 256))) entry("SSD-storage", 256)))
.build(); .build();

View File

@ -17,7 +17,7 @@ public class TestHsBookingItem {
.type(HsBookingItemType.CLOUD_SERVER) .type(HsBookingItemType.CLOUD_SERVER)
.caption("test cloud server booking item") .caption("test cloud server booking item")
.resources(Map.ofEntries( .resources(Map.ofEntries(
entry("CPUs", 2), entry("CPU", 2),
entry("RAM", 4), entry("RAM", 4),
entry("SSD", 50), entry("SSD", 50),
entry("Traffic", 250) entry("Traffic", 250)
@ -30,7 +30,7 @@ public class TestHsBookingItem {
.type(HsBookingItemType.MANAGED_SERVER) .type(HsBookingItemType.MANAGED_SERVER)
.caption("test project booking item") .caption("test project booking item")
.resources(Map.ofEntries( .resources(Map.ofEntries(
entry("CPUs", 2), entry("CPU", 2),
entry("RAM", 4), entry("RAM", 4),
entry("SSD", 50), entry("SSD", 50),
entry("Traffic", 250) entry("Traffic", 250)

View File

@ -38,7 +38,7 @@ class HsBookingItemEntityValidatorUnitTest {
// then // then
assertThat(result).isInstanceOf(ValidationException.class) assertThat(result).isInstanceOf(ValidationException.class)
.hasMessageContaining( .hasMessageContaining(
"'D-12345:test project:Test-Server.resources.CPUs' is required but missing", "'D-12345:test project:Test-Server.resources.CPU' is required but missing",
"'D-12345:test project:Test-Server.resources.RAM' is required but missing", "'D-12345:test project:Test-Server.resources.RAM' is required but missing",
"'D-12345:test project:Test-Server.resources.SSD' is required but missing", "'D-12345:test project:Test-Server.resources.SSD' is required but missing",
"'D-12345:test project:Test-Server.resources.Traffic' is required but missing"); "'D-12345:test project:Test-Server.resources.Traffic' is required but missing");

View File

@ -33,7 +33,7 @@ class HsCloudServerBookingItemValidatorUnitTest {
.project(project) .project(project)
.caption("Test-Server") .caption("Test-Server")
.resources(Map.ofEntries( .resources(Map.ofEntries(
entry("CPUs", 2), entry("CPU", 2),
entry("RAM", 25), entry("RAM", 25),
entry("SSD", 25), entry("SSD", 25),
entry("Traffic", 250), entry("Traffic", 250),
@ -56,11 +56,12 @@ class HsCloudServerBookingItemValidatorUnitTest {
// then // then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=boolean, propertyName=active, defaultValue=true}", "{type=boolean, propertyName=active, defaultValue=true}",
"{type=integer, propertyName=CPUs, min=1, max=32, required=true}", "{type=integer, propertyName=CPU, min=1, max=32, required=true}",
"{type=integer, propertyName=RAM, unit=GB, min=1, max=128, required=true}", "{type=integer, propertyName=RAM, unit=GB, min=1, max=8192, required=true}",
"{type=integer, propertyName=SSD, unit=GB, min=0, max=1000, step=25, required=true}", "{type=integer, propertyName=SSD, unit=GB, min=25, max=1000, step=25, requiresAtLeastOneOf=[SDD, HDD]}",
"{type=integer, propertyName=HDD, unit=GB, min=0, max=4000, step=250, defaultValue=0}", "{type=integer, propertyName=HDD, unit=GB, min=250, max=4000, step=250, requiresAtLeastOneOf=[SSD, HDD]}",
"{type=integer, propertyName=Traffic, unit=GB, min=250, max=10000, step=250, required=true}", "{type=integer, propertyName=Traffic, unit=GB, min=250, max=10000, step=250, requiresAtMaxOneOf=[Bandwidth, Traffic]}",
"{type=integer, propertyName=Bandwidth, unit=GB, min=250, max=10000, step=250, requiresAtMaxOneOf=[Bandwidth, Traffic]}",
"{type=enumeration, propertyName=SLA-Infrastructure, values=[BASIC, EXT8H, EXT4H, EXT2H]}"); "{type=enumeration, propertyName=SLA-Infrastructure, values=[BASIC, EXT8H, EXT4H, EXT2H]}");
} }
@ -71,7 +72,7 @@ class HsCloudServerBookingItemValidatorUnitTest {
.type(CLOUD_SERVER) .type(CLOUD_SERVER)
.caption("Test Cloud-Server") .caption("Test Cloud-Server")
.resources(ofEntries( .resources(ofEntries(
entry("CPUs", 2), entry("CPU", 2),
entry("RAM", 10), entry("RAM", 10),
entry("SSD", 50), entry("SSD", 50),
entry("Traffic", 2500) entry("Traffic", 2500)
@ -81,7 +82,7 @@ class HsCloudServerBookingItemValidatorUnitTest {
.type(MANAGED_SERVER) .type(MANAGED_SERVER)
.caption("Test Managed-Server") .caption("Test Managed-Server")
.resources(ofEntries( .resources(ofEntries(
entry("CPUs", 3), entry("CPU", 3),
entry("RAM", 20), entry("RAM", 20),
entry("SSD", 100), entry("SSD", 100),
entry("Traffic", 3000) entry("Traffic", 3000)
@ -92,7 +93,7 @@ class HsCloudServerBookingItemValidatorUnitTest {
.project(project) .project(project)
.caption("Test Cloud") .caption("Test Cloud")
.resources(ofEntries( .resources(ofEntries(
entry("CPUs", 4), entry("CPU", 4),
entry("RAM", 20), entry("RAM", 20),
entry("SSD", 100), entry("SSD", 100),
entry("Traffic", 5000) entry("Traffic", 5000)
@ -110,7 +111,7 @@ class HsCloudServerBookingItemValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'D-12345:Test-Project:Test Cloud.resources.CPUs' maximum total is 4, but actual total CPUs is 5", "'D-12345:Test-Project:Test Cloud.resources.CPU' maximum total is 4, but actual total CPU is 5",
"'D-12345:Test-Project:Test Cloud.resources.RAM' maximum total is 20 GB, but actual total RAM is 30 GB", "'D-12345:Test-Project:Test Cloud.resources.RAM' maximum total is 20 GB, but actual total RAM is 30 GB",
"'D-12345:Test-Project:Test Cloud.resources.SSD' maximum total is 100 GB, but actual total SSD is 150 GB", "'D-12345:Test-Project:Test Cloud.resources.SSD' maximum total is 100 GB, but actual total SSD is 150 GB",
"'D-12345:Test-Project:Test Cloud.resources.Traffic' maximum total is 5000 GB, but actual total Traffic is 5500 GB" "'D-12345:Test-Project:Test Cloud.resources.Traffic' maximum total is 5000 GB, but actual total Traffic is 5500 GB"

View File

@ -40,7 +40,7 @@ class HsManagedServerBookingItemValidatorUnitTest {
.type(MANAGED_SERVER) .type(MANAGED_SERVER)
.project(project) .project(project)
.resources(Map.ofEntries( .resources(Map.ofEntries(
entry("CPUs", 2), entry("CPU", 2),
entry("RAM", 25), entry("RAM", 25),
entry("SSD", 25), entry("SSD", 25),
entry("Traffic", 250), entry("Traffic", 250),
@ -63,11 +63,12 @@ class HsManagedServerBookingItemValidatorUnitTest {
// then // then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=integer, propertyName=CPUs, min=1, max=32, required=true}", "{type=integer, propertyName=CPU, min=1, max=32, required=true}",
"{type=integer, propertyName=RAM, unit=GB, min=1, max=128, required=true}", "{type=integer, propertyName=RAM, unit=GB, min=1, max=128, required=true}",
"{type=integer, propertyName=SSD, unit=GB, min=25, max=1000, step=25, required=true, isTotalsValidator=true, thresholdPercentage=200}", "{type=integer, propertyName=SSD, unit=GB, min=25, max=2000, step=25, requiresAtLeastOneOf=[SSD, HDD], isTotalsValidator=true, thresholdPercentage=200}",
"{type=integer, propertyName=HDD, unit=GB, min=0, max=4000, step=250, defaultValue=0, isTotalsValidator=true, thresholdPercentage=200}", "{type=integer, propertyName=HDD, unit=GB, min=250, max=10000, step=250, requiresAtLeastOneOf=[SSD, HDD], isTotalsValidator=true, thresholdPercentage=200}",
"{type=integer, propertyName=Traffic, unit=GB, min=250, max=10000, step=250, required=true, isTotalsValidator=true, thresholdPercentage=200}", "{type=integer, propertyName=Traffic, unit=GB, min=250, max=64000, step=250, requiresAtMaxOneOf=[Bandwidth, Traffic], isTotalsValidator=true, thresholdPercentage=200}",
"{type=integer, propertyName=Bandwidth, unit=GB, min=250, max=64000, step=250, requiresAtMaxOneOf=[Bandwidth, Traffic], isTotalsValidator=true, thresholdPercentage=200}",
"{type=enumeration, propertyName=SLA-Platform, values=[BASIC, EXT8H, EXT4H, EXT2H], defaultValue=BASIC}", "{type=enumeration, propertyName=SLA-Platform, values=[BASIC, EXT8H, EXT4H, EXT2H], defaultValue=BASIC}",
"{type=boolean, propertyName=SLA-EMail}", // TODO.impl: falseIf-validation is missing in output "{type=boolean, propertyName=SLA-EMail}", // TODO.impl: falseIf-validation is missing in output
"{type=boolean, propertyName=SLA-Maria}", "{type=boolean, propertyName=SLA-Maria}",
@ -82,7 +83,7 @@ class HsManagedServerBookingItemValidatorUnitTest {
final var subCloudServerBookingItemEntity = HsBookingItemEntity.builder() final var subCloudServerBookingItemEntity = HsBookingItemEntity.builder()
.type(CLOUD_SERVER) .type(CLOUD_SERVER)
.resources(ofEntries( .resources(ofEntries(
entry("CPUs", 2), entry("CPU", 2),
entry("RAM", 10), entry("RAM", 10),
entry("SSD", 50), entry("SSD", 50),
entry("Traffic", 2500) entry("Traffic", 2500)
@ -91,7 +92,7 @@ class HsManagedServerBookingItemValidatorUnitTest {
final HsBookingItemEntity subManagedServerBookingItemEntity = HsBookingItemEntity.builder() final HsBookingItemEntity subManagedServerBookingItemEntity = HsBookingItemEntity.builder()
.type(MANAGED_SERVER) .type(MANAGED_SERVER)
.resources(ofEntries( .resources(ofEntries(
entry("CPUs", 3), entry("CPU", 3),
entry("RAM", 20), entry("RAM", 20),
entry("SSD", 100), entry("SSD", 100),
entry("Traffic", 3000) entry("Traffic", 3000)
@ -101,7 +102,7 @@ class HsManagedServerBookingItemValidatorUnitTest {
.type(PRIVATE_CLOUD) .type(PRIVATE_CLOUD)
.project(project) .project(project)
.resources(ofEntries( .resources(ofEntries(
entry("CPUs", 4), entry("CPU", 4),
entry("RAM", 20), entry("RAM", 20),
entry("SSD", 100), entry("SSD", 100),
entry("Traffic", 5000) entry("Traffic", 5000)
@ -120,7 +121,7 @@ class HsManagedServerBookingItemValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'D-12345:Test-Project:null.resources.CPUs' maximum total is 4, but actual total CPUs is 5", "'D-12345:Test-Project:null.resources.CPU' maximum total is 4, but actual total CPU is 5",
"'D-12345:Test-Project:null.resources.RAM' maximum total is 20 GB, but actual total RAM is 30 GB", "'D-12345:Test-Project:null.resources.RAM' maximum total is 20 GB, but actual total RAM is 30 GB",
"'D-12345:Test-Project:null.resources.SSD' maximum total is 100 GB, but actual total SSD is 150 GB", "'D-12345:Test-Project:null.resources.SSD' maximum total is 100 GB, but actual total SSD is 150 GB",
"'D-12345:Test-Project:null.resources.Traffic' maximum total is 5000 GB, but actual total Traffic is 5500 GB" "'D-12345:Test-Project:null.resources.Traffic' maximum total is 5000 GB, but actual total Traffic is 5500 GB"

View File

@ -29,7 +29,7 @@ class HsManagedWebspaceBookingItemValidatorUnitTest {
.project(project) .project(project)
.caption("Test Managed-Webspace") .caption("Test Managed-Webspace")
.resources(Map.ofEntries( .resources(Map.ofEntries(
entry("CPUs", 2), entry("CPU", 2),
entry("RAM", 25), entry("RAM", 25),
entry("Traffic", 250), entry("Traffic", 250),
entry("SLA-EMail", true) entry("SLA-EMail", true)
@ -41,7 +41,7 @@ class HsManagedWebspaceBookingItemValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'D-12345:Test-Project:Test Managed-Webspace.resources.CPUs' is not expected but is set to '2'", "'D-12345:Test-Project:Test Managed-Webspace.resources.CPU' is not expected but is set to '2'",
"'D-12345:Test-Project:Test Managed-Webspace.resources.RAM' is not expected but is set to '25'", "'D-12345:Test-Project:Test Managed-Webspace.resources.RAM' is not expected but is set to '25'",
"'D-12345:Test-Project:Test Managed-Webspace.resources.SSD' is required but missing", "'D-12345:Test-Project:Test Managed-Webspace.resources.SSD' is required but missing",
"'D-12345:Test-Project:Test Managed-Webspace.resources.SLA-EMail' is not expected but is set to 'true'" "'D-12345:Test-Project:Test Managed-Webspace.resources.SLA-EMail' is not expected but is set to 'true'"
@ -55,11 +55,12 @@ class HsManagedWebspaceBookingItemValidatorUnitTest {
// then // then
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder( assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
"{type=integer, propertyName=SSD, unit=GB, min=1, max=100, step=1, required=true}", "{type=integer, propertyName=SSD, unit=GB, min=1, max=2000, step=1, required=true}",
"{type=integer, propertyName=HDD, unit=GB, min=0, max=250, step=10}", "{type=integer, propertyName=HDD, unit=GB, min=0, max=10000, step=10}",
"{type=integer, propertyName=Traffic, unit=GB, min=10, max=1000, step=10, required=true}", "{type=integer, propertyName=Traffic, unit=GB, min=10, max=64000, step=10, requiresAtMaxOneOf=[Bandwidth, Traffic]}",
"{type=integer, propertyName=Bandwidth, unit=GB, min=10, max=1000, step=10, requiresAtMaxOneOf=[Bandwidth, Traffic]}",
"{type=integer, propertyName=Multi, min=1, max=100, step=1, defaultValue=1}", "{type=integer, propertyName=Multi, min=1, max=100, step=1, defaultValue=1}",
"{type=integer, propertyName=Daemons, min=0, max=10, defaultValue=0}", "{type=integer, propertyName=Daemons, min=0, max=16, defaultValue=0}",
"{type=boolean, propertyName=Online Office Server}", "{type=boolean, propertyName=Online Office Server}",
"{type=enumeration, propertyName=SLA-Platform, values=[BASIC, EXT24H], defaultValue=BASIC}"); "{type=enumeration, propertyName=SLA-Platform, values=[BASIC, EXT24H], defaultValue=BASIC}");
} }

View File

@ -11,6 +11,7 @@ import static java.util.Map.ofEntries;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD; import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
import static net.hostsharing.hsadminng.hs.booking.project.TestHsBookingProject.TEST_PROJECT;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
class HsPrivateCloudBookingItemValidatorUnitTest { class HsPrivateCloudBookingItemValidatorUnitTest {
@ -28,9 +29,10 @@ class HsPrivateCloudBookingItemValidatorUnitTest {
// given // given
final var privateCloudBookingItemEntity = HsBookingItemEntity.builder() final var privateCloudBookingItemEntity = HsBookingItemEntity.builder()
.type(PRIVATE_CLOUD) .type(PRIVATE_CLOUD)
.project(TEST_PROJECT)
.caption("myPC") .caption("myPC")
.resources(ofEntries( .resources(ofEntries(
entry("CPUs", 4), entry("CPU", 4),
entry("RAM", 20), entry("RAM", 20),
entry("SSD", 100), entry("SSD", 100),
entry("Traffic", 5000), entry("Traffic", 5000),
@ -42,7 +44,7 @@ class HsPrivateCloudBookingItemValidatorUnitTest {
.type(MANAGED_SERVER) .type(MANAGED_SERVER)
.caption("myMS-1") .caption("myMS-1")
.resources(ofEntries( .resources(ofEntries(
entry("CPUs", 2), entry("CPU", 2),
entry("RAM", 10), entry("RAM", 10),
entry("SSD", 50), entry("SSD", 50),
entry("Traffic", 2500), entry("Traffic", 2500),
@ -54,7 +56,7 @@ class HsPrivateCloudBookingItemValidatorUnitTest {
.type(CLOUD_SERVER) .type(CLOUD_SERVER)
.caption("myMS-2") .caption("myMS-2")
.resources(ofEntries( .resources(ofEntries(
entry("CPUs", 2), entry("CPU", 2),
entry("RAM", 10), entry("RAM", 10),
entry("SSD", 50), entry("SSD", 50),
entry("Traffic", 2500), entry("Traffic", 2500),
@ -80,7 +82,7 @@ class HsPrivateCloudBookingItemValidatorUnitTest {
.type(PRIVATE_CLOUD) .type(PRIVATE_CLOUD)
.caption("myPC") .caption("myPC")
.resources(ofEntries( .resources(ofEntries(
entry("CPUs", 4), entry("CPU", 4),
entry("RAM", 20), entry("RAM", 20),
entry("SSD", 100), entry("SSD", 100),
entry("Traffic", 5000), entry("Traffic", 5000),
@ -92,7 +94,7 @@ class HsPrivateCloudBookingItemValidatorUnitTest {
.type(MANAGED_SERVER) .type(MANAGED_SERVER)
.caption("myMS-1") .caption("myMS-1")
.resources(ofEntries( .resources(ofEntries(
entry("CPUs", 3), entry("CPU", 3),
entry("RAM", 20), entry("RAM", 20),
entry("SSD", 100), entry("SSD", 100),
entry("Traffic", 3000), entry("Traffic", 3000),
@ -104,7 +106,7 @@ class HsPrivateCloudBookingItemValidatorUnitTest {
.type(CLOUD_SERVER) .type(CLOUD_SERVER)
.caption("myMS-2") .caption("myMS-2")
.resources(ofEntries( .resources(ofEntries(
entry("CPUs", 2), entry("CPU", 2),
entry("RAM", 10), entry("RAM", 10),
entry("SSD", 50), entry("SSD", 50),
entry("Traffic", 2500), entry("Traffic", 2500),
@ -124,7 +126,7 @@ class HsPrivateCloudBookingItemValidatorUnitTest {
// then // then
assertThat(result).containsExactlyInAnyOrder( assertThat(result).containsExactlyInAnyOrder(
"'D-12345:Test-Project:myPC.resources.CPUs' maximum total is 4, but actual total CPUs is 5", "'D-12345:Test-Project:myPC.resources.CPU' maximum total is 4, but actual total CPU is 5",
"'D-12345:Test-Project:myPC.resources.RAM' maximum total is 20 GB, but actual total RAM is 30 GB", "'D-12345:Test-Project:myPC.resources.RAM' maximum total is 20 GB, but actual total RAM is 30 GB",
"'D-12345:Test-Project:myPC.resources.SSD' maximum total is 100 GB, but actual total SSD is 150 GB", "'D-12345:Test-Project:myPC.resources.SSD' maximum total is 100 GB, but actual total SSD is 150 GB",
"'D-12345:Test-Project:myPC.resources.Traffic' maximum total is 5000 GB, but actual total Traffic is 5500 GB", "'D-12345:Test-Project:myPC.resources.Traffic' maximum total is 5000 GB, but actual total Traffic is 5500 GB",

View File

@ -702,7 +702,7 @@ class HsHostingAssetControllerAcceptanceTest extends ContextBasedTestWithCleanup
context.define("superuser-alex@hostsharing.net"); context.define("superuser-alex@hostsharing.net");
final var project = projectRepo.findByCaption(projectCaption).getFirst(); final var project = projectRepo.findByCaption(projectCaption).getFirst();
final var resources = switch (bookingItemType) { final var resources = switch (bookingItemType) {
case MANAGED_SERVER -> Map.<String, Object>ofEntries(entry("CPUs", 1), case MANAGED_SERVER -> Map.<String, Object>ofEntries(entry("CPU", 1),
entry("RAM", 20), entry("RAM", 20),
entry("SSD", 25), entry("SSD", 25),
entry("Traffic", 250)); entry("Traffic", 250));

View File

@ -16,7 +16,7 @@ class HsHostingAssetEntityUnitTest {
.identifier("vm1234") .identifier("vm1234")
.caption("some managed asset") .caption("some managed asset")
.config(Map.ofEntries( .config(Map.ofEntries(
entry("CPUs", 2), entry("CPU", 2),
entry("SSD-storage", 512), entry("SSD-storage", 512),
entry("HDD-storage", 2048))) entry("HDD-storage", 2048)))
.build(); .build();
@ -27,7 +27,7 @@ class HsHostingAssetEntityUnitTest {
.identifier("xyz00") .identifier("xyz00")
.caption("some managed webspace") .caption("some managed webspace")
.config(Map.ofEntries( .config(Map.ofEntries(
entry("CPUs", 2), entry("CPU", 2),
entry("SSD-storage", 512), entry("SSD-storage", 512),
entry("HDD-storage", 2048))) entry("HDD-storage", 2048)))
.build(); .build();
@ -58,7 +58,7 @@ class HsHostingAssetEntityUnitTest {
void toStringContainsAllPropertiesAndResourcesSortedByKey() { void toStringContainsAllPropertiesAndResourcesSortedByKey() {
assertThat(givenWebspace.toString()).isEqualToIgnoringWhitespace( assertThat(givenWebspace.toString()).isEqualToIgnoringWhitespace(
"HsHostingAssetEntity(MANAGED_WEBSPACE, xyz00, some managed webspace, MANAGED_SERVER:vm1234, D-1234500:test project:test cloud server booking item, { \"CPUs\": 2, \"HDD-storage\": 2048, \"SSD-storage\": 512 })"); "HsHostingAssetEntity(MANAGED_WEBSPACE, xyz00, some managed webspace, MANAGED_SERVER:vm1234, D-1234500:test project:test cloud server booking item, { \"CPU\": 2, \"HDD-storage\": 2048, \"SSD-storage\": 512 })");
assertThat(givenUnixUser.toString()).isEqualToIgnoringWhitespace( assertThat(givenUnixUser.toString()).isEqualToIgnoringWhitespace(
"HsHostingAssetEntity(UNIX_USER, xyz00-web, some unix-user, MANAGED_WEBSPACE:xyz00, { \"HDD-hard-quota\": 512, \"HDD-soft-quota\": 256, \"SSD-hard-quota\": 256, \"SSD-soft-quota\": 128 })"); "HsHostingAssetEntity(UNIX_USER, xyz00-web, some unix-user, MANAGED_WEBSPACE:xyz00, { \"HDD-hard-quota\": 512, \"HDD-soft-quota\": 256, \"SSD-hard-quota\": 256, \"SSD-soft-quota\": 128 })");

View File

@ -263,7 +263,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
final var result = jpaAttempt.transacted(() -> { final var result = jpaAttempt.transacted(() -> {
context("superuser-alex@hostsharing.net"); context("superuser-alex@hostsharing.net");
final var foundAsset = em.find(HsHostingAssetEntity.class, givenAssetUuid); final var foundAsset = em.find(HsHostingAssetEntity.class, givenAssetUuid);
foundAsset.getConfig().put("CPUs", 2); foundAsset.getConfig().put("CPU", 2);
foundAsset.getConfig().remove("SSD-storage"); foundAsset.getConfig().remove("SSD-storage");
foundAsset.getConfig().put("HSD-storage", 2048); foundAsset.getConfig().put("HSD-storage", 2048);
return toCleanup(assetRepo.save(foundAsset)); return toCleanup(assetRepo.save(foundAsset));
@ -404,7 +404,7 @@ class HsHostingAssetRepositoryIntegrationTest extends ContextBasedTestWithCleanu
.identifier(identifier) .identifier(identifier)
.caption("some temp cloud asset") .caption("some temp cloud asset")
.config(Map.ofEntries( .config(Map.ofEntries(
entry("CPUs", 1), entry("CPU", 1),
entry("SSD-storage", 256))) entry("SSD-storage", 256)))
.build(); .build();

View File

@ -12,9 +12,9 @@ class HsHostingAssetTypeUnitTest {
assertThat(result).isEqualTo(""" assertThat(result).isEqualTo("""
## HostingAsset Type Structure ## HostingAsset Type Structure
### Webspace+Server ### Server+Webspace
```plantuml ```plantuml
@startuml @startuml
@ -35,6 +35,12 @@ class HsHostingAssetTypeUnitTest {
entity HA_IPV6_NUMBER entity HA_IPV6_NUMBER
} }
package Webspace #99bcdb {
entity HA_MANAGED_WEBSPACE
entity HA_UNIX_USER
entity HA_EMAIL_ALIAS
}
} }
BI_CLOUD_SERVER *--> BI_PRIVATE_CLOUD BI_CLOUD_SERVER *--> BI_PRIVATE_CLOUD
@ -43,10 +49,16 @@ class HsHostingAssetTypeUnitTest {
HA_CLOUD_SERVER *==> BI_CLOUD_SERVER HA_CLOUD_SERVER *==> BI_CLOUD_SERVER
HA_MANAGED_SERVER *==> BI_MANAGED_SERVER HA_MANAGED_SERVER *==> BI_MANAGED_SERVER
HA_MANAGED_WEBSPACE *==> BI_MANAGED_WEBSPACE
HA_MANAGED_WEBSPACE o..> HA_MANAGED_SERVER
HA_UNIX_USER *==> HA_MANAGED_WEBSPACE
HA_EMAIL_ALIAS *==> HA_MANAGED_WEBSPACE
HA_IPV4_NUMBER o..> HA_CLOUD_SERVER HA_IPV4_NUMBER o..> HA_CLOUD_SERVER
HA_IPV4_NUMBER o..> HA_MANAGED_SERVER HA_IPV4_NUMBER o..> HA_MANAGED_SERVER
HA_IPV4_NUMBER o..> HA_MANAGED_WEBSPACE
HA_IPV6_NUMBER o..> HA_CLOUD_SERVER HA_IPV6_NUMBER o..> HA_CLOUD_SERVER
HA_IPV6_NUMBER o..> HA_MANAGED_SERVER HA_IPV6_NUMBER o..> HA_MANAGED_SERVER
HA_IPV6_NUMBER o..> HA_MANAGED_WEBSPACE
package Legend #white { package Legend #white {
SUB_ENTITY1 *--> REQUIRED_PARENT_ENTITY SUB_ENTITY1 *--> REQUIRED_PARENT_ENTITY

View File

@ -22,7 +22,7 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
.type(HsBookingItemType.MANAGED_SERVER) .type(HsBookingItemType.MANAGED_SERVER)
.caption("Test Managed-Server") .caption("Test Managed-Server")
.resources(Map.ofEntries( .resources(Map.ofEntries(
entry("CPUs", 2), entry("CPU", 2),
entry("RAM", 25), entry("RAM", 25),
entry("SSD", 25), entry("SSD", 25),
entry("Traffic", 250), entry("Traffic", 250),
@ -125,6 +125,7 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
.type(MANAGED_WEBSPACE) .type(MANAGED_WEBSPACE)
.bookingItem(HsBookingItemEntity.builder() .bookingItem(HsBookingItemEntity.builder()
.type(HsBookingItemType.MANAGED_WEBSPACE) .type(HsBookingItemType.MANAGED_WEBSPACE)
.project(TEST_PROJECT)
.caption("some ManagedWebspace") .caption("some ManagedWebspace")
.resources(Map.ofEntries(entry("SSD", 25), entry("Traffic", 250))) .resources(Map.ofEntries(entry("SSD", 25), entry("Traffic", 250)))
.build()) .build())

View File

@ -0,0 +1,312 @@
package net.hostsharing.hsadminng.hs.migration;
import com.opencsv.CSVParserBuilder;
import com.opencsv.CSVReader;
import com.opencsv.CSVReaderBuilder;
import net.hostsharing.hsadminng.rbac.context.ContextBasedTest;
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestWatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.transaction.support.TransactionTemplate;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.constraints.NotNull;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
import static java.lang.Boolean.parseBoolean;
import static java.util.Arrays.stream;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
public class CsvDataImport extends ContextBasedTest {
public static final String TEST_DATA_MIGRATION_DATA_PATH = "migration";
public static final String MIGRATION_DATA_PATH = ofNullable(System.getenv("HSADMINNG_MIGRATION_DATA_PATH"))
.orElse(TEST_DATA_MIGRATION_DATA_PATH);
@Value("${spring.datasource.url}")
protected String jdbcUrl;
@Value("${spring.datasource.username}")
protected String postgresAdminUser;
@Value("${hsadminng.superuser}")
protected String rbacSuperuser;
@PersistenceContext
EntityManager em;
@Autowired
TransactionTemplate txTemplate;
@Autowired
JpaAttempt jpaAttempt;
@MockBean
HttpServletRequest request;
private static final List<AssertionError> errors = new ArrayList<>();
public List<String[]> readAllLines(Reader reader) throws Exception {
final var parser = new CSVParserBuilder()
.withSeparator(';')
.withQuoteChar('"')
.build();
final var filteredReader = skippingEmptyAndCommentLines(reader);
try (CSVReader csvReader = new CSVReaderBuilder(filteredReader)
.withCSVParser(parser)
.build()) {
return csvReader.readAll();
}
}
public static Reader skippingEmptyAndCommentLines(Reader reader) throws IOException {
try (var bufferedReader = new BufferedReader(reader);
StringWriter writer = new StringWriter()) {
String line;
while ((line = bufferedReader.readLine()) != null) {
if (!line.isBlank() && !line.startsWith("#")) {
writer.write(line);
writer.write("\n");
}
}
return new StringReader(writer.toString());
}
}
protected static String[] justHeader(final List<String[]> lines) {
return stream(lines.getFirst()).map(String::trim).toArray(String[]::new);
}
protected Reader resourceReader(@NotNull final String resourcePath) {
return new InputStreamReader(requireNonNull(getClass().getClassLoader().getResourceAsStream(resourcePath)));
}
protected List<String[]> withoutHeader(final List<String[]> records) {
return records.subList(1, records.size());
}
String[] trimAll(final String[] record) {
for (int i = 0; i < record.length; ++i) {
if (record[i] != null) {
record[i] = record[i].trim();
}
}
return record;
}
public <T extends RbacObject> T persist(final Integer id, final T entity) {
try {
final var asString = entity.toString();
if ( asString.contains("'null null, null'") || asString.equals("person()")) {
System.err.println("skipping to persist empty record-id " + id + " #" + entity.hashCode() + ": " + entity);
return entity;
}
//System.out.println("persisting #" + entity.hashCode() + ": " + entity);
em.persist(entity);
// uncomment for debugging purposes
// em.flush(); // makes it slow, but produces better error messages
// System.out.println("persisted #" + entity.hashCode() + " as " + entity.getUuid());
} catch (Exception exc) {
System.err.println("failed to persist #" + entity.hashCode() + ": " + entity);
System.err.println(exc);
}
return entity;
}
protected <E> String toFormattedString(final Map<Integer, E> map) {
if ( map.isEmpty() ) {
return "{}";
}
return "{\n" +
map.keySet().stream()
.map(id -> " " + id + "=" + map.get(id).toString())
.map(e -> e.replaceAll("\n ", " ").replace("\n", ""))
.sorted()
.collect(Collectors.joining(",\n")) +
"\n}\n";
}
protected void deleteTestDataFromHsOfficeTables() {
jpaAttempt.transacted(() -> {
context(rbacSuperuser);
// TODO.perf: could we instead skip creating test-data based on an env var?
em.createNativeQuery("delete from hs_hosting_asset where true").executeUpdate();
em.createNativeQuery("delete from hs_booking_item where true").executeUpdate();
em.createNativeQuery("delete from hs_booking_project where true").executeUpdate();
em.createNativeQuery("delete from hs_office_coopassetstransaction where true").executeUpdate();
em.createNativeQuery("delete from hs_office_coopassetstransaction_legacy_id where true").executeUpdate();
em.createNativeQuery("delete from hs_office_coopsharestransaction 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_sepamandate where true").executeUpdate();
em.createNativeQuery("delete from hs_office_sepamandate_legacy_id where true").executeUpdate();
em.createNativeQuery("delete from hs_office_debitor where true").executeUpdate();
em.createNativeQuery("delete from hs_office_bankaccount where true").executeUpdate();
em.createNativeQuery("delete from hs_office_partner where true").executeUpdate();
em.createNativeQuery("delete from hs_office_partner_details where true").executeUpdate();
em.createNativeQuery("delete from hs_office_relation where true").executeUpdate();
em.createNativeQuery("delete from hs_office_contact where true").executeUpdate();
em.createNativeQuery("delete from hs_office_person where true").executeUpdate();
}).assertSuccessful();
}
protected void resetHsOfficeSequences() {
jpaAttempt.transacted(() -> {
context(rbacSuperuser);
em.createNativeQuery("alter sequence hs_office_contact_legacy_id_seq restart with 1000000000;").executeUpdate();
em.createNativeQuery("alter sequence hs_office_coopassetstransaction_legacy_id_seq restart with 1000000000;")
.executeUpdate();
em.createNativeQuery("alter sequence public.hs_office_coopsharestransaction_legacy_id_seq restart with 1000000000;")
.executeUpdate();
em.createNativeQuery("alter sequence public.hs_office_partner_legacy_id_seq restart with 1000000000;")
.executeUpdate();
em.createNativeQuery("alter sequence public.hs_office_sepamandate_legacy_id_seq restart with 1000000000;")
.executeUpdate();
});
}
protected void deleteFromTestTables() {
jpaAttempt.transacted(() -> {
context(rbacSuperuser);
em.createNativeQuery("delete from test_domain where true").executeUpdate();
em.createNativeQuery("delete from test_package where true").executeUpdate();
em.createNativeQuery("delete from test_customer where true").executeUpdate();
}).assertSuccessful();
}
protected void deleteFromRbacTables() {
jpaAttempt.transacted(() -> {
context(rbacSuperuser);
em.createNativeQuery("delete from rbacuser_rv where name not like 'superuser-%'").executeUpdate();
em.createNativeQuery("delete from tx_journal where true").executeUpdate();
em.createNativeQuery("delete from tx_context where true").executeUpdate();
}).assertSuccessful();
}
void logError(final Runnable assertion) {
try {
assertion.run();
} catch (final AssertionError exc) {
errors.add(exc);
}
}
void logErrors() {
assumeThat(errors).isEmpty();
}
}
class Columns {
private final List<String> columnNames;
public Columns(final String[] header) {
columnNames = List.of(header);
}
int indexOf(final String columnName) {
int index = columnNames.indexOf(columnName);
if (index < 0) {
throw new RuntimeException("column name '" + columnName + "' not found in: " + columnNames);
}
return index;
}
}
class Record {
private final Columns columns;
private final String[] row;
public Record(final Columns columns, final String[] row) {
this.columns = columns;
this.row = row;
}
String getString(final String columnName) {
return row[columns.indexOf(columnName)];
}
boolean isEmpty(final String columnName) {
final String value = getString(columnName);
return value == null || value.isBlank();
}
boolean getBoolean(final String columnName) {
final String value = getString(columnName);
return isNotBlank(value) &&
( parseBoolean(value.trim()) || value.trim().startsWith("t"));
}
Integer getInteger(final String columnName) {
final String value = getString(columnName);
return isNotBlank(value) ? Integer.parseInt(value.trim()) : null;
}
BigDecimal getBigDecimal(final String columnName) {
final String value = getString(columnName);
if (isNotBlank(value)) {
return new BigDecimal(value);
}
return null;
}
LocalDate getLocalDate(final String columnName) {
final String dateString = getString(columnName);
if (isNotBlank(dateString)) {
return LocalDate.parse(dateString);
}
return null;
}
}
class OrderedDependedTestsExtension implements TestWatcher, BeforeEachCallback {
private static boolean previousTestsPassed = true;
public void testFailed(ExtensionContext context, Throwable cause) {
previousTestsPassed = false;
}
@Override
public void beforeEach(final ExtensionContext extensionContext) {
assumeThat(previousTestsPassed).isTrue();
}
}
class WriteOnceMap<K, V> extends TreeMap<K, V> {
@Override
public V put(final K k, final V v) {
assertThat(containsKey(k)).describedAs("overwriting " + get(k) + " index " + k + " with " + v).isFalse();
return super.put(k, v);
}
}

View File

@ -0,0 +1,620 @@
package net.hostsharing.hsadminng.hs.migration;
import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidatorRegistry;
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HostingAssetEntitySaveProcessor;
import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.Commit;
import org.springframework.test.annotation.DirtiesContext;
import java.io.Reader;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import static java.util.Arrays.stream;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.toMap;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.IPV4_NUMBER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assumptions.assumeThat;
/*
* This 'test' includes the complete legacy 'office' data import.
*
* There is no code in 'main' because the import is not needed a normal runtime.
* There is some test data in Java resources to verify the data conversion.
* For a real import a main method will be added later
* which reads CSV files from the file system.
*
* When run on a Hostsharing database, it needs the following settings (hsh99_... just examples).
*
* In a real Hostsharing environment, these are created via (the old) hsadmin:
CREATE USER hsh99_admin WITH PASSWORD 'password';
CREATE DATABASE hsh99_hsadminng ENCODING 'UTF8' TEMPLATE template0;
REVOKE ALL ON DATABASE hsh99_hsadminng FROM public; -- why does hsadmin do that?
ALTER DATABASE hsh99_hsadminng OWNER TO hsh99_admin;
CREATE USER hsh99_restricted WITH PASSWORD 'password';
\c hsh99_hsadminng
GRANT ALL PRIVILEGES ON SCHEMA public to hsh99_admin;
* Additionally, we need these settings (because the Hostsharing DB-Admin has no CREATE right):
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- maybe something like that is needed for the 2nd user
-- GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public to hsh99_restricted;
* Then copy the file .tc-environment to a file named .environment (excluded from git) and fill in your specific values.
* To finally import the office data, run:
*
* gw-importHostingAssets # comes from .aliases file and uses .environment
*/
@Tag("importHostingAssets")
@DataJpaTest(properties = {
"spring.datasource.url=${HSADMINNG_POSTGRES_JDBC_URL:jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers}",
"spring.datasource.username=${HSADMINNG_POSTGRES_ADMIN_USERNAME:ADMIN}",
"spring.datasource.password=${HSADMINNG_POSTGRES_ADMIN_PASSWORD:password}",
"hsadminng.superuser=${HSADMINNG_SUPERUSER:superuser-alex@hostsharing.net}"
})
@DirtiesContext
@Import({ Context.class, JpaAttempt.class })
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@ExtendWith(OrderedDependedTestsExtension.class)
public class ImportHostingAssets extends ImportOfficeData {
static final Integer IP_NUMBER_ID_OFFSET = 1000000;
static final Integer HIVE_ID_OFFSET = 2000000;
static final Integer PACKET_ID_OFFSET = 3000000;
record Hive(int hive_id, String hive_name, int inet_addr_id, AtomicReference<HsHostingAssetEntity> serverRef) {}
static Map<Integer, HsBookingProjectEntity> bookingProjects = new WriteOnceMap<>();
static Map<Integer, HsBookingItemEntity> bookingItems = new WriteOnceMap<>();
static Map<Integer, Hive> hives = new WriteOnceMap<>();
static Map<Integer, HsHostingAssetEntity> hostingAssets = new WriteOnceMap<>(); // TODO.impl: separate maps for each type?
@Test
@Order(11010)
void createBookingProjects() {
debitors.forEach((id, debitor) -> {
bookingProjects.put(id, HsBookingProjectEntity.builder()
.caption(debitor.getDefaultPrefix() + " default project")
.debitor(em.find(HsBookingDebitorEntity.class, debitor.getUuid()))
.build());
});
}
@Test
@Order(12010)
void importIpNumbers() {
try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/hosting/inet_addr.csv")) {
final var lines = readAllLines(reader);
importIpNumbers(justHeader(lines), withoutHeader(lines));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Test
@Order(12019)
void verifyIpNumbers() {
assumeThatWeAreImportingControlledTestData();
// no contacts yet => mostly null values
assertThat(firstOfEachType(5, IPV4_NUMBER)).isEqualToIgnoringWhitespace("""
{
1000363=HsHostingAssetEntity(IPV4_NUMBER, 83.223.95.34),
1000381=HsHostingAssetEntity(IPV4_NUMBER, 83.223.95.52),
1000402=HsHostingAssetEntity(IPV4_NUMBER, 83.223.95.73),
1000433=HsHostingAssetEntity(IPV4_NUMBER, 83.223.95.104),
1000457=HsHostingAssetEntity(IPV4_NUMBER, 83.223.95.128)
}
""");
}
@Test
@Order(12030)
void importHives() {
try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/hosting/hive.csv")) {
final var lines = readAllLines(reader);
importHives(justHeader(lines), withoutHeader(lines));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Test
@Order(12039)
void verifyHives() {
assumeThatWeAreImportingControlledTestData();
// no contacts yet => mostly null values
assertThat(toFormattedString(first(5, hives))).isEqualToIgnoringWhitespace("""
{
2000001=Hive[hive_id=1, hive_name=h00, inet_addr_id=358, serverRef=null],
2000002=Hive[hive_id=2, hive_name=h01, inet_addr_id=359, serverRef=null],
2000004=Hive[hive_id=4, hive_name=h02, inet_addr_id=360, serverRef=null],
2000007=Hive[hive_id=7, hive_name=h03, inet_addr_id=361, serverRef=null],
2000013=Hive[hive_id=13, hive_name=h04, inet_addr_id=430, serverRef=null]
}
""");
}
@Test
@Order(13000)
void importPackets() {
try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/hosting/packet.csv")) {
final var lines = readAllLines(reader);
importPackets(justHeader(lines), withoutHeader(lines));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Test
@Order(13009)
void verifyPackets() {
assumeThatWeAreImportingControlledTestData();
assertThat(firstOfEachType(3, CLOUD_SERVER, MANAGED_SERVER, MANAGED_WEBSPACE)).isEqualToIgnoringWhitespace("""
{
3000630=HsHostingAssetEntity(MANAGED_WEBSPACE, hsh00, HA hsh00, MANAGED_SERVER:vm1050, D-1000000:hsh default project:BI hsh00),
3000968=HsHostingAssetEntity(MANAGED_SERVER, vm1061, HA vm1061, D-1015200:rar default project:BI vm1061),
3000978=HsHostingAssetEntity(MANAGED_SERVER, vm1050, HA vm1050, D-1000000:hsh default project:BI vm1050),
3001061=HsHostingAssetEntity(MANAGED_SERVER, vm1068, HA vm1068, D-1000300:mim default project:BI vm1068),
3001094=HsHostingAssetEntity(MANAGED_WEBSPACE, lug00, HA lug00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI lug00),
3001112=HsHostingAssetEntity(MANAGED_WEBSPACE, mim00, HA mim00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI mim00),
3023611=HsHostingAssetEntity(CLOUD_SERVER, vm2097, HA vm2097, D-1101800:wws default project:BI vm2097)
}
""");
assertThat(firstOfEachType(
3,
HsBookingItemType.CLOUD_SERVER,
HsBookingItemType.MANAGED_SERVER,
HsBookingItemType.MANAGED_WEBSPACE)).isEqualToIgnoringWhitespace("""
{
3000630=HsBookingItemEntity(D-1000000:hsh default project, MANAGED_WEBSPACE, [2001-06-01,), BI hsh00),
3000968=HsBookingItemEntity(D-1015200:rar default project, MANAGED_SERVER, [2013-04-01,), BI vm1061),
3000978=HsBookingItemEntity(D-1000000:hsh default project, MANAGED_SERVER, [2013-04-01,), BI vm1050),
3001061=HsBookingItemEntity(D-1000300:mim default project, MANAGED_SERVER, [2013-08-19,), BI vm1068),
3001094=HsBookingItemEntity(D-1000300:mim default project, MANAGED_WEBSPACE, [2013-09-10,), BI lug00),
3001112=HsBookingItemEntity(D-1000300:mim default project, MANAGED_WEBSPACE, [2013-09-17,), BI mim00),
3023611=HsBookingItemEntity(D-1101800:wws default project, CLOUD_SERVER, [2022-08-10,), BI vm2097)
}
""");
}
@Test
@Order(13010)
void importPacketComponents() {
try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/hosting/packet_component.csv")) {
final var lines = readAllLines(reader);
importPacketComponents(justHeader(lines), withoutHeader(lines));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Test
@Order(13019)
void verifyPacketComponents() {
assumeThatWeAreImportingControlledTestData();
// no contacts yet => mostly null values
assertThat(firstOfEachType(5, CLOUD_SERVER, MANAGED_SERVER, MANAGED_WEBSPACE))
.isEqualToIgnoringWhitespace("""
{
3000630=HsHostingAssetEntity(MANAGED_WEBSPACE, hsh00, HA hsh00, MANAGED_SERVER:vm1050, D-1000000:hsh default project:BI hsh00),
3000968=HsHostingAssetEntity(MANAGED_SERVER, vm1061, HA vm1061, D-1015200:rar default project:BI vm1061),
3000978=HsHostingAssetEntity(MANAGED_SERVER, vm1050, HA vm1050, D-1000000:hsh default project:BI vm1050),
3001061=HsHostingAssetEntity(MANAGED_SERVER, vm1068, HA vm1068, D-1000300:mim default project:BI vm1068),
3001094=HsHostingAssetEntity(MANAGED_WEBSPACE, lug00, HA lug00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI lug00),
3001112=HsHostingAssetEntity(MANAGED_WEBSPACE, mim00, HA mim00, MANAGED_SERVER:vm1068, D-1000300:mim default project:BI mim00),
3001447=HsHostingAssetEntity(MANAGED_SERVER, vm1093, HA vm1093, D-1000000:hsh default project:BI vm1093),
3019959=HsHostingAssetEntity(MANAGED_WEBSPACE, dph00, HA dph00, MANAGED_SERVER:vm1093, D-1101900:dph default project:BI dph00),
3023611=HsHostingAssetEntity(CLOUD_SERVER, vm2097, HA vm2097, D-1101800:wws default project:BI vm2097)
}
""");
assertThat(firstOfEachType(
5,
HsBookingItemType.CLOUD_SERVER,
HsBookingItemType.MANAGED_SERVER,
HsBookingItemType.MANAGED_WEBSPACE))
.isEqualToIgnoringWhitespace("""
{
3000630=HsBookingItemEntity(D-1000000:hsh default project, MANAGED_WEBSPACE, [2001-06-01,), BI hsh00, { "HDD": 10, "Multi": 25, "SLA-Platform": "EXT24H", "SSD": 16, "Traffic": 50}),
3000968=HsBookingItemEntity(D-1015200:rar default project, MANAGED_SERVER, [2013-04-01,), BI vm1061, { "CPU": 6, "HDD": 250, "RAM": 14, "SLA-EMail": true, "SLA-Maria": true, "SLA-Office": true, "SLA-PgSQL": true, "SLA-Platform": "EXT4H", "SLA-Web": true, "SSD": 375, "Traffic": 250}),
3000978=HsBookingItemEntity(D-1000000:hsh default project, MANAGED_SERVER, [2013-04-01,), BI vm1050, { "CPU": 4, "HDD": 250, "RAM": 32, "SLA-EMail": true, "SLA-Maria": true, "SLA-Office": true, "SLA-PgSQL": true, "SLA-Platform": "EXT4H", "SLA-Web": true, "SSD": 150, "Traffic": 250}),
3001061=HsBookingItemEntity(D-1000300:mim default project, MANAGED_SERVER, [2013-08-19,), BI vm1068, { "CPU": 2, "HDD": 250, "RAM": 4, "SLA-EMail": true, "SLA-Maria": true, "SLA-Office": true, "SLA-PgSQL": true, "SLA-Platform": "EXT2H", "SLA-Web": true, "Traffic": 250}),
3001094=HsBookingItemEntity(D-1000300:mim default project, MANAGED_WEBSPACE, [2013-09-10,), BI lug00, { "Multi": 5, "SLA-Platform": "EXT24H", "SSD": 1, "Traffic": 10}),
3001112=HsBookingItemEntity(D-1000300:mim default project, MANAGED_WEBSPACE, [2013-09-17,), BI mim00, { "Multi": 5, "SLA-Platform": "EXT24H", "SSD": 3, "Traffic": 20}),
3001447=HsBookingItemEntity(D-1000000:hsh default project, MANAGED_SERVER, [2014-11-28,), BI vm1093, { "CPU": 6, "HDD": 500, "RAM": 16, "SLA-EMail": true, "SLA-Maria": true, "SLA-Office": true, "SLA-PgSQL": true, "SLA-Platform": "EXT4H", "SLA-Web": true, "SSD": 300, "Traffic": 250}),
3019959=HsBookingItemEntity(D-1101900:dph default project, MANAGED_WEBSPACE, [2021-06-02,), BI dph00, { "Multi": 1, "SLA-Platform": "EXT24H", "SSD": 25, "Traffic": 20}),
3023611=HsBookingItemEntity(D-1101800:wws default project, CLOUD_SERVER, [2022-08-10,), BI vm2097, { "CPU": 8, "RAM": 12, "SLA-Infrastructure": "EXT4H", "SSD": 25, "Traffic": 250})
}
""");
}
@Test
@Order(11400)
void validateBookingItems() {
bookingItems.forEach((id, bi) -> {
try {
HsBookingItemEntityValidatorRegistry.validated(bi);
} catch (final Exception exc) {
System.err.println("validation failed for id:" + id + "( " + bi + "): " + exc.getMessage());
}
});
}
@Test
@Order(11410)
void validateHostingAssets() {
hostingAssets.forEach((id, ha) -> {
try {
new HostingAssetEntitySaveProcessor(ha)
.preprocessEntity()
.validateEntity();
} catch (final Exception exc) {
System.err.println("validation failed for id:" + id + "( " + ha + "): " + exc.getMessage());
}
});
}
@Test
@Order(19000)
@Commit
void persistHostingAssetEntities() {
System.out.println("PERSISTING hosting-assets to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'");
jpaAttempt.transacted(() -> {
context(rbacSuperuser);
bookingProjects.forEach(this::persist);
}).assertSuccessful();
jpaAttempt.transacted(() -> {
context(rbacSuperuser);
bookingItems.forEach(this::persistRecursively);
}).assertSuccessful();
persistHostingAssetsOfType(CLOUD_SERVER);
persistHostingAssetsOfType(MANAGED_SERVER);
persistHostingAssetsOfType(MANAGED_WEBSPACE);
persistHostingAssetsOfType(IPV4_NUMBER);
}
@Test
@Order(99999)
void logErrors() {
super.logErrors();
}
private void persistRecursively(final Integer key, final HsBookingItemEntity bi) {
if (bi.getParentItem() != null) {
persistRecursively(key, HsBookingItemEntityValidatorRegistry.validated(bi.getParentItem()));
}
persist(key, HsBookingItemEntityValidatorRegistry.validated(bi));
}
private void persistHostingAssetsOfType(final HsHostingAssetType hsHostingAssetType) {
jpaAttempt.transacted(() -> {
context(rbacSuperuser);
hostingAssets.forEach((key, ha) -> {
if (ha.getType() == hsHostingAssetType) {
new HostingAssetEntitySaveProcessor(ha)
.preprocessEntity()
.validateEntity()
.prepareForSave()
.saveUsing(entity -> persist(key, entity))
.validateContext();
}
}
);
}).assertSuccessful();
}
private void importIpNumbers(final String[] header, final List<String[]> records) {
final var columns = new Columns(header);
records.stream()
.map(this::trimAll)
.map(row -> new Record(columns, row))
.forEach(rec -> {
final var ipNumber = HsHostingAssetEntity.builder()
.type(IPV4_NUMBER)
.identifier(rec.getString("inet_addr"))
.caption(rec.getString("description"))
.build();
hostingAssets.put(IP_NUMBER_ID_OFFSET + rec.getInteger("inet_addr_id"), ipNumber);
});
}
private void importHives(final String[] header, final List<String[]> records) {
final var columns = new Columns(header);
records.stream()
.map(this::trimAll)
.map(row -> new Record(columns, row))
.forEach(rec -> {
final var hive_id = rec.getInteger("hive_id");
final var hive = new Hive(
hive_id,
rec.getString("hive_name"),
rec.getInteger("inet_addr_id"),
new AtomicReference<>());
hives.put(HIVE_ID_OFFSET + hive_id, hive);
});
}
private void importPackets(final String[] header, final List<String[]> records) {
final var columns = new Columns(header);
records.stream()
.map(this::trimAll)
.map(row -> new Record(columns, row))
.forEach(rec -> {
final var packet_id = rec.getInteger("packet_id");
final var basepacket_code = rec.getString("basepacket_code");
final var packet_name = rec.getString("packet_name");
final var bp_id = rec.getInteger("bp_id");
final var hive_id = rec.getInteger("hive_id");
final var created = rec.getLocalDate("created");
final var cancelled = rec.getLocalDate("cancelled");
final var cur_inet_addr_id = rec.getInteger("cur_inet_addr_id");
final var old_inet_addr_id = rec.getInteger("old_inet_addr_id");
final var free = rec.getBoolean("free");
assertThat(old_inet_addr_id)
.as("packet.old_inet_addr_id not supported, but is not null for " + packet_name)
.isNull();
final var biType = determineBiType(basepacket_code);
final var bookingItem = HsBookingItemEntity.builder()
.type(biType)
.caption("BI " + packet_name)
.project(bookingProjects.get(bp_id))
.validity(toPostgresDateRange(created, cancelled))
.build();
bookingItems.put(PACKET_ID_OFFSET + packet_id, bookingItem);
final var haType = determineHaType(basepacket_code);
logError(() -> assertThat(!free || haType == MANAGED_WEBSPACE || bookingItem.getRelatedProject().getDebitor().getDefaultPrefix().equals("hsh"))
.as("packet.free only supported for Hostsharing-Assets and ManagedWebspace in customer-ManagedServer, but is set for " + packet_name)
.isTrue());
final var asset = HsHostingAssetEntity.builder()
.isLoaded(haType == MANAGED_WEBSPACE) // this turns off identifier validation to accept former default prefixes
.type(haType)
.identifier(packet_name)
.bookingItem(bookingItem)
.caption("HA " + packet_name)
.build();
hostingAssets.put(PACKET_ID_OFFSET + packet_id, asset);
if (haType == MANAGED_SERVER) {
hive(hive_id).serverRef.set(asset);
}
if (cur_inet_addr_id != null) {
ipNumber(cur_inet_addr_id).setAssignedToAsset(asset);
}
});
// once we know all hosting assets, we can set the parentAsset for managed webspaces
records.stream()
.map(this::trimAll)
.map(row -> new Record(columns, row))
.forEach(rec -> {
final var packet_id = rec.getInteger("packet_id");
final var basepacket_code = rec.getString("basepacket_code");
final var hive_id = rec.getInteger("hive_id");
final var haType = determineHaType(basepacket_code);
if (haType == MANAGED_WEBSPACE) {
final var managedWebspace = pac(packet_id);
final var parentAsset = hive(hive_id).serverRef.get();
managedWebspace.setParentAsset(parentAsset);
managedWebspace.getBookingItem().setParentItem(parentAsset.getBookingItem());
}
});
}
private void importPacketComponents(final String[] header, final List<String[]> records) {
final var columns = new Columns(header);
records.stream()
.map(this::trimAll)
.map(row -> new Record(columns, row))
.forEach(rec -> {
// final var packet_component_id = rec.getInteger("packet_component_id"); not needed
final var packet_id = rec.getInteger("packet_id");
final var quantity = rec.getInteger("quantity");
final var basecomponent_code = rec.getString("basecomponent_code");
// final var created = rec.getLocalDate("created"); TODO.spec: can we do without?
// final var cancelled = rec.getLocalDate("cancelled"); TODO.spec: can we do without?
Function<Integer, Object> convert = (v -> v);
final var asset = pac(packet_id);
final var name = switch (basecomponent_code) {
case "DAEMON" -> "Daemons";
case "MULTI" -> "Multi";
case "CPU" -> "CPU";
case "RAM" -> returning("RAM", convert = v -> v/1024);
case "QUOTA" -> returning("SSD", convert = v -> v/1024);
case "STORAGE" -> returning("HDD", convert = v -> v/1024);
case "TRAFFIC" -> "Traffic";
case "OFFICE" -> returning("Online Office Server", convert = v -> v == 1);
case "SLABASIC" -> switch (asset.getType()) {
case CLOUD_SERVER -> "SLA-Infrastructure";
case MANAGED_SERVER -> "SLA-Platform";
case MANAGED_WEBSPACE -> "SLA-Platform";
default -> throw new IllegalArgumentException("SLABASIC not defined for " + asset.getType());
};
case "SLAINFR2H" -> "SLA-Infrastructure";
case "SLAINFR4H" -> "SLA-Infrastructure";
case "SLAINFR8H" -> "SLA-Infrastructure";
case "SLAEXT24H" -> "SLA-Platform";
case "SLAPLAT2H" -> "SLA-Platform";
case "SLAPLAT4H" -> "SLA-Platform";
case "SLAPLAT8H" -> "SLA-Platform";
case "SLAWEB2H" -> "SLA-Web";
case "SLAWEB4H" -> "SLA-Web";
case "SLAWEB8H" -> "SLA-Web";
case "SLAMAIL2H" -> "SLA-EMail";
case "SLAMAIL4H" -> "SLA-EMail";
case "SLAMAIL8H" -> "SLA-EMail";
case "SLAMARIA2H" -> "SLA-Maria";
case "SLAMARIA4H" -> "SLA-Maria";
case "SLAMARIA8H" -> "SLA-Maria";
case "SLAPGSQL2H" -> "SLA-PgSQL";
case "SLAPGSQL4H" -> "SLA-PgSQL";
case "SLAPGSQL8H" -> "SLA-PgSQL";
case "SLAOFFIC2H" -> "SLA-Office";
case "SLAOFFIC4H" -> "SLA-Office";
case "SLAOFFIC8H" -> "SLA-Office";
case "BANDWIDTH" -> "Bandwidth";
default -> throw new IllegalArgumentException("unknown basecomponent_code: " + basecomponent_code);
};
if (name.equals("SLA-Infrastructure")) {
final var slaValue = switch (basecomponent_code) {
case "SLABASIC" -> "BASIC";
case "SLAINFR2H" -> "EXT2H";
case "SLAINFR4H" -> "EXT4H";
case "SLAINFR8H" -> "EXT8H";
default -> throw new IllegalArgumentException("unknown basecomponent_code: " + basecomponent_code);
};
asset.getBookingItem().getResources().put(name, slaValue);
} else if (name.equals("SLA-Platform")) {
final var slaValue = switch (basecomponent_code) {
case "SLABASIC" -> "BASIC";
case "SLAEXT24H" -> "EXT24H";
case "SLAPLAT2H" -> "EXT2H";
case "SLAPLAT4H" -> "EXT4H";
case "SLAPLAT8H" -> "EXT8H";
default -> throw new IllegalArgumentException("unknown basecomponent_code: " + basecomponent_code);
};
if ( ofNullable(asset.getBookingItem().getResources().get(name)).map("BASIC"::equals).orElse(true) ) {
asset.getBookingItem().getResources().put(name, slaValue);
}
} else if (name.startsWith("SLA")) {
asset.getBookingItem().getResources().put(name, true);
} else if (quantity > 0) {
asset.getBookingItem().getResources().put(name, convert.apply(quantity));
}
});
}
<V> V returning(final V value, final Object... assignments) {
return value;
}
private static @NotNull HsBookingItemType determineBiType(final String basepacket_code) {
return switch (basepacket_code) {
case "SRV/CLD" -> HsBookingItemType.CLOUD_SERVER;
case "SRV/MGD" -> HsBookingItemType.MANAGED_SERVER;
case "PAC/WEB" -> HsBookingItemType.MANAGED_WEBSPACE;
default -> throw new IllegalArgumentException(
"unknown basepacket_code: " + basepacket_code);
};
}
private static @NotNull HsHostingAssetType determineHaType(final String basepacket_code) {
return switch (basepacket_code) {
case "SRV/CLD" -> CLOUD_SERVER;
case "SRV/MGD" -> MANAGED_SERVER;
case "PAC/WEB" -> MANAGED_WEBSPACE;
default -> throw new IllegalArgumentException(
"unknown basepacket_code: " + basepacket_code);
};
}
private static HsHostingAssetEntity ipNumber(final Integer inet_addr_id) {
return inet_addr_id != null ? hostingAssets.get(IP_NUMBER_ID_OFFSET + inet_addr_id) : null;
}
private static Hive hive(final Integer hive_id) {
return hive_id != null ? hives.get(HIVE_ID_OFFSET + hive_id) : null;
}
private static HsHostingAssetEntity pac(final Integer packet_id) {
return packet_id != null ? hostingAssets.get(PACKET_ID_OFFSET + packet_id) : null;
}
private String firstOfEachType(
final int maxCount,
final HsHostingAssetType... types) {
return toFormattedString(stream(types)
.flatMap(t ->
hostingAssets.entrySet().stream()
.filter(hae -> hae.getValue().getType() == t)
.limit(maxCount)
)
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue)));
}
private String firstOfEachType(
final int maxCount,
final HsBookingItemType... types) {
return toFormattedString(stream(types)
.flatMap(t ->
bookingItems.entrySet().stream()
.filter(bie -> bie.getValue().getType() == t)
.limit(maxCount)
)
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue)));
}
private Map<Integer, Object> first(
final int maxCount,
final Map<Integer, ?> entities) {
return entities.entrySet().stream()
.limit(maxCount)
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
}
@Override
protected void assumeThatWeAreExplicitlyImportingOfficeData() {
assumeThat(false).isTrue();
}
protected static boolean isImportingControlledTestData() {
return MIGRATION_DATA_PATH.equals(TEST_DATA_MIGRATION_DATA_PATH);
}
protected static void assumeThatWeAreImportingControlledTestData() {
assumeThat(isImportingControlledTestData()).isTrue();
}
}

View File

@ -1,10 +1,6 @@
package net.hostsharing.hsadminng.hs.office.migration; package net.hostsharing.hsadminng.hs.migration;
import com.opencsv.CSVParserBuilder;
import com.opencsv.CSVReader;
import com.opencsv.CSVReaderBuilder;
import net.hostsharing.hsadminng.context.Context; import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.rbac.context.ContextBasedTest;
import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity; import net.hostsharing.hsadminng.hs.office.bankaccount.HsOfficeBankAccountEntity;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity; import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionEntity; import net.hostsharing.hsadminng.hs.office.coopassets.HsOfficeCoopAssetsTransactionEntity;
@ -26,34 +22,17 @@ import net.hostsharing.hsadminng.rbac.test.JpaAttempt;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.*; import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestWatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.test.annotation.Commit;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.transaction.support.TransactionTemplate;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.constraints.NotNull;
import java.io.*; import java.io.*;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static java.lang.Boolean.parseBoolean;
import static java.util.Arrays.stream; import static java.util.Arrays.stream;
import static java.util.Objects.requireNonNull;
import static java.util.Optional.ofNullable; import static java.util.Optional.ofNullable;
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange; import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
@ -96,9 +75,9 @@ import static org.assertj.core.api.Fail.fail;
* To finally import the office data, run: * To finally import the office data, run:
* *
* import-office-tables # comes from .aliases file and uses .environment * gw-importOfficeTables # comes from .aliases file and uses .environment
*/ */
@Tag("import") @Tag("importOfficeData")
@DataJpaTest(properties = { @DataJpaTest(properties = {
"spring.datasource.url=${HSADMINNG_POSTGRES_JDBC_URL:jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers}", "spring.datasource.url=${HSADMINNG_POSTGRES_JDBC_URL:jdbc:tc:postgresql:15.5-bookworm:///spring_boot_testcontainers}",
"spring.datasource.username=${HSADMINNG_POSTGRES_ADMIN_USERNAME:ADMIN}", "spring.datasource.username=${HSADMINNG_POSTGRES_ADMIN_USERNAME:ADMIN}",
@ -109,7 +88,7 @@ import static org.assertj.core.api.Fail.fail;
@Import({ Context.class, JpaAttempt.class }) @Import({ Context.class, JpaAttempt.class })
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@ExtendWith(OrderedDependedTestsExtension.class) @ExtendWith(OrderedDependedTestsExtension.class)
public class ImportOfficeData extends ContextBasedTest { public class ImportOfficeData extends CsvDataImport {
private static final String[] SUBSCRIBER_ROLES = new String[] { private static final String[] SUBSCRIBER_ROLES = new String[] {
"subscriber:operations-discussion", "subscriber:operations-discussion",
@ -123,15 +102,16 @@ public class ImportOfficeData extends ContextBasedTest {
new String[]{"partner", "vip-contact", "ex-partner", "billing", "contractual", "operation"}, 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
public static final int MAX_NUMBER_OF_TEST_DATA_PARTNERS = 100; public static final int MAX_NUMBER_OF_TEST_DATA_PARTNERS = 100;
public static final String MIGRATION_DATA_PATH = ofNullable(System.getenv("HSADMINNG_MIGRATION_DATA_PATH")).orElse("migration") + "/";
static int relationId = 2000000; static int relationId = 2000000;
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 512167, // 11139, partner without contractual contact
512170, // 11142, partner without contractual contact 512170, // 11142, partner without contractual contact
511725, // 10764, partner without contractual contact
// 512171, // 11143, partner without partner contact -- exc
-1 -1
); );
@ -140,44 +120,23 @@ public class ImportOfficeData extends ContextBasedTest {
-1 -1
); );
@Value("${spring.datasource.url}") static Map<Integer, HsOfficeContactEntity> contacts = new WriteOnceMap<>();
private String jdbcUrl; static Map<Integer, HsOfficePersonEntity> persons = new WriteOnceMap<>();
static Map<Integer, HsOfficePartnerEntity> partners = new WriteOnceMap<>();
static Map<Integer, HsOfficeDebitorEntity> debitors = new WriteOnceMap<>();
static Map<Integer, HsOfficeMembershipEntity> memberships = new WriteOnceMap<>();
@Value("${spring.datasource.username}") static Map<Integer, HsOfficeRelationEntity> relations = new WriteOnceMap<>();
private String postgresAdminUser; static Map<Integer, HsOfficeSepaMandateEntity> sepaMandates = new WriteOnceMap<>();
static Map<Integer, HsOfficeBankAccountEntity> bankAccounts = new WriteOnceMap<>();
@Value("${hsadminng.superuser}") static Map<Integer, HsOfficeCoopSharesTransactionEntity> coopShares = new WriteOnceMap<>();
private String rbacSuperuser; static Map<Integer, HsOfficeCoopAssetsTransactionEntity> coopAssets = new WriteOnceMap<>();
private static Map<Integer, HsOfficeContactEntity> contacts = new WriteOnceMap<>();
private static Map<Integer, HsOfficePersonEntity> persons = new WriteOnceMap<>();
private static Map<Integer, HsOfficePartnerEntity> partners = new WriteOnceMap<>();
private static Map<Integer, HsOfficeDebitorEntity> debitors = new WriteOnceMap<>();
private static Map<Integer, HsOfficeMembershipEntity> memberships = new WriteOnceMap<>();
private static Map<Integer, HsOfficeRelationEntity> relations = new WriteOnceMap<>();
private static Map<Integer, HsOfficeSepaMandateEntity> sepaMandates = new WriteOnceMap<>();
private static Map<Integer, HsOfficeBankAccountEntity> bankAccounts = new WriteOnceMap<>();
private static Map<Integer, HsOfficeCoopSharesTransactionEntity> coopShares = new WriteOnceMap<>();
private static Map<Integer, HsOfficeCoopAssetsTransactionEntity> coopAssets = new WriteOnceMap<>();
@PersistenceContext
EntityManager em;
@Autowired
TransactionTemplate txTemplate;
@Autowired
JpaAttempt jpaAttempt;
@MockBean
HttpServletRequest request;
@Test @Test
@Order(1010) @Order(1010)
void importBusinessPartners() { void importBusinessPartners() {
try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "business-partners.csv")) { try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/office/business_partners.csv")) {
final var lines = readAllLines(reader); final var lines = readAllLines(reader);
importBusinessPartners(justHeader(lines), withoutHeader(lines)); importBusinessPartners(justHeader(lines), withoutHeader(lines));
} catch (Exception e) { } catch (Exception e) {
@ -193,28 +152,39 @@ public class ImportOfficeData extends ContextBasedTest {
// no contacts yet => mostly null values // no contacts yet => mostly null values
assertThat(toFormattedString(partners)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(partners)).isEqualToIgnoringWhitespace("""
{ {
17=partner(P-10017: null null, null), 100=partner(P-10003: null null, null),
20=partner(P-10020: null null, null), 120=partner(P-10020: null null, null),
22=partner(P-11022: null null, null), 122=partner(P-11022: null null, null),
90=partner(P-19090: null null, null), 132=partner(P-10152: null null, null),
99=partner(P-19999: null null, null) 190=partner(P-19090: null null, null),
199=partner(P-19999: null null, null),
213=partner(P-10000: null null, null),
541=partner(P-11018: null null, null),
542=partner(P-11019: null null, null)
} }
"""); """);
assertThat(toFormattedString(contacts)).isEqualTo("{}"); assertThat(toFormattedString(contacts)).isEqualTo("{}");
assertThat(toFormattedString(debitors)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(debitors)).isEqualToIgnoringWhitespace("""
{ {
17=debitor(D-1001700: rel(anchor='null null, null', type='DEBITOR'), mih), 100=debitor(D-1000300: rel(anchor='null null, null', type='DEBITOR'), mim),
20=debitor(D-1002000: rel(anchor='null null, null', type='DEBITOR'), xyz), 120=debitor(D-1002000: rel(anchor='null null, null', type='DEBITOR'), xyz),
22=debitor(D-1102200: rel(anchor='null null, null', type='DEBITOR'), xxx), 122=debitor(D-1102200: rel(anchor='null null, null', type='DEBITOR'), xxx),
90=debitor(D-1909000: rel(anchor='null null, null', type='DEBITOR'), yyy), 132=debitor(D-1015200: rel(anchor='null null, null', type='DEBITOR'), rar),
99=debitor(D-1999900: rel(anchor='null null, null', type='DEBITOR'), zzz) 190=debitor(D-1909000: rel(anchor='null null, null', type='DEBITOR'), yyy),
199=debitor(D-1999900: rel(anchor='null null, null', type='DEBITOR'), zzz),
213=debitor(D-1000000: rel(anchor='null null, null', type='DEBITOR'), hsh),
541=debitor(D-1101800: rel(anchor='null null, null', type='DEBITOR'), wws),
542=debitor(D-1101900: rel(anchor='null null, null', type='DEBITOR'), dph)
} }
"""); """);
assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace("""
{ {
17=Membership(M-1001700, P-10017, [2000-12-06,), ACTIVE), 100=Membership(M-1000300, P-10003, [2000-12-06,), ACTIVE),
20=Membership(M-1002000, P-10020, [2000-12-06,2016-01-01), UNKNOWN), 120=Membership(M-1002000, P-10020, [2000-12-06,2016-01-01), UNKNOWN),
22=Membership(M-1102200, P-11022, [2021-04-01,), ACTIVE) 122=Membership(M-1102200, P-11022, [2021-04-01,), ACTIVE),
132=Membership(M-1015200, P-10152, [2003-07-12,), ACTIVE),
541=Membership(M-1101800, P-11018, [2021-05-17,), ACTIVE),
542=Membership(M-1101900, P-11019, [2021-05-25,), ACTIVE)
} }
"""); """);
} }
@ -222,8 +192,7 @@ public class ImportOfficeData extends ContextBasedTest {
@Test @Test
@Order(1020) @Order(1020)
void importContacts() { void importContacts() {
try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/office/contacts.csv")) {
try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "contacts.csv")) {
final var lines = readAllLines(reader); final var lines = readAllLines(reader);
importContacts(justHeader(lines), withoutHeader(lines)); importContacts(justHeader(lines), withoutHeader(lines));
} catch (Exception e) { } catch (Exception e) {
@ -238,83 +207,151 @@ public class ImportOfficeData extends ContextBasedTest {
assertThat(toFormattedString(partners)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(partners)).isEqualToIgnoringWhitespace("""
{ {
17=partner(P-10017: NP Mellies, Michael, Herr Michael Mellies ), 100=partner(P-10003: ?? Michael Mellis, Herr Michael Mellis , Michael Mellis),
20=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),
22=partner(P-11022: ?? Test PS, Petra Schmidt , Test PS), 122=partner(P-11022: ?? Test PS, Petra Schmidt , Test PS),
90=partner(P-19090: NP Camus, Cecilia, Frau Cecilia Camus ), 132=partner(P-10152: ?? Ragnar IT-Beratung, Herr Ragnar Richter , Ragnar IT-Beratung),
99=partner(P-19999: null null, null) 190=partner(P-19090: NP Camus, Cecilia, Frau Cecilia Camus ),
199=partner(P-19999: null null, null),
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),
542=partner(P-11019: ?? Das Perfekte Haus, Herr Richard Wiese , Das Perfekte Haus)
} }
"""); """);
assertThat(toFormattedString(contacts)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(contacts)).isEqualToIgnoringWhitespace("""
{ {
1101=contact(caption='Herr Michael Mellies ', emailAddresses='{ "main": "mih@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"}'),
1401=contact(caption='Frau Frauke Fanninga ', emailAddresses='{ "main": "ff@example.org"}'), 132=contact(caption='Herr Ragnar Richter , Ragnar IT-Beratung', emailAddresses='{ "main": "hostsharing@ragnar-richter.de"}'),
1501=contact(caption='Frau Cecilia Camus ', emailAddresses='{ "main": "cc@example.org"}') 1401=contact(caption='Frau Frauke Fanninga ', emailAddresses='{ "main": "ff@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"}'),
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"}'),
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"}'),
90629=contact(caption='Ragnar Richter ', emailAddresses='{ "main": "mail@ragnar-richter..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"}')
}
"""); """);
assertThat(toFormattedString(persons)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(persons)).isEqualToIgnoringWhitespace("""
{ {
1=person(personType='LP', tradeName='Hostsharing eG'), 100=person(personType='??', tradeName='Michael Mellis', familyName='Mellis', givenName='Michael'),
1101=person(personType='NP', familyName='Mellies', 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', familyName='Meyer-Billing', givenName='Jenny'),
1201=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-Billing', givenName='Jenny'), 1202=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-Operation', givenName='Andrew'),
1202=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-Operation', givenName='Andrew'), 1203=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-Contract', givenName='Philip'),
1203=person(personType='LP', tradeName='JM GmbH', familyName='Meyer-Contract', givenName='Philip'), 1204=person(personType='LP', tradeName='JM GmbH', 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', familyName='Richter', givenName='Ragnar'),
1401=person(personType='NP', familyName='Fanninga', givenName='Frauke'), 1401=person(personType='NP', familyName='Fanninga', givenName='Frauke'),
1501=person(personType='NP', familyName='Camus', givenName='Cecilia') 1501=person(personType='NP', familyName='Camus', givenName='Cecilia'),
} 212=person(personType='LP', tradeName='Hostsharing e.G.', familyName='Hostsharing', givenName='Hostmaster'),
90436=person(personType='??', tradeName='Wasserwerk Südholstein', familyName='Milberg', givenName='Christiane'),
90437=person(personType='??', tradeName='Das Perfekte Haus', familyName='Wiese', givenName='Richard'),
90438=person(personType='??', tradeName='Wasswerwerk Südholstein', familyName='Metzger', givenName='Karim'),
90590=person(personType='??', tradeName='Das Perfekte Haus', familyName='Wiese', givenName='Inhaber R.'),
90629=person(personType='NP', familyName='Richter', givenName='Ragnar'),
90677=person(personType='NP', familyName='Henning', givenName='Eike'),
90698=person(personType='NP', familyName='Henning', givenName='Jan')
}
"""); """);
assertThat(toFormattedString(debitors)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(debitors)).isEqualToIgnoringWhitespace("""
{ {
17=debitor(D-1001700: rel(anchor='NP Mellies, Michael', type='DEBITOR', holder='NP Mellies, Michael'), mih), 100=debitor(D-1000300: rel(anchor='?? Michael Mellis', type='DEBITOR', holder='?? Michael Mellis'), mim),
20=debitor(D-1002000: rel(anchor='LP JM GmbH', type='DEBITOR', holder='LP JM GmbH'), xyz), 120=debitor(D-1002000: rel(anchor='LP JM GmbH', type='DEBITOR', holder='LP JM GmbH'), xyz),
22=debitor(D-1102200: rel(anchor='?? Test PS', type='DEBITOR', holder='?? Test PS'), xxx), 122=debitor(D-1102200: rel(anchor='?? Test PS', type='DEBITOR', holder='?? Test PS'), xxx),
90=debitor(D-1909000: rel(anchor='NP Camus, Cecilia', type='DEBITOR', holder='NP Camus, Cecilia'), yyy), 132=debitor(D-1015200: rel(anchor='?? Ragnar IT-Beratung', type='DEBITOR', holder='?? Ragnar IT-Beratung'), rar),
99=debitor(D-1999900: rel(anchor='null null, null', type='DEBITOR'), zzz) 190=debitor(D-1909000: rel(anchor='NP Camus, Cecilia', type='DEBITOR', holder='NP Camus, Cecilia'), yyy),
199=debitor(D-1999900: rel(anchor='null null, null', type='DEBITOR'), zzz),
213=debitor(D-1000000: rel(anchor='LP Hostsharing e.G.', type='DEBITOR', holder='LP Hostsharing e.G.'), hsh),
541=debitor(D-1101800: rel(anchor='?? Wasserwerk Südholstein', type='DEBITOR', holder='?? Wasserwerk Südholstein'), wws),
542=debitor(D-1101900: rel(anchor='?? Das Perfekte Haus', type='DEBITOR', holder='?? Das Perfekte Haus'), dph)
} }
"""); """);
assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace("""
{ {
17=Membership(M-1001700, P-10017, [2000-12-06,), ACTIVE), 100=Membership(M-1000300, P-10003, [2000-12-06,), ACTIVE),
20=Membership(M-1002000, P-10020, [2000-12-06,2016-01-01), UNKNOWN), 120=Membership(M-1002000, P-10020, [2000-12-06,2016-01-01), UNKNOWN),
22=Membership(M-1102200, P-11022, [2021-04-01,), ACTIVE) 122=Membership(M-1102200, P-11022, [2021-04-01,), ACTIVE),
132=Membership(M-1015200, P-10152, [2003-07-12,), ACTIVE),
541=Membership(M-1101800, P-11018, [2021-05-17,), ACTIVE),
542=Membership(M-1101900, P-11019, [2021-05-25,), ACTIVE)
} }
"""); """);
assertThat(toFormattedString(relations)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(relations)).isEqualToIgnoringWhitespace("""
{ {
2000000=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), 2000000=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'),
2000001=rel(anchor='NP Mellies, Michael', type='DEBITOR', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), 2000001=rel(anchor='?? Michael Mellis', type='DEBITOR', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'),
2000002=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), 2000002=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='?? Ragnar IT-Beratung', contact='Herr Ragnar Richter , Ragnar IT-Beratung'),
2000003=rel(anchor='LP JM GmbH', type='DEBITOR', holder='LP JM GmbH', contact='Frau Dr. Jenny Meyer-Billing , JM GmbH'), 2000003=rel(anchor='?? Ragnar IT-Beratung', type='DEBITOR', holder='?? Ragnar IT-Beratung', contact='Herr Ragnar Richter , Ragnar IT-Beratung'),
2000004=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='?? Test PS', contact='Petra Schmidt , Test PS'), 2000004=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='LP Hostsharing e.G.', contact='Firma Hostmaster Hostsharing , Hostsharing e.G.'),
2000005=rel(anchor='?? Test PS', type='DEBITOR', holder='?? Test PS', contact='Petra Schmidt , Test PS'), 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 eG', type='PARTNER', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus '), 2000006=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg , Wasserwerk Südholstein'),
2000007=rel(anchor='NP Camus, Cecilia', type='DEBITOR', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus '), 2000007=rel(anchor='?? Wasserwerk Südholstein', type='DEBITOR', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg , Wasserwerk Südholstein'),
2000008=rel(anchor='LP Hostsharing eG', type='PARTNER', holder='null null, null'), 2000008=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese , Das Perfekte Haus'),
2000009=rel(anchor='null null, null', type='DEBITOR'), 2000009=rel(anchor='?? Das Perfekte Haus', type='DEBITOR', holder='?? Das Perfekte Haus', contact='Herr Inhaber R. Wiese , Das Perfekte Haus'),
2000010=rel(anchor='NP Mellies, Michael', type='OPERATIONS', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), 2000010=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'),
2000011=rel(anchor='NP Mellies, Michael', type='REPRESENTATIVE', holder='NP Mellies, Michael', contact='Herr Michael Mellies '), 2000011=rel(anchor='LP JM GmbH', type='DEBITOR', holder='LP JM GmbH', contact='Frau Dr. Jenny Meyer-Billing , JM GmbH'),
2000012=rel(anchor='LP JM GmbH', type='EX_PARTNER', holder='LP JM e.K.', contact='JM e.K.'), 2000012=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000013=rel(anchor='LP JM GmbH', type='OPERATIONS', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), 2000013=rel(anchor='?? Test PS', type='DEBITOR', holder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000014=rel(anchor='LP JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), 2000014=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus '),
2000015=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='operations-announce', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'), 2000015=rel(anchor='NP Camus, Cecilia', type='DEBITOR', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus '),
2000016=rel(anchor='LP JM GmbH', type='REPRESENTATIVE', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), 2000016=rel(anchor='LP Hostsharing e.G.', type='PARTNER', holder='null null, null'),
2000017=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='members-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), 2000017=rel(anchor='null null, null', type='DEBITOR'),
2000018=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='customers-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , JM GmbH'), 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 JM GmbH', type='VIP_CONTACT', holder='LP JM GmbH', contact='Frau Tammy Meyer-VIP , JM GmbH'), 2000019=rel(anchor='LP Hostsharing e.G.', type='REPRESENTATIVE', holder='LP Hostsharing e.G.', contact='Firma Hostmaster Hostsharing , Hostsharing e.G.'),
2000020=rel(anchor='?? Test PS', type='OPERATIONS', holder='?? Test PS', contact='Petra Schmidt , Test PS'), 2000020=rel(anchor='?? Michael Mellis', type='OPERATIONS', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'),
2000021=rel(anchor='?? Test PS', type='REPRESENTATIVE', holder='?? Test PS', contact='Petra Schmidt , Test PS'), 2000021=rel(anchor='?? Michael Mellis', type='REPRESENTATIVE', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'),
2000022=rel(anchor='NP Mellies, Michael', type='SUBSCRIBER', mark='operations-announce', holder='NP Fanninga, Frauke', contact='Frau Frauke Fanninga '), 2000022=rel(anchor='?? Michael Mellis', type='SUBSCRIBER', mark='operations-discussion', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'),
2000023=rel(anchor='NP Camus, Cecilia', type='OPERATIONS', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus '), 2000023=rel(anchor='?? Michael Mellis', type='SUBSCRIBER', mark='operations-announce', holder='?? Michael Mellis', contact='Herr Michael Mellis , Michael Mellis'),
2000024=rel(anchor='NP Camus, Cecilia', type='REPRESENTATIVE', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus ') 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='members-announce', 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='?? Ragnar IT-Beratung', type='OPERATIONS', holder='?? Ragnar IT-Beratung', contact='Herr Ragnar Richter , Ragnar IT-Beratung'),
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='SUBSCRIBER', mark='operations-announce', 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='LP JM GmbH', type='OPERATIONS', holder='LP JM GmbH', contact='Herr Andrew Meyer-Operation , JM GmbH'),
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='SUBSCRIBER', mark='operations-announce', 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='SUBSCRIBER', mark='members-announce', holder='LP JM GmbH', contact='Herr Philip Meyer-Contract , 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='VIP_CONTACT', holder='LP JM GmbH', contact='Frau Tammy Meyer-VIP , JM GmbH'),
2000038=rel(anchor='?? Test PS', type='OPERATIONS', holder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000039=rel(anchor='?? Test PS', type='REPRESENTATIVE', holder='?? Test PS', contact='Petra Schmidt , Test PS'),
2000040=rel(anchor='LP JM GmbH', type='SUBSCRIBER', mark='operations-announce', holder='NP Fanninga, Frauke', contact='Frau Frauke Fanninga '),
2000041=rel(anchor='NP Camus, Cecilia', type='OPERATIONS', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus '),
2000042=rel(anchor='NP Camus, Cecilia', type='REPRESENTATIVE', holder='NP Camus, Cecilia', contact='Frau Cecilia Camus '),
2000043=rel(anchor='?? Wasserwerk Südholstein', type='REPRESENTATIVE', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg , Wasserwerk Südholstein'),
2000044=rel(anchor='?? Wasserwerk Südholstein', type='SUBSCRIBER', mark='generalversammlung', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg , Wasserwerk Südholstein'),
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='?? Wasserwerk Südholstein', type='SUBSCRIBER', mark='members-discussion', holder='?? Wasserwerk Südholstein', contact='Frau Christiane Milberg , Wasserwerk Südholstein'),
2000047=rel(anchor='?? Das Perfekte Haus', type='OPERATIONS', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese , Das Perfekte Haus'),
2000048=rel(anchor='?? Das Perfekte Haus', type='REPRESENTATIVE', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese , Das Perfekte Haus'),
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='?? Das Perfekte Haus', type='SUBSCRIBER', mark='operations-announce', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese , Das Perfekte Haus'),
2000051=rel(anchor='?? Das Perfekte Haus', type='SUBSCRIBER', mark='generalversammlung', holder='?? Das Perfekte Haus', contact='Herr Richard Wiese , Das Perfekte Haus'),
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='SUBSCRIBER', mark='members-discussion', 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='?? Wasserwerk Südholstein', type='SUBSCRIBER', mark='operations-discussion', holder='?? Wasswerwerk Südholstein', contact='Herr Karim Metzger , Wasswerwerk Südholstein'),
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='?? Ragnar IT-Beratung', type='REPRESENTATIVE', holder='NP Richter, Ragnar', contact='Ragnar Richter '),
2000058=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='generalversammlung', holder='NP Richter, Ragnar', contact='Ragnar Richter '),
2000059=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='members-announce', holder='NP Richter, Ragnar', contact='Ragnar Richter '),
2000060=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='members-discussion', holder='NP Richter, Ragnar', contact='Ragnar Richter '),
2000061=rel(anchor='?? Ragnar IT-Beratung', type='OPERATIONS', holder='NP Henning, Eike', contact='Eike Henning '),
2000062=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='operations-discussion', holder='NP Henning, Eike', contact='Eike Henning '),
2000063=rel(anchor='?? Ragnar IT-Beratung', type='SUBSCRIBER', mark='operations-announce', holder='NP Henning, Eike', contact='Eike Henning '),
2000064=rel(anchor='?? Ragnar IT-Beratung', type='OPERATIONS', holder='NP Henning, Jan', contact='Jan Henning ')
} }
"""); """);
} }
@ -322,8 +359,9 @@ public class ImportOfficeData extends ContextBasedTest {
@Test @Test
@Order(1030) @Order(1030)
void importSepaMandates() { void importSepaMandates() {
assumeThatWeAreExplicitlyImportingOfficeData();
try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "sepa-mandates.csv")) { try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/office/sepa_mandates.csv")) {
final var lines = readAllLines(reader); final var lines = readAllLines(reader);
importSepaMandates(justHeader(lines), withoutHeader(lines)); importSepaMandates(justHeader(lines), withoutHeader(lines));
} catch (Exception e) { } catch (Exception e) {
@ -334,20 +372,29 @@ public class ImportOfficeData extends ContextBasedTest {
@Test @Test
@Order(1039) @Order(1039)
void verifySepaMandates() { void verifySepaMandates() {
assumeThatWeAreExplicitlyImportingOfficeData();
assumeThatWeAreImportingControlledTestData(); assumeThatWeAreImportingControlledTestData();
assertThat(toFormattedString(bankAccounts)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(bankAccounts)).isEqualToIgnoringWhitespace("""
{ {
234234=bankAccount(DE37500105177419788228: holder='Michael Mellies', bic='INGDDEFFXXX'), 132=bankAccount(DE37500105177419788228: holder='Michael Mellis', bic='GENODEF1HH2'),
235600=bankAccount(DE02300209000106531065: holder='JM e.K.', bic='CMCIDEDD'), 234234=bankAccount(DE37500105177419788228: holder='Michael Mellis', bic='INGDDEFFXXX'),
235662=bankAccount(DE49500105174516484892: holder='JM GmbH', bic='INGDDEFFXXX') 235600=bankAccount(DE02300209000106531065: holder='JM e.K.', bic='CMCIDEDD'),
235662=bankAccount(DE49500105174516484892: holder='JM GmbH', bic='INGDDEFFXXX'),
30=bankAccount(DE02300209000106531065: holder='Ragnar Richter', bic='GENODEM1GLS'),
386=bankAccount(DE49500105174516484892: holder='Wasserwerk Suedholstein', bic='NOLADE21WHO'),
387=bankAccount(DE89370400440532013000: holder='Richard Wiese Das Perfekte Haus', bic='COBADEFFXXX')
} }
"""); """);
assertThat(toFormattedString(sepaMandates)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(sepaMandates)).isEqualToIgnoringWhitespace("""
{ {
234234=SEPA-Mandate(DE37500105177419788228, MH12345, 2004-06-12, [2004-06-15,)), 132=SEPA-Mandate(DE37500105177419788228, HS-10003-20140801, 2013-12-01, [2013-12-01,)),
235600=SEPA-Mandate(DE02300209000106531065, JM33344, 2004-01-15, [2004-01-20,2005-06-28)), 234234=SEPA-Mandate(DE37500105177419788228, MH12345, 2004-06-12, [2004-06-15,)),
235662=SEPA-Mandate(DE49500105174516484892, JM33344, 2005-06-28, [2005-07-01,)) 235600=SEPA-Mandate(DE02300209000106531065, JM33344, 2004-01-15, [2004-01-20,2005-06-28)),
235662=SEPA-Mandate(DE49500105174516484892, JM33344, 2005-06-28, [2005-07-01,)),
30=SEPA-Mandate(DE02300209000106531065, HS-10152-20140801, 2013-12-01, [2013-12-01,2016-02-16)),
386=SEPA-Mandate(DE49500105174516484892, HS-11018-20210512, 2021-05-12, [2021-05-17,)),
387=SEPA-Mandate(DE89370400440532013000, HS-11019-20210519, 2021-05-19, [2021-05-25,))
} }
"""); """);
} }
@ -355,7 +402,9 @@ public class ImportOfficeData extends ContextBasedTest {
@Test @Test
@Order(1040) @Order(1040)
void importCoopShares() { void importCoopShares() {
try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "share-transactions.csv")) { assumeThatWeAreExplicitlyImportingOfficeData();
try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/office/share_transactions.csv")) {
final var lines = readAllLines(reader); final var lines = readAllLines(reader);
importCoopShares(justHeader(lines), withoutHeader(lines)); importCoopShares(justHeader(lines), withoutHeader(lines));
} catch (Exception e) { } catch (Exception e) {
@ -366,23 +415,32 @@ public class ImportOfficeData extends ContextBasedTest {
@Test @Test
@Order(1041) @Order(1041)
void verifyCoopShares() { void verifyCoopShares() {
assumeThatWeAreExplicitlyImportingOfficeData();
assumeThatWeAreImportingControlledTestData(); assumeThatWeAreImportingControlledTestData();
assertThat(toFormattedString(coopShares)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(coopShares)).isEqualToIgnoringWhitespace("""
{ {
33443=CoopShareTransaction(M-1001700: 2000-12-06, SUBSCRIPTION, 20, 1001700, initial share subscription), 241=CoopShareTransaction(M-1000300: 2011-12-05, SUBSCRIPTION, 16, 1000300),
33451=CoopShareTransaction(M-1002000: 2000-12-06, SUBSCRIPTION, 2, 1002000, initial share subscription), 279=CoopShareTransaction(M-1015200: 2013-10-21, SUBSCRIPTION, 1, 1015200),
33701=CoopShareTransaction(M-1001700: 2005-01-10, SUBSCRIPTION, 40, 1001700, increase), 33451=CoopShareTransaction(M-1002000: 2000-12-06, SUBSCRIPTION, 2, 1002000, initial share subscription),
33810=CoopShareTransaction(M-1002000: 2016-12-31, CANCELLATION, 22, 1002000, membership ended) 33701=CoopShareTransaction(M-1000300: 2005-01-10, SUBSCRIPTION, 40, 1000300, increase),
} 33810=CoopShareTransaction(M-1002000: 2016-12-31, CANCELLATION, 22, 1002000, membership ended),
3=CoopShareTransaction(M-1000300: 2000-12-06, SUBSCRIPTION, 80, 1000300, initial share subscription),
523=CoopShareTransaction(M-1000300: 2020-12-08, SUBSCRIPTION, 96, 1000300, Kapitalerhoehung),
562=CoopShareTransaction(M-1101800: 2021-05-17, SUBSCRIPTION, 4, 1101800, Beitritt),
563=CoopShareTransaction(M-1101900: 2021-05-25, SUBSCRIPTION, 1, 1101900, Beitritt),
721=CoopShareTransaction(M-1000300: 2023-10-10, SUBSCRIPTION, 96, 1000300, Kapitalerhoehung),
90=CoopShareTransaction(M-1015200: 2003-07-12, SUBSCRIPTION, 1, 1015200)
}
"""); """);
} }
@Test @Test
@Order(1050) @Order(1050)
void importCoopAssets() { void importCoopAssets() {
assumeThatWeAreExplicitlyImportingOfficeData();
try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "asset-transactions.csv")) { try (Reader reader = resourceReader(MIGRATION_DATA_PATH + "/office/asset_transactions.csv")) {
final var lines = readAllLines(reader); final var lines = readAllLines(reader);
importCoopAssets(justHeader(lines), withoutHeader(lines)); importCoopAssets(justHeader(lines), withoutHeader(lines));
} catch (Exception e) { } catch (Exception e) {
@ -393,20 +451,29 @@ public class ImportOfficeData extends ContextBasedTest {
@Test @Test
@Order(1059) @Order(1059)
void verifyCoopAssets() { void verifyCoopAssets() {
assumeThatWeAreExplicitlyImportingOfficeData();
assumeThatWeAreImportingControlledTestData(); assumeThatWeAreImportingControlledTestData();
assertThat(toFormattedString(coopAssets)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(coopAssets)).isEqualToIgnoringWhitespace("""
{ {
30000=CoopAssetsTransaction(M-1001700: 2000-12-06, DEPOSIT, 1280.00, 1001700, for subscription A), 1093=CoopAssetsTransaction(M-1000300: 2023-10-05, DEPOSIT, 3072, 1000300, Kapitalerhoehung - Ueberweisung),
31000=CoopAssetsTransaction(M-1002000: 2000-12-06, DEPOSIT, 128.00, 1002000, for subscription B), 1094=CoopAssetsTransaction(M-1000300: 2023-10-06, DEPOSIT, 3072, 1000300, Kapitalerhoehung - Ueberweisung),
32000=CoopAssetsTransaction(M-1001700: 2005-01-10, DEPOSIT, 2560.00, 1001700, for subscription C), 31000=CoopAssetsTransaction(M-1002000: 2000-12-06, DEPOSIT, 128.00, 1002000, for subscription B),
33001=CoopAssetsTransaction(M-1001700: 2005-01-10, TRANSFER, -512.00, 1001700, for transfer to 10), 32000=CoopAssetsTransaction(M-1000300: 2005-01-10, DEPOSIT, 2560.00, 1000300, for subscription C),
33002=CoopAssetsTransaction(M-1002000: 2005-01-10, ADOPTION, 512.00, 1002000, for transfer from 7), 33001=CoopAssetsTransaction(M-1000300: 2005-01-10, TRANSFER, -512.00, 1000300, for transfer to 10),
34001=CoopAssetsTransaction(M-1002000: 2016-12-31, CLEARING, -8.00, 1002000, for cancellation D), 33002=CoopAssetsTransaction(M-1002000: 2005-01-10, ADOPTION, 512.00, 1002000, for transfer from 7),
34002=CoopAssetsTransaction(M-1002000: 2016-12-31, DISBURSAL, -100.00, 1002000, for cancellation D), 34001=CoopAssetsTransaction(M-1002000: 2016-12-31, CLEARING, -8.00, 1002000, for cancellation D),
34003=CoopAssetsTransaction(M-1002000: 2016-12-31, LOSS, -20.00, 1002000, for cancellation D), 34002=CoopAssetsTransaction(M-1002000: 2016-12-31, DISBURSAL, -100.00, 1002000, for cancellation D),
35001=CoopAssetsTransaction(M-1909000: 2024-01-15, DEPOSIT, 128.00, 1909000, for subscription E), 34003=CoopAssetsTransaction(M-1002000: 2016-12-31, LOSS, -20.00, 1002000, for cancellation D),
35002=CoopAssetsTransaction(M-1909000: 2024-01-20, ADJUSTMENT, -128.00, 1909000, chargeback for subscription E, M-1909000:DEP:+128.00) 35001=CoopAssetsTransaction(M-1909000: 2024-01-15, DEPOSIT, 128.00, 1909000, for subscription E),
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),
442=CoopAssetsTransaction(M-1015200: 2003-07-07, DEPOSIT, 64, 1015200),
577=CoopAssetsTransaction(M-1000300: 2011-12-12, DEPOSIT, 1024, 1000300),
632=CoopAssetsTransaction(M-1015200: 2013-10-21, DEPOSIT, 64, 1015200),
885=CoopAssetsTransaction(M-1000300: 2020-12-15, DEPOSIT, 6144, 1000300, Einzahlung),
924=CoopAssetsTransaction(M-1101800: 2021-05-21, DEPOSIT, 256, 1101800, Beitritt - Lastschrift),
925=CoopAssetsTransaction(M-1101900: 2021-05-31, DEPOSIT, 64, 1101900, Beitritt - Lastschrift)
} }
"""); """);
} }
@ -414,13 +481,18 @@ public class ImportOfficeData extends ContextBasedTest {
@Test @Test
@Order(1099) @Order(1099)
void verifyMemberships() { void verifyMemberships() {
assumeThatWeAreExplicitlyImportingOfficeData();
assumeThatWeAreImportingControlledTestData(); assumeThatWeAreImportingControlledTestData();
assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace(""" assertThat(toFormattedString(memberships)).isEqualToIgnoringWhitespace("""
{ {
17=Membership(M-1001700, P-10017, [2000-12-06,), ACTIVE), 100=Membership(M-1000300, P-10003, [2000-12-06,), ACTIVE),
20=Membership(M-1002000, P-10020, [2000-12-06,2016-01-01), UNKNOWN), 120=Membership(M-1002000, P-10020, [2000-12-06,2016-01-01), UNKNOWN),
22=Membership(M-1102200, P-11022, [2021-04-01,), ACTIVE), 122=Membership(M-1102200, P-11022, [2021-04-01,), ACTIVE),
90=Membership(M-1909000, P-19090, empty, INVALID) 132=Membership(M-1015200, P-10152, [2003-07-12,), ACTIVE),
190=Membership(M-1909000, P-19090, empty, INVALID),
541=Membership(M-1101800, P-11018, [2021-05-17,), ACTIVE),
542=Membership(M-1101900, P-11019, [2021-05-25,), ACTIVE)
} }
"""); """);
} }
@ -431,11 +503,11 @@ public class ImportOfficeData extends ContextBasedTest {
partners.forEach((id, p) -> { partners.forEach((id, p) -> {
final var partnerRel = p.getPartnerRel(); final var partnerRel = p.getPartnerRel();
assertThat(partnerRel).describedAs("partner " + id + " without partnerRel").isNotNull(); assertThat(partnerRel).describedAs("partner " + id + " without partnerRel").isNotNull();
if ( id != 99 ) { if ( id != 199 ) {
assertThat(partnerRel.getContact()).describedAs("partner " + id + " without partnerRel.contact").isNotNull(); logError( () -> assertThat(partnerRel.getContact()).describedAs("partner " + id + " without partnerRel.contact").isNotNull());
assertThat(partnerRel.getContact().getCaption()).describedAs("partner " + id + " without valid partnerRel.contact").isNotNull(); logError( () -> assertThat(partnerRel.getContact().getCaption()).describedAs("partner " + id + " without valid partnerRel.contact").isNotNull());
assertThat(partnerRel.getHolder()).describedAs("partner " + id + " without partnerRel.relHolder").isNotNull(); logError( () -> assertThat(partnerRel.getHolder()).describedAs("partner " + id + " without partnerRel.relHolder").isNotNull());
assertThat(partnerRel.getHolder().getPersonType()).describedAs("partner " + id + " without valid partnerRel.relHolder").isNotNull(); logError( () -> assertThat(partnerRel.getHolder().getPersonType()).describedAs("partner " + id + " without valid partnerRel.relHolder").isNotNull());
} }
}); });
} }
@ -523,13 +595,29 @@ public class ImportOfficeData extends ContextBasedTest {
} }
@Test @Test
@Order(9000) @Order(3005)
@Commit void removeEmptyPersons() {
void persistEntities() { // avoid a error when persisting the deliberately invalid partner entry #99
final var idsToRemove = new HashSet<Integer>();
persons.forEach( (id, p) -> {
if ( p.getPersonType() == null ||
(p.getFamilyName() == null && p.getGivenName() == null && p.getTradeName() == null) ) {
idsToRemove.add(id);
}
});
idsToRemove.forEach(id -> persons.remove(id));
System.out.println("PERSISTING to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'"); assumeThatWeAreImportingControlledTestData();
assertThat(idsToRemove.size()).isEqualTo(0);
}
@Test
@Order(9000)
void persistOfficeEntities() {
System.out.println("PERSISTING office data to database '" + jdbcUrl + "' as user '" + postgresAdminUser + "'");
deleteTestDataFromHsOfficeTables(); deleteTestDataFromHsOfficeTables();
resetFromHsOfficeSequences(); resetHsOfficeSequences();
deleteFromTestTables(); deleteFromTestTables();
deleteFromRbacTables(); deleteFromRbacTables();
@ -542,6 +630,8 @@ public class ImportOfficeData extends ContextBasedTest {
jpaAttempt.transacted(() -> { jpaAttempt.transacted(() -> {
context(rbacSuperuser); context(rbacSuperuser);
persons.forEach(this::persist); persons.forEach(this::persist);
relations.forEach( (id, rel) -> this.persist(id, rel.getAnchor()) );
relations.forEach( (id, rel) -> this.persist(id, rel.getHolder()) );
}).assertSuccessful(); }).assertSuccessful();
jpaAttempt.transacted(() -> { jpaAttempt.transacted(() -> {
@ -602,18 +692,8 @@ public class ImportOfficeData extends ContextBasedTest {
} }
private void persist(final Integer id, final RbacObject entity) { protected void assumeThatWeAreExplicitlyImportingOfficeData() {
try { // not throwing AssumptionException
//System.out.println("persisting #" + entity.hashCode() + ": " + entity);
em.persist(entity);
// uncomment for debugging purposes
// em.flush();
// System.out.println("persisted #" + entity.hashCode() + " as " + entity.getUuid());
} catch (Exception exc) {
System.err.println("failed to persist #" + entity.hashCode() + ": " + entity);
System.err.println(exc);
}
} }
private static boolean isImportingControlledTestData() { private static boolean isImportingControlledTestData() {
@ -624,62 +704,6 @@ public class ImportOfficeData extends ContextBasedTest {
assumeThat(partners.size()).isLessThanOrEqualTo(MAX_NUMBER_OF_TEST_DATA_PARTNERS); assumeThat(partners.size()).isLessThanOrEqualTo(MAX_NUMBER_OF_TEST_DATA_PARTNERS);
} }
private void deleteTestDataFromHsOfficeTables() {
jpaAttempt.transacted(() -> {
context(rbacSuperuser);
em.createNativeQuery("delete from hs_hosting_asset where true").executeUpdate();
em.createNativeQuery("delete from hs_booking_item where true").executeUpdate();
em.createNativeQuery("delete from hs_booking_project where true").executeUpdate();
em.createNativeQuery("delete from hs_office_coopassetstransaction where true").executeUpdate();
em.createNativeQuery("delete from hs_office_coopassetstransaction_legacy_id where true").executeUpdate();
em.createNativeQuery("delete from hs_office_coopsharestransaction 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_sepamandate where true").executeUpdate();
em.createNativeQuery("delete from hs_office_sepamandate_legacy_id where true").executeUpdate();
em.createNativeQuery("delete from hs_office_debitor where true").executeUpdate();
em.createNativeQuery("delete from hs_office_bankaccount where true").executeUpdate();
em.createNativeQuery("delete from hs_office_partner where true").executeUpdate();
em.createNativeQuery("delete from hs_office_partner_details where true").executeUpdate();
em.createNativeQuery("delete from hs_office_relation where true").executeUpdate();
em.createNativeQuery("delete from hs_office_contact where true").executeUpdate();
em.createNativeQuery("delete from hs_office_person where true").executeUpdate();
}).assertSuccessful();
}
private void resetFromHsOfficeSequences() {
jpaAttempt.transacted(() -> {
context(rbacSuperuser);
em.createNativeQuery("alter sequence hs_office_contact_legacy_id_seq restart with 1000000000;").executeUpdate();
em.createNativeQuery("alter sequence hs_office_coopassetstransaction_legacy_id_seq restart with 1000000000;")
.executeUpdate();
em.createNativeQuery("alter sequence public.hs_office_coopsharestransaction_legacy_id_seq restart with 1000000000;")
.executeUpdate();
em.createNativeQuery("alter sequence public.hs_office_partner_legacy_id_seq restart with 1000000000;")
.executeUpdate();
em.createNativeQuery("alter sequence public.hs_office_sepamandate_legacy_id_seq restart with 1000000000;")
.executeUpdate();
});
}
private void deleteFromTestTables() {
jpaAttempt.transacted(() -> {
context(rbacSuperuser);
em.createNativeQuery("delete from test_domain where true").executeUpdate();
em.createNativeQuery("delete from test_package where true").executeUpdate();
em.createNativeQuery("delete from test_customer where true").executeUpdate();
}).assertSuccessful();
}
private void deleteFromRbacTables() {
jpaAttempt.transacted(() -> {
context(rbacSuperuser);
em.createNativeQuery("delete from rbacuser_rv where name not like 'superuser-%'").executeUpdate();
em.createNativeQuery("delete from tx_journal where true").executeUpdate();
em.createNativeQuery("delete from tx_context where true").executeUpdate();
}).assertSuccessful();
}
private <E extends RbacObject> void updateLegacyIds( private <E extends RbacObject> void updateLegacyIds(
Map<Integer, E> entities, Map<Integer, E> entities,
final String legacyIdTable, final String legacyIdTable,
@ -698,59 +722,25 @@ public class ImportOfficeData extends ContextBasedTest {
); );
} }
public List<String[]> readAllLines(Reader reader) throws Exception {
final var parser = new CSVParserBuilder()
.withSeparator(';')
.withQuoteChar('"')
.build();
final var filteredReader = skippingEmptyAndCommentLines(reader);
try (CSVReader csvReader = new CSVReaderBuilder(filteredReader)
.withCSVParser(parser)
.build()) {
return csvReader.readAll();
}
}
public static Reader skippingEmptyAndCommentLines(Reader reader) throws IOException {
try (var bufferedReader = new BufferedReader(reader);
StringWriter writer = new StringWriter()) {
String line;
while ((line = bufferedReader.readLine()) != null) {
if (!line.isBlank() && !line.startsWith("#")) {
writer.write(line);
writer.write("\n");
}
}
return new StringReader(writer.toString());
}
}
private void importBusinessPartners(final String[] header, final List<String[]> records) { private void importBusinessPartners(final String[] header, final List<String[]> records) {
final var columns = new Columns(header); final var columns = new Columns(header);
final var mandant = HsOfficePersonEntity.builder()
.personType(HsOfficePersonType.LEGAL_PERSON)
.tradeName("Hostsharing eG")
.build();
persons.put(1, mandant);
records.stream() records.stream()
.map(this::trimAll) .map(this::trimAll)
.map(row -> new Record(columns, row)) .map(row -> new Record(columns, row))
.forEach(rec -> { .forEach(rec -> {
if (this.IGNORE_BUSINESS_PARTNERS.contains(rec.getInteger("bp_id"))) { final Integer bpId = rec.getInteger("bp_id");
if (IGNORE_BUSINESS_PARTNERS.contains(bpId)) {
return; return;
} }
final var person = HsOfficePersonEntity.builder().build(); final var person = HsOfficePersonEntity.builder().build();
final var partnerRel = addRelation( final var partnerRel = addRelation(
HsOfficeRelationType.PARTNER, mandant, person, HsOfficeRelationType.PARTNER,
null, // is set after contacts when the person for 'Hostsharing eG' is known
person,
null // is set during contacts import depending on assigned roles null // is set during contacts import depending on assigned roles
); );
@ -759,7 +749,7 @@ public class ImportOfficeData extends ContextBasedTest {
.details(HsOfficePartnerDetailsEntity.builder().build()) .details(HsOfficePartnerDetailsEntity.builder().build())
.partnerRel(partnerRel) .partnerRel(partnerRel)
.build(); .build();
partners.put(rec.getInteger("bp_id"), partner); partners.put(bpId, partner);
final var debitorRel = addRelation( final var debitorRel = addRelation(
HsOfficeRelationType.DEBITOR, partnerRel.getHolder(), // partner person HsOfficeRelationType.DEBITOR, partnerRel.getHolder(), // partner person
@ -777,7 +767,7 @@ public class ImportOfficeData extends ContextBasedTest {
.vatBusiness("GROSS".equals(rec.getString("indicator_vat"))) // TODO: remove .vatBusiness("GROSS".equals(rec.getString("indicator_vat"))) // TODO: remove
.vatId(rec.getString("uid_vat")) .vatId(rec.getString("uid_vat"))
.build(); .build();
debitors.put(rec.getInteger("bp_id"), debitor); debitors.put(bpId, debitor);
if (isNotBlank(rec.getString("member_since"))) { if (isNotBlank(rec.getString("member_since"))) {
assertThat(rec.getInteger("member_id")).isEqualTo(partner.getPartnerNumber()); assertThat(rec.getInteger("member_id")).isEqualTo(partner.getPartnerNumber());
@ -793,7 +783,7 @@ public class ImportOfficeData extends ContextBasedTest {
? HsOfficeMembershipStatus.ACTIVE ? HsOfficeMembershipStatus.ACTIVE
: HsOfficeMembershipStatus.UNKNOWN) : HsOfficeMembershipStatus.UNKNOWN)
.build(); .build();
memberships.put(rec.getInteger("bp_id"), membership); memberships.put(bpId, membership);
} }
}); });
} }
@ -807,6 +797,10 @@ public class ImportOfficeData extends ContextBasedTest {
.map(row -> new Record(columns, row)) .map(row -> new Record(columns, row))
.forEach(rec -> { .forEach(rec -> {
final var bpId = rec.getInteger("bp_id"); final var bpId = rec.getInteger("bp_id");
if (IGNORE_BUSINESS_PARTNERS.contains(bpId)) {
return;
}
final var member = ofNullable(memberships.get(bpId)) final var member = ofNullable(memberships.get(bpId))
.orElseGet(() -> createOnDemandMembership(bpId)); .orElseGet(() -> createOnDemandMembership(bpId));
@ -958,10 +952,10 @@ public class ImportOfficeData extends ContextBasedTest {
final var contactId = rec.getInteger("contact_id"); final var contactId = rec.getInteger("contact_id");
final var bpId = rec.getInteger("bp_id"); final var bpId = rec.getInteger("bp_id");
if (this.IGNORE_CONTACTS.contains(contactId)) { if (IGNORE_CONTACTS.contains(contactId)) {
return; return;
} }
if (this.IGNORE_BUSINESS_PARTNERS.contains(bpId)) { if (IGNORE_BUSINESS_PARTNERS.contains(bpId)) {
return; return;
} }
@ -1019,6 +1013,7 @@ public class ImportOfficeData extends ContextBasedTest {
}); });
assertNoMissingContractualRelations(); assertNoMissingContractualRelations();
useHostsharingAsPartnerAnchor();
} }
private static void assertNoMissingContractualRelations() { private static void assertNoMissingContractualRelations() {
@ -1038,6 +1033,16 @@ public class ImportOfficeData extends ContextBasedTest {
} }
} }
private static void useHostsharingAsPartnerAnchor() {
final var mandant = persons.values().stream()
.filter(p -> p.getTradeName().startsWith("Hostsharing e"))
.findFirst()
.orElseThrow();
relations.values().stream()
.filter(r -> r.getType() == HsOfficeRelationType.PARTNER)
.forEach(r -> r.setAnchor(mandant));
}
private static boolean containsRole(final Record rec, final String role) { private static boolean containsRole(final Record rec, final String role) {
final var roles = rec.getString("roles"); final var roles = rec.getString("roles");
return ("," + roles + ",").contains("," + role + ","); return ("," + roles + ",").contains("," + role + ",");
@ -1128,27 +1133,6 @@ public class ImportOfficeData extends ContextBasedTest {
return contact; return contact;
} }
private <E> String toFormattedString(final Map<Integer, E> map) {
if ( map.isEmpty() ) {
return "{}";
}
return "{\n" +
map.keySet().stream()
.map(id -> " " + id + "=" + map.get(id).toString())
.map(e -> e.replaceAll("\n ", " ").replace("\n", ""))
.collect(Collectors.joining(",\n")) +
"\n}\n";
}
private String[] trimAll(final String[] record) {
for (int i = 0; i < record.length; ++i) {
if (record[i] != null) {
record[i] = record[i].trim();
}
}
return record;
}
private Map<String, String> toPhoneNumbers(final Record rec) { private Map<String, String> toPhoneNumbers(final Record rec) {
final var phoneNumbers = new LinkedHashMap<String, String>(); final var phoneNumbers = new LinkedHashMap<String, String>();
if (isNotBlank(rec.getString("phone_private"))) if (isNotBlank(rec.getString("phone_private")))
@ -1218,104 +1202,4 @@ public class ImportOfficeData extends ContextBasedTest {
private String toName(final String salut, final String title, final String firstname, final String lastname) { private String toName(final String salut, final String title, final String firstname, final String lastname) {
return toCaption(salut, title, firstname, lastname, null); return toCaption(salut, title, firstname, lastname, null);
} }
private Reader resourceReader(@NotNull final String resourcePath) {
return new InputStreamReader(requireNonNull(getClass().getClassLoader().getResourceAsStream(resourcePath)));
}
private static String[] justHeader(final List<String[]> lines) {
return stream(lines.getFirst()).map(String::trim).toArray(String[]::new);
}
private List<String[]> withoutHeader(final List<String[]> records) {
return records.subList(1, records.size());
}
}
class Columns {
private final List<String> columnNames;
public Columns(final String[] header) {
columnNames = List.of(header);
}
int indexOf(final String columnName) {
int index = columnNames.indexOf(columnName);
if (index < 0) {
throw new RuntimeException("column name '" + columnName + "' not found in: " + columnNames);
}
return index;
}
}
class Record {
private final Columns columns;
private final String[] row;
public Record(final Columns columns, final String[] row) {
this.columns = columns;
this.row = row;
}
String getString(final String columnName) {
return row[columns.indexOf(columnName)];
}
boolean isEmpty(final String columnName) {
final String value = getString(columnName);
return value == null || value.isBlank();
}
boolean getBoolean(final String columnName) {
final String value = getString(columnName);
return isNotBlank(value) &&
( parseBoolean(value.trim()) || value.trim().startsWith("t"));
}
Integer getInteger(final String columnName) {
final String value = getString(columnName);
return isNotBlank(value) ? Integer.parseInt(value.trim()) : 0;
}
BigDecimal getBigDecimal(final String columnName) {
final String value = getString(columnName);
if (isNotBlank(value)) {
return new BigDecimal(value);
}
return null;
}
LocalDate getLocalDate(final String columnName) {
final String dateString = getString(columnName);
if (isNotBlank(dateString)) {
return LocalDate.parse(dateString);
}
return null;
}
}
class OrderedDependedTestsExtension implements TestWatcher, BeforeEachCallback {
private static boolean previousTestsPassed = true;
public void testFailed(ExtensionContext context, Throwable cause) {
previousTestsPassed = false;
}
@Override
public void beforeEach(final ExtensionContext extensionContext) {
assumeThat(previousTestsPassed).isTrue();
}
}
class WriteOnceMap<K, V> extends TreeMap<K, V> {
@Override
public V put(final K k, final V v) {
assertThat(containsKey(k)).describedAs("overwriting " + get(k) + " index " + k + " with " + v).isFalse();
return super.put(k, v);
}
} }

View File

@ -1,11 +0,0 @@
member_asset_id; bp_id; date; action; amount; comment
30000; 17; 2000-12-06; PAYMENT; 1280.00; for subscription A
31000; 20; 2000-12-06; PAYMENT; 128.00; for subscription B
32000; 17; 2005-01-10; PAYMENT; 2560.00; for subscription C
33001; 17; 2005-01-10; HANDOVER; -512.00; for transfer to 10
33002; 20; 2005-01-10; ADOPTION; 512.00; for transfer from 7
34001; 20; 2016-12-31; CLEARING; -8.00; for cancellation D
34002; 20; 2016-12-31; PAYBACK; -100.00; for cancellation D
34003; 20; 2016-12-31; LOSS; -20.00; for cancellation D
35001; 90; 2024-01-15; PAYMENT; 128.00; for subscription E
35002; 90; 2024-01-20; ADJUSTMENT;-128.00; chargeback for subscription E
1 member_asset_id bp_id date action amount comment
2 30000 17 2000-12-06 PAYMENT 1280.00 for subscription A
3 31000 20 2000-12-06 PAYMENT 128.00 for subscription B
4 32000 17 2005-01-10 PAYMENT 2560.00 for subscription C
5 33001 17 2005-01-10 HANDOVER -512.00 for transfer to 10
6 33002 20 2005-01-10 ADOPTION 512.00 for transfer from 7
7 34001 20 2016-12-31 CLEARING -8.00 for cancellation D
8 34002 20 2016-12-31 PAYBACK -100.00 for cancellation D
9 34003 20 2016-12-31 LOSS -20.00 for cancellation D
10 35001 90 2024-01-15 PAYMENT 128.00 for subscription E
11 35002 90 2024-01-20 ADJUSTMENT -128.00 chargeback for subscription E

View File

@ -1,6 +0,0 @@
bp_id;member_id;member_code;member_since;member_until;member_role;author_contract;nondisc_contract;free;exempt_vat;indicator_vat;uid_vat
17;10017;hsh00-mih;2000-12-06;;Aufsichtsrat;2006-10-15;2001-10-15;false;false;NET;DE-VAT-007
20;10020;hsh00-xyz;2000-12-06;2015-12-31;;;;false;false;GROSS;
22;11022;hsh00-xxx;2021-04-01;;;;;true;true;GROSS;
90;19090;hsh00-yyy;;;;;;true;true;GROSS;
99;19999;hsh00-zzz;;;;;;false;false;GROSS;
1 bp_id member_id member_code member_since member_until member_role author_contract nondisc_contract free exempt_vat indicator_vat uid_vat
2 17 10017 hsh00-mih 2000-12-06 Aufsichtsrat 2006-10-15 2001-10-15 false false NET DE-VAT-007
3 20 10020 hsh00-xyz 2000-12-06 2015-12-31 false false GROSS
4 22 11022 hsh00-xxx 2021-04-01 true true GROSS
5 90 19090 hsh00-yyy true true GROSS
6 99 19999 hsh00-zzz false false GROSS

View File

@ -1,20 +0,0 @@
contact_id; bp_id; salut; first_name; last_name; title; firma; co; street; zipcode;city; country; phone_private; phone_office; phone_mobile; fax; email; roles
# eine natürliche Person, implizites contractual
1101; 17; Herr; Michael; Mellies; ; ; ; Kleine Freiheit 50; 26524; Hage; DE; ; +49 4931 123456; +49 1522 123456;; mih@example.org; partner,contractual,billing,operation
# eine juristische Person mit drei separaten Ansprechpartnern, vip-contact und ex-partner
1200; 20;; ; ; ; JM e.K.;; Wiesenweg 15; 12335; Berlin; DE; +49 30 6666666; +49 30 5555555; ; +49 30 6666666; jm-ex-partner@example.org; ex-partner
1201; 20; Frau; Jenny; Meyer-Billing; Dr.; JM GmbH;; Waldweg 5; 11001; Berlin; DE; +49 30 7777777; +49 30 1111111; ; +49 30 2222222; jm-billing@example.org; billing
1202; 20; Herr; Andrew; Meyer-Operation; ; JM GmbH;; Waldweg 5; 11001; Berlin; DE; +49 30 6666666; +49 30 3333333; ; +49 30 4444444; am-operation@example.org; operation,vip-contact,subscriber:operations-announce
1203; 20; Herr; Philip; Meyer-Contract; ; JM GmbH;; Waldweg 5; 11001; Berlin; DE; +49 30 6666666; +49 30 5555555; ; +49 30 6666666; pm-partner@example.org; partner,contractual,subscriber:members-announce,subscriber:customers-announce
1204; 20; Frau; Tammy; Meyer-VIP; ; JM GmbH;; Waldweg 5; 11001; Berlin; DE; +49 30 999999; +49 30 999999; ; +49 30 6666666; tm-vip@example.org; vip-contact
# eine juristische Person mit nur einem Ansprechpartner und explizitem contractual
1301; 22; ; Petra; Schmidt; ; Test PS;; ; ; ; ; ; ; ; ; ps@example.com; partner,billing,contractual,operation
# eine natürliche Person, die nur Subscriber ist
1401; 17; Frau; Frauke; Fanninga; ; ; ; Am Walde 1; 29456; Hitzacker; DE; ; ; ;; ff@example.org; subscriber:operations-announce
# eine natürliche Person als Partner
1501; 90; Frau; Cecilia; Camus; ; ; ; Rue d'Avignion 60; 45000; Orléans; FR; ; ; ;; cc@example.org; partner,contractual,billing,operation
1 contact_id; bp_id; salut; first_name; last_name; title; firma; co; street; zipcode;city; country; phone_private; phone_office; phone_mobile; fax; email; roles
2 # eine natürliche Person, implizites contractual
3 1101; 17; Herr; Michael; Mellies; ; ; ; Kleine Freiheit 50; 26524; Hage; DE; ; +49 4931 123456; +49 1522 123456;; mih@example.org; partner,contractual,billing,operation
4 # eine juristische Person mit drei separaten Ansprechpartnern, vip-contact und ex-partner
5 1200; 20;; ; ; ; JM e.K.;; Wiesenweg 15; 12335; Berlin; DE; +49 30 6666666; +49 30 5555555; ; +49 30 6666666; jm-ex-partner@example.org; ex-partner
6 1201; 20; Frau; Jenny; Meyer-Billing; Dr.; JM GmbH;; Waldweg 5; 11001; Berlin; DE; +49 30 7777777; +49 30 1111111; ; +49 30 2222222; jm-billing@example.org; billing
7 1202; 20; Herr; Andrew; Meyer-Operation; ; JM GmbH;; Waldweg 5; 11001; Berlin; DE; +49 30 6666666; +49 30 3333333; ; +49 30 4444444; am-operation@example.org; operation,vip-contact,subscriber:operations-announce
8 1203; 20; Herr; Philip; Meyer-Contract; ; JM GmbH;; Waldweg 5; 11001; Berlin; DE; +49 30 6666666; +49 30 5555555; ; +49 30 6666666; pm-partner@example.org; partner,contractual,subscriber:members-announce,subscriber:customers-announce
9 1204; 20; Frau; Tammy; Meyer-VIP; ; JM GmbH;; Waldweg 5; 11001; Berlin; DE; +49 30 999999; +49 30 999999; ; +49 30 6666666; tm-vip@example.org; vip-contact
10 # eine juristische Person mit nur einem Ansprechpartner und explizitem contractual
11 1301; 22; ; Petra; Schmidt; ; Test PS;; ; ; ; ; ; ; ; ; ps@example.com; partner,billing,contractual,operation
12 # eine natürliche Person, die nur Subscriber ist
13 1401; 17; Frau; Frauke; Fanninga; ; ; ; Am Walde 1; 29456; Hitzacker; DE; ; ; ;; ff@example.org; subscriber:operations-announce
14 # eine natürliche Person als Partner
15 1501; 90; Frau; Cecilia; Camus; ; ; ; Rue d'Avignion 60; 45000; Orléans; FR; ; ; ;; cc@example.org; partner,contractual,billing,operation

View File

@ -20,58 +20,58 @@ dump() {
dump "select bp_id, member_id, member_code, member_since, member_until, member_role, author_contract, nondisc_contract, free, exempt_vat, indicator_vat, uid_vat dump "select bp_id, member_id, member_code, member_since, member_until, member_role, author_contract, nondisc_contract, free, exempt_vat, indicator_vat, uid_vat
from business_partner from business_partner
order by bp_id" \ order by bp_id" \
"business-partners.csv" "office/business_partners.csv"
dump "select contact_id, bp_id, salut, first_name, last_name, title, firma, co, street, zipcode, city, country, phone_private, phone_office, phone_mobile, fax, email, array_to_string(array_agg(role), ',') as roles dump "select contact_id, bp_id, salut, first_name, last_name, title, firma, co, street, zipcode, city, country, phone_private, phone_office, phone_mobile, fax, email, array_to_string(array_agg(role), ',') as roles
from contact from contact
left join contactrole_ref using(contact_id) left join contactrole_ref using(contact_id)
group by contact_id group by contact_id
order by contact_id" \ order by contact_id" \
"contacts.csv" "office/contacts.csv"
dump "select sepa_mandat_id, bp_id, bank_customer, bank_name, bank_iban, bank_bic, mandat_ref, mandat_signed, mandat_since, mandat_until, mandat_used dump "select sepa_mandat_id, bp_id, bank_customer, bank_name, bank_iban, bank_bic, mandat_ref, mandat_signed, mandat_since, mandat_until, mandat_used
from sepa_mandat from sepa_mandat
order by sepa_mandat_id" \ order by sepa_mandat_id" \
"sepa-mandates.csv" "office/sepa_mandates.csv"
dump "select member_asset_id, bp_id, date, action, amount, comment dump "select member_asset_id, bp_id, date, action, amount, comment
from member_asset from member_asset
WHERE bp_id NOT IN (511912) WHERE bp_id NOT IN (511912)
order by member_asset_id" \ order by member_asset_id" \
"asset-transactions.csv" "office/asset_transactions.csv"
dump "select member_share_id, bp_id, date, action, quantity, comment dump "select member_share_id, bp_id, date, action, quantity, comment
from member_share from member_share
WHERE bp_id NOT IN (511912) WHERE bp_id NOT IN (511912)
order by member_share_id" \ order by member_share_id" \
"share-transactions.csv" "office/share_transactions.csv"
dump "select inet_addr_id, inet_addr, description dump "select inet_addr_id, inet_addr, description
from inet_addr from inet_addr
order by inet_addr_id" \ order by inet_addr_id" \
"inet_addr.csv" "hosting/inet_addr.csv"
dump "select hive_id, hive_name, inet_addr_id, description dump "select hive_id, hive_name, inet_addr_id, description
from hive from hive
order by hive_id" \ order by hive_id" \
"hive.csv" "hosting/hive.csv"
dump "select packet_id, basepacket_code, packet_name, bp_id, hive_id, created, cancelled, cur_inet_addr_id, old_inet_addr_id, free dump "select packet_id, basepacket_code, packet_name, bp_id, hive_id, created, cancelled, cur_inet_addr_id, old_inet_addr_id, free
from packet from packet
left join basepacket using (basepacket_id) left join basepacket using (basepacket_id)
order by packet_id" \ order by packet_id" \
"packet.csv" "hosting/packet.csv"
dump "select packet_component_id, packet_id, quantity, basecomponent_code, created, cancelled dump "select packet_component_id, packet_id, quantity, basecomponent_code, created, cancelled
from packet_component from packet_component
left join basecomponent using (basecomponent_id) left join basecomponent using (basecomponent_id)
order by packet_component_id" \ order by packet_component_id" \
"packet_component.csv" "hosting/packet_component.csv"
dump "select unixuser_id, name, comment, shell, homedir, locked, packet_id, userid, quota_softlimit, quota_hardlimit, storage_softlimit, storage_hardlimit dump "select unixuser_id, name, comment, shell, homedir, locked, packet_id, userid, quota_softlimit, quota_hardlimit, storage_softlimit, storage_hardlimit
from unixuser from unixuser
order by unixuser_id" \ order by unixuser_id" \
"unixuser.csv" "hosting/unixuser.csv"
# weil das fehlt, muss group by komplett gesetzt werden: alter table domain add constraint PK_domain primary key (domain_id); # weil das fehlt, muss group by komplett gesetzt werden: alter table domain add constraint PK_domain primary key (domain_id);
dump "select domain_id, domain_name, domain_since, domain_dns_master, domain_owner, valid_subdomain_names, passenger_python, passenger_nodejs, passenger_ruby, fcgi_php_bin, array_to_string(array_agg(domain_option_name), ',') as domainoptions dump "select domain_id, domain_name, domain_since, domain_dns_master, domain_owner, valid_subdomain_names, passenger_python, passenger_nodejs, passenger_ruby, fcgi_php_bin, array_to_string(array_agg(domain_option_name), ',') as domainoptions
@ -80,7 +80,7 @@ dump "select domain_id, domain_name, domain_since, domain_dns_master, domain_own
left join domain_option using (domain_option_id) left join domain_option using (domain_option_id)
group by domain.domain_id, domain.domain_name, domain_since, domain_dns_master, domain_owner, valid_subdomain_names, passenger_python, passenger_nodejs, passenger_ruby, fcgi_php_bin group by domain.domain_id, domain.domain_name, domain_since, domain_dns_master, domain_owner, valid_subdomain_names, passenger_python, passenger_nodejs, passenger_ruby, fcgi_php_bin
order by domain.domain_id" \ order by domain.domain_id" \
"domain.csv" "hosting/domain.csv"
dump "select emailaddr_id, domain_id, localpart, subdomain, target dump "select emailaddr_id, domain_id, localpart, subdomain, target
from emailaddr from emailaddr
@ -90,14 +90,14 @@ dump "select emailaddr_id, domain_id, localpart, subdomain, target
dump "select emailalias_id, pac_id, name, target dump "select emailalias_id, pac_id, name, target
from emailalias from emailalias
order by emailalias_id" \ order by emailalias_id" \
"emailalias.csv" "hosting/emailalias.csv"
dump "select dbuser_id, engine, packet_id, name dump "select dbuser_id, engine, packet_id, name
from database_user from database_user
order by dbuser_id" \ order by dbuser_id" \
"database_user.csv" "hosting/database_user.csv"
dump "select database_id, engine, packet_id, name, owner, encoding dump "select database_id, engine, packet_id, name, owner, encoding
from database from database
order by database_id" \ order by database_id" \
"database.csv" "hosting/database.csv"

View File

@ -0,0 +1,26 @@
hive_id;hive_name;inet_addr_id;description
1;h00;358;
2;h01;359;
4;h02;360;
7;h03;361;
13;h04;430;
14;h50;433;
20;h05;354;
21;h06;355;
22;h07;357;
28;h60;363;
31;h63;431;
37;h67;381;
38;h97;537;
39;h96;536;
45;h74;485;
50;h82;514;
128;h19;565;
148;h50;522;
163;h92;457;
173;h25;1759;
192;h93;1778;
193;h95;1779;
205;vm1107;1861;
208;vm1110;1864;
210;vm1112;1833;
1 hive_id hive_name inet_addr_id description
2 1 h00 358
3 2 h01 359
4 4 h02 360
5 7 h03 361
6 13 h04 430
7 14 h50 433
8 20 h05 354
9 21 h06 355
10 22 h07 357
11 28 h60 363
12 31 h63 431
13 37 h67 381
14 38 h97 537
15 39 h96 536
16 45 h74 485
17 50 h82 514
18 128 h19 565
19 148 h50 522
20 163 h92 457
21 173 h25 1759
22 192 h93 1778
23 193 h95 1779
24 205 vm1107 1861
25 208 vm1110 1864
26 210 vm1112 1833

View File

@ -0,0 +1,10 @@
inet_addr_id;inet_addr;description
363;83.223.95.34;
381;83.223.95.52;
402;83.223.95.73;
433;83.223.95.104;
457;83.223.95.128;
473;83.223.95.144;
574;83.223.95.245;
1168;83.223.79.72;
1790;83.223.94.179;
1 inet_addr_id inet_addr description
2 363 83.223.95.34
3 381 83.223.95.52
4 402 83.223.95.73
5 433 83.223.95.104
6 457 83.223.95.128
7 473 83.223.95.144
8 574 83.223.95.245
9 1168 83.223.79.72
10 1790 83.223.94.179

View File

@ -0,0 +1,10 @@
packet_id;basepacket_code;packet_name;bp_id;hive_id;created;cancelled;cur_inet_addr_id;old_inet_addr_id;free
630;PAC/WEB;hsh00;213;14;2001-06-01;;473;;1
968;SRV/MGD;vm1061;132;28;2013-04-01;;363;;0
978;SRV/MGD;vm1050;213;14;2013-04-01;;433;;1
1061;SRV/MGD;vm1068;100;37;2013-08-19;;381;;f
1094;PAC/WEB;lug00;100;37;2013-09-10;;1168;;1
1112;PAC/WEB;mim00;100;37;2013-09-17;;402;;1
1447;SRV/MGD;vm1093;213;163;2014-11-28;;457;;t
19959;PAC/WEB;dph00;542;163;2021-06-02;;574;;0
23611;SRV/CLD;vm2097;541;;2022-08-10;;1790;;0
1 packet_id basepacket_code packet_name bp_id hive_id created cancelled cur_inet_addr_id old_inet_addr_id free
2 630 PAC/WEB hsh00 213 14 2001-06-01 473 1
3 968 SRV/MGD vm1061 132 28 2013-04-01 363 0
4 978 SRV/MGD vm1050 213 14 2013-04-01 433 1
5 1061 SRV/MGD vm1068 100 37 2013-08-19 381 f
6 1094 PAC/WEB lug00 100 37 2013-09-10 1168 1
7 1112 PAC/WEB mim00 100 37 2013-09-17 402 1
8 1447 SRV/MGD vm1093 213 163 2014-11-28 457 t
9 19959 PAC/WEB dph00 542 163 2021-06-02 574 0
10 23611 SRV/CLD vm2097 541 2022-08-10 1790 0

View File

@ -0,0 +1,143 @@
packet_component_id;packet_id;quantity;basecomponent_code;created;cancelled
46105;1094;10;TRAFFIC;2017-03-27;
46109;1094;5;MULTI;2017-03-27;
46111;1094;0;DAEMON;2017-03-27;
46113;1094;1024;QUOTA;2017-03-27;
46117;1112;0;DAEMON;2017-03-27;
46121;1112;20;TRAFFIC;2017-03-27;
46122;1112;5;MULTI;2017-03-27;
46123;1112;3072;QUOTA;2017-03-27;
143133;1094;1;SLABASIC;2017-09-01;
143483;1112;1;SLABASIC;2017-09-01;
757383;1112;0;SLAEXT24H;;
770533;1094;0;SLAEXT24H;;
784283;1112;0;OFFICE;;
797433;1094;0;OFFICE;;
1228033;1112;0;STORAGE;;
1241433;1094;0;STORAGE;;
1266451;978;0;SLAPLAT4H;2021-10-05;
1266452;978;250;TRAFFIC;2021-10-05;
1266453;978;0;SLAPLAT8H;2021-10-05;
1266454;978;0;SLAMAIL4H;2021-10-05;
1266455;978;0;SLAMARIA8H;2021-10-05;
1266456;978;0;SLAPGSQL4H;2021-10-05;
1266457;978;0;SLAWEB4H;2021-10-05;
1266458;978;0;SLAMARIA4H;2021-10-05;
1266459;978;0;SLAPGSQL8H;2021-10-05;
1266460;978;0;SLAOFFIC8H;2021-10-05;
1266461;978;0;SLAWEB8H;2021-10-05;
1266462;978;256000;STORAGE;2021-10-05;
1266463;978;153600;QUOTA;2021-10-05;
1266464;978;0;SLAOFFIC4H;2021-10-05;
1266465;978;32768;RAM;2021-10-05;
1266466;978;4;CPU;2021-10-05;
1266467;978;1;SLABASIC;2021-10-05;
1266468;978;0;SLAMAIL8H;2021-10-05;
1275583;978;0;SLAPLAT2H;2022-04-20;
1280533;978;0;SLAWEB2H;2022-04-20;
1285483;978;0;SLAMARIA2H;2022-04-20;
1290433;978;0;SLAPGSQL2H;2022-04-20;
1295383;978;0;SLAMAIL2H;2022-04-20;
1300333;978;0;SLAOFFIC2H;2022-04-20;
1305933;1447;0;SLAWEB2H;2022-05-02;
1305934;1447;0;SLAPLAT4H;2022-05-02;
1305935;1447;0;SLAWEB8H;2022-05-02;
1305936;1447;0;SLAOFFIC4H;2022-05-02;
1305937;1447;0;SLAMARIA4H;2022-05-02;
1305938;1447;0;SLAOFFIC8H;2022-05-02;
1305939;1447;1;SLABASIC;2022-05-02;
1305940;1447;0;SLAMAIL8H;2022-05-02;
1305941;1447;0;SLAPGSQL4H;2022-05-02;
1305942;1447;6;CPU;2022-05-02;
1305943;1447;250;TRAFFIC;2022-05-02;
1305944;1447;0;SLAOFFIC2H;2022-05-02;
1305945;1447;0;SLAMAIL4H;2022-05-02;
1305946;1447;0;SLAPGSQL2H;2022-05-02;
1305947;1447;0;SLAMARIA2H;2022-05-02;
1305948;1447;0;SLAMARIA8H;2022-05-02;
1305949;1447;0;SLAWEB4H;2022-05-02;
1305950;1447;16384;RAM;2022-05-02;
1305951;1447;0;SLAPGSQL8H;2022-05-02;
1305952;1447;512000;STORAGE;2022-05-02;
1305953;1447;0;SLAMAIL2H;2022-05-02;
1305954;1447;0;SLAPLAT2H;2022-05-02;
1305955;1447;0;SLAPLAT8H;2022-05-02;
1305956;1447;307200;QUOTA;2022-05-02;
1312013;23611;1;SLABASIC;2022-08-10;
1312014;23611;0;BANDWIDTH;2022-08-10;
1312015;23611;12288;RAM;2022-08-10;
1312016;23611;25600;QUOTA;2022-08-10;
1312017;23611;0;SLAINFR8H;2022-08-10;
1312018;23611;0;STORAGE;2022-08-10;
1312019;23611;0;SLAINFR2H;2022-08-10;
1312020;23611;8;CPU;2022-08-10;
1312021;23611;250;TRAFFIC;2022-08-10;
1312022;23611;0;SLAINFR4H;2022-08-10;
1313883;978;0;BANDWIDTH;;
1316583;1447;0;BANDWIDTH;;
1338074;968;0;SLAMARIA2H;2023-09-05;
1338075;968;384000;QUOTA;2023-09-05;
1338076;968;1;SLAMAIL8H;2023-09-05;
1338077;968;0;BANDWIDTH;2023-09-05;
1338078;968;0;SLAWEB2H;2023-09-05;
1338079;968;0;SLAOFFIC4H;2023-09-05;
1338080;968;256000;STORAGE;2023-09-05;
1338081;968;0;SLAPLAT4H;2023-09-05;
1338082;968;0;SLAPGSQL2H;2023-09-05;
1338083;968;0;SLAPLAT2H;2023-09-05;
1338084;968;250;TRAFFIC;2023-09-05;
1338085;968;1;SLAMARIA8H;2023-09-05;
1338086;968;0;SLAPGSQL4H;2023-09-05;
1338087;968;0;SLAMAIL2H;2023-09-05;
1338088;968;1;SLAPLAT8H;2023-09-05;
1338089;968;0;SLAWEB4H;2023-09-05;
1338090;968;6;CPU;2023-09-05;
1338091;968;1;SLAPGSQL8H;2023-09-05;
1338092;968;0;SLAMARIA4H;2023-09-05;
1338093;968;0;SLAMAIL4H;2023-09-05;
1338094;968;14336;RAM;2023-09-05;
1338095;968;0;SLAOFFIC2H;2023-09-05;
1338096;968;0;SLAOFFIC8H;2023-09-05;
1338097;968;1;SLABASIC;2023-09-05;
1338098;968;1;SLAWEB8H;2023-09-05;
1339228;19959;20;TRAFFIC;2023-10-27;
1339229;19959;1;SLABASIC;2023-10-27;
1339230;19959;0;DAEMON;2023-10-27;
1339231;19959;25600;QUOTA;2023-10-27;
1339232;19959;0;STORAGE;2023-10-27;
1339233;19959;0;SLAEXT24H;2023-10-27;
1339234;19959;0;OFFICE;2023-10-27;
1339235;19959;1;MULTI;2023-10-27;
1341088;1061;0;SLAOFFIC2H;2023-12-14;
1341089;1061;0;SLAOFFIC8H;2023-12-14;
1341090;1061;256000;STORAGE;2023-12-14;
1341091;1061;0;SLAMAIL4H;2023-12-14;
1341092;1061;0;SLAMAIL2H;2023-12-14;
1341093;1061;0;SLAPLAT2H;2023-12-14;
1341094;1061;4096;RAM;2023-12-14;
1341095;1061;0;SLAPLAT4H;2023-12-14;
1341096;1061;1;SLAPGSQL8H;2023-12-14;
1341097;1061;2;CPU;2023-12-14;
1341098;1061;0;QUOTA;2023-12-14;
1341099;1061;0;SLAMAIL8H;2023-12-14;
1341100;1061;1;SLABASIC;2023-12-14;
1341101;1061;1;SLAMARIA8H;2023-12-14;
1341102;1061;0;SLAPGSQL4H;2023-12-14;
1341103;1061;0;SLAPGSQL2H;2023-12-14;
1341104;1061;0;SLAMARIA4H;2023-12-14;
1341105;1061;0;SLAOFFIC4H;2023-12-14;
1341106;1061;1;SLAPLAT8H;2023-12-14;
1341107;1061;0;BANDWIDTH;2023-12-14;
1341108;1061;1;SLAWEB8H;2023-12-14;
1341109;1061;0;SLAWEB2H;2023-12-14;
1341110;1061;0;SLAMARIA2H;2023-12-14;
1341111;1061;250;TRAFFIC;2023-12-14;
1341112;1061;0;SLAWEB4H;2023-12-14;
1346628;630;0;SLAEXT24H;2024-03-19;
1346629;630;0;OFFICE;2024-03-19;
1346630;630;16384;QUOTA;2024-03-19;
1346631;630;0;DAEMON;2024-03-19;
1346632;630;10240;STORAGE;2024-03-19;
1346633;630;1;SLABASIC;2024-03-19;
1346634;630;50;TRAFFIC;2024-03-19;
1346635;630;25;MULTI;2024-03-19;
1 packet_component_id packet_id quantity basecomponent_code created cancelled
2 46105 1094 10 TRAFFIC 2017-03-27
3 46109 1094 5 MULTI 2017-03-27
4 46111 1094 0 DAEMON 2017-03-27
5 46113 1094 1024 QUOTA 2017-03-27
6 46117 1112 0 DAEMON 2017-03-27
7 46121 1112 20 TRAFFIC 2017-03-27
8 46122 1112 5 MULTI 2017-03-27
9 46123 1112 3072 QUOTA 2017-03-27
10 143133 1094 1 SLABASIC 2017-09-01
11 143483 1112 1 SLABASIC 2017-09-01
12 757383 1112 0 SLAEXT24H
13 770533 1094 0 SLAEXT24H
14 784283 1112 0 OFFICE
15 797433 1094 0 OFFICE
16 1228033 1112 0 STORAGE
17 1241433 1094 0 STORAGE
18 1266451 978 0 SLAPLAT4H 2021-10-05
19 1266452 978 250 TRAFFIC 2021-10-05
20 1266453 978 0 SLAPLAT8H 2021-10-05
21 1266454 978 0 SLAMAIL4H 2021-10-05
22 1266455 978 0 SLAMARIA8H 2021-10-05
23 1266456 978 0 SLAPGSQL4H 2021-10-05
24 1266457 978 0 SLAWEB4H 2021-10-05
25 1266458 978 0 SLAMARIA4H 2021-10-05
26 1266459 978 0 SLAPGSQL8H 2021-10-05
27 1266460 978 0 SLAOFFIC8H 2021-10-05
28 1266461 978 0 SLAWEB8H 2021-10-05
29 1266462 978 256000 STORAGE 2021-10-05
30 1266463 978 153600 QUOTA 2021-10-05
31 1266464 978 0 SLAOFFIC4H 2021-10-05
32 1266465 978 32768 RAM 2021-10-05
33 1266466 978 4 CPU 2021-10-05
34 1266467 978 1 SLABASIC 2021-10-05
35 1266468 978 0 SLAMAIL8H 2021-10-05
36 1275583 978 0 SLAPLAT2H 2022-04-20
37 1280533 978 0 SLAWEB2H 2022-04-20
38 1285483 978 0 SLAMARIA2H 2022-04-20
39 1290433 978 0 SLAPGSQL2H 2022-04-20
40 1295383 978 0 SLAMAIL2H 2022-04-20
41 1300333 978 0 SLAOFFIC2H 2022-04-20
42 1305933 1447 0 SLAWEB2H 2022-05-02
43 1305934 1447 0 SLAPLAT4H 2022-05-02
44 1305935 1447 0 SLAWEB8H 2022-05-02
45 1305936 1447 0 SLAOFFIC4H 2022-05-02
46 1305937 1447 0 SLAMARIA4H 2022-05-02
47 1305938 1447 0 SLAOFFIC8H 2022-05-02
48 1305939 1447 1 SLABASIC 2022-05-02
49 1305940 1447 0 SLAMAIL8H 2022-05-02
50 1305941 1447 0 SLAPGSQL4H 2022-05-02
51 1305942 1447 6 CPU 2022-05-02
52 1305943 1447 250 TRAFFIC 2022-05-02
53 1305944 1447 0 SLAOFFIC2H 2022-05-02
54 1305945 1447 0 SLAMAIL4H 2022-05-02
55 1305946 1447 0 SLAPGSQL2H 2022-05-02
56 1305947 1447 0 SLAMARIA2H 2022-05-02
57 1305948 1447 0 SLAMARIA8H 2022-05-02
58 1305949 1447 0 SLAWEB4H 2022-05-02
59 1305950 1447 16384 RAM 2022-05-02
60 1305951 1447 0 SLAPGSQL8H 2022-05-02
61 1305952 1447 512000 STORAGE 2022-05-02
62 1305953 1447 0 SLAMAIL2H 2022-05-02
63 1305954 1447 0 SLAPLAT2H 2022-05-02
64 1305955 1447 0 SLAPLAT8H 2022-05-02
65 1305956 1447 307200 QUOTA 2022-05-02
66 1312013 23611 1 SLABASIC 2022-08-10
67 1312014 23611 0 BANDWIDTH 2022-08-10
68 1312015 23611 12288 RAM 2022-08-10
69 1312016 23611 25600 QUOTA 2022-08-10
70 1312017 23611 0 SLAINFR8H 2022-08-10
71 1312018 23611 0 STORAGE 2022-08-10
72 1312019 23611 0 SLAINFR2H 2022-08-10
73 1312020 23611 8 CPU 2022-08-10
74 1312021 23611 250 TRAFFIC 2022-08-10
75 1312022 23611 0 SLAINFR4H 2022-08-10
76 1313883 978 0 BANDWIDTH
77 1316583 1447 0 BANDWIDTH
78 1338074 968 0 SLAMARIA2H 2023-09-05
79 1338075 968 384000 QUOTA 2023-09-05
80 1338076 968 1 SLAMAIL8H 2023-09-05
81 1338077 968 0 BANDWIDTH 2023-09-05
82 1338078 968 0 SLAWEB2H 2023-09-05
83 1338079 968 0 SLAOFFIC4H 2023-09-05
84 1338080 968 256000 STORAGE 2023-09-05
85 1338081 968 0 SLAPLAT4H 2023-09-05
86 1338082 968 0 SLAPGSQL2H 2023-09-05
87 1338083 968 0 SLAPLAT2H 2023-09-05
88 1338084 968 250 TRAFFIC 2023-09-05
89 1338085 968 1 SLAMARIA8H 2023-09-05
90 1338086 968 0 SLAPGSQL4H 2023-09-05
91 1338087 968 0 SLAMAIL2H 2023-09-05
92 1338088 968 1 SLAPLAT8H 2023-09-05
93 1338089 968 0 SLAWEB4H 2023-09-05
94 1338090 968 6 CPU 2023-09-05
95 1338091 968 1 SLAPGSQL8H 2023-09-05
96 1338092 968 0 SLAMARIA4H 2023-09-05
97 1338093 968 0 SLAMAIL4H 2023-09-05
98 1338094 968 14336 RAM 2023-09-05
99 1338095 968 0 SLAOFFIC2H 2023-09-05
100 1338096 968 0 SLAOFFIC8H 2023-09-05
101 1338097 968 1 SLABASIC 2023-09-05
102 1338098 968 1 SLAWEB8H 2023-09-05
103 1339228 19959 20 TRAFFIC 2023-10-27
104 1339229 19959 1 SLABASIC 2023-10-27
105 1339230 19959 0 DAEMON 2023-10-27
106 1339231 19959 25600 QUOTA 2023-10-27
107 1339232 19959 0 STORAGE 2023-10-27
108 1339233 19959 0 SLAEXT24H 2023-10-27
109 1339234 19959 0 OFFICE 2023-10-27
110 1339235 19959 1 MULTI 2023-10-27
111 1341088 1061 0 SLAOFFIC2H 2023-12-14
112 1341089 1061 0 SLAOFFIC8H 2023-12-14
113 1341090 1061 256000 STORAGE 2023-12-14
114 1341091 1061 0 SLAMAIL4H 2023-12-14
115 1341092 1061 0 SLAMAIL2H 2023-12-14
116 1341093 1061 0 SLAPLAT2H 2023-12-14
117 1341094 1061 4096 RAM 2023-12-14
118 1341095 1061 0 SLAPLAT4H 2023-12-14
119 1341096 1061 1 SLAPGSQL8H 2023-12-14
120 1341097 1061 2 CPU 2023-12-14
121 1341098 1061 0 QUOTA 2023-12-14
122 1341099 1061 0 SLAMAIL8H 2023-12-14
123 1341100 1061 1 SLABASIC 2023-12-14
124 1341101 1061 1 SLAMARIA8H 2023-12-14
125 1341102 1061 0 SLAPGSQL4H 2023-12-14
126 1341103 1061 0 SLAPGSQL2H 2023-12-14
127 1341104 1061 0 SLAMARIA4H 2023-12-14
128 1341105 1061 0 SLAOFFIC4H 2023-12-14
129 1341106 1061 1 SLAPLAT8H 2023-12-14
130 1341107 1061 0 BANDWIDTH 2023-12-14
131 1341108 1061 1 SLAWEB8H 2023-12-14
132 1341109 1061 0 SLAWEB2H 2023-12-14
133 1341110 1061 0 SLAMARIA2H 2023-12-14
134 1341111 1061 250 TRAFFIC 2023-12-14
135 1341112 1061 0 SLAWEB4H 2023-12-14
136 1346628 630 0 SLAEXT24H 2024-03-19
137 1346629 630 0 OFFICE 2024-03-19
138 1346630 630 16384 QUOTA 2024-03-19
139 1346631 630 0 DAEMON 2024-03-19
140 1346632 630 10240 STORAGE 2024-03-19
141 1346633 630 1 SLABASIC 2024-03-19
142 1346634 630 50 TRAFFIC 2024-03-19
143 1346635 630 25 MULTI 2024-03-19

View File

@ -0,0 +1,19 @@
member_asset_id; bp_id; date; action; amount; comment
358; 100; 2000-12-06; PAYMENT; 5120; for subscription A
442; 132; 2003-07-07; PAYMENT; 64;
577; 100; 2011-12-12; PAYMENT; 1024;
632; 132; 2013-10-21; PAYMENT; 64;
885; 100; 2020-12-15; PAYMENT; 6144; Einzahlung
924; 541; 2021-05-21; PAYMENT; 256; Beitritt - Lastschrift
925; 542; 2021-05-31; PAYMENT; 64; Beitritt - Lastschrift
1093; 100; 2023-10-05; PAYMENT; 3072; Kapitalerhoehung - Ueberweisung
1094; 100; 2023-10-06; PAYMENT; 3072; Kapitalerhoehung - Ueberweisung
31000; 120; 2000-12-06; PAYMENT; 128.00; for subscription B
32000; 100; 2005-01-10; PAYMENT; 2560.00; for subscription C
33001; 100; 2005-01-10; HANDOVER; -512.00; for transfer to 10
33002; 120; 2005-01-10; ADOPTION; 512.00; for transfer from 7
34001; 120; 2016-12-31; CLEARING; -8.00; for cancellation D
34002; 120; 2016-12-31; PAYBACK; -100.00; for cancellation D
34003; 120; 2016-12-31; LOSS; -20.00; for cancellation D
35001; 190; 2024-01-15; PAYMENT; 128.00; for subscription E
35002; 190; 2024-01-20; ADJUSTMENT;-128.00; chargeback for subscription E
1 member_asset_id bp_id date action amount comment
2 358 100 2000-12-06 PAYMENT 5120 for subscription A
3 442 132 2003-07-07 PAYMENT 64
4 577 100 2011-12-12 PAYMENT 1024
5 632 132 2013-10-21 PAYMENT 64
6 885 100 2020-12-15 PAYMENT 6144 Einzahlung
7 924 541 2021-05-21 PAYMENT 256 Beitritt - Lastschrift
8 925 542 2021-05-31 PAYMENT 64 Beitritt - Lastschrift
9 1093 100 2023-10-05 PAYMENT 3072 Kapitalerhoehung - Ueberweisung
10 1094 100 2023-10-06 PAYMENT 3072 Kapitalerhoehung - Ueberweisung
11 31000 120 2000-12-06 PAYMENT 128.00 for subscription B
12 32000 100 2005-01-10 PAYMENT 2560.00 for subscription C
13 33001 100 2005-01-10 HANDOVER -512.00 for transfer to 10
14 33002 120 2005-01-10 ADOPTION 512.00 for transfer from 7
15 34001 120 2016-12-31 CLEARING -8.00 for cancellation D
16 34002 120 2016-12-31 PAYBACK -100.00 for cancellation D
17 34003 120 2016-12-31 LOSS -20.00 for cancellation D
18 35001 190 2024-01-15 PAYMENT 128.00 for subscription E
19 35002 190 2024-01-20 ADJUSTMENT -128.00 chargeback for subscription E

View File

@ -0,0 +1,10 @@
bp_id;member_id;member_code;member_since;member_until;member_role;author_contract;nondisc_contract;free;exempt_vat;indicator_vat;uid_vat
100;10003;hsh00-mim;2000-12-06;;Aufsichtsrat;;2001-04-24;0;0;GROSS;DE217249198
132;10152;hsh00-rar;2003-07-12;;;;;0;0;GROSS;DE 236 109 136
213;10000;hsh00-hsh;;;Hostsharing eG;;;1;0;GROSS;
541;11018;hsh00-wws;2021-05-17;;;;;0;0;GROSS;
542;11019;hsh00-dph;2021-05-25;;;;;0;0;GROSS;
120;10020;hsh00-xyz;2000-12-06;2015-12-31;;;;false;false;GROSS;
122;11022;hsh00-xxx;2021-04-01;;;;;true;true;GROSS;
190;19090;hsh00-yyy;;;;;;true;true;GROSS;
199;19999;hsh00-zzz;;;;;;false;false;GROSS;
1 bp_id member_id member_code member_since member_until member_role author_contract nondisc_contract free exempt_vat indicator_vat uid_vat
2 100 10003 hsh00-mim 2000-12-06 Aufsichtsrat 2001-04-24 0 0 GROSS DE217249198
3 132 10152 hsh00-rar 2003-07-12 0 0 GROSS DE 236 109 136
4 213 10000 hsh00-hsh Hostsharing eG 1 0 GROSS
5 541 11018 hsh00-wws 2021-05-17 0 0 GROSS
6 542 11019 hsh00-dph 2021-05-25 0 0 GROSS
7 120 10020 hsh00-xyz 2000-12-06 2015-12-31 false false GROSS
8 122 11022 hsh00-xxx 2021-04-01 true true GROSS
9 190 19090 hsh00-yyy true true GROSS
10 199 19999 hsh00-zzz false false GROSS

View File

@ -0,0 +1,35 @@
contact_id; bp_id; salut; first_name; last_name; title; firma; co; street; zipcode; city; country; phone_private; phone_office; phone_mobile; fax; email; roles
# Hostsharing, the mandate itself
212; 213; Firma; Hostmaster; Hostsharing; ; Hostsharing e.G.; ; ; ; ; Germany; ; ; ; ; hostmaster@hostsharing.net; billing,operation,contractual,partner
# some natural persons
100; 100; Herr; Michael; Mellis; ; Michael Mellis; ; Dr. Bolte Str. 50; 26524; Hage; Germany; ; +49 4931/1234567; +49/1522123455; +49 40 912345-9; michael@Mellis.example.org; billing,operation,contractual,partner,subscriber:members-announce,subscriber:operations-announce,subscriber:operations-discussion,subscriber:members-discussion,subscriber:generalversammlung
132; 132; Herr; Ragnar; Richter; ; Ragnar IT-Beratung; ; Vioktoriastraße 114; 70197; Stuttgart; Germany; +49 711 987654-1; +49 711 987654-2; ; +49 711 87654-3; hostsharing@ragnar-richter.de; billing,operation,partner,subscriber:operations-announce,subscriber:operations-discussion
# eine juristische Person mit drei separaten Ansprechpartnern, vip-contact und ex-partner
1200; 120; ; ; ; ; JM e.K.; ; Wiesenweg 15; 12335; Berlin; DE; +49 30 6666666; +49 30 5555555; ; +49 30 6666666; jm-ex-partner@example.org; ex-partner
1201; 120; Frau; Jenny; Meyer-Billing; Dr.; JM GmbH; ; Waldweg 5; 11001; Berlin; DE; +49 30 7777777; +49 30 1111111; ; +49 30 2222222; jm-billing@example.org; billing
1202; 120; Herr; Andrew; Meyer-Operation; ; JM GmbH; ; Waldweg 5; 11001; Berlin; DE; +49 30 6666666; +49 30 3333333; ; +49 30 4444444; am-operation@example.org; operation,vip-contact,subscriber:operations-announce
1203; 120; Herr; Philip; Meyer-Contract; ; JM GmbH; ; Waldweg 5; 11001; Berlin; DE; +49 30 6666666; +49 30 5555555; ; +49 30 6666666; pm-partner@example.org; partner,contractual,subscriber:members-announce,subscriber:customers-announce
1204; 120; Frau; Tammy; Meyer-VIP; ; JM GmbH; ; Waldweg 5; 11001; Berlin; DE; +49 30 999999; +49 30 999999; ; +49 30 6666666; tm-vip@example.org; vip-contact
# eine juristische Person mit nur einem Ansprechpartner und explizitem contractual
1301; 122; ; Petra; Schmidt; ; Test PS; ; ; ; ; ; ; ; ; ; ps@example.com; partner,billing,contractual,operation
# eine natürliche Person, die nur Subscriber ist
1401; 120; Frau; Frauke; Fanninga; ; ; ; Am Walde 1; 29456; Hitzacker; DE; ; ; ; ; ff@example.org; subscriber:operations-announce
# eine natürliche Person als Partner
1501; 190; Frau; Cecilia; Camus; ; ; ; Rue d'Avignion 60; 45000; Orléans; FR; ; ; ; ; cc@example.org; partner,contractual,billing,operation
# some more contacts of realistic business partners
90436; 541; Frau; Christiane; Milberg; ; Wasserwerk Südholstein; ; Am Wasserwerk 1-3; 25491; Hetlingen; Germany; ; ; +49 4103 12345-1; ; rechnung@ww-sholst.example.org; billing,partner,subscriber:members-discussion,contractual,subscriber:members-announce,subscriber:generalversammlung
90437; 542; Herr; Richard; Wiese; ; Das Perfekte Haus; ; Kennedyplatz 11; 45279; Essen; Germany; ; ; +49-172-12345; ; admin@das-perfekte-haus.example.org; operation,partner,subscriber:members-discussion,contractual,subscriber:operations-announce,subscriber:operations-discussion,subscriber:members-announce,subscriber:generalversammlung
90438; 541; Herr; Karim; Metzger; ; Wasswerwerk Südholstein; ; Am Wasserwerk 1-3; 25491; Hetlingen; Germany; ; +49 4103 12345-2; ; ; karim.metzger@ww-sholst.example.org; operation,subscriber:operations-announce,subscriber:operations-discussion
90590; 542; Herr; Inhaber R.; Wiese; ; Das Perfekte Haus; Client-ID 515217; Essen, Kastanienallee 81; 30127; Hannover; Germany; ; ; ; ; 515217@kkemail.example.org; billing
90629; 132; ; Ragnar; Richter; ; ; ; ; ; ; ; ; ; ; ; mail@ragnar-richter..example.org; contractual,subscriber:members-announce,subscriber:members-discussion,subscriber:generalversammlung
90677; 132; ; Eike; Henning; ; ; ; ; ; ; ; ; ; ; ; hostsharing@eike-henning..example.org; operation,subscriber:operations-announce,subscriber:operations-discussion
90698; 132; ; Jan; Henning; ; ; ; ; ; ; ; ; 01577 12345678; ; ; mail@jan-henning.example.org; operation
1 contact_id; bp_id; salut; first_name; last_name; title; firma; co; street; zipcode; city; country; phone_private; phone_office; phone_mobile; fax; email; roles
2 # Hostsharing, the mandate itself
3 212; 213; Firma; Hostmaster; Hostsharing; ; Hostsharing e.G.; ; ; ; ; Germany; ; ; ; ; hostmaster@hostsharing.net; billing,operation,contractual,partner
4 # some natural persons
5 100; 100; Herr; Michael; Mellis; ; Michael Mellis; ; Dr. Bolte Str. 50; 26524; Hage; Germany; ; +49 4931/1234567; +49/1522123455; +49 40 912345-9; michael@Mellis.example.org; billing,operation,contractual,partner,subscriber:members-announce,subscriber:operations-announce,subscriber:operations-discussion,subscriber:members-discussion,subscriber:generalversammlung
6 132; 132; Herr; Ragnar; Richter; ; Ragnar IT-Beratung; ; Vioktoriastraße 114; 70197; Stuttgart; Germany; +49 711 987654-1; +49 711 987654-2; ; +49 711 87654-3; hostsharing@ragnar-richter.de; billing,operation,partner,subscriber:operations-announce,subscriber:operations-discussion
7 # eine juristische Person mit drei separaten Ansprechpartnern, vip-contact und ex-partner
8 1200; 120; ; ; ; ; JM e.K.; ; Wiesenweg 15; 12335; Berlin; DE; +49 30 6666666; +49 30 5555555; ; +49 30 6666666; jm-ex-partner@example.org; ex-partner
9 1201; 120; Frau; Jenny; Meyer-Billing; Dr.; JM GmbH; ; Waldweg 5; 11001; Berlin; DE; +49 30 7777777; +49 30 1111111; ; +49 30 2222222; jm-billing@example.org; billing
10 1202; 120; Herr; Andrew; Meyer-Operation; ; JM GmbH; ; Waldweg 5; 11001; Berlin; DE; +49 30 6666666; +49 30 3333333; ; +49 30 4444444; am-operation@example.org; operation,vip-contact,subscriber:operations-announce
11 1203; 120; Herr; Philip; Meyer-Contract; ; JM GmbH; ; Waldweg 5; 11001; Berlin; DE; +49 30 6666666; +49 30 5555555; ; +49 30 6666666; pm-partner@example.org; partner,contractual,subscriber:members-announce,subscriber:customers-announce
12 1204; 120; Frau; Tammy; Meyer-VIP; ; JM GmbH; ; Waldweg 5; 11001; Berlin; DE; +49 30 999999; +49 30 999999; ; +49 30 6666666; tm-vip@example.org; vip-contact
13 # eine juristische Person mit nur einem Ansprechpartner und explizitem contractual
14 1301; 122; ; Petra; Schmidt; ; Test PS; ; ; ; ; ; ; ; ; ; ps@example.com; partner,billing,contractual,operation
15 # eine natürliche Person, die nur Subscriber ist
16 1401; 120; Frau; Frauke; Fanninga; ; ; ; Am Walde 1; 29456; Hitzacker; DE; ; ; ; ; ff@example.org; subscriber:operations-announce
17 # eine natürliche Person als Partner
18 1501; 190; Frau; Cecilia; Camus; ; ; ; Rue d'Avignion 60; 45000; Orléans; FR; ; ; ; ; cc@example.org; partner,contractual,billing,operation
19 # some more contacts of realistic business partners
20 90436; 541; Frau; Christiane; Milberg; ; Wasserwerk Südholstein; ; Am Wasserwerk 1-3; 25491; Hetlingen; Germany; ; ; +49 4103 12345-1; ; rechnung@ww-sholst.example.org; billing,partner,subscriber:members-discussion,contractual,subscriber:members-announce,subscriber:generalversammlung
21 90437; 542; Herr; Richard; Wiese; ; Das Perfekte Haus; ; Kennedyplatz 11; 45279; Essen; Germany; ; ; +49-172-12345; ; admin@das-perfekte-haus.example.org; operation,partner,subscriber:members-discussion,contractual,subscriber:operations-announce,subscriber:operations-discussion,subscriber:members-announce,subscriber:generalversammlung
22 90438; 541; Herr; Karim; Metzger; ; Wasswerwerk Südholstein; ; Am Wasserwerk 1-3; 25491; Hetlingen; Germany; ; +49 4103 12345-2; ; ; karim.metzger@ww-sholst.example.org; operation,subscriber:operations-announce,subscriber:operations-discussion
23 90590; 542; Herr; Inhaber R.; Wiese; ; Das Perfekte Haus; Client-ID 515217; Essen, Kastanienallee 81; 30127; Hannover; Germany; ; ; ; ; 515217@kkemail.example.org; billing
24 90629; 132; ; Ragnar; Richter; ; ; ; ; ; ; ; ; ; ; ; mail@ragnar-richter..example.org; contractual,subscriber:members-announce,subscriber:members-discussion,subscriber:generalversammlung
25 90677; 132; ; Eike; Henning; ; ; ; ; ; ; ; ; ; ; ; hostsharing@eike-henning..example.org; operation,subscriber:operations-announce,subscriber:operations-discussion
26 90698; 132; ; Jan; Henning; ; ; ; ; ; ; ; ; 01577 12345678; ; ; mail@jan-henning.example.org; operation

View File

@ -0,0 +1,8 @@
sepa_mandat_id;bp_id;bank_customer;bank_name;bank_iban;bank_bic;mandat_ref;mandat_signed;mandat_since;mandat_until;mandat_used
30;132;Ragnar Richter;GLS Gemeinschaftsbank eG;DE02300209000106531065;GENODEM1GLS;HS-10152-20140801;2013-12-01;2013-12-01;2016-02-15;2014-01-20
132;100;Michael Mellis;Hamburger Volksbank;DE37500105177419788228;GENODEF1HH2;HS-10003-20140801;2013-12-01;2013-12-01;;2022-12-31
386;541;Wasserwerk Suedholstein;Sparkasse Westholstein;DE49500105174516484892;NOLADE21WHO;HS-11018-20210512;2021-05-12;2021-05-17;;2022-12-31
387;542;Richard Wiese Das Perfekte Haus;Commerzbank Wuppertal;DE89370400440532013000;COBADEFFXXX;HS-11019-20210519;2021-05-19;2021-05-25;;2022-12-31
234234;100;Michael Mellis;ING Bank AG;DE37500105177419788228;INGDDEFFXXX;MH12345;2004-06-12;2004-06-15;;2022-10-20
235600;120;JM e.K.;Targobank AG;DE02300209000106531065;CMCIDEDD;JM33344;2004-01-15;2004-01-20;2005-06-27 ;2016-01-18
235662;120;JM GmbH;ING Bank AG;DE49500105174516484892;INGDDEFFXXX;JM33344;2005-06-28;2005-07-01;;2016-01-18
1 sepa_mandat_id bp_id bank_customer bank_name bank_iban bank_bic mandat_ref mandat_signed mandat_since mandat_until mandat_used
2 30 132 Ragnar Richter GLS Gemeinschaftsbank eG DE02300209000106531065 GENODEM1GLS HS-10152-20140801 2013-12-01 2013-12-01 2016-02-15 2014-01-20
3 132 100 Michael Mellis Hamburger Volksbank DE37500105177419788228 GENODEF1HH2 HS-10003-20140801 2013-12-01 2013-12-01 2022-12-31
4 386 541 Wasserwerk Suedholstein Sparkasse Westholstein DE49500105174516484892 NOLADE21WHO HS-11018-20210512 2021-05-12 2021-05-17 2022-12-31
5 387 542 Richard Wiese Das Perfekte Haus Commerzbank Wuppertal DE89370400440532013000 COBADEFFXXX HS-11019-20210519 2021-05-19 2021-05-25 2022-12-31
6 234234 100 Michael Mellis ING Bank AG DE37500105177419788228 INGDDEFFXXX MH12345 2004-06-12 2004-06-15 2022-10-20
7 235600 120 JM e.K. Targobank AG DE02300209000106531065 CMCIDEDD JM33344 2004-01-15 2004-01-20 2005-06-27 2016-01-18
8 235662 120 JM GmbH ING Bank AG DE49500105174516484892 INGDDEFFXXX JM33344 2005-06-28 2005-07-01 2016-01-18

View File

@ -0,0 +1,12 @@
member_share_id;bp_id;date;action;quantity;comment
3;100;2000-12-06;SUBSCRIPTION;80;initial share subscription
90;132;2003-07-12;SUBSCRIPTION;1;
241;100;2011-12-05;SUBSCRIPTION;16;
279;132;2013-10-21;SUBSCRIPTION;1;
523;100;2020-12-08;SUBSCRIPTION;96;Kapitalerhoehung
562;541;2021-05-17;SUBSCRIPTION;4;Beitritt
563;542;2021-05-25;SUBSCRIPTION;1;Beitritt
721;100;2023-10-10;SUBSCRIPTION;96;Kapitalerhoehung
33451;120;2000-12-06;SUBSCRIPTION;2;initial share subscription
33701;100;2005-01-10;SUBSCRIPTION;40;increase
33810;120;2016-12-31;UNSUBSCRIPTION;22;membership ended
1 member_share_id bp_id date action quantity comment
2 3 100 2000-12-06 SUBSCRIPTION 80 initial share subscription
3 90 132 2003-07-12 SUBSCRIPTION 1
4 241 100 2011-12-05 SUBSCRIPTION 16
5 279 132 2013-10-21 SUBSCRIPTION 1
6 523 100 2020-12-08 SUBSCRIPTION 96 Kapitalerhoehung
7 562 541 2021-05-17 SUBSCRIPTION 4 Beitritt
8 563 542 2021-05-25 SUBSCRIPTION 1 Beitritt
9 721 100 2023-10-10 SUBSCRIPTION 96 Kapitalerhoehung
10 33451 120 2000-12-06 SUBSCRIPTION 2 initial share subscription
11 33701 100 2005-01-10 SUBSCRIPTION 40 increase
12 33810 120 2016-12-31 UNSUBSCRIPTION 22 membership ended

View File

@ -1,4 +0,0 @@
sepa_mandat_id; bp_id; bank_customer; bank_name; bank_iban; bank_bic; mandat_ref; mandat_signed; mandat_since; mandat_until; mandat_used
234234; 17; Michael Mellies; ING Bank AG; DE37500105177419788228; INGDDEFFXXX; MH12345; 2004-06-12; 2004-06-15; ; 2022-10-20
235600; 20; JM e.K.; Targobank AG; DE02300209000106531065; CMCIDEDD; JM33344; 2004-01-15; 2004-01-20;2005-06-27 ;2016-01-18
235662; 20; JM GmbH; ING Bank AG; DE49500105174516484892; INGDDEFFXXX; JM33344; 2005-06-28; 2005-07-01; ; 2016-01-18
1 sepa_mandat_id bp_id bank_customer bank_name bank_iban bank_bic mandat_ref mandat_signed mandat_since mandat_until mandat_used
2 234234 17 Michael Mellies ING Bank AG DE37500105177419788228 INGDDEFFXXX MH12345 2004-06-12 2004-06-15 2022-10-20
3 235600 20 JM e.K. Targobank AG DE02300209000106531065 CMCIDEDD JM33344 2004-01-15 2004-01-20 2005-06-27 2016-01-18
4 235662 20 JM GmbH ING Bank AG DE49500105174516484892 INGDDEFFXXX JM33344 2005-06-28 2005-07-01 2016-01-18

View File

@ -1,5 +0,0 @@
member_share_id;bp_id; date; action; quantity; comment
33443; 17; 2000-12-06; SUBSCRIPTION; 20; initial share subscription
33451; 20; 2000-12-06; SUBSCRIPTION; 2; initial share subscription
33701; 17; 2005-01-10; SUBSCRIPTION; 40; increase
33810; 20; 2016-12-31; UNSUBSCRIPTION; 22; membership ended
1 member_share_id bp_id date action quantity comment
2 33443 17 2000-12-06 SUBSCRIPTION 20 initial share subscription
3 33451 20 2000-12-06 SUBSCRIPTION 2 initial share subscription
4 33701 17 2005-01-10 SUBSCRIPTION 40 increase
5 33810 20 2016-12-31 UNSUBSCRIPTION 22 membership ended